public void setBreakpointDefaults( Key<? extends Breakpoint> category, BreakpointDefaults defaults) { Class typeCls = null; if (LineBreakpoint.CATEGORY.toString().equals(category.toString())) { typeCls = JavaLineBreakpointType.class; } else if (MethodBreakpoint.CATEGORY.toString().equals(category.toString())) { typeCls = JavaMethodBreakpointType.class; } else if (FieldBreakpoint.CATEGORY.toString().equals(category.toString())) { typeCls = JavaFieldBreakpointType.class; } else if (ExceptionBreakpoint.CATEGORY.toString().equals(category.toString())) { typeCls = JavaExceptionBreakpointType.class; } if (typeCls != null) { XBreakpointType<XBreakpoint<?>, ?> type = XDebuggerUtil.getInstance().findBreakpointType(typeCls); ((XBreakpointManagerImpl) getXBreakpointManager()) .getBreakpointDefaults(type) .setSuspendPolicy(Breakpoint.transformSuspendPolicy(defaults.getSuspendPolicy())); } // myBreakpointDefaults.put(category, defaults); }
private static <T extends Breakpoint> Element getCategoryGroupElement( @NotNull final Map<Key<? extends Breakpoint>, Element> categoryToElementMap, @NotNull final Key<T> category, @NotNull final Element parentNode) { Element group = categoryToElementMap.get(category); if (group == null) { group = new Element(category.toString()); categoryToElementMap.put(category, group); parentNode.addContent(group); } return group; }
/** * @return breakpoints of one of the category: LINE_BREAKPOINTS, EXCEPTION_BREAKPOINTS, * FIELD_BREAKPOINTS, METHOD_BREAKPOINTS */ public <T extends Breakpoint> Breakpoint[] getBreakpoints(@NotNull final Key<T> category) { ApplicationManager.getApplication().assertIsDispatchThread(); removeInvalidBreakpoints(); final ArrayList<Breakpoint> breakpoints = new ArrayList<Breakpoint>(); for (Breakpoint breakpoint : getBreakpoints()) { if (category.equals(breakpoint.getCategory())) { breakpoints.add(breakpoint); } } return breakpoints.toArray(new Breakpoint[breakpoints.size()]); }
/** @param category breakpoint category, null if the category does not matter */ @Nullable public <T extends BreakpointWithHighlighter> T findBreakpoint( final Document document, final int offset, @Nullable final Key<T> category) { for (final Breakpoint breakpoint : getBreakpoints()) { if (breakpoint instanceof BreakpointWithHighlighter && ((BreakpointWithHighlighter) breakpoint).isAt(document, offset)) { if (category == null || category.equals(breakpoint.getCategory())) { //noinspection CastConflictsWithInstanceof,unchecked return (T) breakpoint; } } } return null; }
/** Author: msk */ public class EditorsSplitters extends IdePanePanel implements UISettingsListener, Disposable { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.EditorsSplitters"); private static final String PINNED = "pinned"; private static final String CURRENT_IN_TAB = "current-in-tab"; private static final Key<Object> DUMMY_KEY = Key.create("EditorsSplitters.dummy.key"); private EditorWindow myCurrentWindow; private final Set<EditorWindow> myWindows = new CopyOnWriteArraySet<EditorWindow>(); private final FileEditorManagerImpl myManager; private Element mySplittersElement; // temporarily used during initialization int myInsideChange; private final MyFocusWatcher myFocusWatcher; private final Alarm myIconUpdaterAlarm = new Alarm(); private final UIBuilder myUIBuilder = new UIBuilder(); EditorsSplitters( final FileEditorManagerImpl manager, DockManager dockManager, boolean createOwnDockableContainer) { super(new BorderLayout()); myManager = manager; myFocusWatcher = new MyFocusWatcher(); setFocusTraversalPolicy(new MyFocusTraversalPolicy()); clear(); if (createOwnDockableContainer) { DockableEditorTabbedContainer dockable = new DockableEditorTabbedContainer(myManager.getProject(), this, false); Disposer.register(manager.getProject(), dockable); dockManager.register(dockable); } KeymapManagerListener keymapListener = new KeymapManagerListener() { @Override public void activeKeymapChanged(Keymap keymap) { invalidate(); repaint(); } }; KeymapManager.getInstance().addKeymapManagerListener(keymapListener, this); } public FileEditorManagerImpl getManager() { return myManager; } public void clear() { for (EditorWindow window : myWindows) { window.dispose(); } removeAll(); myWindows.clear(); setCurrentWindow(null); repaint(); // revalidate doesn't repaint correctly after "Close All" } void startListeningFocus() { myFocusWatcher.install(this); } private void stopListeningFocus() { myFocusWatcher.deinstall(this); } @Override public void dispose() { myIconUpdaterAlarm.cancelAllRequests(); stopListeningFocus(); } @Nullable public VirtualFile getCurrentFile() { if (myCurrentWindow != null) { return myCurrentWindow.getSelectedFile(); } return null; } private boolean showEmptyText() { return myCurrentWindow == null || myCurrentWindow.getFiles().length == 0; } @Override protected void paintComponent(Graphics g) { if (showEmptyText()) { Graphics2D gg = IdeBackgroundUtil.withFrameBackground(g, this); super.paintComponent(gg); g.setColor(UIUtil.isUnderDarcula() ? UIUtil.getBorderColor() : new Color(0, 0, 0, 50)); g.drawLine(0, 0, getWidth(), 0); } } public void writeExternal(final Element element) { if (getComponentCount() != 0) { final Component comp = getComponent(0); LOG.assertTrue(comp instanceof JPanel); final JPanel panel = (JPanel) comp; if (panel.getComponentCount() != 0) { element.addContent(writePanel(panel)); } } } @SuppressWarnings("HardCodedStringLiteral") private Element writePanel(final JPanel panel) { final Component comp = panel.getComponent(0); if (comp instanceof Splitter) { final Splitter splitter = (Splitter) comp; final Element res = new Element("splitter"); res.setAttribute("split-orientation", splitter.getOrientation() ? "vertical" : "horizontal"); res.setAttribute("split-proportion", Float.toString(splitter.getProportion())); final Element first = new Element("split-first"); first.addContent(writePanel((JPanel) splitter.getFirstComponent())); final Element second = new Element("split-second"); second.addContent(writePanel((JPanel) splitter.getSecondComponent())); res.addContent(first); res.addContent(second); return res; } else if (comp instanceof JBTabs) { final Element res = new Element("leaf"); Integer limit = UIUtil.getClientProperty( ((JBTabs) comp).getComponent(), JBTabsImpl.SIDE_TABS_SIZE_LIMIT_KEY); if (limit != null) { res.setAttribute(JBTabsImpl.SIDE_TABS_SIZE_LIMIT_KEY.toString(), String.valueOf(limit)); } writeWindow(res, findWindowWith(comp)); return res; } else if (comp instanceof EditorWindow.TCompForTablessMode) { EditorWithProviderComposite composite = ((EditorWindow.TCompForTablessMode) comp).myEditor; Element res = new Element("leaf"); res.addContent(writeComposite(composite.getFile(), composite, false, composite)); return res; } else { LOG.error(comp != null ? comp.getClass().getName() : null); return null; } } private void writeWindow(@NotNull Element res, @Nullable EditorWindow window) { if (window != null) { EditorWithProviderComposite[] composites = window.getEditors(); for (int i = 0; i < composites.length; i++) { VirtualFile file = window.getFileAt(i); res.addContent( writeComposite( file, composites[i], window.isFilePinned(file), window.getSelectedEditor())); } } } @NotNull private Element writeComposite( VirtualFile file, EditorWithProviderComposite composite, boolean pinned, EditorWithProviderComposite selectedEditor) { Element fileElement = new Element("file"); fileElement.setAttribute("leaf-file-name", file.getName()); // TODO: all files composite.currentStateAsHistoryEntry().writeExternal(fileElement, getManager().getProject()); fileElement.setAttribute(PINNED, Boolean.toString(pinned)); fileElement.setAttribute(CURRENT_IN_TAB, Boolean.toString(composite.equals(selectedEditor))); return fileElement; } public void openFiles() { if (mySplittersElement != null) { initializeProgress(); final JPanel comp = myUIBuilder.process(mySplittersElement, getTopPanel()); UIUtil.invokeAndWaitIfNeeded( new Runnable() { @Override public void run() { if (comp != null) { removeAll(); add(comp, BorderLayout.CENTER); mySplittersElement = null; } // clear empty splitters for (EditorWindow window : getWindows()) { if (window.getEditors().length == 0) { for (EditorWindow sibling : window.findSiblings()) { sibling.unsplit(false); } } } } }); } } private static void initializeProgress() { ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); if (indicator != null) { indicator.setText(IdeBundle.message("loading.editors")); } } public int getEditorsCount() { return mySplittersElement == null ? 0 : countFiles(mySplittersElement); } private double myProgressStep; public void setProgressStep(double step) { myProgressStep = step; } private void updateProgress() { ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); if (indicator != null) { indicator.setFraction(indicator.getFraction() + myProgressStep); } } private static int countFiles(Element element) { Integer value = new ConfigTreeReader<Integer>() { @Override protected Integer processFiles( @NotNull List<Element> fileElements, @Nullable Integer context) { return fileElements.size(); } @Override protected Integer processSplitter( @NotNull Element element, @Nullable Element firstChild, @Nullable Element secondChild, @Nullable Integer context) { Integer first = process(firstChild, null); Integer second = process(secondChild, null); return (first == null ? 0 : first) + (second == null ? 0 : second); } }.process(element, null); return value == null ? 0 : value; } public void readExternal(final Element element) { mySplittersElement = element; } @NotNull public VirtualFile[] getOpenFiles() { final Set<VirtualFile> files = new ArrayListSet<VirtualFile>(); for (final EditorWindow myWindow : myWindows) { final EditorWithProviderComposite[] editors = myWindow.getEditors(); for (final EditorWithProviderComposite editor : editors) { VirtualFile file = editor.getFile(); // background thread may call this method when invalid file is being removed // do not return it here as it will quietly drop out soon if (file.isValid()) { files.add(file); } } } return VfsUtilCore.toVirtualFileArray(files); } @NotNull public VirtualFile[] getSelectedFiles() { final Set<VirtualFile> files = new ArrayListSet<VirtualFile>(); for (final EditorWindow window : myWindows) { final VirtualFile file = window.getSelectedFile(); if (file != null) { files.add(file); } } final VirtualFile[] virtualFiles = VfsUtilCore.toVirtualFileArray(files); final VirtualFile currentFile = getCurrentFile(); if (currentFile != null) { for (int i = 0; i != virtualFiles.length; ++i) { if (Comparing.equal(virtualFiles[i], currentFile)) { virtualFiles[i] = virtualFiles[0]; virtualFiles[0] = currentFile; break; } } } return virtualFiles; } @NotNull public FileEditor[] getSelectedEditors() { List<FileEditor> editors = new ArrayList<FileEditor>(); Set<EditorWindow> windows = new THashSet<EditorWindow>(myWindows); final EditorWindow currentWindow = getCurrentWindow(); if (currentWindow != null) { windows.add(currentWindow); } for (final EditorWindow window : windows) { final EditorWithProviderComposite composite = window.getSelectedEditor(); if (composite != null) { editors.add(composite.getSelectedEditor()); } } return editors.toArray(new FileEditor[editors.size()]); } public void updateFileIcon(@NotNull final VirtualFile file) { updateFileIconLater(file); } private void updateFileIconImmediately(final VirtualFile file) { final Collection<EditorWindow> windows = findWindows(file); for (EditorWindow window : windows) { window.updateFileIcon(file); } } private final Set<VirtualFile> myFilesToUpdateIconsFor = new HashSet<VirtualFile>(); private void updateFileIconLater(VirtualFile file) { myFilesToUpdateIconsFor.add(file); myIconUpdaterAlarm.cancelAllRequests(); myIconUpdaterAlarm.addRequest( () -> { if (myManager.getProject().isDisposed()) return; for (VirtualFile file1 : myFilesToUpdateIconsFor) { updateFileIconImmediately(file1); } myFilesToUpdateIconsFor.clear(); }, 200, ModalityState.stateForComponent(this)); } void updateFileColor(@NotNull final VirtualFile file) { final Collection<EditorWindow> windows = findWindows(file); for (final EditorWindow window : windows) { final int index = window.findEditorIndex(window.findFileComposite(file)); LOG.assertTrue(index != -1); window.setForegroundAt(index, getManager().getFileColor(file)); window.setWaveColor(index, getManager().isProblem(file) ? JBColor.red : null); } } public void trimToSize(final int editor_tab_limit) { for (final EditorWindow window : myWindows) { window.trimToSize(editor_tab_limit, null, true); } } public void setTabsPlacement(final int tabPlacement) { final EditorWindow[] windows = getWindows(); for (int i = 0; i != windows.length; ++i) { windows[i].setTabsPlacement(tabPlacement); } } void setTabLayoutPolicy(int scrollTabLayout) { final EditorWindow[] windows = getWindows(); for (int i = 0; i != windows.length; ++i) { windows[i].setTabLayoutPolicy(scrollTabLayout); } } void updateFileName(@Nullable final VirtualFile updatedFile) { final EditorWindow[] windows = getWindows(); for (int i = 0; i != windows.length; ++i) { for (VirtualFile file : windows[i].getFiles()) { if (updatedFile == null || file.getName().equals(updatedFile.getName())) { windows[i].updateFileName(file); } } } Project project = myManager.getProject(); final IdeFrame frame = getFrame(project); if (frame != null) { VirtualFile file = getCurrentFile(); File ioFile = file == null ? null : new File(file.getPresentableUrl()); String fileTitle = null; if (file != null) { fileTitle = DumbService.isDumb(project) ? file.getName() : FrameTitleBuilder.getInstance().getFileTitle(project, file); } frame.setFileTitle(fileTitle, ioFile); } } protected IdeFrame getFrame(Project project) { final WindowManagerEx windowManagerEx = WindowManagerEx.getInstanceEx(); final IdeFrame frame = windowManagerEx.getFrame(project); LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode() || frame != null); return frame; } boolean isInsideChange() { return myInsideChange > 0; } private void setCurrentWindow(@Nullable final EditorWindow currentWindow) { if (currentWindow != null && !myWindows.contains(currentWindow)) { throw new IllegalArgumentException(currentWindow + " is not a member of this container"); } myCurrentWindow = currentWindow; } void updateFileBackgroundColor(@NotNull VirtualFile file) { final EditorWindow[] windows = getWindows(); for (int i = 0; i != windows.length; ++i) { windows[i].updateFileBackgroundColor(file); } } int getSplitCount() { if (getComponentCount() > 0) { JPanel panel = (JPanel) getComponent(0); return getSplitCount(panel); } return 0; } private static int getSplitCount(JComponent component) { if (component.getComponentCount() > 0) { final JComponent firstChild = (JComponent) component.getComponent(0); if (firstChild instanceof Splitter) { final Splitter splitter = (Splitter) firstChild; return getSplitCount(splitter.getFirstComponent()) + getSplitCount(splitter.getSecondComponent()); } return 1; } return 0; } protected void afterFileClosed(VirtualFile file) {} protected void afterFileOpen(VirtualFile file) {} @Nullable JBTabs getTabsAt(RelativePoint point) { Point thisPoint = point.getPoint(this); Component c = SwingUtilities.getDeepestComponentAt(this, thisPoint.x, thisPoint.y); while (c != null) { if (c instanceof JBTabs) { return (JBTabs) c; } c = c.getParent(); } return null; } boolean isEmptyVisible() { EditorWindow[] windows = getWindows(); for (EditorWindow each : windows) { if (!each.isEmptyVisible()) { return false; } } return true; } @Nullable private VirtualFile findNextFile(final VirtualFile file) { final EditorWindow[] windows = getWindows(); // TODO: use current file as base for (int i = 0; i != windows.length; ++i) { final VirtualFile[] files = windows[i].getFiles(); for (final VirtualFile fileAt : files) { if (!Comparing.equal(fileAt, file)) { return fileAt; } } } return null; } void closeFile(VirtualFile file, boolean moveFocus) { final List<EditorWindow> windows = findWindows(file); if (!windows.isEmpty()) { final VirtualFile nextFile = findNextFile(file); for (final EditorWindow window : windows) { LOG.assertTrue(window.getSelectedEditor() != null); window.closeFile(file, false, moveFocus); if (window.getTabCount() == 0 && nextFile != null && myManager.getProject().isOpen()) { EditorWithProviderComposite newComposite = myManager.newEditorComposite(nextFile); window.setEditor(newComposite, moveFocus); // newComposite can be null } } // cleanup windows with no tabs for (final EditorWindow window : windows) { if (window.isDisposed()) { // call to window.unsplit() which might make its sibling disposed continue; } if (window.getTabCount() == 0) { window.unsplit(false); } } } } @Override public void uiSettingsChanged(UISettings source) { if (!myManager.getProject().isOpen()) return; for (VirtualFile file : getOpenFiles()) { updateFileBackgroundColor(file); updateFileIcon(file); updateFileColor(file); } } private final class MyFocusTraversalPolicy extends IdeFocusTraversalPolicy { @Override public final Component getDefaultComponentImpl(final Container focusCycleRoot) { if (myCurrentWindow != null) { final EditorWithProviderComposite selectedEditor = myCurrentWindow.getSelectedEditor(); if (selectedEditor != null) { return IdeFocusTraversalPolicy.getPreferredFocusedComponent( selectedEditor.getComponent(), this); } } return IdeFocusTraversalPolicy.getPreferredFocusedComponent(EditorsSplitters.this, this); } } @Nullable public JPanel getTopPanel() { return getComponentCount() > 0 ? (JPanel) getComponent(0) : null; } public EditorWindow getCurrentWindow() { return myCurrentWindow; } public EditorWindow getOrCreateCurrentWindow(final VirtualFile file) { final List<EditorWindow> windows = findWindows(file); if (getCurrentWindow() == null) { final Iterator<EditorWindow> iterator = myWindows.iterator(); if (!windows.isEmpty()) { setCurrentWindow(windows.get(0), false); } else if (iterator.hasNext()) { setCurrentWindow(iterator.next(), false); } else { createCurrentWindow(); } } else if (!windows.isEmpty()) { if (!windows.contains(getCurrentWindow())) { setCurrentWindow(windows.get(0), false); } } return getCurrentWindow(); } void createCurrentWindow() { LOG.assertTrue(myCurrentWindow == null); setCurrentWindow(createEditorWindow()); add(myCurrentWindow.myPanel, BorderLayout.CENTER); } protected EditorWindow createEditorWindow() { return new EditorWindow(this); } /** * sets the window passed as a current ('focused') window among all splitters. All file openings * will be done inside this current window * * @param window a window to be set as current * @param requestFocus whether to request focus to the editor currently selected in this window */ void setCurrentWindow(@Nullable final EditorWindow window, final boolean requestFocus) { final EditorWithProviderComposite newEditor = window == null ? null : window.getSelectedEditor(); Runnable fireRunnable = () -> getManager().fireSelectionChanged(newEditor); setCurrentWindow(window); getManager().updateFileName(window == null ? null : window.getSelectedFile()); if (window != null) { final EditorWithProviderComposite selectedEditor = window.getSelectedEditor(); if (selectedEditor != null) { fireRunnable.run(); } if (requestFocus) { window.requestFocus(true); } } else { fireRunnable.run(); } } void addWindow(EditorWindow window) { myWindows.add(window); } void removeWindow(EditorWindow window) { myWindows.remove(window); if (myCurrentWindow == window) { myCurrentWindow = null; } } boolean containsWindow(EditorWindow window) { return myWindows.contains(window); } // --------------------------------------------------------- public EditorWithProviderComposite[] getEditorsComposites() { List<EditorWithProviderComposite> res = new ArrayList<EditorWithProviderComposite>(); for (final EditorWindow myWindow : myWindows) { final EditorWithProviderComposite[] editors = myWindow.getEditors(); ContainerUtil.addAll(res, editors); } return res.toArray(new EditorWithProviderComposite[res.size()]); } // --------------------------------------------------------- @NotNull public List<EditorWithProviderComposite> findEditorComposites(@NotNull VirtualFile file) { List<EditorWithProviderComposite> res = new ArrayList<EditorWithProviderComposite>(); for (final EditorWindow window : myWindows) { final EditorWithProviderComposite fileComposite = window.findFileComposite(file); if (fileComposite != null) { res.add(fileComposite); } } return res; } @NotNull private List<EditorWindow> findWindows(final VirtualFile file) { List<EditorWindow> res = new ArrayList<EditorWindow>(); for (final EditorWindow window : myWindows) { if (window.findFileComposite(file) != null) { res.add(window); } } return res; } @NotNull public EditorWindow[] getWindows() { return myWindows.toArray(new EditorWindow[myWindows.size()]); } @NotNull EditorWindow[] getOrderedWindows() { final List<EditorWindow> res = new ArrayList<EditorWindow>(); // Collector for windows in tree ordering: class Inner { private void collect(final JPanel panel) { final Component comp = panel.getComponent(0); if (comp instanceof Splitter) { final Splitter splitter = (Splitter) comp; collect((JPanel) splitter.getFirstComponent()); collect((JPanel) splitter.getSecondComponent()); } else if (comp instanceof JPanel || comp instanceof JBTabs) { final EditorWindow window = findWindowWith(comp); if (window != null) { res.add(window); } } } } // get root component and traverse splitters tree: if (getComponentCount() != 0) { final Component comp = getComponent(0); LOG.assertTrue(comp instanceof JPanel); final JPanel panel = (JPanel) comp; if (panel.getComponentCount() != 0) { new Inner().collect(panel); } } LOG.assertTrue(res.size() == myWindows.size()); return res.toArray(new EditorWindow[res.size()]); } @Nullable private EditorWindow findWindowWith(final Component component) { if (component != null) { for (final EditorWindow window : myWindows) { if (SwingUtilities.isDescendingFrom(component, window.myPanel)) { return window; } } } return null; } public boolean isFloating() { return false; } public boolean isPreview() { return false; } private final class MyFocusWatcher extends FocusWatcher { @Override protected void focusedComponentChanged(final Component component, final AWTEvent cause) { EditorWindow newWindow = null; if (component != null) { newWindow = findWindowWith(component); } else if (cause instanceof ContainerEvent && cause.getID() == ContainerEvent.COMPONENT_REMOVED) { // do not change current window in case of child removal as in JTable.removeEditor // otherwise Escape in a toolwindow will not focus editor with JTable content return; } setCurrentWindow(newWindow); setCurrentWindow(newWindow, false); } } private abstract static class ConfigTreeReader<T> { @Nullable public T process(@Nullable Element element, @Nullable T context) { if (element == null) { return null; } final Element splitterElement = element.getChild("splitter"); if (splitterElement != null) { final Element first = splitterElement.getChild("split-first"); final Element second = splitterElement.getChild("split-second"); return processSplitter(splitterElement, first, second, context); } final Element leaf = element.getChild("leaf"); if (leaf == null) { return null; } List<Element> fileElements = leaf.getChildren("file"); final List<Element> children = new ArrayList<Element>(fileElements.size()); // trim to EDITOR_TAB_LIMIT, ignoring CLOSE_NON_MODIFIED_FILES_FIRST policy int toRemove = fileElements.size() - UISettings.getInstance().EDITOR_TAB_LIMIT; for (Element fileElement : fileElements) { if (toRemove <= 0 || Boolean.valueOf(fileElement.getAttributeValue(PINNED)).booleanValue()) { children.add(fileElement); } else { toRemove--; } } return processFiles(children, context); } @Nullable protected abstract T processFiles(@NotNull List<Element> fileElements, @Nullable T context); @Nullable protected abstract T processSplitter( @NotNull Element element, @Nullable Element firstChild, @Nullable Element secondChild, @Nullable T context); } private class UIBuilder extends ConfigTreeReader<JPanel> { @Override protected JPanel processFiles(@NotNull List<Element> fileElements, final JPanel context) { final Ref<EditorWindow> windowRef = new Ref<EditorWindow>(); UIUtil.invokeAndWaitIfNeeded( new Runnable() { @Override public void run() { windowRef.set(context == null ? createEditorWindow() : findWindowWith(context)); } }); final EditorWindow window = windowRef.get(); LOG.assertTrue(window != null); VirtualFile focusedFile = null; for (int i = 0; i < fileElements.size(); i++) { final Element file = fileElements.get(i); if (i == 0) { EditorTabbedContainer tabbedPane = window.getTabbedPane(); if (tabbedPane != null) { try { int limit = Integer.parseInt( file.getParentElement() .getAttributeValue( JBTabsImpl.SIDE_TABS_SIZE_LIMIT_KEY.toString(), String.valueOf(JBTabsImpl.DEFAULT_MAX_TAB_WIDTH))); UIUtil.putClientProperty( tabbedPane.getComponent(), JBTabsImpl.SIDE_TABS_SIZE_LIMIT_KEY, limit); } catch (NumberFormatException e) { // ignore } } } try { final FileEditorManagerImpl fileEditorManager = getManager(); Element historyElement = file.getChild(HistoryEntry.TAG); final HistoryEntry entry = HistoryEntry.createLight(fileEditorManager.getProject(), historyElement); final VirtualFile virtualFile = entry.getFile(); if (virtualFile == null) throw new InvalidDataException("No file exists: " + entry.getFilePointer().getUrl()); Document document = ApplicationManager.getApplication() .runReadAction( new Computable<Document>() { @Override public Document compute() { return virtualFile.isValid() ? FileDocumentManager.getInstance().getDocument(virtualFile) : null; } }); final boolean isCurrentInTab = Boolean.valueOf(file.getAttributeValue(CURRENT_IN_TAB)).booleanValue(); Boolean pin = Boolean.valueOf(file.getAttributeValue(PINNED)); fileEditorManager.openFileImpl4( window, virtualFile, entry, isCurrentInTab, isCurrentInTab, pin, i); if (isCurrentInTab) { focusedFile = virtualFile; } if (document != null) { // This is just to make sure document reference is kept on stack till this point // so that document is available for folding state deserialization in HistoryEntry // constructor // and that document will be created only once during file opening document.putUserData(DUMMY_KEY, null); } updateProgress(); } catch (InvalidDataException e) { if (ApplicationManager.getApplication().isUnitTestMode()) { LOG.error(e); } } } if (focusedFile != null) { getManager().addSelectionRecord(focusedFile, window); } return window.myPanel; } @Override protected JPanel processSplitter( @NotNull Element splitterElement, Element firstChild, Element secondChild, final JPanel context) { if (context == null) { final boolean orientation = "vertical".equals(splitterElement.getAttributeValue("split-orientation")); final float proportion = Float.valueOf(splitterElement.getAttributeValue("split-proportion")).floatValue(); final JPanel firstComponent = process(firstChild, null); final JPanel secondComponent = process(secondChild, null); final Ref<JPanel> panelRef = new Ref<JPanel>(); UIUtil.invokeAndWaitIfNeeded( new Runnable() { @Override public void run() { JPanel panel = new JPanel(new BorderLayout()); panel.setOpaque(false); Splitter splitter = new OnePixelSplitter(orientation, proportion, 0.1f, 0.9f); panel.add(splitter, BorderLayout.CENTER); splitter.setFirstComponent(firstComponent); splitter.setSecondComponent(secondComponent); panelRef.set(panel); } }); return panelRef.get(); } final Ref<JPanel> firstComponent = new Ref<JPanel>(); final Ref<JPanel> secondComponent = new Ref<JPanel>(); UIUtil.invokeAndWaitIfNeeded( new Runnable() { @Override public void run() { if (context.getComponent(0) instanceof Splitter) { Splitter splitter = (Splitter) context.getComponent(0); firstComponent.set((JPanel) splitter.getFirstComponent()); secondComponent.set((JPanel) splitter.getSecondComponent()); } else { firstComponent.set(context); secondComponent.set(context); } } }); process(firstChild, firstComponent.get()); process(secondChild, secondComponent.get()); return context; } } }
public abstract class PsiDocumentManagerBase extends PsiDocumentManager implements DocumentListener { static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.PsiDocumentManagerImpl"); private static final Key<Document> HARD_REF_TO_DOCUMENT = Key.create("HARD_REFERENCE_TO_DOCUMENT"); private static final Key<PsiFile> HARD_REF_TO_PSI = Key.create("HARD_REFERENCE_TO_PSI"); private static final Key<List<Runnable>> ACTION_AFTER_COMMIT = Key.create("ACTION_AFTER_COMMIT"); protected final Project myProject; private final PsiManager myPsiManager; private final DocumentCommitProcessor myDocumentCommitProcessor; protected final Set<Document> myUncommittedDocuments = ContainerUtil.newConcurrentSet(); private final Map<Document, Pair<CharSequence, Long>> myLastCommittedTexts = ContainerUtil.newConcurrentMap(); protected boolean myStopTrackingDocuments; protected boolean myPerformBackgroundCommit = true; private volatile boolean myIsCommitInProgress; private final PsiToDocumentSynchronizer mySynchronizer; private final List<Listener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); protected PsiDocumentManagerBase( @NotNull final Project project, @NotNull PsiManager psiManager, @NotNull MessageBus bus, @NonNls @NotNull final DocumentCommitProcessor documentCommitProcessor) { myProject = project; myPsiManager = psiManager; myDocumentCommitProcessor = documentCommitProcessor; mySynchronizer = new PsiToDocumentSynchronizer(this, bus); myPsiManager.addPsiTreeChangeListener(mySynchronizer); bus.connect() .subscribe( PsiDocumentTransactionListener.TOPIC, new PsiDocumentTransactionListener() { @Override public void transactionStarted(@NotNull Document document, @NotNull PsiFile file) { myUncommittedDocuments.remove(document); } @Override public void transactionCompleted(@NotNull Document document, @NotNull PsiFile file) {} }); } @Override @Nullable public PsiFile getPsiFile(@NotNull Document document) { final PsiFile userData = document.getUserData(HARD_REF_TO_PSI); if (userData != null) return userData; PsiFile psiFile = getCachedPsiFile(document); if (psiFile != null) return psiFile; final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); if (virtualFile == null || !virtualFile.isValid()) return null; psiFile = getPsiFile(virtualFile); if (psiFile == null) return null; fireFileCreated(document, psiFile); return psiFile; } public static void cachePsi(@NotNull Document document, @Nullable PsiFile file) { document.putUserData(HARD_REF_TO_PSI, file); } @Override public PsiFile getCachedPsiFile(@NotNull Document document) { final PsiFile userData = document.getUserData(HARD_REF_TO_PSI); if (userData != null) return userData; final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); if (virtualFile == null || !virtualFile.isValid()) return null; return getCachedPsiFile(virtualFile); } @Nullable FileViewProvider getCachedViewProvider(@NotNull Document document) { final VirtualFile virtualFile = getVirtualFile(document); if (virtualFile == null) return null; return getCachedViewProvider(virtualFile); } private FileViewProvider getCachedViewProvider(@NotNull VirtualFile virtualFile) { return ((PsiManagerEx) myPsiManager).getFileManager().findCachedViewProvider(virtualFile); } private static VirtualFile getVirtualFile(@NotNull Document document) { final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); if (virtualFile == null || !virtualFile.isValid()) return null; return virtualFile; } @Nullable PsiFile getCachedPsiFile(@NotNull VirtualFile virtualFile) { return ((PsiManagerEx) myPsiManager).getFileManager().getCachedPsiFile(virtualFile); } @Nullable private PsiFile getPsiFile(@NotNull VirtualFile virtualFile) { return ((PsiManagerEx) myPsiManager).getFileManager().findFile(virtualFile); } @Nullable @Override public Document getDocument(@NotNull PsiFile file) { if (file instanceof PsiBinaryFile) return null; Document document = getCachedDocument(file); if (document != null) { if (!file.getViewProvider().isPhysical() && document.getUserData(HARD_REF_TO_PSI) == null) { PsiUtilCore.ensureValid(file); cachePsi(document, file); } return document; } FileViewProvider viewProvider = file.getViewProvider(); if (!viewProvider.isEventSystemEnabled()) return null; document = FileDocumentManager.getInstance().getDocument(viewProvider.getVirtualFile()); if (document != null) { if (document.getTextLength() != file.getTextLength()) { String message = "Document/PSI mismatch: " + file + " (" + file.getClass() + "); physical=" + viewProvider.isPhysical(); if (document.getTextLength() + file.getTextLength() < 8096) { message += "\n=== document ===\n" + document.getText() + "\n=== PSI ===\n" + file.getText(); } throw new AssertionError(message); } if (!viewProvider.isPhysical()) { PsiUtilCore.ensureValid(file); cachePsi(document, file); file.putUserData(HARD_REF_TO_DOCUMENT, document); } } return document; } @Override public Document getCachedDocument(@NotNull PsiFile file) { if (!file.isPhysical()) return null; VirtualFile vFile = file.getViewProvider().getVirtualFile(); return FileDocumentManager.getInstance().getCachedDocument(vFile); } @Override public void commitAllDocuments() { ApplicationManager.getApplication().assertIsDispatchThread(); if (myUncommittedDocuments.isEmpty()) return; final Document[] documents = getUncommittedDocuments(); for (Document document : documents) { commitDocument(document); } LOG.assertTrue(!hasUncommitedDocuments(), myUncommittedDocuments); } @Override public void performForCommittedDocument( @NotNull final Document doc, @NotNull final Runnable action) { final Document document = doc instanceof DocumentWindow ? ((DocumentWindow) doc).getDelegate() : doc; if (isCommitted(document)) { action.run(); } else { addRunOnCommit(document, action); } } private final Map<Object, Runnable> actionsWhenAllDocumentsAreCommitted = new LinkedHashMap<Object, Runnable>(); // accessed from EDT only private static final Object PERFORM_ALWAYS_KEY = new Object() { @Override @NonNls public String toString() { return "PERFORM_ALWAYS"; } }; /** * Cancel previously registered action and schedules (new) action to be executed when all * documents are committed. * * @param key the (unique) id of the action. * @param action The action to be executed after automatic commit. This action will overwrite any * action which was registered under this key earlier. The action will be executed in EDT. * @return true if action has been run immediately, or false if action was scheduled for execution * later. */ public boolean cancelAndRunWhenAllCommitted( @NonNls @NotNull Object key, @NotNull final Runnable action) { ApplicationManager.getApplication().assertIsDispatchThread(); if (myProject.isDisposed()) { action.run(); return true; } if (myUncommittedDocuments.isEmpty()) { action.run(); if (!hasUncommitedDocuments()) { assert actionsWhenAllDocumentsAreCommitted.isEmpty() : actionsWhenAllDocumentsAreCommitted; } return true; } actionsWhenAllDocumentsAreCommitted.put(key, action); return false; } public static void addRunOnCommit(@NotNull Document document, @NotNull Runnable action) { synchronized (ACTION_AFTER_COMMIT) { List<Runnable> list = document.getUserData(ACTION_AFTER_COMMIT); if (list == null) { document.putUserData(ACTION_AFTER_COMMIT, list = new SmartList<Runnable>()); } list.add(action); } } @Override public void commitDocument(@NotNull final Document doc) { final Document document = doc instanceof DocumentWindow ? ((DocumentWindow) doc).getDelegate() : doc; if (!isCommitted(document)) { doCommit(document); } } // public for Upsource public boolean finishCommit( @NotNull final Document document, @NotNull final List<Processor<Document>> finishProcessors, final boolean synchronously, @NotNull final Object reason) { assert !myProject.isDisposed() : "Already disposed"; final boolean[] ok = {true}; ApplicationManager.getApplication() .runWriteAction( new CommitToPsiFileAction(document, myProject) { @Override public void run() { ok[0] = finishCommitInWriteAction(document, finishProcessors, synchronously); } }); if (ok[0]) { // otherwise changes maybe not synced to the document yet, and injectors will crash if (!mySynchronizer.isDocumentAffectedByTransactions(document)) { InjectedLanguageManager.getInstance(myProject).startRunInjectors(document, synchronously); } // run after commit actions outside write action runAfterCommitActions(document); if (DebugUtil.DO_EXPENSIVE_CHECKS && !ApplicationInfoImpl.isInPerformanceTest()) { checkAllElementsValid(document, reason); } } return ok[0]; } protected boolean finishCommitInWriteAction( @NotNull final Document document, @NotNull final List<Processor<Document>> finishProcessors, final boolean synchronously) { if (myProject.isDisposed()) return false; assert !(document instanceof DocumentWindow); myIsCommitInProgress = true; boolean success = true; try { final FileViewProvider viewProvider = getCachedViewProvider(document); if (viewProvider != null) { for (Processor<Document> finishRunnable : finishProcessors) { success = finishRunnable.process(document); if (synchronously) { assert success : finishRunnable + " in " + finishProcessors; } if (!success) { break; } } if (success) { myLastCommittedTexts.remove(document); viewProvider.contentsSynchronized(); } } else { handleCommitWithoutPsi(document); } } finally { myDocumentCommitProcessor.log( "in PDI.finishDoc: ", null, synchronously, success, myUncommittedDocuments); if (success) { myUncommittedDocuments.remove(document); myDocumentCommitProcessor.log( "in PDI.finishDoc: removed doc", null, synchronously, success, myUncommittedDocuments); } myIsCommitInProgress = false; myDocumentCommitProcessor.log( "in PDI.finishDoc: exit", null, synchronously, success, myUncommittedDocuments); } return success; } private void checkAllElementsValid(@NotNull Document document, @NotNull final Object reason) { final PsiFile psiFile = getCachedPsiFile(document); if (psiFile != null) { psiFile.accept( new PsiRecursiveElementWalkingVisitor() { @Override public void visitElement(PsiElement element) { if (!element.isValid()) { throw new AssertionError( "Commit to '" + psiFile.getVirtualFile() + "' has led to invalid element: " + element + "; Reason: '" + reason + "'"); } } }); } } private void doCommit(@NotNull final Document document) { assert !myIsCommitInProgress : "Do not call commitDocument() from inside PSI change listener"; ApplicationManager.getApplication() .runWriteAction( new Runnable() { @Override public void run() { // otherwise there are many clients calling commitAllDocs() on PSI childrenChanged() if (getSynchronizer().isDocumentAffectedByTransactions(document)) return; myIsCommitInProgress = true; try { myDocumentCommitProcessor.commitSynchronously(document, myProject); } finally { myIsCommitInProgress = false; } assert !isInUncommittedSet(document) : "Document :" + document; } }); } @Override public <T> T commitAndRunReadAction(@NotNull final Computable<T> computation) { final Ref<T> ref = Ref.create(null); commitAndRunReadAction( new Runnable() { @Override public void run() { ref.set(computation.compute()); } }); return ref.get(); } @Override public void reparseFiles(@NotNull Collection<VirtualFile> files, boolean includeOpenFiles) { FileContentUtilCore.reparseFiles(files); } @Override public void commitAndRunReadAction(@NotNull final Runnable runnable) { final Application application = ApplicationManager.getApplication(); if (SwingUtilities.isEventDispatchThread()) { commitAllDocuments(); runnable.run(); } else { if (ApplicationManager.getApplication().isReadAccessAllowed()) { LOG.error( "Don't call commitAndRunReadAction inside ReadAction, it will cause a deadlock otherwise. " + Thread.currentThread()); } final Semaphore s1 = new Semaphore(); final Semaphore s2 = new Semaphore(); final boolean[] committed = {false}; application.runReadAction( new Runnable() { @Override public void run() { if (myUncommittedDocuments.isEmpty()) { runnable.run(); committed[0] = true; } else { s1.down(); s2.down(); final Runnable commitRunnable = new Runnable() { @Override public void run() { commitAllDocuments(); s1.up(); s2.waitFor(); } }; final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); if (progressIndicator == null) { ApplicationManager.getApplication().invokeLater(commitRunnable); } else { ApplicationManager.getApplication() .invokeLater(commitRunnable, progressIndicator.getModalityState()); } } } }); if (!committed[0]) { s1.waitFor(); application.runReadAction( new Runnable() { @Override public void run() { s2.up(); runnable.run(); } }); } } } /** * Schedules action to be executed when all documents are committed. * * @return true if action has been run immediately, or false if action was scheduled for execution * later. */ @Override public boolean performWhenAllCommitted(@NotNull final Runnable action) { ApplicationManager.getApplication().assertIsDispatchThread(); assert !myProject.isDisposed() : "Already disposed: " + myProject; if (myUncommittedDocuments.isEmpty()) { action.run(); return true; } CompositeRunnable actions = (CompositeRunnable) actionsWhenAllDocumentsAreCommitted.get(PERFORM_ALWAYS_KEY); if (actions == null) { actions = new CompositeRunnable(); actionsWhenAllDocumentsAreCommitted.put(PERFORM_ALWAYS_KEY, actions); } actions.add(action); myDocumentCommitProcessor.log( "PDI: added performWhenAllCommitted", null, false, action, myUncommittedDocuments); return false; } private static class CompositeRunnable extends ArrayList<Runnable> implements Runnable { @Override public void run() { for (Runnable runnable : this) { runnable.run(); } } } private void runAfterCommitActions(@NotNull Document document) { ApplicationManager.getApplication().assertIsDispatchThread(); List<Runnable> list; synchronized (ACTION_AFTER_COMMIT) { list = document.getUserData(ACTION_AFTER_COMMIT); if (list != null) { list = new ArrayList<Runnable>(list); document.putUserData(ACTION_AFTER_COMMIT, null); } } if (list != null) { for (final Runnable runnable : list) { runnable.run(); } } if (!hasUncommitedDocuments() && !actionsWhenAllDocumentsAreCommitted.isEmpty()) { List<Object> keys = new ArrayList<Object>(actionsWhenAllDocumentsAreCommitted.keySet()); for (Object key : keys) { try { Runnable action = actionsWhenAllDocumentsAreCommitted.remove(key); myDocumentCommitProcessor.log( "Running after commit runnable: ", null, false, key, action); action.run(); } catch (Throwable e) { LOG.error(e); } } } } @Override public void addListener(@NotNull Listener listener) { myListeners.add(listener); } @Override public void removeListener(@NotNull Listener listener) { myListeners.remove(listener); } @Override public boolean isDocumentBlockedByPsi(@NotNull Document doc) { return false; } @Override public void doPostponedOperationsAndUnblockDocument(@NotNull Document doc) {} void fireDocumentCreated(@NotNull Document document, PsiFile file) { for (Listener listener : myListeners) { listener.documentCreated(document, file); } } private void fireFileCreated(@NotNull Document document, @NotNull PsiFile file) { for (Listener listener : myListeners) { listener.fileCreated(file, document); } } @Override @NotNull public CharSequence getLastCommittedText(@NotNull Document document) { Pair<CharSequence, Long> pair = myLastCommittedTexts.get(document); return pair != null ? pair.first : document.getImmutableCharSequence(); } @Override public long getLastCommittedStamp(@NotNull Document document) { Pair<CharSequence, Long> pair = myLastCommittedTexts.get(document); return pair != null ? pair.second : document.getModificationStamp(); } @Override @NotNull public Document[] getUncommittedDocuments() { ApplicationManager.getApplication().assertIsDispatchThread(); Document[] documents = myUncommittedDocuments.toArray(new Document[myUncommittedDocuments.size()]); return ArrayUtil.stripTrailingNulls(documents); } boolean isInUncommittedSet(@NotNull Document document) { if (document instanceof DocumentWindow) return isInUncommittedSet(((DocumentWindow) document).getDelegate()); return myUncommittedDocuments.contains(document); } @Override public boolean isUncommited(@NotNull Document document) { return !isCommitted(document); } @Override public boolean isCommitted(@NotNull Document document) { if (document instanceof DocumentWindow) return isCommitted(((DocumentWindow) document).getDelegate()); if (getSynchronizer().isInSynchronization(document)) return true; return !((DocumentEx) document).isInEventsHandling() && !isInUncommittedSet(document); } @Override public boolean hasUncommitedDocuments() { return !myIsCommitInProgress && !myUncommittedDocuments.isEmpty(); } @Override public void beforeDocumentChange(@NotNull DocumentEvent event) { if (myStopTrackingDocuments) return; final Document document = event.getDocument(); if (!(document instanceof DocumentWindow) && !myLastCommittedTexts.containsKey(document)) { myLastCommittedTexts.put( document, Pair.create(document.getImmutableCharSequence(), document.getModificationStamp())); } VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); boolean isRelevant = virtualFile != null && isRelevant(virtualFile); final FileViewProvider viewProvider = getCachedViewProvider(document); boolean inMyProject = viewProvider != null && viewProvider.getManager() == myPsiManager; if (!isRelevant || !inMyProject) { return; } final List<PsiFile> files = viewProvider.getAllFiles(); PsiFile psiCause = null; for (PsiFile file : files) { if (file == null) { throw new AssertionError( "View provider " + viewProvider + " (" + viewProvider.getClass() + ") returned null in its files array: " + files + " for file " + viewProvider.getVirtualFile()); } if (mySynchronizer.isInsideAtomicChange(file)) { psiCause = file; } } if (psiCause == null) { beforeDocumentChangeOnUnlockedDocument(viewProvider); } ((SingleRootFileViewProvider) viewProvider).beforeDocumentChanged(psiCause); } protected void beforeDocumentChangeOnUnlockedDocument( @NotNull final FileViewProvider viewProvider) {} @Override public void documentChanged(DocumentEvent event) { if (myStopTrackingDocuments) return; final Document document = event.getDocument(); VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); boolean isRelevant = virtualFile != null && isRelevant(virtualFile); final FileViewProvider viewProvider = getCachedViewProvider(document); if (viewProvider == null) { handleCommitWithoutPsi(document); return; } boolean inMyProject = viewProvider.getManager() == myPsiManager; if (!isRelevant || !inMyProject) { myLastCommittedTexts.remove(document); return; } ApplicationManager.getApplication().assertWriteAccessAllowed(); final List<PsiFile> files = viewProvider.getAllFiles(); boolean commitNecessary = true; for (PsiFile file : files) { if (mySynchronizer.isInsideAtomicChange(file)) { commitNecessary = false; continue; } assert file instanceof PsiFileImpl || "mock.file".equals(file.getName()) && ApplicationManager.getApplication().isUnitTestMode() : event + "; file=" + file + "; allFiles=" + files + "; viewProvider=" + viewProvider; } boolean forceCommit = ApplicationManager.getApplication().hasWriteAction(ExternalChangeAction.class) && (SystemProperties.getBooleanProperty("idea.force.commit.on.external.change", false) || ApplicationManager.getApplication().isHeadlessEnvironment() && !ApplicationManager.getApplication().isUnitTestMode()); // Consider that it's worth to perform complete re-parse instead of merge if the whole document // text is replaced and // current document lines number is roughly above 5000. This makes sense in situations when // external change is performed // for the huge file (that causes the whole document to be reloaded and 'merge' way takes a // while to complete). if (event.isWholeTextReplaced() && document.getTextLength() > 100000) { document.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, Boolean.TRUE); } if (commitNecessary) { assert !(document instanceof DocumentWindow); myUncommittedDocuments.add(document); myDocumentCommitProcessor.log( "added uncommitted doc", null, false, myProject, document, ((DocumentEx) document).isInBulkUpdate()); if (forceCommit) { commitDocument(document); } else if (!((DocumentEx) document).isInBulkUpdate() && myPerformBackgroundCommit) { myDocumentCommitProcessor.commitAsynchronously(myProject, document, event); } } else { myLastCommittedTexts.remove(document); } } void handleCommitWithoutPsi(@NotNull Document document) { final Pair<CharSequence, Long> prevPair = myLastCommittedTexts.remove(document); if (prevPair == null) { return; } if (!myProject.isInitialized() || myProject.isDisposed()) { return; } myUncommittedDocuments.remove(document); VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); if (virtualFile == null || !FileIndexFacade.getInstance(myProject).isInContent(virtualFile)) { return; } final PsiFile psiFile = getPsiFile(document); if (psiFile == null) { return; } // we can end up outside write action here if the document has forUseInNonAWTThread=true ApplicationManager.getApplication() .runWriteAction( new ExternalChangeAction() { @Override public void run() { psiFile.getViewProvider().beforeContentsSynchronized(); synchronized (PsiLock.LOCK) { final int oldLength = prevPair.first.length(); PsiManagerImpl manager = (PsiManagerImpl) psiFile.getManager(); BlockSupportImpl.sendBeforeChildrenChangeEvent(manager, psiFile, true); BlockSupportImpl.sendBeforeChildrenChangeEvent(manager, psiFile, false); if (psiFile instanceof PsiFileImpl) { ((PsiFileImpl) psiFile).onContentReload(); } BlockSupportImpl.sendAfterChildrenChangedEvent( manager, psiFile, oldLength, false); BlockSupportImpl.sendAfterChildrenChangedEvent(manager, psiFile, oldLength, true); } psiFile.getViewProvider().contentsSynchronized(); } }); } private boolean isRelevant(@NotNull VirtualFile virtualFile) { return !virtualFile.getFileType().isBinary() && !myProject.isDisposed(); } public static boolean checkConsistency(@NotNull PsiFile psiFile, @NotNull Document document) { // todo hack if (psiFile.getVirtualFile() == null) return true; CharSequence editorText = document.getCharsSequence(); int documentLength = document.getTextLength(); if (psiFile.textMatches(editorText)) { LOG.assertTrue(psiFile.getTextLength() == documentLength); return true; } char[] fileText = psiFile.textToCharArray(); @SuppressWarnings("NonConstantStringShouldBeStringBuffer") @NonNls String error = "File '" + psiFile.getName() + "' text mismatch after reparse. " + "File length=" + fileText.length + "; Doc length=" + documentLength + "\n"; int i = 0; for (; i < documentLength; i++) { if (i >= fileText.length) { error += "editorText.length > psiText.length i=" + i + "\n"; break; } if (i >= editorText.length()) { error += "editorText.length > psiText.length i=" + i + "\n"; break; } if (editorText.charAt(i) != fileText[i]) { error += "first unequal char i=" + i + "\n"; break; } } // error += "*********************************************" + "\n"; // if (i <= 500){ // error += "Equal part:" + editorText.subSequence(0, i) + "\n"; // } // else{ // error += "Equal part start:\n" + editorText.subSequence(0, 200) + "\n"; // error += "................................................" + "\n"; // error += "................................................" + "\n"; // error += "................................................" + "\n"; // error += "Equal part end:\n" + editorText.subSequence(i - 200, i) + "\n"; // } error += "*********************************************" + "\n"; error += "Editor Text tail:(" + (documentLength - i) + ")\n"; // + editorText.subSequence(i, Math.min(i + 300, documentLength)) + "\n"; error += "*********************************************" + "\n"; error += "Psi Text tail:(" + (fileText.length - i) + ")\n"; error += "*********************************************" + "\n"; if (document instanceof DocumentWindow) { error += "doc: '" + document.getText() + "'\n"; error += "psi: '" + psiFile.getText() + "'\n"; error += "ast: '" + psiFile.getNode().getText() + "'\n"; error += psiFile.getLanguage() + "\n"; PsiElement context = InjectedLanguageManager.getInstance(psiFile.getProject()).getInjectionHost(psiFile); if (context != null) { error += "context: " + context + "; text: '" + context.getText() + "'\n"; error += "context file: " + context.getContainingFile() + "\n"; } error += "document window ranges: " + Arrays.asList(((DocumentWindow) document).getHostRanges()) + "\n"; } LOG.error(error); // document.replaceString(0, documentLength, psiFile.getText()); return false; } @TestOnly public void clearUncommittedDocuments() { myLastCommittedTexts.clear(); myUncommittedDocuments.clear(); mySynchronizer.cleanupForNextTest(); } @TestOnly public void disableBackgroundCommit(@NotNull Disposable parentDisposable) { assert myPerformBackgroundCommit; myPerformBackgroundCommit = false; Disposer.register( parentDisposable, new Disposable() { @Override public void dispose() { myPerformBackgroundCommit = true; } }); } @NotNull public PsiToDocumentSynchronizer getSynchronizer() { return mySynchronizer; } }
public class FindUsagesManager implements JDOMExternalizable { private static final Logger LOG = Logger.getInstance("#com.intellij.find.findParameterUsages.FindUsagesManager"); private enum FileSearchScope { FROM_START, FROM_END, AFTER_CARET, BEFORE_CARET } private static final Key<String> KEY_START_USAGE_AGAIN = Key.create("KEY_START_USAGE_AGAIN"); @NonNls private static final String VALUE_START_USAGE_AGAIN = "START_AGAIN"; private final Project myProject; private final com.intellij.usages.UsageViewManager myAnotherManager; private boolean myToOpenInNewTab = false; public static class SearchData { public SmartPsiElementPointer[] myElements = null; public FindUsagesOptions myOptions = null; public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final SearchData that = (SearchData) o; return Arrays.equals(myElements, that.myElements) && (myOptions != null ? myOptions.equals(that.myOptions) : that.myOptions == null); } public int hashCode() { return myElements != null ? Arrays.hashCode(myElements) : 0; } } private SearchData myLastSearchInFileData = new SearchData(); private final List<SearchData> myFindUsagesHistory = ContainerUtil.createLockFreeCopyOnWriteList(); public FindUsagesManager( @NotNull Project project, @NotNull com.intellij.usages.UsageViewManager anotherManager) { myProject = project; myAnotherManager = anotherManager; } public boolean canFindUsages(@NotNull final PsiElement element) { for (FindUsagesHandlerFactory factory : Extensions.getExtensions(FindUsagesHandlerFactory.EP_NAME, myProject)) { try { if (factory.canFindUsages(element)) { return true; } } catch (IndexNotReadyException e) { throw e; } catch (Exception e) { LOG.error(e); } } return false; } public void clearFindingNextUsageInFile() { myLastSearchInFileData.myOptions = null; myLastSearchInFileData.myElements = null; } public boolean findNextUsageInFile(FileEditor editor) { return findUsageInFile(editor, FileSearchScope.AFTER_CARET); } public boolean findPreviousUsageInFile(FileEditor editor) { return findUsageInFile(editor, FileSearchScope.BEFORE_CARET); } @Override public void readExternal(Element element) throws InvalidDataException { myToOpenInNewTab = JDOMExternalizer.readBoolean(element, "OPEN_NEW_TAB"); } @Override public void writeExternal(Element element) throws WriteExternalException { JDOMExternalizer.write(element, "OPEN_NEW_TAB", myToOpenInNewTab); } private boolean findUsageInFile(@NotNull FileEditor editor, @NotNull FileSearchScope direction) { PsiElement[] elements = restorePsiElements(myLastSearchInFileData, true); if (elements == null) return false; if (elements.length == 0) return true; // all elements have invalidated UsageInfoToUsageConverter.TargetElementsDescriptor descriptor = new UsageInfoToUsageConverter.TargetElementsDescriptor(elements); // todo TextEditor textEditor = (TextEditor) editor; Document document = textEditor.getEditor().getDocument(); PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(document); if (psiFile == null) return false; final FindUsagesHandler handler = getFindUsagesHandler(elements[0], false); if (handler == null) return false; findUsagesInEditor( descriptor, handler, psiFile, direction, myLastSearchInFileData.myOptions, textEditor); return true; } // returns null if cannot find, empty Pair if all elements have been changed @Nullable private PsiElement[] restorePsiElements(SearchData searchData, final boolean showErrorMessage) { if (searchData == null) return null; SmartPsiElementPointer[] lastSearchElements = searchData.myElements; if (lastSearchElements == null) return null; List<PsiElement> elements = new ArrayList<PsiElement>(); for (SmartPsiElementPointer pointer : lastSearchElements) { PsiElement element = pointer.getElement(); if (element != null) elements.add(element); } if (elements.isEmpty() && showErrorMessage) { Messages.showMessageDialog( myProject, FindBundle.message("find.searched.elements.have.been.changed.error"), FindBundle.message("cannot.search.for.usages.title"), Messages.getInformationIcon()); // SCR #10022 // clearFindingNextUsageInFile(); return PsiElement.EMPTY_ARRAY; } return PsiUtilCore.toPsiElementArray(elements); } private void initLastSearchElement( final FindUsagesOptions findUsagesOptions, UsageInfoToUsageConverter.TargetElementsDescriptor descriptor) { myLastSearchInFileData = createSearchData(descriptor.getAllElements(), findUsagesOptions); } private SearchData createSearchData( @NotNull List<? extends PsiElement> psiElements, final FindUsagesOptions findUsagesOptions) { SearchData data = new SearchData(); data.myElements = new SmartPsiElementPointer[psiElements.size()]; int idx = 0; for (PsiElement psiElement : psiElements) { data.myElements[idx++] = SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(psiElement); } data.myOptions = findUsagesOptions; return data; } @Nullable public FindUsagesHandler getFindUsagesHandler( PsiElement element, final boolean forHighlightUsages) { for (FindUsagesHandlerFactory factory : Extensions.getExtensions(FindUsagesHandlerFactory.EP_NAME, myProject)) { if (factory.canFindUsages(element)) { final FindUsagesHandler handler = factory.createFindUsagesHandler(element, forHighlightUsages); if (handler == FindUsagesHandler.NULL_HANDLER) return null; if (handler != null) { return handler; } } } return null; } @Nullable public FindUsagesHandler getNewFindUsagesHandler( @NotNull PsiElement element, final boolean forHighlightUsages) { for (FindUsagesHandlerFactory factory : Extensions.getExtensions(FindUsagesHandlerFactory.EP_NAME, myProject)) { if (factory.canFindUsages(element)) { Class<? extends FindUsagesHandlerFactory> aClass = factory.getClass(); FindUsagesHandlerFactory copy = (FindUsagesHandlerFactory) new ConstructorInjectionComponentAdapter(aClass.getName(), aClass) .getComponentInstance(myProject.getPicoContainer()); final FindUsagesHandler handler = copy.createFindUsagesHandler(element, forHighlightUsages); if (handler == FindUsagesHandler.NULL_HANDLER) return null; if (handler != null) { return handler; } } } return null; } public void findUsages( @NotNull PsiElement psiElement, final PsiFile scopeFile, final FileEditor editor, boolean showDialog) { doShowDialogAndStartFind(psiElement, scopeFile, editor, showDialog, true); } private void doShowDialogAndStartFind( @NotNull PsiElement psiElement, PsiFile scopeFile, FileEditor editor, boolean showDialog, boolean useMaximalScope) { FindUsagesHandler handler = getNewFindUsagesHandler(psiElement, false); if (handler == null) return; boolean singleFile = scopeFile != null; AbstractFindUsagesDialog dialog = handler.getFindUsagesDialog(singleFile, shouldOpenInNewTab(), mustOpenInNewTab()); if (showDialog) { dialog.show(); if (!dialog.isOK()) return; } else { dialog.close(DialogWrapper.OK_EXIT_CODE); } setOpenInNewTab(dialog.isShowInSeparateWindow()); FindUsagesOptions findUsagesOptions = dialog.calcFindUsagesOptions(); if (!showDialog && useMaximalScope) { findUsagesOptions.searchScope = getMaximalScope(handler); } clearFindingNextUsageInFile(); LOG.assertTrue(handler.getPsiElement().isValid()); PsiElement[] primaryElements = handler.getPrimaryElements(); checkNotNull(primaryElements, handler, "getPrimaryElements()"); PsiElement[] secondaryElements = handler.getSecondaryElements(); checkNotNull(secondaryElements, handler, "getSecondaryElements()"); UsageInfoToUsageConverter.TargetElementsDescriptor descriptor = new UsageInfoToUsageConverter.TargetElementsDescriptor(primaryElements, secondaryElements); if (singleFile) { findUsagesOptions = findUsagesOptions.clone(); editor.putUserData(KEY_START_USAGE_AGAIN, null); findUsagesInEditor( descriptor, handler, scopeFile, FileSearchScope.FROM_START, findUsagesOptions, editor); } else { findUsages( descriptor, handler, dialog.isSkipResultsWhenOneUsage(), dialog.isShowInSeparateWindow(), findUsagesOptions); } } public void showSettingsAndFindUsages(@NotNull NavigationItem[] targets) { UsageTarget[] usageTargets = (UsageTarget[]) targets; PsiElement[] elements = getPsiElements(usageTargets); if (elements.length == 0) return; PsiElement psiElement = elements[0]; doShowDialogAndStartFind(psiElement, null, null, true, false); } private static void checkNotNull( @NotNull PsiElement[] primaryElements, @NotNull FindUsagesHandler handler, @NonNls @NotNull String methodName) { for (PsiElement element : primaryElements) { if (element == null) { LOG.error( handler + "." + methodName + " has returned array with null elements: " + Arrays.asList(primaryElements)); } } } public boolean isUsed(@NotNull PsiElement element, @NotNull FindUsagesOptions findUsagesOptions) { FindUsagesHandler handler = getFindUsagesHandler(element, true); if (handler == null) return false; UsageInfoToUsageConverter.TargetElementsDescriptor descriptor = new UsageInfoToUsageConverter.TargetElementsDescriptor(element); UsageSearcher usageSearcher = createUsageSearcher(descriptor, handler, findUsagesOptions, null); final AtomicBoolean used = new AtomicBoolean(); usageSearcher.generate( new Processor<Usage>() { @Override public boolean process(final Usage usage) { used.set(true); return false; } }); return used.get(); } @NotNull public static ProgressIndicator startProcessUsages( @NotNull FindUsagesHandler handler, @NotNull UsageInfoToUsageConverter.TargetElementsDescriptor descriptor, @NotNull final Processor<Usage> processor, @NotNull FindUsagesOptions findUsagesOptions, @NotNull final Runnable onComplete) { final UsageSearcher usageSearcher = createUsageSearcher(descriptor, handler, findUsagesOptions, null); final ProgressIndicatorBase indicator = new ProgressIndicatorBase(); ApplicationManager.getApplication() .executeOnPooledThread( new Runnable() { @Override public void run() { try { ProgressManager.getInstance() .runProcess( new Runnable() { @Override public void run() { usageSearcher.generate(processor); } }, indicator); } finally { onComplete.run(); } } }); return indicator; } @NotNull public UsageViewPresentation createPresentation( @NotNull FindUsagesHandler handler, @NotNull FindUsagesOptions findUsagesOptions) { PsiElement element = handler.getPsiElement(); LOG.assertTrue(element.isValid()); return createPresentation(element, findUsagesOptions, myToOpenInNewTab); } private void setOpenInNewTab(final boolean toOpenInNewTab) { if (!mustOpenInNewTab()) { myToOpenInNewTab = toOpenInNewTab; } } private boolean shouldOpenInNewTab() { return mustOpenInNewTab() || myToOpenInNewTab; } private boolean mustOpenInNewTab() { Content selectedContent = UsageViewManager.getInstance(myProject).getSelectedContent(true); return selectedContent != null && selectedContent.isPinned(); } private static UsageSearcher createUsageSearcher( @NotNull final UsageInfoToUsageConverter.TargetElementsDescriptor descriptor, @NotNull final FindUsagesHandler handler, @NotNull FindUsagesOptions _options, final PsiFile scopeFile) { final FindUsagesOptions options = _options.clone(); return new UsageSearcher() { @Override public void generate(@NotNull final Processor<Usage> processor) { if (scopeFile != null) { options.searchScope = new LocalSearchScope(scopeFile); } final Processor<UsageInfo> usageInfoProcessor = new CommonProcessors.UniqueProcessor<UsageInfo>( new Processor<UsageInfo>() { @Override public boolean process(UsageInfo usageInfo) { return processor.process( UsageInfoToUsageConverter.convert(descriptor, usageInfo)); } }); final List<? extends PsiElement> elements = ApplicationManager.getApplication() .runReadAction( new Computable<List<? extends PsiElement>>() { @Override public List<? extends PsiElement> compute() { return descriptor.getAllElements(); } }); options.fastTrack = new SearchRequestCollector(new SearchSession()); try { for (final PsiElement element : elements) { ApplicationManager.getApplication() .runReadAction( new Runnable() { @Override public void run() { LOG.assertTrue(element.isValid()); } }); handler.processElementUsages(element, usageInfoProcessor, options); for (CustomUsageSearcher searcher : Extensions.getExtensions(CustomUsageSearcher.EP_NAME)) { try { searcher.processElementUsages(element, processor, options); } catch (IndexNotReadyException e) { DumbService.getInstance(element.getProject()) .showDumbModeNotification("Find usages is not available during indexing"); } catch (Exception e) { LOG.error(e); } } } Project project = ApplicationManager.getApplication() .runReadAction( new Computable<Project>() { @Override public Project compute() { return scopeFile != null ? scopeFile.getProject() : !elements.isEmpty() ? elements.get(0).getProject() : handler.getProject(); } }); PsiSearchHelper.SERVICE .getInstance(project) .processRequests( options.fastTrack, new ReadActionProcessor<PsiReference>() { @Override public boolean processInReadAction(final PsiReference ref) { return !ref.getElement().isValid() || usageInfoProcessor.process(new UsageInfo(ref)); } }); } finally { options.fastTrack = null; } } }; } private static PsiElement2UsageTargetAdapter[] convertToUsageTargets( final List<? extends PsiElement> elementsToSearch) { final ArrayList<PsiElement2UsageTargetAdapter> targets = new ArrayList<PsiElement2UsageTargetAdapter>(elementsToSearch.size()); for (PsiElement element : elementsToSearch) { convertToUsageTarget(targets, element); } return targets.toArray(new PsiElement2UsageTargetAdapter[targets.size()]); } private void findUsages( @NotNull final UsageInfoToUsageConverter.TargetElementsDescriptor descriptor, @NotNull final FindUsagesHandler handler, final boolean toSkipUsagePanelWhenOneUsage, final boolean toOpenInNewTab, @NotNull final FindUsagesOptions findUsagesOptions) { List<? extends PsiElement> elements = descriptor.getAllElements(); if (elements.isEmpty()) { throw new AssertionError(handler + " " + findUsagesOptions); } final UsageTarget[] targets = convertToUsageTargets(elements); myAnotherManager.searchAndShowUsages( targets, new Factory<UsageSearcher>() { @Override public UsageSearcher create() { return createUsageSearcher(descriptor, handler, findUsagesOptions, null); } }, !toSkipUsagePanelWhenOneUsage, true, createPresentation(elements.get(0), findUsagesOptions, toOpenInNewTab), null); addToHistory(elements, findUsagesOptions); } @NotNull private static UsageViewPresentation createPresentation( @NotNull PsiElement psiElement, @NotNull FindUsagesOptions findUsagesOptions, boolean toOpenInNewTab) { UsageViewPresentation presentation = new UsageViewPresentation(); String scopeString = findUsagesOptions.searchScope == null ? null : findUsagesOptions.searchScope.getDisplayName(); presentation.setScopeText(scopeString); String usagesString = generateUsagesString(findUsagesOptions); presentation.setUsagesString(usagesString); String title = scopeString == null ? FindBundle.message( "find.usages.of.element.panel.title", usagesString, UsageViewUtil.getLongName(psiElement)) : FindBundle.message( "find.usages.of.element.in.scope.panel.title", usagesString, UsageViewUtil.getLongName(psiElement), scopeString); presentation.setTabText(title); presentation.setTabName( FindBundle.message( "find.usages.of.element.tab.name", usagesString, UsageViewUtil.getShortName(psiElement))); presentation.setTargetsNodeText(StringUtil.capitalize(UsageViewUtil.getType(psiElement))); presentation.setOpenInNewTab(toOpenInNewTab); return presentation; } private void findUsagesInEditor( @NotNull UsageInfoToUsageConverter.TargetElementsDescriptor descriptor, @NotNull FindUsagesHandler handler, @NotNull PsiFile scopeFile, @NotNull FileSearchScope direction, @NotNull final FindUsagesOptions findUsagesOptions, @NotNull FileEditor fileEditor) { initLastSearchElement(findUsagesOptions, descriptor); clearStatusBar(); final FileEditorLocation currentLocation = fileEditor.getCurrentLocation(); final UsageSearcher usageSearcher = createUsageSearcher(descriptor, handler, findUsagesOptions, scopeFile); AtomicBoolean usagesWereFound = new AtomicBoolean(); Usage fUsage = findSiblingUsage(usageSearcher, direction, currentLocation, usagesWereFound, fileEditor); if (fUsage != null) { fUsage.navigate(true); fUsage.selectInEditor(); } else if (!usagesWereFound.get()) { String message = getNoUsagesFoundMessage(descriptor.getPrimaryElements()[0]) + " in " + scopeFile.getName(); showHintOrStatusBarMessage(message, fileEditor); } else { fileEditor.putUserData(KEY_START_USAGE_AGAIN, VALUE_START_USAGE_AGAIN); showHintOrStatusBarMessage( getSearchAgainMessage(descriptor.getPrimaryElements()[0], direction), fileEditor); } } private static String getNoUsagesFoundMessage(PsiElement psiElement) { String elementType = UsageViewUtil.getType(psiElement); String elementName = UsageViewUtil.getShortName(psiElement); return FindBundle.message( "find.usages.of.element_type.element_name.not.found.message", elementType, elementName); } private void clearStatusBar() { StatusBar.Info.set("", myProject); } private static String getSearchAgainMessage(PsiElement element, final FileSearchScope direction) { String message = getNoUsagesFoundMessage(element); if (direction == FileSearchScope.AFTER_CARET) { AnAction action = ActionManager.getInstance().getAction(IdeActions.ACTION_FIND_NEXT); String shortcutsText = KeymapUtil.getFirstKeyboardShortcutText(action); if (shortcutsText.isEmpty()) { message = FindBundle.message("find.search.again.from.top.action.message", message); } else { message = FindBundle.message("find.search.again.from.top.hotkey.message", message, shortcutsText); } } else { String shortcutsText = KeymapUtil.getFirstKeyboardShortcutText( ActionManager.getInstance().getAction(IdeActions.ACTION_FIND_PREVIOUS)); if (shortcutsText.isEmpty()) { message = FindBundle.message("find.search.again.from.bottom.action.message", message); } else { message = FindBundle.message( "find.search.again.from.bottom.hotkey.message", message, shortcutsText); } } return message; } private void showHintOrStatusBarMessage(String message, FileEditor fileEditor) { if (fileEditor instanceof TextEditor) { TextEditor textEditor = (TextEditor) fileEditor; showEditorHint(message, textEditor.getEditor()); } else { StatusBar.Info.set(message, myProject); } } private static Usage findSiblingUsage( @NotNull final UsageSearcher usageSearcher, @NotNull FileSearchScope dir, final FileEditorLocation currentLocation, @NotNull final AtomicBoolean usagesWereFound, @NotNull FileEditor fileEditor) { if (fileEditor.getUserData(KEY_START_USAGE_AGAIN) != null) { dir = dir == FileSearchScope.AFTER_CARET ? FileSearchScope.FROM_START : FileSearchScope.FROM_END; } final FileSearchScope direction = dir; final AtomicReference<Usage> foundUsage = new AtomicReference<Usage>(); usageSearcher.generate( new Processor<Usage>() { @Override public boolean process(Usage usage) { usagesWereFound.set(true); if (direction == FileSearchScope.FROM_START) { foundUsage.compareAndSet(null, usage); return false; } if (direction == FileSearchScope.FROM_END) { foundUsage.set(usage); } else if (direction == FileSearchScope.AFTER_CARET) { if (Comparing.compare(usage.getLocation(), currentLocation) > 0) { foundUsage.set(usage); return false; } } else if (direction == FileSearchScope.BEFORE_CARET) { if (Comparing.compare(usage.getLocation(), currentLocation) >= 0) { return false; } while (true) { Usage found = foundUsage.get(); if (found == null) { if (foundUsage.compareAndSet(null, usage)) break; } else { if (Comparing.compare(found.getLocation(), usage.getLocation()) < 0 && foundUsage.compareAndSet(found, usage)) break; } } } return true; } }); fileEditor.putUserData(KEY_START_USAGE_AGAIN, null); return foundUsage.get(); } private static void convertToUsageTarget( @NotNull List<PsiElement2UsageTargetAdapter> targets, @NotNull PsiElement elementToSearch) { if (elementToSearch instanceof NavigationItem) { targets.add(new PsiElement2UsageTargetAdapter(elementToSearch)); } else { throw new IllegalArgumentException( "Wrong usage target:" + elementToSearch + "; " + elementToSearch.getClass()); } } private static String generateUsagesString(final FindUsagesOptions selectedOptions) { return selectedOptions.generateUsagesString(); } private static void showEditorHint(String message, final Editor editor) { JComponent component = HintUtil.createInformationLabel(message); final LightweightHint hint = new LightweightHint(component); HintManagerImpl.getInstanceImpl() .showEditorHint( hint, editor, HintManager.UNDER, HintManager.HIDE_BY_ANY_KEY | HintManager.HIDE_BY_TEXT_CHANGE | HintManager.HIDE_BY_SCROLLING, 0, false); } public static String getHelpID(PsiElement element) { return LanguageFindUsages.INSTANCE.forLanguage(element.getLanguage()).getHelpId(element); } private void addToHistory( final List<? extends PsiElement> elements, final FindUsagesOptions findUsagesOptions) { SearchData data = createSearchData(elements, findUsagesOptions); myFindUsagesHistory.remove(data); myFindUsagesHistory.add(data); // todo configure history depth limit if (myFindUsagesHistory.size() > 15) { myFindUsagesHistory.remove(0); } } public void rerunAndRecallFromHistory(@NotNull SearchData searchData) { myFindUsagesHistory.remove(searchData); PsiElement[] elements = restorePsiElements(searchData, true); if (elements == null || elements.length == 0) return; UsageInfoToUsageConverter.TargetElementsDescriptor descriptor = new UsageInfoToUsageConverter.TargetElementsDescriptor(elements); final FindUsagesHandler handler = getFindUsagesHandler(elements[0], false); if (handler == null) return; findUsages(descriptor, handler, false, false, searchData.myOptions); } // most recent entry is at the end of the list public List<SearchData> getFindUsageHistory() { removeInvalidElementsFromHistory(); return Collections.unmodifiableList(myFindUsagesHistory); } private void removeInvalidElementsFromHistory() { for (SearchData data : myFindUsagesHistory) { PsiElement[] elements = restorePsiElements(data, false); if (elements == null || elements.length == 0) myFindUsagesHistory.remove(data); } } @NotNull private static PsiElement[] getPsiElements(@NotNull UsageTarget[] targets) { List<PsiElement> result = new ArrayList<PsiElement>(); for (UsageTarget target : targets) { if (target instanceof PsiElementUsageTarget) { PsiElement element = ((PsiElementUsageTarget) target).getElement(); if (element != null) { result.add(element); } } } return PsiUtilCore.toPsiElementArray(result); } @NotNull public static GlobalSearchScope getMaximalScope(@NotNull FindUsagesHandler handler) { PsiElement element = handler.getPsiElement(); Project project = element.getProject(); PsiFile file = element.getContainingFile(); if (file != null && ProjectFileIndex.SERVICE .getInstance(project) .isInContent(file.getViewProvider().getVirtualFile())) { return GlobalSearchScope.projectScope(project); } return GlobalSearchScope.allScope(project); } }
public class StructureViewComponent extends SimpleToolWindowPanel implements TreeActionsOwner, DataProvider, StructureView.Scrollable { private static final Logger LOG = Logger.getInstance("#com.intellij.ide.structureView.newStructureView.StructureViewComponent"); @NonNls private static final String ourHelpID = "viewingStructure.fileStructureView"; private StructureTreeBuilder myAbstractTreeBuilder; private FileEditor myFileEditor; private final TreeModelWrapper myTreeModelWrapper; private StructureViewState myStructureViewState; private boolean myAutoscrollFeedback; private final Alarm myAutoscrollAlarm = new Alarm(); private final CopyPasteDelegator myCopyPasteDelegator; private final MyAutoScrollToSourceHandler myAutoScrollToSourceHandler; private final AutoScrollFromSourceHandler myAutoScrollFromSourceHandler; private static final Key<StructureViewState> STRUCTURE_VIEW_STATE_KEY = Key.create("STRUCTURE_VIEW_STATE"); private final Project myProject; private final StructureViewModel myTreeModel; private static int ourSettingsModificationCount; private Tree myTree; public StructureViewComponent( FileEditor editor, StructureViewModel structureViewModel, Project project) { this(editor, structureViewModel, project, true); } public StructureViewComponent( final FileEditor editor, final StructureViewModel structureViewModel, final Project project, final boolean showRootNode) { super(true, true); myProject = project; myFileEditor = editor; myTreeModel = structureViewModel; myTreeModelWrapper = new TreeModelWrapper(myTreeModel, this); SmartTreeStructure treeStructure = new SmartTreeStructure(project, myTreeModelWrapper) { public void rebuildTree() { if (!isDisposed()) { super.rebuildTree(); } } public boolean isToBuildChildrenInBackground(final Object element) { return getRootElement() == element; } protected TreeElementWrapper createTree() { return new StructureViewTreeElementWrapper(myProject, myModel.getRoot(), myModel); } @Override public String toString() { return "structure view tree structure(model=" + myTreeModel + ")"; } }; final DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode(treeStructure.getRootElement())); myTree = new Tree(model); myTree.setRootVisible(showRootNode); myTree.setShowsRootHandles(true); myAbstractTreeBuilder = new StructureTreeBuilder( project, myTree, (DefaultTreeModel) myTree.getModel(), treeStructure, myTreeModelWrapper) { @Override protected boolean validateNode(Object child) { return isValid(child); } }; Disposer.register(this, myAbstractTreeBuilder); Disposer.register( myAbstractTreeBuilder, new Disposable() { public void dispose() { storeState(); } }); setContent(ScrollPaneFactory.createScrollPane(myAbstractTreeBuilder.getTree())); myAbstractTreeBuilder.getTree().setCellRenderer(new NodeRenderer()); myAutoScrollToSourceHandler = new MyAutoScrollToSourceHandler(); myAutoScrollFromSourceHandler = new MyAutoScrollFromSourceHandler(myProject, this); JComponent toolbarComponent = ActionManager.getInstance() .createActionToolbar(ActionPlaces.STRUCTURE_VIEW_TOOLBAR, createActionGroup(), true) .getComponent(); setToolbar(toolbarComponent); installTree(); myCopyPasteDelegator = new CopyPasteDelegator(myProject, getTree()) { @NotNull protected PsiElement[] getSelectedElements() { return getSelectedPsiElements(); } }; } private void installTree() { getTree().getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); myAutoScrollToSourceHandler.install(getTree()); myAutoScrollFromSourceHandler.install(); TreeUtil.installActions(getTree()); new TreeSpeedSearch(getTree()); addTreeKeyListener(); addTreeMouseListeners(); restoreState(); } private PsiElement[] getSelectedPsiElements() { return filterPsiElements(getSelectedElements()); } @NotNull private static PsiElement[] filterPsiElements(Object[] selectedElements) { if (selectedElements == null) { return PsiElement.EMPTY_ARRAY; } ArrayList<PsiElement> psiElements = new ArrayList<PsiElement>(); for (Object selectedElement : selectedElements) { if (selectedElement instanceof PsiElement) { psiElements.add((PsiElement) selectedElement); } } return PsiUtilBase.toPsiElementArray(psiElements); } private Object[] getSelectedElements() { final JTree tree = getTree(); return tree != null ? convertPathsToValues(tree.getSelectionPaths()) : ArrayUtil.EMPTY_OBJECT_ARRAY; } @Nullable private Object[] getSelectedTreeElements() { final JTree tree = getTree(); return tree != null ? convertPathsToTreeElements(tree.getSelectionPaths()) : null; } private static Object[] convertPathsToValues(TreePath[] selectionPaths) { if (selectionPaths != null) { List<Object> result = new ArrayList<Object>(); for (TreePath selectionPath : selectionPaths) { final Object userObject = ((DefaultMutableTreeNode) selectionPath.getLastPathComponent()).getUserObject(); if (userObject instanceof AbstractTreeNode) { Object value = ((AbstractTreeNode) userObject).getValue(); if (value instanceof StructureViewTreeElement) { value = ((StructureViewTreeElement) value).getValue(); } result.add(value); } } return ArrayUtil.toObjectArray(result); } else { return null; } } @Nullable private static Object[] convertPathsToTreeElements(TreePath[] selectionPaths) { if (selectionPaths != null) { Object[] result = new Object[selectionPaths.length]; for (int i = 0; i < selectionPaths.length; i++) { Object userObject = ((DefaultMutableTreeNode) selectionPaths[i].getLastPathComponent()).getUserObject(); if (!(userObject instanceof AbstractTreeNode)) return null; result[i] = ((AbstractTreeNode) userObject).getValue(); } return result; } else { return null; } } private void addTreeMouseListeners() { EditSourceOnDoubleClickHandler.install(getTree()); CustomizationUtil.installPopupHandler( getTree(), IdeActions.GROUP_STRUCTURE_VIEW_POPUP, ActionPlaces.STRUCTURE_VIEW_POPUP); } private void addTreeKeyListener() { getTree() .addKeyListener( new KeyAdapter() { public void keyPressed(KeyEvent e) { if (KeyEvent.VK_ENTER == e.getKeyCode()) { DataContext dataContext = DataManager.getInstance().getDataContext(getTree()); OpenSourceUtil.openSourcesFrom(dataContext, false); } else if (KeyEvent.VK_ESCAPE == e.getKeyCode()) { if (e.isConsumed()) { return; } PsiCopyPasteManager copyPasteManager = PsiCopyPasteManager.getInstance(); boolean[] isCopied = new boolean[1]; if (copyPasteManager.getElements(isCopied) != null && !isCopied[0]) { copyPasteManager.clear(); e.consume(); } } } }); } public void storeState() { if (!isDisposed()) { myStructureViewState = getState(); myFileEditor.putUserData(STRUCTURE_VIEW_STATE_KEY, myStructureViewState); } } public StructureViewState getState() { StructureViewState structureViewState = new StructureViewState(); if (getTree() != null) { structureViewState.setExpandedElements(getExpandedElements()); structureViewState.setSelectedElements(getSelectedElements()); } return structureViewState; } private Object[] getExpandedElements() { final JTree tree = getTree(); if (tree == null) return ArrayUtil.EMPTY_OBJECT_ARRAY; final List<TreePath> expandedPaths = TreeUtil.collectExpandedPaths(tree); return convertPathsToValues(expandedPaths.toArray(new TreePath[expandedPaths.size()])); } public void restoreState() { myStructureViewState = myFileEditor.getUserData(STRUCTURE_VIEW_STATE_KEY); if (myStructureViewState == null) { TreeUtil.expand(getTree(), 2); } else { expandStoredElements(); selectStoredElements(); myFileEditor.putUserData(STRUCTURE_VIEW_STATE_KEY, null); myStructureViewState = null; } } private void selectStoredElements() { Object[] selectedPsiElements = null; if (myStructureViewState != null) { selectedPsiElements = myStructureViewState.getSelectedElements(); } if (selectedPsiElements == null) { getTree().setSelectionPath(new TreePath(getRootNode().getPath())); } else { for (Object element : selectedPsiElements) { if (element instanceof PsiElement && !((PsiElement) element).isValid()) { continue; } addSelectionPathTo(element); } } } public void addSelectionPathTo(final Object element) { DefaultMutableTreeNode node = myAbstractTreeBuilder.getNodeForElement(element); if (node != null) { final JTree tree = getTree(); final TreePath path = new TreePath(node.getPath()); if (node == tree.getModel().getRoot() && !tree.isExpanded(path)) tree.expandPath(path); tree.addSelectionPath(path); } } private DefaultMutableTreeNode getRootNode() { return (DefaultMutableTreeNode) getTree().getModel().getRoot(); } private void expandStoredElements() { Object[] expandedPsiElements = null; if (myStructureViewState != null) { expandedPsiElements = myStructureViewState.getExpandedElements(); } if (expandedPsiElements == null) { getTree().expandPath(new TreePath(getRootNode().getPath())); } else { for (Object element : expandedPsiElements) { if (element instanceof PsiElement && !((PsiElement) element).isValid()) { continue; } expandPathToElement(element); } } } protected ActionGroup createActionGroup() { DefaultActionGroup result = new DefaultActionGroup(); Sorter[] sorters = myTreeModel.getSorters(); for (final Sorter sorter : sorters) { if (sorter.isVisible()) { result.add(new TreeActionWrapper(sorter, this)); } } if (sorters.length > 0) { result.addSeparator(); } Grouper[] groupers = myTreeModel.getGroupers(); for (Grouper grouper : groupers) { result.add(new TreeActionWrapper(grouper, this)); } Filter[] filters = myTreeModel.getFilters(); for (Filter filter : filters) { result.add(new TreeActionWrapper(filter, this)); } if (myTreeModel instanceof ProvidingTreeModel) { final Collection<NodeProvider> providers = ((ProvidingTreeModel) myTreeModel).getNodeProviders(); for (NodeProvider provider : providers) { result.add(new TreeActionWrapper(provider, this)); } } result.add(new ExpandAllAction(getTree())); result.add(new CollapseAllAction(getTree())); if (showScrollToFromSourceActions()) { result.addSeparator(); result.add(myAutoScrollToSourceHandler.createToggleAction()); result.add(myAutoScrollFromSourceHandler.createToggleAction()); } return result; } protected boolean showScrollToFromSourceActions() { return true; } public FileEditor getFileEditor() { return myFileEditor; } public AsyncResult<AbstractTreeNode> expandPathToElement(Object element) { if (myAbstractTreeBuilder == null) return new AsyncResult.Rejected<AbstractTreeNode>(); ArrayList<AbstractTreeNode> pathToElement = getPathToElement(element); if (pathToElement.isEmpty()) return new AsyncResult.Rejected<AbstractTreeNode>(); final AsyncResult<AbstractTreeNode> result = new AsyncResult<AbstractTreeNode>(); final AbstractTreeNode toExpand = pathToElement.get(pathToElement.size() - 1); myAbstractTreeBuilder.expand( toExpand, new Runnable() { public void run() { result.setDone(toExpand); } }); return result; } public boolean select(final Object element, final boolean requestFocus) { myAbstractTreeBuilder .getReady(this) .doWhenDone( new Runnable() { public void run() { expandPathToElement(element) .doWhenDone( new AsyncResult.Handler<AbstractTreeNode>() { public void run(AbstractTreeNode abstractTreeNode) { myAbstractTreeBuilder.select( abstractTreeNode, new Runnable() { public void run() { if (requestFocus) { IdeFocusManager.getInstance(myProject) .requestFocus(myAbstractTreeBuilder.getTree(), false); } } }); } }); } }); return true; } private ArrayList<AbstractTreeNode> getPathToElement(Object element) { ArrayList<AbstractTreeNode> result = new ArrayList<AbstractTreeNode>(); final AbstractTreeStructure treeStructure = myAbstractTreeBuilder.getTreeStructure(); if (treeStructure != null) { addToPath( (AbstractTreeNode) treeStructure.getRootElement(), element, result, new THashSet<Object>()); } return result; } private static boolean addToPath( AbstractTreeNode<?> rootElement, Object element, ArrayList<AbstractTreeNode> result, Collection<Object> processedElements) { Object value = rootElement.getValue(); if (value instanceof StructureViewTreeElement) { value = ((StructureViewTreeElement) value).getValue(); } if (!processedElements.add(value)) { return false; } if (Comparing.equal(value, element)) { result.add(0, rootElement); return true; } Collection<? extends AbstractTreeNode> children = rootElement.getChildren(); for (AbstractTreeNode child : children) { if (addToPath(child, element, result, processedElements)) { result.add(0, rootElement); return true; } } return false; } private static DefaultMutableTreeNode findInChildren( DefaultMutableTreeNode currentTreeNode, AbstractTreeNode topPathElement) { for (int i = 0; i < currentTreeNode.getChildCount(); i++) { TreeNode child = currentTreeNode.getChildAt(i); if (((DefaultMutableTreeNode) child).getUserObject().equals(topPathElement)) { return (DefaultMutableTreeNode) child; } } return null; } private void scrollToSelectedElement() { if (myAutoscrollFeedback) { myAutoscrollFeedback = false; return; } StructureViewFactoryImpl structureViewFactory = (StructureViewFactoryImpl) StructureViewFactoryEx.getInstance(myProject); if (!structureViewFactory.getState().AUTOSCROLL_FROM_SOURCE) { return; } myAutoscrollAlarm.cancelAllRequests(); myAutoscrollAlarm.addRequest( new Runnable() { public void run() { if (myAbstractTreeBuilder == null) { return; } try { selectViewableElement(); } catch (IndexNotReadyException ignore) { } } }, 1000); } private void selectViewableElement() { PsiDocumentManager.getInstance(myProject).commitAllDocuments(); final Object currentEditorElement = myTreeModel.getCurrentEditorElement(); if (currentEditorElement != null) { select(currentEditorElement, false); } } public void dispose() { LOG.assertTrue(EventQueue.isDispatchThread(), Thread.currentThread().getName()); myAbstractTreeBuilder = null; // this will also dispose wrapped TreeModel myTreeModelWrapper.dispose(); myFileEditor = null; } public boolean isDisposed() { return myAbstractTreeBuilder == null; } public void centerSelectedRow() { TreePath path = getTree().getSelectionPath(); if (path == null) { return; } myAutoScrollToSourceHandler.setShouldAutoScroll(false); TreeUtil.showRowCentered(getTree(), getTree().getRowForPath(path), false); myAutoScrollToSourceHandler.setShouldAutoScroll(true); } public void setActionActive(String name, boolean state) { StructureViewFactoryEx.getInstanceEx(myProject).setActiveAction(name, state); rebuild(); TreeUtil.expand(getTree(), 2); } protected void rebuild() { storeState(); ++ourSettingsModificationCount; ((SmartTreeStructure) myAbstractTreeBuilder.getTreeStructure()).rebuildTree(); myAbstractTreeBuilder.updateFromRoot(); restoreState(); } public boolean isActionActive(String name) { return !myProject.isDisposed() && StructureViewFactoryEx.getInstanceEx(myProject).isActionActive(name); } public AbstractTreeStructure getTreeStructure() { return myAbstractTreeBuilder.getTreeStructure(); } public JTree getTree() { return myTree; } private final class MyAutoScrollToSourceHandler extends AutoScrollToSourceHandler { private boolean myShouldAutoScroll = true; public void setShouldAutoScroll(boolean shouldAutoScroll) { myShouldAutoScroll = shouldAutoScroll; } protected boolean isAutoScrollMode() { return myShouldAutoScroll && !myProject.isDisposed() && ((StructureViewFactoryImpl) StructureViewFactory.getInstance(myProject)) .getState() .AUTOSCROLL_MODE; } protected void setAutoScrollMode(boolean state) { ((StructureViewFactoryImpl) StructureViewFactory.getInstance(myProject)) .getState() .AUTOSCROLL_MODE = state; } protected void scrollToSource(Component tree) { if (myAbstractTreeBuilder == null) return; myAutoscrollFeedback = true; Navigatable editSourceDescriptor = PlatformDataKeys.NAVIGATABLE.getData(DataManager.getInstance().getDataContext(getTree())); if (myFileEditor != null && editSourceDescriptor != null && editSourceDescriptor.canNavigateToSource()) { editSourceDescriptor.navigate(false); } } } private class MyAutoScrollFromSourceHandler extends AutoScrollFromSourceHandler { private FileEditorPositionListener myFileEditorPositionListener; private MyAutoScrollFromSourceHandler(Project project, Disposable parentDisposable) { super(project, parentDisposable); } public void install() { addEditorCaretListener(); } public void dispose() { myTreeModel.removeEditorPositionListener(myFileEditorPositionListener); } private void addEditorCaretListener() { myFileEditorPositionListener = new FileEditorPositionListener() { public void onCurrentElementChanged() { scrollToSelectedElement(); } }; myTreeModel.addEditorPositionListener(myFileEditorPositionListener); } protected boolean isAutoScrollMode() { StructureViewFactoryImpl structureViewFactory = (StructureViewFactoryImpl) StructureViewFactory.getInstance(myProject); return structureViewFactory.getState().AUTOSCROLL_FROM_SOURCE; } protected void setAutoScrollMode(boolean state) { StructureViewFactoryImpl structureViewFactory = (StructureViewFactoryImpl) StructureViewFactory.getInstance(myProject); structureViewFactory.getState().AUTOSCROLL_FROM_SOURCE = state; final FileEditor[] selectedEditors = FileEditorManager.getInstance(myProject).getSelectedEditors(); if (selectedEditors.length > 0 && state) { scrollToSelectedElement(); } } } public Object getData(String dataId) { if (LangDataKeys.PSI_ELEMENT.is(dataId)) { TreePath path = getSelectedUniquePath(); if (path == null) return null; DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent(); Object userObject = node.getUserObject(); if (!(userObject instanceof AbstractTreeNode)) return null; AbstractTreeNode descriptor = (AbstractTreeNode) userObject; Object element = descriptor.getValue(); if (element instanceof StructureViewTreeElement) { element = ((StructureViewTreeElement) element).getValue(); } if (!(element instanceof PsiElement)) return null; if (!((PsiElement) element).isValid()) return null; return element; } if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) { return convertToPsiElementsArray(getSelectedElements()); } if (PlatformDataKeys.FILE_EDITOR.is(dataId)) { return myFileEditor; } if (PlatformDataKeys.CUT_PROVIDER.is(dataId)) { return myCopyPasteDelegator.getCutProvider(); } if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) { return myCopyPasteDelegator.getCopyProvider(); } if (PlatformDataKeys.PASTE_PROVIDER.is(dataId)) { return myCopyPasteDelegator.getPasteProvider(); } if (PlatformDataKeys.NAVIGATABLE.is(dataId)) { Object[] selectedElements = getSelectedTreeElements(); if (selectedElements == null || selectedElements.length == 0) return null; if (selectedElements[0] instanceof Navigatable) { return selectedElements[0]; } } if (PlatformDataKeys.HELP_ID.is(dataId)) { return getHelpID(); } return super.getData(dataId); } @Nullable private static PsiElement[] convertToPsiElementsArray(final Object[] selectedElements) { if (selectedElements == null) return null; ArrayList<PsiElement> psiElements = new ArrayList<PsiElement>(); for (Object selectedElement : selectedElements) { if (selectedElement instanceof PsiElement && ((PsiElement) selectedElement).isValid()) { psiElements.add((PsiElement) selectedElement); } } return PsiUtilBase.toPsiElementArray(psiElements); } @Nullable private TreePath getSelectedUniquePath() { JTree tree = getTree(); if (tree == null) return null; TreePath[] paths = tree.getSelectionPaths(); return paths == null || paths.length != 1 ? null : paths[0]; } public StructureViewModel getTreeModel() { return myTreeModel; } public boolean navigateToSelectedElement(boolean requestFocus) { return select(myTreeModel.getCurrentEditorElement(), requestFocus); } public void doUpdate() { assert ApplicationManager.getApplication().isUnitTestMode(); myAbstractTreeBuilder.addRootToUpdate(); } // todo [kirillk] dirty hack for discovering invalid psi elements, to delegate it to a proper // place after 8.1 public static boolean isValid(Object treeElement) { if (treeElement instanceof StructureViewTreeElementWrapper) { final StructureViewTreeElementWrapper wrapper = (StructureViewTreeElementWrapper) treeElement; if (wrapper.getValue() instanceof PsiTreeElementBase) { final PsiTreeElementBase psiNode = (PsiTreeElementBase) wrapper.getValue(); return psiNode.isValid(); } } return true; } public static class StructureViewTreeElementWrapper extends TreeElementWrapper implements NodeDescriptorProvidingKey { private long childrenStamp = -1; private long modificationCountForChildren = ourSettingsModificationCount; public StructureViewTreeElementWrapper( Project project, TreeElement value, TreeModel treeModel) { super(project, value, treeModel); } @NotNull public Object getKey() { StructureViewTreeElement element = (StructureViewTreeElement) getValue(); if (element instanceof NodeDescriptorProvidingKey) return ((NodeDescriptorProvidingKey) element).getKey(); Object value = element.getValue(); return value == null ? this : value; } @NotNull public Collection<AbstractTreeNode> getChildren() { if (ourSettingsModificationCount != modificationCountForChildren) { resetChildren(); modificationCountForChildren = ourSettingsModificationCount; } final Object o = unwrapValue(getValue()); long currentStamp; if ((o instanceof PsiElement && ((PsiElement) o).getNode() instanceof CompositeElement && childrenStamp != (currentStamp = ((CompositeElement) ((PsiElement) o).getNode()).getModificationCount())) || (o instanceof ModificationTracker && childrenStamp != (currentStamp = ((ModificationTracker) o).getModificationCount()))) { resetChildren(); childrenStamp = currentStamp; } try { return super.getChildren(); } catch (IndexNotReadyException ignore) { return Collections.emptyList(); } } @Override public boolean isAlwaysShowPlus() { if (getElementInfoProvider() != null) { return getElementInfoProvider().isAlwaysShowsPlus((StructureViewTreeElement) getValue()); } return true; } @Override public boolean isAlwaysLeaf() { if (getElementInfoProvider() != null) { return getElementInfoProvider().isAlwaysLeaf((StructureViewTreeElement) getValue()); } return false; } @Nullable private StructureViewModel.ElementInfoProvider getElementInfoProvider() { if (myTreeModel instanceof StructureViewModel.ElementInfoProvider) { return ((StructureViewModel.ElementInfoProvider) myTreeModel); } else if (myTreeModel instanceof TreeModelWrapper) { StructureViewModel model = ((TreeModelWrapper) myTreeModel).getModel(); if (model instanceof StructureViewModel.ElementInfoProvider) { return (StructureViewModel.ElementInfoProvider) model; } } return null; } protected TreeElementWrapper createChildNode(final TreeElement child) { return new StructureViewTreeElementWrapper(myProject, child, myTreeModel); } @Override protected GroupWrapper createGroupWrapper( final Project project, Group group, final TreeModel treeModel) { return new StructureViewGroup(project, group, treeModel); } public boolean equals(Object o) { if (o instanceof StructureViewTreeElementWrapper) { return Comparing.equal( unwrapValue(getValue()), unwrapValue(((StructureViewTreeElementWrapper) o).getValue())); } else if (o instanceof StructureViewTreeElement) { return Comparing.equal(unwrapValue(getValue()), ((StructureViewTreeElement) o).getValue()); } return false; } private static Object unwrapValue(Object o) { if (o instanceof StructureViewTreeElement) { return ((StructureViewTreeElement) o).getValue(); } else { return o; } } public int hashCode() { final Object o = unwrapValue(getValue()); return o != null ? o.hashCode() : 0; } private class StructureViewGroup extends GroupWrapper { public StructureViewGroup(Project project, Group group, TreeModel treeModel) { super(project, group, treeModel); } @Override protected TreeElementWrapper createChildNode(TreeElement child) { return new StructureViewTreeElementWrapper(getProject(), child, myTreeModel); } @Override protected GroupWrapper createGroupWrapper(Project project, Group group, TreeModel treeModel) { return new StructureViewGroup(project, group, treeModel); } @Override public boolean isAlwaysShowPlus() { return true; } } } public String getHelpID() { return ourHelpID; } public Dimension getCurrentSize() { return myTree.getSize(); } public void setReferenceSizeWhileInitializing(Dimension size) { _setRefSize(size); if (size != null) { myAbstractTreeBuilder .getReady(this) .doWhenDone( new Runnable() { public void run() { _setRefSize(null); } }); } } private void _setRefSize(Dimension size) { myTree.setPreferredSize(size); myTree.setMinimumSize(size); myTree.setMaximumSize(size); myTree.revalidate(); myTree.repaint(); } }
/** @author Gregory Shrago */ public class QuickEditHandler extends DocumentAdapter implements Disposable { private final Project myProject; private final QuickEditAction myAction; private final Editor myEditor; private final Document myOrigDocument; private final Document myNewDocument; private final PsiFile myNewFile; private final LightVirtualFile myNewVirtualFile; private final long myOrigCreationStamp; private EditorWindow mySplittedWindow; private boolean myCommittingToOriginal; private final PsiFile myInjectedFile; private final List<Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer>> myMarkers = ContainerUtil.newLinkedList(); @Nullable private final RangeMarker myAltFullRange; private static final Key<String> REPLACEMENT_KEY = Key.create("REPLACEMENT_KEY"); QuickEditHandler( Project project, @NotNull PsiFile injectedFile, final PsiFile origFile, Editor editor, QuickEditAction action) { myProject = project; myEditor = editor; myAction = action; myOrigDocument = editor.getDocument(); Place shreds = InjectedLanguageUtil.getShreds(injectedFile); FileType fileType = injectedFile.getFileType(); Language language = injectedFile.getLanguage(); PsiLanguageInjectionHost.Shred firstShred = ContainerUtil.getFirstItem(shreds); PsiFileFactory factory = PsiFileFactory.getInstance(project); String text = InjectedLanguageManager.getInstance(project).getUnescapedText(injectedFile); String newFileName = StringUtil.notNullize(language.getDisplayName(), "Injected") + " Fragment " + "(" + origFile.getName() + ":" + firstShred.getHost().getTextRange().getStartOffset() + ")" + "." + fileType.getDefaultExtension(); // preserve \r\n as it is done in MultiHostRegistrarImpl myNewFile = factory.createFileFromText(newFileName, language, text, true, false); myNewVirtualFile = ObjectUtils.assertNotNull((LightVirtualFile) myNewFile.getVirtualFile()); myNewVirtualFile.setOriginalFile(origFile.getVirtualFile()); assert myNewFile != null : "PSI file is null"; assert myNewFile.getTextLength() == myNewVirtualFile.getContent().length() : "PSI / Virtual file text mismatch"; myNewVirtualFile.setOriginalFile(origFile.getVirtualFile()); // suppress possible errors as in injected mode myNewFile.putUserData( InjectedLanguageUtil.FRANKENSTEIN_INJECTION, injectedFile.getUserData(InjectedLanguageUtil.FRANKENSTEIN_INJECTION)); myNewFile.putUserData(FileContextUtil.INJECTED_IN_ELEMENT, shreds.getHostPointer()); myNewDocument = PsiDocumentManager.getInstance(project).getDocument(myNewFile); assert myNewDocument != null; EditorActionManager.getInstance() .setReadonlyFragmentModificationHandler(myNewDocument, new MyQuietHandler()); myOrigCreationStamp = myOrigDocument.getModificationStamp(); // store creation stamp for UNDO tracking myOrigDocument.addDocumentListener(this, this); myNewDocument.addDocumentListener(this, this); EditorFactory editorFactory = ObjectUtils.assertNotNull(EditorFactory.getInstance()); // not FileEditorManager listener because of RegExp checker and alike editorFactory.addEditorFactoryListener( new EditorFactoryAdapter() { int useCount; @Override public void editorCreated(@NotNull EditorFactoryEvent event) { if (event.getEditor().getDocument() != myNewDocument) return; useCount++; } @Override public void editorReleased(@NotNull EditorFactoryEvent event) { if (event.getEditor().getDocument() != myNewDocument) return; if (--useCount > 0) return; if (Boolean.TRUE.equals( myNewVirtualFile.getUserData(FileEditorManagerImpl.CLOSING_TO_REOPEN))) return; Disposer.dispose(QuickEditHandler.this); } }, this); if ("JAVA".equals(firstShred.getHost().getLanguage().getID())) { PsiLanguageInjectionHost.Shred lastShred = ContainerUtil.getLastItem(shreds); myAltFullRange = myOrigDocument.createRangeMarker( firstShred.getHostRangeMarker().getStartOffset(), lastShred.getHostRangeMarker().getEndOffset()); myAltFullRange.setGreedyToLeft(true); myAltFullRange.setGreedyToRight(true); initGuardedBlocks(shreds); myInjectedFile = null; } else { initMarkers(shreds); myAltFullRange = null; myInjectedFile = injectedFile; } } public boolean isValid() { boolean valid = myNewVirtualFile.isValid() && (myAltFullRange == null && myInjectedFile.isValid() || myAltFullRange != null && myAltFullRange.isValid()); if (valid) { for (Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer> t : myMarkers) { if (!t.first.isValid() || !t.second.isValid() || t.third.getElement() == null) { valid = false; break; } } } return valid; } public void navigate(int injectedOffset) { if (myAction.isShowInBalloon()) { final JComponent component = myAction.createBalloonComponent(myNewFile); if (component != null) { final Balloon balloon = JBPopupFactory.getInstance() .createBalloonBuilder(component) .setShadow(true) .setAnimationCycle(0) .setHideOnClickOutside(true) .setHideOnKeyOutside(true) .setHideOnAction(false) .setFillColor(UIUtil.getControlColor()) .createBalloon(); new AnAction() { @Override public void actionPerformed(AnActionEvent e) { balloon.hide(); } }.registerCustomShortcutSet(CommonShortcuts.ESCAPE, component); Disposer.register(myNewFile.getProject(), balloon); final Balloon.Position position = QuickEditAction.getBalloonPosition(myEditor); RelativePoint point = JBPopupFactory.getInstance().guessBestPopupLocation(myEditor); if (position == Balloon.Position.above) { final Point p = point.getPoint(); point = new RelativePoint( point.getComponent(), new Point(p.x, p.y - myEditor.getLineHeight())); } balloon.show(point, position); } } else { final FileEditorManagerEx fileEditorManager = FileEditorManagerEx.getInstanceEx(myProject); final FileEditor[] editors = fileEditorManager.getEditors(myNewVirtualFile); if (editors.length == 0) { final EditorWindow curWindow = fileEditorManager.getCurrentWindow(); mySplittedWindow = curWindow.split(SwingConstants.HORIZONTAL, false, myNewVirtualFile, true); } Editor editor = fileEditorManager.openTextEditor( new OpenFileDescriptor(myProject, myNewVirtualFile, injectedOffset), true); // fold missing values if (editor != null) { editor.putUserData(QuickEditAction.QUICK_EDIT_HANDLER, this); final FoldingModel foldingModel = editor.getFoldingModel(); foldingModel.runBatchFoldingOperation( () -> { for (RangeMarker o : ContainerUtil.reverse(((DocumentEx) myNewDocument).getGuardedBlocks())) { String replacement = o.getUserData(REPLACEMENT_KEY); if (StringUtil.isEmpty(replacement)) continue; FoldRegion region = foldingModel.addFoldRegion(o.getStartOffset(), o.getEndOffset(), replacement); if (region != null) region.setExpanded(false); } }); } SwingUtilities.invokeLater( () -> myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE)); } } @Override public void documentChanged(DocumentEvent e) { UndoManager undoManager = UndoManager.getInstance(myProject); boolean undoOrRedo = undoManager.isUndoInProgress() || undoManager.isRedoInProgress(); if (undoOrRedo) { // allow undo/redo up until 'creation stamp' back in time // and check it after action is completed if (e.getDocument() == myOrigDocument) { //noinspection SSBasedInspection SwingUtilities.invokeLater( () -> { if (myOrigCreationStamp > myOrigDocument.getModificationStamp()) { closeEditor(); } }); } } else if (e.getDocument() == myNewDocument) { commitToOriginal(e); if (!isValid()) { ApplicationManager.getApplication() .invokeLater(() -> closeEditor(), myProject.getDisposed()); } } else if (e.getDocument() == myOrigDocument) { if (myCommittingToOriginal || myAltFullRange != null && myAltFullRange.isValid()) return; ApplicationManager.getApplication().invokeLater(() -> closeEditor(), myProject.getDisposed()); } } private void closeEditor() { boolean unsplit = false; if (mySplittedWindow != null && !mySplittedWindow.isDisposed()) { final EditorWithProviderComposite[] editors = mySplittedWindow.getEditors(); if (editors.length == 1 && Comparing.equal(editors[0].getFile(), myNewVirtualFile)) { unsplit = true; } } FileEditorManager.getInstance(myProject).closeFile(myNewVirtualFile); if (unsplit) { for (EditorWindow editorWindow : mySplittedWindow.findSiblings()) { editorWindow.unsplit(true); } } } public void initMarkers(Place shreds) { SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(myProject); int curOffset = -1; for (PsiLanguageInjectionHost.Shred shred : shreds) { final RangeMarker rangeMarker = myNewDocument.createRangeMarker( shred.getRange().getStartOffset() + shred.getPrefix().length(), shred.getRange().getEndOffset() - shred.getSuffix().length()); final TextRange rangeInsideHost = shred.getRangeInsideHost(); PsiLanguageInjectionHost host = shred.getHost(); RangeMarker origMarker = myOrigDocument.createRangeMarker( rangeInsideHost.shiftRight(host.getTextRange().getStartOffset())); SmartPsiElementPointer<PsiLanguageInjectionHost> elementPointer = smartPointerManager.createSmartPsiElementPointer(host); Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer> markers = Trinity.<RangeMarker, RangeMarker, SmartPsiElementPointer>create( origMarker, rangeMarker, elementPointer); myMarkers.add(markers); origMarker.setGreedyToRight(true); rangeMarker.setGreedyToRight(true); if (origMarker.getStartOffset() > curOffset) { origMarker.setGreedyToLeft(true); rangeMarker.setGreedyToLeft(true); } curOffset = origMarker.getEndOffset(); } initGuardedBlocks(shreds); } private void initGuardedBlocks(Place shreds) { int origOffset = -1; int curOffset = 0; for (PsiLanguageInjectionHost.Shred shred : shreds) { Segment hostRangeMarker = shred.getHostRangeMarker(); int start = shred.getRange().getStartOffset() + shred.getPrefix().length(); int end = shred.getRange().getEndOffset() - shred.getSuffix().length(); if (curOffset < start) { RangeMarker guard = myNewDocument.createGuardedBlock(curOffset, start); if (curOffset == 0 && shred == shreds.get(0)) guard.setGreedyToLeft(true); String padding = origOffset < 0 ? "" : myOrigDocument.getText().substring(origOffset, hostRangeMarker.getStartOffset()); guard.putUserData(REPLACEMENT_KEY, fixQuotes(padding)); } curOffset = end; origOffset = hostRangeMarker.getEndOffset(); } if (curOffset < myNewDocument.getTextLength()) { RangeMarker guard = myNewDocument.createGuardedBlock(curOffset, myNewDocument.getTextLength()); guard.setGreedyToRight(true); guard.putUserData(REPLACEMENT_KEY, ""); } } private void commitToOriginal(final DocumentEvent e) { VirtualFile origVirtualFile = PsiUtilCore.getVirtualFile(myNewFile.getContext()); myCommittingToOriginal = true; try { if (origVirtualFile == null || !ReadonlyStatusHandler.getInstance(myProject) .ensureFilesWritable(origVirtualFile) .hasReadonlyFiles()) { PostprocessReformattingAspect.getInstance(myProject) .disablePostprocessFormattingInside( () -> { if (myAltFullRange != null) { altCommitToOriginal(e); return; } commitToOriginalInner(); }); PsiDocumentManager.getInstance(myProject) .doPostponedOperationsAndUnblockDocument(myOrigDocument); } } finally { myCommittingToOriginal = false; } } private void commitToOriginalInner() { final String text = myNewDocument.getText(); final Map< PsiLanguageInjectionHost, Set<Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer>>> map = ContainerUtil.classify( myMarkers.iterator(), new Convertor< Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer>, PsiLanguageInjectionHost>() { @Override public PsiLanguageInjectionHost convert( final Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer> o) { final PsiElement element = o.third.getElement(); return (PsiLanguageInjectionHost) element; } }); PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); documentManager.commitDocument(myOrigDocument); // commit here and after each manipulator update int localInsideFileCursor = 0; for (PsiLanguageInjectionHost host : map.keySet()) { if (host == null) continue; String hostText = host.getText(); ProperTextRange insideHost = null; StringBuilder sb = new StringBuilder(); for (Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer> entry : map.get(host)) { RangeMarker origMarker = entry.first; // check for validity? int hostOffset = host.getTextRange().getStartOffset(); ProperTextRange localInsideHost = new ProperTextRange( origMarker.getStartOffset() - hostOffset, origMarker.getEndOffset() - hostOffset); RangeMarker rangeMarker = entry.second; ProperTextRange localInsideFile = new ProperTextRange( Math.max(localInsideFileCursor, rangeMarker.getStartOffset()), rangeMarker.getEndOffset()); if (insideHost != null) { // append unchanged inter-markers fragment sb.append( hostText.substring(insideHost.getEndOffset(), localInsideHost.getStartOffset())); } sb.append( localInsideFile.getEndOffset() <= text.length() && !localInsideFile.isEmpty() ? localInsideFile.substring(text) : ""); localInsideFileCursor = localInsideFile.getEndOffset(); insideHost = insideHost == null ? localInsideHost : insideHost.union(localInsideHost); } assert insideHost != null; ElementManipulators.getManipulator(host).handleContentChange(host, insideHost, sb.toString()); documentManager.commitDocument(myOrigDocument); } } private void altCommitToOriginal(@NotNull DocumentEvent e) { final PsiFile origPsiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(myOrigDocument); String newText = myNewDocument.getText(); // prepare guarded blocks LinkedHashMap<String, String> replacementMap = new LinkedHashMap<String, String>(); int count = 0; for (RangeMarker o : ContainerUtil.reverse(((DocumentEx) myNewDocument).getGuardedBlocks())) { String replacement = o.getUserData(REPLACEMENT_KEY); String tempText = "REPLACE" + (count++) + Long.toHexString(StringHash.calc(replacement)); newText = newText.substring(0, o.getStartOffset()) + tempText + newText.substring(o.getEndOffset()); replacementMap.put(tempText, replacement); } // run preformat processors final int hostStartOffset = myAltFullRange.getStartOffset(); myEditor.getCaretModel().moveToOffset(hostStartOffset); for (CopyPastePreProcessor preProcessor : Extensions.getExtensions(CopyPastePreProcessor.EP_NAME)) { newText = preProcessor.preprocessOnPaste(myProject, origPsiFile, myEditor, newText, null); } myOrigDocument.replaceString(hostStartOffset, myAltFullRange.getEndOffset(), newText); // replace temp strings for guarded blocks for (String tempText : replacementMap.keySet()) { int idx = CharArrayUtil.indexOf( myOrigDocument.getCharsSequence(), tempText, hostStartOffset, myAltFullRange.getEndOffset()); myOrigDocument.replaceString(idx, idx + tempText.length(), replacementMap.get(tempText)); } // JAVA: fix occasional char literal concatenation fixDocumentQuotes(myOrigDocument, hostStartOffset - 1); fixDocumentQuotes(myOrigDocument, myAltFullRange.getEndOffset()); // reformat PsiDocumentManager.getInstance(myProject).commitDocument(myOrigDocument); Runnable task = () -> { try { CodeStyleManager.getInstance(myProject) .reformatRange(origPsiFile, hostStartOffset, myAltFullRange.getEndOffset(), true); } catch (IncorrectOperationException e1) { // LOG.error(e); } }; DocumentUtil.executeInBulk(myOrigDocument, true, task); PsiElement newInjected = InjectedLanguageManager.getInstance(myProject) .findInjectedElementAt(origPsiFile, hostStartOffset); DocumentWindow documentWindow = newInjected == null ? null : InjectedLanguageUtil.getDocumentWindow(newInjected); if (documentWindow != null) { myEditor.getCaretModel().moveToOffset(documentWindow.injectedToHost(e.getOffset())); myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE); } } private static String fixQuotes(String padding) { if (padding.isEmpty()) return padding; if (padding.startsWith("'")) padding = '\"' + padding.substring(1); if (padding.endsWith("'")) padding = padding.substring(0, padding.length() - 1) + "\""; return padding; } private static void fixDocumentQuotes(Document doc, int offset) { if (doc.getCharsSequence().charAt(offset) == '\'') { doc.replaceString(offset, offset + 1, "\""); } } @Override public void dispose() { // noop } @TestOnly public PsiFile getNewFile() { return myNewFile; } public boolean changesRange(TextRange range) { if (myAltFullRange != null) { return range.intersects(myAltFullRange.getStartOffset(), myAltFullRange.getEndOffset()); } else if (!myMarkers.isEmpty()) { TextRange hostRange = TextRange.create( myMarkers.get(0).first.getStartOffset(), myMarkers.get(myMarkers.size() - 1).first.getEndOffset()); return range.intersects(hostRange); } return false; } private static class MyQuietHandler implements ReadonlyFragmentModificationHandler { @Override public void handle(final ReadOnlyFragmentModificationException e) { // nothing } } }
/** @author ik Date: 24.10.2003 */ public class PsiClassImplUtil { private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.PsiClassImplUtil"); private static final Key<ParameterizedCachedValue<MembersMap, PsiClass>> MAP_IN_CLASS_KEY = Key.create("MAP_KEY"); private PsiClassImplUtil() {} public static void cacheEverything(PsiClass aClass) { getValues(aClass).getValue(aClass); } @NotNull public static PsiField[] getAllFields(@NotNull PsiClass aClass) { List<PsiField> map = getAllByMap(aClass, MemberType.FIELD); return map.toArray(new PsiField[map.size()]); } @NotNull public static PsiMethod[] getAllMethods(@NotNull PsiClass aClass) { List<PsiMethod> methods = getAllByMap(aClass, MemberType.METHOD); return methods.toArray(new PsiMethod[methods.size()]); } @NotNull public static PsiClass[] getAllInnerClasses(@NotNull PsiClass aClass) { List<PsiClass> classes = getAllByMap(aClass, MemberType.CLASS); return classes.toArray(new PsiClass[classes.size()]); } @Nullable public static PsiField findFieldByName( @NotNull PsiClass aClass, String name, boolean checkBases) { List<PsiMember> byMap = findByMap(aClass, name, checkBases, MemberType.FIELD); return byMap.isEmpty() ? null : (PsiField) byMap.get(0); } @NotNull public static PsiMethod[] findMethodsByName( @NotNull PsiClass aClass, String name, boolean checkBases) { List<PsiMember> methods = findByMap(aClass, name, checkBases, MemberType.METHOD); //noinspection SuspiciousToArrayCall return methods.toArray(new PsiMethod[methods.size()]); } @Nullable public static PsiMethod findMethodBySignature( @NotNull PsiClass aClass, @NotNull PsiMethod patternMethod, final boolean checkBases) { final List<PsiMethod> result = findMethodsBySignature(aClass, patternMethod, checkBases, true); return result.isEmpty() ? null : result.get(0); } // ----------------------------- findMethodsBySignature ----------------------------------- @NotNull public static PsiMethod[] findMethodsBySignature( @NotNull PsiClass aClass, @NotNull PsiMethod patternMethod, final boolean checkBases) { List<PsiMethod> methods = findMethodsBySignature(aClass, patternMethod, checkBases, false); return methods.toArray(new PsiMethod[methods.size()]); } @NotNull private static List<PsiMethod> findMethodsBySignature( @NotNull PsiClass aClass, @NotNull PsiMethod patternMethod, boolean checkBases, boolean stopOnFirst) { final PsiMethod[] methodsByName = aClass.findMethodsByName(patternMethod.getName(), checkBases); if (methodsByName.length == 0) return Collections.emptyList(); final List<PsiMethod> methods = new SmartList<PsiMethod>(); final MethodSignature patternSignature = patternMethod.getSignature(PsiSubstitutor.EMPTY); for (final PsiMethod method : methodsByName) { final PsiClass superClass = method.getContainingClass(); final PsiSubstitutor substitutor; if (checkBases && !aClass.equals(superClass)) { substitutor = TypeConversionUtil.getSuperClassSubstitutor(superClass, aClass, PsiSubstitutor.EMPTY); } else { substitutor = PsiSubstitutor.EMPTY; } final MethodSignature signature = method.getSignature(substitutor); if (signature.equals(patternSignature)) { methods.add(method); if (stopOnFirst) { break; } } } return methods; } // ---------------------------------------------------------------------------------------- @Nullable public static PsiClass findInnerByName( @NotNull PsiClass aClass, String name, boolean checkBases) { List<PsiMember> byMap = findByMap(aClass, name, checkBases, MemberType.CLASS); return byMap.isEmpty() ? null : (PsiClass) byMap.get(0); } @NotNull private static List<PsiMember> findByMap( @NotNull PsiClass aClass, String name, boolean checkBases, @NotNull MemberType type) { if (name == null) return Collections.emptyList(); if (checkBases) { Map<String, List<Pair<PsiMember, PsiSubstitutor>>> allMethodsMap = getMap(aClass, type); List<Pair<PsiMember, PsiSubstitutor>> list = allMethodsMap.get(name); if (list == null) return Collections.emptyList(); List<PsiMember> ret = new ArrayList<PsiMember>(list.size()); for (final Pair<PsiMember, PsiSubstitutor> info : list) { ret.add(info.getFirst()); } return ret; } else { PsiMember[] members = null; switch (type) { case METHOD: members = aClass.getMethods(); break; case CLASS: members = aClass.getInnerClasses(); break; case FIELD: members = aClass.getFields(); break; } List<PsiMember> list = new ArrayList<PsiMember>(); for (PsiMember member : members) { if (name.equals(member.getName())) { list.add(member); } } return list; } } @NotNull public static <T extends PsiMember> List<Pair<T, PsiSubstitutor>> getAllWithSubstitutorsByMap( @NotNull PsiClass aClass, @NotNull MemberType type) { Map<String, List<Pair<PsiMember, PsiSubstitutor>>> allMap = getMap(aClass, type); //noinspection unchecked return (List) allMap.get(ALL); } @NotNull private static <T extends PsiMember> List<T> getAllByMap( @NotNull PsiClass aClass, @NotNull MemberType type) { List<Pair<T, PsiSubstitutor>> pairs = getAllWithSubstitutorsByMap(aClass, type); final List<T> ret = new ArrayList<T>(pairs.size()); //noinspection ForLoopReplaceableByForEach for (int i = 0; i < pairs.size(); i++) { Pair<T, PsiSubstitutor> pair = pairs.get(i); T t = pair.getFirst(); LOG.assertTrue(t != null, aClass); ret.add(t); } return ret; } @NonNls private static final String ALL = "Intellij-IDEA-ALL"; public enum MemberType { CLASS, FIELD, METHOD } @NotNull private static MembersMap buildAllMaps(@NotNull PsiClass psiClass) { final List<Pair<PsiMember, PsiSubstitutor>> classes = new ArrayList<Pair<PsiMember, PsiSubstitutor>>(); final List<Pair<PsiMember, PsiSubstitutor>> fields = new ArrayList<Pair<PsiMember, PsiSubstitutor>>(); final List<Pair<PsiMember, PsiSubstitutor>> methods = new ArrayList<Pair<PsiMember, PsiSubstitutor>>(); FilterScopeProcessor<MethodCandidateInfo> processor = new FilterScopeProcessor<MethodCandidateInfo>( new OrFilter( ElementClassFilter.METHOD, ElementClassFilter.FIELD, ElementClassFilter.CLASS)) { @Override protected void add(PsiElement element, PsiSubstitutor substitutor) { if (element instanceof PsiMethod) { methods.add(Pair.create((PsiMember) element, substitutor)); } else if (element instanceof PsiField) { fields.add(Pair.create((PsiMember) element, substitutor)); } else if (element instanceof PsiClass) { classes.add(Pair.create((PsiMember) element, substitutor)); } } }; processDeclarationsInClassNotCached( psiClass, processor, ResolveState.initial(), null, null, psiClass, false, PsiUtil.getLanguageLevel(psiClass)); MembersMap result = new MembersMap(MemberType.class); result.put(MemberType.CLASS, generateMapByList(classes)); result.put(MemberType.METHOD, generateMapByList(methods)); result.put(MemberType.FIELD, generateMapByList(fields)); return result; } @NotNull private static Map<String, List<Pair<PsiMember, PsiSubstitutor>>> generateMapByList( @NotNull final List<Pair<PsiMember, PsiSubstitutor>> list) { Map<String, List<Pair<PsiMember, PsiSubstitutor>>> map = new THashMap<String, List<Pair<PsiMember, PsiSubstitutor>>>(); map.put(ALL, list); for (final Pair<PsiMember, PsiSubstitutor> info : list) { PsiMember element = info.getFirst(); String currentName = element.getName(); List<Pair<PsiMember, PsiSubstitutor>> listByName = map.get(currentName); if (listByName == null) { listByName = new ArrayList<Pair<PsiMember, PsiSubstitutor>>(1); map.put(currentName, listByName); } listByName.add(info); } return map; } private static Map<String, List<Pair<PsiMember, PsiSubstitutor>>> getMap( @NotNull PsiClass aClass, @NotNull MemberType type) { ParameterizedCachedValue<MembersMap, PsiClass> value = getValues(aClass); return value.getValue(aClass).get(type); } @NotNull private static ParameterizedCachedValue<MembersMap, PsiClass> getValues( @NotNull PsiClass aClass) { ParameterizedCachedValue<MembersMap, PsiClass> value = aClass.getUserData(MAP_IN_CLASS_KEY); if (value == null) { value = CachedValuesManager.getManager(aClass.getProject()) .createParameterizedCachedValue(ByNameCachedValueProvider.INSTANCE, false); // Do not cache for nonphysical elements if (aClass.isPhysical()) { value = ((UserDataHolderEx) aClass).putUserDataIfAbsent(MAP_IN_CLASS_KEY, value); } } return value; } private static class ClassIconRequest { @NotNull private final PsiClass psiClass; private final int flags; private final Icon symbolIcon; private ClassIconRequest(@NotNull PsiClass psiClass, int flags, Icon symbolIcon) { this.psiClass = psiClass; this.flags = flags; this.symbolIcon = symbolIcon; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof ClassIconRequest)) return false; ClassIconRequest that = (ClassIconRequest) o; return flags == that.flags && psiClass.equals(that.psiClass); } @Override public int hashCode() { int result = psiClass.hashCode(); result = 31 * result + flags; return result; } } private static final Function<ClassIconRequest, Icon> FULL_ICON_EVALUATOR = new NullableFunction<ClassIconRequest, Icon>() { @Override public Icon fun(ClassIconRequest r) { if (!r.psiClass.isValid() || r.psiClass.getProject().isDisposed()) return null; final boolean isLocked = (r.flags & Iconable.ICON_FLAG_READ_STATUS) != 0 && !r.psiClass.isWritable(); Icon symbolIcon = r.symbolIcon != null ? r.symbolIcon : ElementPresentationUtil.getClassIconOfKind( r.psiClass, ElementPresentationUtil.getClassKind(r.psiClass)); RowIcon baseIcon = ElementPresentationUtil.createLayeredIcon(symbolIcon, r.psiClass, isLocked); return ElementPresentationUtil.addVisibilityIcon(r.psiClass, r.flags, baseIcon); } }; public static Icon getClassIcon(final int flags, @NotNull PsiClass aClass) { return getClassIcon(flags, aClass, null); } public static Icon getClassIcon(int flags, @NotNull PsiClass aClass, @Nullable Icon symbolIcon) { Icon base = Iconable.LastComputedIcon.get(aClass, flags); if (base == null) { if (symbolIcon == null) { symbolIcon = ElementPresentationUtil.getClassIconOfKind( aClass, ElementPresentationUtil.getBasicClassKind(aClass)); } RowIcon baseIcon = ElementBase.createLayeredIcon(aClass, symbolIcon, 0); base = ElementPresentationUtil.addVisibilityIcon(aClass, flags, baseIcon); } return IconDeferrer.getInstance() .defer(base, new ClassIconRequest(aClass, flags, symbolIcon), FULL_ICON_EVALUATOR); } @NotNull public static SearchScope getClassUseScope(@NotNull PsiClass aClass) { if (aClass instanceof PsiAnonymousClass) { return new LocalSearchScope(aClass); } final GlobalSearchScope maximalUseScope = ResolveScopeManager.getElementUseScope(aClass); PsiFile file = aClass.getContainingFile(); if (PsiImplUtil.isInServerPage(file)) return maximalUseScope; final PsiClass containingClass = aClass.getContainingClass(); if (aClass.hasModifierProperty(PsiModifier.PUBLIC) || aClass.hasModifierProperty(PsiModifier.PROTECTED)) { return containingClass == null ? maximalUseScope : containingClass.getUseScope(); } else if (aClass.hasModifierProperty(PsiModifier.PRIVATE) || aClass instanceof PsiTypeParameter) { PsiClass topClass = PsiUtil.getTopLevelClass(aClass); return new LocalSearchScope(topClass == null ? aClass.getContainingFile() : topClass); } else { PsiPackage aPackage = null; if (file instanceof PsiJavaFile) { aPackage = JavaPsiFacade.getInstance(aClass.getProject()) .findPackage(((PsiJavaFile) file).getPackageName()); } if (aPackage == null) { PsiDirectory dir = file.getContainingDirectory(); if (dir != null) { aPackage = JavaDirectoryService.getInstance().getPackage(dir); } } if (aPackage != null) { SearchScope scope = PackageScope.packageScope(aPackage, false); scope = scope.intersectWith(maximalUseScope); return scope; } return new LocalSearchScope(file); } } public static boolean isMainOrPremainMethod(@NotNull PsiMethod method) { if (!PsiType.VOID.equals(method.getReturnType())) return false; String name = method.getName(); if (!("main".equals(name) || "premain".equals(name))) return false; PsiElementFactory factory = JavaPsiFacade.getInstance(method.getProject()).getElementFactory(); MethodSignature signature = method.getSignature(PsiSubstitutor.EMPTY); try { MethodSignature main = createSignatureFromText(factory, "void main(String[] args);"); if (MethodSignatureUtil.areSignaturesEqual(signature, main)) return true; MethodSignature premain = createSignatureFromText( factory, "void premain(String args, java.lang.instrument.Instrumentation i);"); if (MethodSignatureUtil.areSignaturesEqual(signature, premain)) return true; } catch (IncorrectOperationException e) { LOG.error(e); } return false; } @NotNull private static MethodSignature createSignatureFromText( @NotNull PsiElementFactory factory, @NotNull String text) { return factory.createMethodFromText(text, null).getSignature(PsiSubstitutor.EMPTY); } private static class MembersMap extends EnumMap<MemberType, Map<String, List<Pair<PsiMember, PsiSubstitutor>>>> { public MembersMap(@NotNull Class<MemberType> keyType) { super(keyType); } } private static class ByNameCachedValueProvider implements ParameterizedCachedValueProvider<MembersMap, PsiClass> { private static final ByNameCachedValueProvider INSTANCE = new ByNameCachedValueProvider(); @Override public CachedValueProvider.Result<MembersMap> compute(@NotNull PsiClass myClass) { MembersMap map = buildAllMaps(myClass); return new CachedValueProvider.Result<MembersMap>( map, PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT); } } public static boolean processDeclarationsInClass( @NotNull PsiClass aClass, @NotNull final PsiScopeProcessor processor, @NotNull ResolveState state, @Nullable Set<PsiClass> visited, PsiElement last, @NotNull PsiElement place, boolean isRaw) { if (last instanceof PsiTypeParameterList || last instanceof PsiModifierList) { return true; // TypeParameterList and ModifierList do not see our declarations } if (visited != null && visited.contains(aClass)) return true; PsiSubstitutor substitutor = state.get(PsiSubstitutor.KEY); isRaw = isRaw || PsiUtil.isRawSubstitutor(aClass, substitutor); ParameterizedCachedValue<MembersMap, PsiClass> cache = getValues(aClass); // aClass.getUserData(MAP_IN_CLASS_KEY); boolean upToDate = cache.hasUpToDateValue(); LanguageLevel languageLevel = PsiUtil.getLanguageLevel(place); if ( /*true || */ upToDate) { final NameHint nameHint = processor.getHint(NameHint.KEY); if (nameHint != null) { String name = nameHint.getName(state); return processCachedMembersByName( aClass, processor, state, visited, last, place, isRaw, substitutor, cache.getValue(aClass), name, languageLevel); } } return processDeclarationsInClassNotCached( aClass, processor, state, visited, last, place, isRaw, languageLevel); } private static boolean processCachedMembersByName( @NotNull PsiClass aClass, @NotNull PsiScopeProcessor processor, @NotNull ResolveState state, @Nullable Set<PsiClass> visited, PsiElement last, @NotNull PsiElement place, boolean isRaw, @NotNull PsiSubstitutor substitutor, @NotNull MembersMap value, String name, @NotNull LanguageLevel languageLevel) { final ElementClassHint classHint = processor.getHint(ElementClassHint.KEY); PsiElementFactory factory = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory(); if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.FIELD)) { final PsiField fieldByName = aClass.findFieldByName(name, false); if (fieldByName != null) { processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, aClass); if (!processor.execute(fieldByName, state)) return false; } else { final Map<String, List<Pair<PsiMember, PsiSubstitutor>>> allFieldsMap = value.get(MemberType.FIELD); final List<Pair<PsiMember, PsiSubstitutor>> list = allFieldsMap.get(name); if (list != null) { for (final Pair<PsiMember, PsiSubstitutor> candidate : list) { PsiMember candidateField = candidate.getFirst(); PsiSubstitutor finalSubstitutor = obtainFinalSubstitutor( candidateField.getContainingClass(), candidate.getSecond(), aClass, substitutor, factory, languageLevel); processor.handleEvent( PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, candidateField.getContainingClass()); if (!processor.execute(candidateField, state.put(PsiSubstitutor.KEY, finalSubstitutor))) return false; } } } } if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.CLASS)) { if (last != null && last.getParent() == aClass) { if (last instanceof PsiClass) { if (!processor.execute(last, state)) return false; } // Parameters final PsiTypeParameterList list = aClass.getTypeParameterList(); if (list != null && !list.processDeclarations(processor, state, last, place)) return false; } if (!(last instanceof PsiReferenceList)) { final PsiClass classByName = aClass.findInnerClassByName(name, false); if (classByName != null) { processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, aClass); if (!processor.execute(classByName, state)) return false; } else { Map<String, List<Pair<PsiMember, PsiSubstitutor>>> allClassesMap = value.get(MemberType.CLASS); List<Pair<PsiMember, PsiSubstitutor>> list = allClassesMap.get(name); if (list != null) { for (final Pair<PsiMember, PsiSubstitutor> candidate : list) { PsiMember inner = candidate.getFirst(); PsiClass containingClass = inner.getContainingClass(); if (containingClass != null) { PsiSubstitutor finalSubstitutor = obtainFinalSubstitutor( containingClass, candidate.getSecond(), aClass, substitutor, factory, languageLevel); processor.handleEvent( PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, containingClass); if (!processor.execute(inner, state.put(PsiSubstitutor.KEY, finalSubstitutor))) return false; } } } } } } if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.METHOD)) { if (processor instanceof MethodResolverProcessor) { final MethodResolverProcessor methodResolverProcessor = (MethodResolverProcessor) processor; if (methodResolverProcessor.isConstructor()) { final PsiMethod[] constructors = aClass.getConstructors(); methodResolverProcessor.handleEvent( PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, aClass); for (PsiMethod constructor : constructors) { if (!methodResolverProcessor.execute(constructor, state)) return false; } return true; } } Map<String, List<Pair<PsiMember, PsiSubstitutor>>> allMethodsMap = value.get(MemberType.METHOD); List<Pair<PsiMember, PsiSubstitutor>> list = allMethodsMap.get(name); if (list != null) { for (final Pair<PsiMember, PsiSubstitutor> candidate : list) { ProgressIndicatorProvider.checkCanceled(); PsiMethod candidateMethod = (PsiMethod) candidate.getFirst(); if (processor instanceof MethodResolverProcessor) { if (candidateMethod.isConstructor() != ((MethodResolverProcessor) processor).isConstructor()) continue; } final PsiClass containingClass = candidateMethod.getContainingClass(); if (visited != null && visited.contains(candidateMethod.getContainingClass())) { continue; } PsiSubstitutor finalSubstitutor = obtainFinalSubstitutor( containingClass, candidate.getSecond(), aClass, substitutor, factory, languageLevel); finalSubstitutor = checkRaw(isRaw, factory, candidateMethod, finalSubstitutor); processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, containingClass); if (!processor.execute(candidateMethod, state.put(PsiSubstitutor.KEY, finalSubstitutor))) return false; } if (visited != null) { for (Pair<PsiMember, PsiSubstitutor> aList : list) { visited.add(aList.getFirst().getContainingClass()); } } } } return true; } private static PsiSubstitutor checkRaw( boolean isRaw, @NotNull PsiElementFactory factory, @NotNull PsiMethod candidateMethod, @NotNull PsiSubstitutor substitutor) { if (isRaw && !candidateMethod.hasModifierProperty( PsiModifier.STATIC)) { // static methods are not erased due to raw overriding PsiTypeParameter[] methodTypeParameters = candidateMethod.getTypeParameters(); substitutor = factory.createRawSubstitutor(substitutor, methodTypeParameters); } return substitutor; } public static PsiSubstitutor obtainFinalSubstitutor( @NotNull PsiClass candidateClass, @NotNull PsiSubstitutor candidateSubstitutor, @NotNull PsiClass aClass, @NotNull PsiSubstitutor substitutor, @NotNull PsiElementFactory elementFactory, @NotNull LanguageLevel languageLevel) { if (PsiUtil.isRawSubstitutor(aClass, substitutor)) { return elementFactory.createRawSubstitutor(candidateClass); } final PsiType containingType = elementFactory.createType(candidateClass, candidateSubstitutor, languageLevel); PsiType type = substitutor.substitute(containingType); if (!(type instanceof PsiClassType)) return candidateSubstitutor; return ((PsiClassType) type).resolveGenerics().getSubstitutor(); } private static boolean processDeclarationsInClassNotCached( @NotNull PsiClass aClass, @NotNull PsiScopeProcessor processor, @NotNull ResolveState state, @Nullable Set<PsiClass> visited, PsiElement last, @NotNull PsiElement place, boolean isRaw, @NotNull LanguageLevel languageLevel) { if (visited == null) visited = new THashSet<PsiClass>(); if (!visited.add(aClass)) return true; processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, aClass); final ElementClassHint classHint = processor.getHint(ElementClassHint.KEY); final NameHint nameHint = processor.getHint(NameHint.KEY); if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.FIELD)) { if (nameHint != null) { final PsiField fieldByName = aClass.findFieldByName(nameHint.getName(state), false); if (fieldByName != null && !processor.execute(fieldByName, state)) return false; } else { final PsiField[] fields = aClass.getFields(); for (final PsiField field : fields) { if (!processor.execute(field, state)) return false; } } } PsiElementFactory factory = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory(); if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.METHOD)) { PsiSubstitutor baseSubstitutor = state.get(PsiSubstitutor.KEY); final PsiMethod[] methods = nameHint != null ? aClass.findMethodsByName(nameHint.getName(state), false) : aClass.getMethods(); for (final PsiMethod method : methods) { PsiSubstitutor finalSubstitutor = checkRaw(isRaw, factory, method, baseSubstitutor); ResolveState methodState = finalSubstitutor == baseSubstitutor ? state : state.put(PsiSubstitutor.KEY, finalSubstitutor); if (!processor.execute(method, methodState)) return false; } } if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.CLASS)) { if (last != null && last.getParent() == aClass) { // Parameters final PsiTypeParameterList list = aClass.getTypeParameterList(); if (list != null && !list.processDeclarations(processor, ResolveState.initial(), last, place)) return false; } if (!(last instanceof PsiReferenceList) && !(last instanceof PsiModifierList)) { // Inners if (nameHint != null) { final PsiClass inner = aClass.findInnerClassByName(nameHint.getName(state), false); if (inner != null) { if (!processor.execute(inner, state)) return false; } } else { final PsiClass[] inners = aClass.getInnerClasses(); for (final PsiClass inner : inners) { if (!processor.execute(inner, state)) return false; } } } } return last instanceof PsiReferenceList || processSuperTypes( aClass, processor, visited, last, place, state, isRaw, factory, languageLevel); } private static boolean processSuperTypes( @NotNull PsiClass aClass, @NotNull PsiScopeProcessor processor, @Nullable Set<PsiClass> visited, PsiElement last, @NotNull PsiElement place, @NotNull ResolveState state, boolean isRaw, @NotNull PsiElementFactory factory, @NotNull LanguageLevel languageLevel) { boolean resolved = false; for (final PsiClassType superType : aClass.getSuperTypes()) { final PsiClassType.ClassResolveResult superTypeResolveResult = superType.resolveGenerics(); PsiClass superClass = superTypeResolveResult.getElement(); if (superClass == null) continue; PsiSubstitutor finalSubstitutor = obtainFinalSubstitutor( superClass, superTypeResolveResult.getSubstitutor(), aClass, state.get(PsiSubstitutor.KEY), factory, languageLevel); if (aClass instanceof PsiTypeParameter && PsiUtil.isRawSubstitutor(superClass, finalSubstitutor)) { finalSubstitutor = PsiSubstitutor.EMPTY; } if (!processDeclarationsInClass( superClass, processor, state.put(PsiSubstitutor.KEY, finalSubstitutor), visited, last, place, isRaw)) { resolved = true; } } return !resolved; } @Nullable public static PsiClass getSuperClass(@NotNull PsiClass psiClass) { PsiManager manager = psiClass.getManager(); GlobalSearchScope resolveScope = psiClass.getResolveScope(); final JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject()); if (psiClass.isInterface()) { return facade.findClass(CommonClassNames.JAVA_LANG_OBJECT, resolveScope); } if (psiClass.isEnum()) { return facade.findClass(CommonClassNames.JAVA_LANG_ENUM, resolveScope); } if (psiClass instanceof PsiAnonymousClass) { PsiClassType baseClassReference = ((PsiAnonymousClass) psiClass).getBaseClassType(); PsiClass baseClass = baseClassReference.resolve(); if (baseClass == null || baseClass.isInterface()) return facade.findClass(CommonClassNames.JAVA_LANG_OBJECT, resolveScope); return baseClass; } if (CommonClassNames.JAVA_LANG_OBJECT.equals(psiClass.getQualifiedName())) return null; final PsiClassType[] referenceElements = psiClass.getExtendsListTypes(); if (referenceElements.length == 0) return facade.findClass(CommonClassNames.JAVA_LANG_OBJECT, resolveScope); PsiClass psiResoved = referenceElements[0].resolve(); return psiResoved == null ? facade.findClass(CommonClassNames.JAVA_LANG_OBJECT, resolveScope) : psiResoved; } @NotNull public static PsiClass[] getSupers(@NotNull PsiClass psiClass) { final PsiClass[] supers = getSupersInner(psiClass); for (final PsiClass aSuper : supers) { LOG.assertTrue(aSuper != null); } return supers; } @NotNull private static PsiClass[] getSupersInner(@NotNull PsiClass psiClass) { PsiClassType[] extendsListTypes = psiClass.getExtendsListTypes(); PsiClassType[] implementsListTypes = psiClass.getImplementsListTypes(); if (psiClass.isInterface()) { return resolveClassReferenceList( extendsListTypes, psiClass.getManager(), psiClass.getResolveScope(), true); } if (psiClass instanceof PsiAnonymousClass) { PsiAnonymousClass psiAnonymousClass = (PsiAnonymousClass) psiClass; PsiClassType baseClassReference = psiAnonymousClass.getBaseClassType(); PsiClass baseClass = baseClassReference.resolve(); if (baseClass != null) { if (baseClass.isInterface()) { PsiClass objectClass = JavaPsiFacade.getInstance(psiClass.getProject()) .findClass(CommonClassNames.JAVA_LANG_OBJECT, psiClass.getResolveScope()); return objectClass != null ? new PsiClass[] {objectClass, baseClass} : new PsiClass[] {baseClass}; } return new PsiClass[] {baseClass}; } PsiClass objectClass = JavaPsiFacade.getInstance(psiClass.getProject()) .findClass(CommonClassNames.JAVA_LANG_OBJECT, psiClass.getResolveScope()); return objectClass != null ? new PsiClass[] {objectClass} : PsiClass.EMPTY_ARRAY; } if (psiClass instanceof PsiTypeParameter) { if (extendsListTypes.length == 0) { final PsiClass objectClass = JavaPsiFacade.getInstance(psiClass.getProject()) .findClass(CommonClassNames.JAVA_LANG_OBJECT, psiClass.getResolveScope()); return objectClass != null ? new PsiClass[] {objectClass} : PsiClass.EMPTY_ARRAY; } return resolveClassReferenceList( extendsListTypes, psiClass.getManager(), psiClass.getResolveScope(), false); } PsiClass[] interfaces = resolveClassReferenceList( implementsListTypes, psiClass.getManager(), psiClass.getResolveScope(), false); PsiClass superClass = getSuperClass(psiClass); if (superClass == null) return interfaces; PsiClass[] types = new PsiClass[interfaces.length + 1]; types[0] = superClass; System.arraycopy(interfaces, 0, types, 1, interfaces.length); return types; } @NotNull public static PsiClassType[] getSuperTypes(@NotNull PsiClass psiClass) { if (psiClass instanceof PsiAnonymousClass) { PsiClassType baseClassType = ((PsiAnonymousClass) psiClass).getBaseClassType(); PsiClass baseClass = baseClassType.resolve(); if (baseClass == null || !baseClass.isInterface()) { return new PsiClassType[] {baseClassType}; } else { PsiClassType objectType = PsiType.getJavaLangObject(psiClass.getManager(), psiClass.getResolveScope()); return new PsiClassType[] {objectType, baseClassType}; } } PsiClassType[] extendsTypes = psiClass.getExtendsListTypes(); PsiClassType[] implementsTypes = psiClass.getImplementsListTypes(); boolean hasExtends = extendsTypes.length != 0; int extendsListLength = extendsTypes.length + (hasExtends ? 0 : 1); PsiClassType[] result = new PsiClassType[extendsListLength + implementsTypes.length]; System.arraycopy(extendsTypes, 0, result, 0, extendsTypes.length); if (!hasExtends) { if (CommonClassNames.JAVA_LANG_OBJECT.equals(psiClass.getQualifiedName())) { return PsiClassType.EMPTY_ARRAY; } PsiManager manager = psiClass.getManager(); PsiClassType objectType = PsiType.getJavaLangObject(manager, psiClass.getResolveScope()); result[0] = objectType; } System.arraycopy(implementsTypes, 0, result, extendsListLength, implementsTypes.length); for (int i = 0; i < result.length; i++) { PsiClassType type = result[i]; result[i] = (PsiClassType) PsiUtil.captureToplevelWildcards(type, psiClass); } return result; } @NotNull private static PsiClassType getAnnotationSuperType( @NotNull PsiClass psiClass, @NotNull PsiElementFactory factory) { return factory.createTypeByFQClassName( "java.lang.annotation.Annotation", psiClass.getResolveScope()); } private static PsiClassType getEnumSuperType( @NotNull PsiClass psiClass, @NotNull PsiElementFactory factory) { PsiClassType superType; final PsiManager manager = psiClass.getManager(); final PsiClass enumClass = JavaPsiFacade.getInstance(manager.getProject()) .findClass("java.lang.Enum", psiClass.getResolveScope()); if (enumClass == null) { try { superType = (PsiClassType) factory.createTypeFromText("java.lang.Enum", null); } catch (IncorrectOperationException e) { superType = null; } } else { final PsiTypeParameter[] typeParameters = enumClass.getTypeParameters(); PsiSubstitutor substitutor = PsiSubstitutor.EMPTY; if (typeParameters.length == 1) { substitutor = substitutor.put(typeParameters[0], factory.createType(psiClass)); } superType = new PsiImmediateClassType(enumClass, substitutor); } return superType; } @NotNull public static PsiClass[] getInterfaces(@NotNull PsiTypeParameter typeParameter) { final PsiClassType[] referencedTypes = typeParameter.getExtendsListTypes(); if (referencedTypes.length == 0) { return PsiClass.EMPTY_ARRAY; } final List<PsiClass> result = new ArrayList<PsiClass>(referencedTypes.length); for (PsiClassType referencedType : referencedTypes) { final PsiClass psiClass = referencedType.resolve(); if (psiClass != null && psiClass.isInterface()) { result.add(psiClass); } } return result.toArray(new PsiClass[result.size()]); } @NotNull public static PsiClass[] getInterfaces(@NotNull PsiClass psiClass) { if (psiClass.isInterface()) { final PsiClassType[] extendsListTypes = psiClass.getExtendsListTypes(); return resolveClassReferenceList( extendsListTypes, psiClass.getManager(), psiClass.getResolveScope(), false); } if (psiClass instanceof PsiAnonymousClass) { PsiClassType baseClassReference = ((PsiAnonymousClass) psiClass).getBaseClassType(); PsiClass baseClass = baseClassReference.resolve(); return baseClass != null && baseClass.isInterface() ? new PsiClass[] {baseClass} : PsiClass.EMPTY_ARRAY; } final PsiClassType[] implementsListTypes = psiClass.getImplementsListTypes(); return resolveClassReferenceList( implementsListTypes, psiClass.getManager(), psiClass.getResolveScope(), false); } @NotNull private static PsiClass[] resolveClassReferenceList( @NotNull PsiClassType[] listOfTypes, @NotNull PsiManager manager, @NotNull GlobalSearchScope resolveScope, boolean includeObject) { PsiClass objectClass = JavaPsiFacade.getInstance(manager.getProject()) .findClass(CommonClassNames.JAVA_LANG_OBJECT, resolveScope); if (objectClass == null) includeObject = false; if (listOfTypes.length == 0) { if (includeObject) return new PsiClass[] {objectClass}; return PsiClass.EMPTY_ARRAY; } int referenceCount = listOfTypes.length; if (includeObject) referenceCount++; PsiClass[] resolved = new PsiClass[referenceCount]; int resolvedCount = 0; if (includeObject) resolved[resolvedCount++] = objectClass; for (PsiClassType reference : listOfTypes) { PsiClass refResolved = reference.resolve(); if (refResolved != null) resolved[resolvedCount++] = refResolved; } if (resolvedCount < referenceCount) { PsiClass[] shorter = new PsiClass[resolvedCount]; System.arraycopy(resolved, 0, shorter, 0, resolvedCount); resolved = shorter; } return resolved; } @NotNull public static List<Pair<PsiMethod, PsiSubstitutor>> findMethodsAndTheirSubstitutorsByName( @NotNull PsiClass psiClass, String name, boolean checkBases) { if (!checkBases) { final PsiMethod[] methodsByName = psiClass.findMethodsByName(name, false); final List<Pair<PsiMethod, PsiSubstitutor>> ret = new ArrayList<Pair<PsiMethod, PsiSubstitutor>>(methodsByName.length); for (final PsiMethod method : methodsByName) { ret.add(new Pair<PsiMethod, PsiSubstitutor>(method, PsiSubstitutor.EMPTY)); } return ret; } Map<String, List<Pair<PsiMember, PsiSubstitutor>>> map = getMap(psiClass, MemberType.METHOD); @SuppressWarnings("unchecked") List<Pair<PsiMethod, PsiSubstitutor>> list = (List) map.get(name); return list == null ? Collections.<Pair<PsiMethod, PsiSubstitutor>>emptyList() : Collections.unmodifiableList(list); } @NotNull public static PsiClassType[] getExtendsListTypes(@NotNull PsiClass psiClass) { if (psiClass.isEnum()) { PsiClassType enumSuperType = getEnumSuperType( psiClass, JavaPsiFacade.getInstance(psiClass.getProject()).getElementFactory()); return enumSuperType == null ? PsiClassType.EMPTY_ARRAY : new PsiClassType[] {enumSuperType}; } if (psiClass.isAnnotationType()) { return new PsiClassType[] { getAnnotationSuperType( psiClass, JavaPsiFacade.getInstance(psiClass.getProject()).getElementFactory()) }; } final PsiReferenceList extendsList = psiClass.getExtendsList(); if (extendsList != null) { return extendsList.getReferencedTypes(); } return PsiClassType.EMPTY_ARRAY; } @NotNull public static PsiClassType[] getImplementsListTypes(@NotNull PsiClass psiClass) { final PsiReferenceList extendsList = psiClass.getImplementsList(); if (extendsList != null) { return extendsList.getReferencedTypes(); } return PsiClassType.EMPTY_ARRAY; } public static boolean isClassEquivalentTo(@NotNull PsiClass aClass, PsiElement another) { if (aClass == another) return true; if (!(another instanceof PsiClass)) return false; String name1 = aClass.getName(); if (name1 == null) return false; if (!another.isValid()) return false; String name2 = ((PsiClass) another).getName(); if (name2 == null) return false; if (name1.hashCode() != name2.hashCode()) return false; if (!name1.equals(name2)) return false; String qName1 = aClass.getQualifiedName(); String qName2 = ((PsiClass) another).getQualifiedName(); if (qName1 == null || qName2 == null) { //noinspection StringEquality if (qName1 != qName2) return false; if (aClass instanceof PsiTypeParameter && another instanceof PsiTypeParameter) { PsiTypeParameter p1 = (PsiTypeParameter) aClass; PsiTypeParameter p2 = (PsiTypeParameter) another; return p1.getIndex() == p2.getIndex() && aClass.getManager().areElementsEquivalent(p1.getOwner(), p2.getOwner()); } else { return false; } } if (qName1.hashCode() != qName2.hashCode() || !qName1.equals(qName2)) { return false; } if (originalElement(aClass).equals(originalElement((PsiClass) another))) { return true; } final PsiFile file1 = aClass.getContainingFile().getOriginalFile(); final PsiFile file2 = another.getContainingFile().getOriginalFile(); // see com.intellij.openapi.vcs.changes.PsiChangeTracker // see com.intellij.psi.impl.PsiFileFactoryImpl#createFileFromText(CharSequence,PsiFile) final PsiFile original1 = file1.getUserData(PsiFileFactory.ORIGINAL_FILE); final PsiFile original2 = file2.getUserData(PsiFileFactory.ORIGINAL_FILE); if (original1 == original2 && original1 != null || original1 == file2 || original2 == file1 || file1 == file2) { return compareClassSeqNumber(aClass, (PsiClass) another); } final FileIndexFacade fileIndex = ServiceManager.getService(file1.getProject(), FileIndexFacade.class); final VirtualFile vfile1 = file1.getViewProvider().getVirtualFile(); final VirtualFile vfile2 = file2.getViewProvider().getVirtualFile(); boolean lib1 = fileIndex.isInLibraryClasses(vfile1); boolean lib2 = fileIndex.isInLibraryClasses(vfile2); return (fileIndex.isInSource(vfile1) || lib1) && (fileIndex.isInSource(vfile2) || lib2); } private static boolean compareClassSeqNumber( @NotNull PsiClass aClass, @NotNull PsiClass another) { // there may be several classes in one file, they must not be equal int index1 = getSeqNumber(aClass); if (index1 == -1) return true; int index2 = getSeqNumber(another); return index1 == index2; } private static int getSeqNumber(@NotNull PsiClass aClass) { // sequence number of this class among its parent' child classes named the same PsiElement parent = aClass.getParent(); if (parent == null) return -1; int seqNo = 0; for (PsiElement child : parent.getChildren()) { if (child == aClass) return seqNo; if (child instanceof PsiClass && Comparing.strEqual(aClass.getName(), ((PsiClass) child).getName())) { seqNo++; } } return -1; } @NotNull private static PsiElement originalElement(@NotNull PsiClass aClass) { final PsiElement originalElement = aClass.getOriginalElement(); ASTNode node = originalElement.getNode(); if (node != null) { final PsiCompiledElement compiled = node.getUserData(ClsElementImpl.COMPILED_ELEMENT); if (compiled != null) { return compiled; } } return originalElement; } public static boolean isFieldEquivalentTo(@NotNull PsiField field, PsiElement another) { if (!(another instanceof PsiField)) return false; String name1 = field.getName(); if (name1 == null) return false; if (!another.isValid()) return false; String name2 = ((PsiField) another).getName(); if (!name1.equals(name2)) return false; PsiClass aClass1 = field.getContainingClass(); PsiClass aClass2 = ((PsiField) another).getContainingClass(); return aClass1 != null && aClass2 != null && field.getManager().areElementsEquivalent(aClass1, aClass2); } public static boolean isMethodEquivalentTo(@NotNull PsiMethod method1, PsiElement another) { if (method1 == another) return true; if (!(another instanceof PsiMethod)) return false; PsiMethod method2 = (PsiMethod) another; if (!another.isValid()) return false; if (!method1.getName().equals(method2.getName())) return false; PsiClass aClass1 = method1.getContainingClass(); PsiClass aClass2 = method2.getContainingClass(); PsiManager manager = method1.getManager(); if (!(aClass1 != null && aClass2 != null && manager.areElementsEquivalent(aClass1, aClass2))) return false; PsiParameter[] parameters1 = method1.getParameterList().getParameters(); PsiParameter[] parameters2 = method2.getParameterList().getParameters(); if (parameters1.length != parameters2.length) return false; for (int i = 0; i < parameters1.length; i++) { PsiParameter parameter1 = parameters1[i]; PsiParameter parameter2 = parameters2[i]; PsiType type1 = parameter1.getType(); PsiType type2 = parameter2.getType(); if (!compareParamTypes(manager, type1, type2)) return false; } return true; } private static boolean compareParamTypes( @NotNull PsiManager manager, @NotNull PsiType type1, @NotNull PsiType type2) { if (type1 instanceof PsiArrayType) { return type2 instanceof PsiArrayType && compareParamTypes( manager, ((PsiArrayType) type1).getComponentType(), ((PsiArrayType) type2).getComponentType()); } if (!(type1 instanceof PsiClassType) || !(type2 instanceof PsiClassType)) { return type1.equals(type2); } PsiClass class1 = ((PsiClassType) type1).resolve(); PsiClass class2 = ((PsiClassType) type2).resolve(); if (class1 instanceof PsiTypeParameter && class2 instanceof PsiTypeParameter) { return Comparing.equal(class1.getName(), class2.getName()) && ((PsiTypeParameter) class1).getIndex() == ((PsiTypeParameter) class2).getIndex(); } return manager.areElementsEquivalent(class1, class2); } }
/** @author gregsh */ public abstract class EditorTextFieldCellRenderer implements TableCellRenderer, Disposable { private static final Key<SimpleRendererComponent> MY_PANEL_PROPERTY = Key.create("EditorTextFieldCellRenderer.MyEditorPanel"); private final Project myProject; private final FileType myFileType; private final boolean myInheritFontFromLaF; protected EditorTextFieldCellRenderer( @Nullable Project project, @Nullable FileType fileType, @NotNull Disposable parent) { this(project, fileType, true, parent); } protected EditorTextFieldCellRenderer( @Nullable Project project, @Nullable FileType fileType, boolean inheritFontFromLaF, @NotNull Disposable parent) { myProject = project; myFileType = fileType; myInheritFontFromLaF = inheritFontFromLaF; Disposer.register(parent, this); } protected abstract String getText(JTable table, Object value, int row, int column); @Nullable protected TextAttributes getTextAttributes(JTable table, Object value, int row, int column) { return null; } @NotNull protected EditorColorsScheme getColorScheme(final JTable table) { return getEditorPanel(table).getEditor().getColorsScheme(); } protected void customizeEditor( @NotNull EditorEx editor, JTable table, Object value, boolean selected, int row, int column) { String text = getText(table, value, row, column); getEditorPanel(table).setText(text, getTextAttributes(table, value, row, column), selected); } @Override public Component getTableCellRendererComponent( JTable table, Object value, boolean selected, boolean focused, int row, int column) { RendererComponent panel = getEditorPanel(table); EditorEx editor = panel.getEditor(); editor.getColorsScheme().setEditorFontSize(table.getFont().getSize()); editor .getColorsScheme() .setColor(EditorColors.SELECTION_BACKGROUND_COLOR, table.getSelectionBackground()); editor .getColorsScheme() .setColor(EditorColors.SELECTION_FOREGROUND_COLOR, table.getSelectionForeground()); editor.setBackgroundColor(selected ? table.getSelectionBackground() : table.getBackground()); panel.setSelected(!Comparing.equal(editor.getBackgroundColor(), table.getBackground())); panel.setBorder( null); // prevents double border painting when ExtendedItemRendererComponentWrapper is used customizeEditor(editor, table, value, selected, row, column); return panel; } @NotNull private RendererComponent getEditorPanel(final JTable table) { RendererComponent panel = UIUtil.getClientProperty(table, MY_PANEL_PROPERTY); if (panel != null) { DelegateColorScheme scheme = (DelegateColorScheme) panel.getEditor().getColorsScheme(); scheme.setDelegate(EditorColorsManager.getInstance().getGlobalScheme()); return panel; } panel = createRendererComponent(myProject, myFileType, myInheritFontFromLaF); Disposer.register(this, panel); Disposer.register( this, new Disposable() { @Override public void dispose() { UIUtil.putClientProperty(table, MY_PANEL_PROPERTY, null); } }); table.putClientProperty(MY_PANEL_PROPERTY, panel); return panel; } @NotNull protected RendererComponent createRendererComponent( @Nullable Project project, @Nullable FileType fileType, boolean inheritFontFromLaF) { return new AbbreviatingRendererComponent(project, fileType, inheritFontFromLaF); } @Override public void dispose() {} public abstract static class RendererComponent extends CellRendererPanel implements Disposable { private final EditorEx myEditor; private final EditorTextField myTextField; protected TextAttributes myTextAttributes; private boolean mySelected; public RendererComponent( Project project, @Nullable FileType fileType, boolean inheritFontFromLaF) { Pair<EditorTextField, EditorEx> pair = createEditor(project, fileType, inheritFontFromLaF); myTextField = pair.first; myEditor = pair.second; add(myEditor.getContentComponent()); } public EditorEx getEditor() { return myEditor; } @NotNull private static Pair<EditorTextField, EditorEx> createEditor( Project project, @Nullable FileType fileType, boolean inheritFontFromLaF) { EditorTextField field = new EditorTextField(new MyDocument(), project, fileType, false, false); field.setSupplementary(true); field.setFontInheritedFromLAF(inheritFontFromLaF); field.addNotify(); // creates editor EditorEx editor = (EditorEx) ObjectUtils.assertNotNull(field.getEditor()); editor.setRendererMode(true); editor.setColorsScheme(editor.createBoundColorSchemeDelegate(null)); editor.getSettings().setCaretRowShown(false); editor.getScrollPane().setBorder(null); return Pair.create(field, editor); } public void setText(String text, @Nullable TextAttributes textAttributes, boolean selected) { myTextAttributes = textAttributes; mySelected = selected; setText(text); } public abstract void setText(String text); @Override public void setBackground(Color bg) { // allows for striped tables if (myEditor != null) { myEditor.setBackgroundColor(bg); } super.setBackground(bg); } @Override public void dispose() { remove(myEditor.getContentComponent()); myTextField.removeNotify(); } protected void setTextToEditor(String text) { myEditor.getMarkupModel().removeAllHighlighters(); myEditor.getDocument().setText(text); ((EditorImpl) myEditor).resetSizes(); myEditor.getHighlighter().setText(text); if (myTextAttributes != null) { myEditor .getMarkupModel() .addRangeHighlighter( 0, myEditor.getDocument().getTextLength(), HighlighterLayer.ADDITIONAL_SYNTAX, myTextAttributes, HighlighterTargetArea.EXACT_RANGE); } ((EditorImpl) myEditor).setPaintSelection(mySelected); SelectionModel selectionModel = myEditor.getSelectionModel(); selectionModel.setSelection(0, mySelected ? myEditor.getDocument().getTextLength() : 0); } } public static class SimpleRendererComponent extends RendererComponent implements Disposable { public SimpleRendererComponent( Project project, @Nullable FileType fileType, boolean inheritFontFromLaF) { super(project, fileType, inheritFontFromLaF); } public void setText(String text) { setTextToEditor(text); } } public static class AbbreviatingRendererComponent extends RendererComponent { private static final char ABBREVIATION_SUFFIX = '\u2026'; // 2026 '...' private static final char RETURN_SYMBOL = '\u23ce'; private final StringBuilder myDocumentTextBuilder = new StringBuilder(); private Dimension myPreferredSize; private String myRawText; public AbbreviatingRendererComponent( Project project, @Nullable FileType fileType, boolean inheritFontFromLaF) { super(project, fileType, inheritFontFromLaF); } @Override public void setText(String text) { myRawText = text; myPreferredSize = null; } @Override public Dimension getPreferredSize() { if (myPreferredSize == null) { int maxLineLength = 0; int linesCount = 0; for (LineTokenizer lt = new LineTokenizer(myRawText); !lt.atEnd(); lt.advance()) { maxLineLength = Math.max(maxLineLength, lt.getLength()); linesCount++; } FontMetrics fontMetrics = ((EditorImpl) getEditor()) .getFontMetrics( myTextAttributes != null ? myTextAttributes.getFontType() : Font.PLAIN); int preferredHeight = getEditor().getLineHeight() * Math.max(1, linesCount); int preferredWidth = fontMetrics.charWidth('m') * maxLineLength; Insets insets = getInsets(); if (insets != null) { preferredHeight += insets.top + insets.bottom; preferredWidth += insets.left + insets.right; } myPreferredSize = new Dimension(preferredWidth, preferredHeight); } return myPreferredSize; } @Override protected void paintChildren(Graphics g) { updateText(g.getClipBounds()); super.paintChildren(g); } private void updateText(Rectangle clip) { FontMetrics fontMetrics = ((EditorImpl) getEditor()) .getFontMetrics( myTextAttributes != null ? myTextAttributes.getFontType() : Font.PLAIN); Insets insets = getInsets(); int maxLineWidth = getWidth() - (insets != null ? insets.left + insets.right : 0); myDocumentTextBuilder.setLength(0); boolean singleLineMode = getHeight() / (float) getEditor().getLineHeight() < 1.1f; if (singleLineMode) { appendAbbreviated( myDocumentTextBuilder, myRawText, 0, myRawText.length(), fontMetrics, maxLineWidth, true); } else { int lineHeight = getEditor().getLineHeight(); int firstVisibleLine = clip.y / lineHeight; float visibleLinesCountFractional = clip.height / (float) lineHeight; int linesToAppend = 1 + (int) visibleLinesCountFractional; LineTokenizer lt = new LineTokenizer(myRawText); for (int line = 0; !lt.atEnd() && line < firstVisibleLine; lt.advance(), line++) { myDocumentTextBuilder.append('\n'); } for (int line = 0; !lt.atEnd() && line < linesToAppend; lt.advance(), line++) { int start = lt.getOffset(); int end = start + lt.getLength(); appendAbbreviated( myDocumentTextBuilder, myRawText, start, end, fontMetrics, maxLineWidth, false); if (lt.getLineSeparatorLength() > 0) { myDocumentTextBuilder.append('\n'); } } } setTextToEditor(myDocumentTextBuilder.toString()); } private static void appendAbbreviated( StringBuilder to, String text, int start, int end, FontMetrics metrics, int maxWidth, boolean replaceLineTerminators) { int abbreviationLength = abbreviationLength(text, start, end, metrics, maxWidth, replaceLineTerminators); if (!replaceLineTerminators) { to.append(text, start, start + abbreviationLength); } else { CharSequenceSubSequence subSeq = new CharSequenceSubSequence(text, start, start + abbreviationLength); for (LineTokenizer lt = new LineTokenizer(subSeq); !lt.atEnd(); lt.advance()) { to.append(subSeq, lt.getOffset(), lt.getOffset() + lt.getLength()); if (lt.getLineSeparatorLength() > 0) { to.append(RETURN_SYMBOL); } } } if (abbreviationLength != end - start) { to.append(ABBREVIATION_SUFFIX); } } private static int abbreviationLength( String text, int start, int end, FontMetrics metrics, int maxWidth, boolean replaceSeparators) { if (metrics.charWidth('m') * (end - start) <= maxWidth) return end - start; int abbrWidth = metrics.charWidth(ABBREVIATION_SUFFIX); int abbrLength = 0; CharSequenceSubSequence subSeq = new CharSequenceSubSequence(text, start, end); for (LineTokenizer lt = new LineTokenizer(subSeq); !lt.atEnd(); lt.advance()) { for (int i = 0; i < lt.getLength(); i++, abbrLength++) { abbrWidth += metrics.charWidth(subSeq.charAt(lt.getOffset() + i)); if (abbrWidth >= maxWidth) return abbrLength; } if (replaceSeparators && lt.getLineSeparatorLength() != 0) { abbrWidth += metrics.charWidth(RETURN_SYMBOL); if (abbrWidth >= maxWidth) return abbrLength; abbrLength += lt.getLineSeparatorLength(); } } return abbrLength; } } private static class MyDocument extends UserDataHolderBase implements DocumentEx { RangeMarkerTree<RangeMarkerEx> myRangeMarkers = new RangeMarkerTree<RangeMarkerEx>(this) {}; char[] myChars = ArrayUtil.EMPTY_CHAR_ARRAY; String myString = ""; LineSet myLineSet = LineSet.createLineSet(myString); @Override public void setText(@NotNull CharSequence text) { String s = StringUtil.convertLineSeparators(text.toString()); myChars = new char[s.length()]; s.getChars(0, s.length(), myChars, 0); myString = new String(myChars); myLineSet = LineSet.createLineSet(myString); } @Override public void setStripTrailingSpacesEnabled(boolean isEnabled) {} @NotNull @Override public LineIterator createLineIterator() { return myLineSet.createIterator(); } @Override public void setModificationStamp(long modificationStamp) {} @Override public void addEditReadOnlyListener(@NotNull EditReadOnlyListener listener) {} @Override public void removeEditReadOnlyListener(@NotNull EditReadOnlyListener listener) {} @Override public void replaceText(@NotNull CharSequence chars, long newModificationStamp) {} @Override public void moveText(int srcStart, int srcEnd, int dstOffset) {} @Override public void suppressGuardedExceptions() {} @Override public void unSuppressGuardedExceptions() {} @Override public boolean isInEventsHandling() { return false; } @Override public void clearLineModificationFlags() {} @Override public boolean removeRangeMarker(@NotNull RangeMarkerEx rangeMarker) { return myRangeMarkers.removeInterval(rangeMarker); } @Override public void registerRangeMarker( @NotNull RangeMarkerEx rangeMarker, int start, int end, boolean greedyToLeft, boolean greedyToRight, int layer) { myRangeMarkers.addInterval(rangeMarker, start, end, greedyToLeft, greedyToRight, layer); } @Override public boolean isInBulkUpdate() { return false; } @Override public void setInBulkUpdate(boolean value) {} @NotNull @Override public List<RangeMarker> getGuardedBlocks() { return Collections.emptyList(); } @Override public boolean processRangeMarkers(@NotNull Processor<? super RangeMarker> processor) { return myRangeMarkers.process(processor); } @Override public boolean processRangeMarkersOverlappingWith( int start, int end, @NotNull Processor<? super RangeMarker> processor) { return myRangeMarkers.processOverlappingWith(start, end, processor); } @NotNull @Override public String getText() { return myString; } @NotNull @Override public String getText(@NotNull TextRange range) { return range.substring(getText()); } @NotNull @Override public CharSequence getCharsSequence() { return myString; } @NotNull @Override public CharSequence getImmutableCharSequence() { return getText(); } @NotNull @Override public char[] getChars() { return myChars; } @Override public int getTextLength() { return myChars.length; } @Override public int getLineCount() { return myLineSet.findLineIndex(myChars.length) + 1; } @Override public int getLineNumber(int offset) { return myLineSet.findLineIndex(offset); } @Override public int getLineStartOffset(int line) { return myChars.length == 0 ? 0 : myLineSet.getLineStart(line); } @Override public int getLineEndOffset(int line) { return myChars.length == 0 ? 0 : myLineSet.getLineEnd(line); } @Override public void insertString(int offset, @NotNull CharSequence s) {} @Override public void deleteString(int startOffset, int endOffset) {} @Override public void replaceString(int startOffset, int endOffset, @NotNull CharSequence s) {} @Override public boolean isWritable() { return false; } @Override public long getModificationStamp() { return 0; } @Override public void fireReadOnlyModificationAttempt() {} @Override public void addDocumentListener(@NotNull DocumentListener listener) {} @Override public void addDocumentListener( @NotNull DocumentListener listener, @NotNull Disposable parentDisposable) {} @Override public void removeDocumentListener(@NotNull DocumentListener listener) {} @NotNull @Override public RangeMarker createRangeMarker(int startOffset, int endOffset) { return new RangeMarkerImpl(this, startOffset, endOffset, true) {}; } @NotNull @Override public RangeMarker createRangeMarker( int startOffset, int endOffset, boolean surviveOnExternalChange) { return null; } @Override public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) {} @Override public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) {} @Override public void setReadOnly(boolean isReadOnly) {} @NotNull @Override public RangeMarker createGuardedBlock(int startOffset, int endOffset) { return null; } @Override public void removeGuardedBlock(@NotNull RangeMarker block) {} @Nullable @Override public RangeMarker getOffsetGuard(int offset) { return null; } @Nullable @Override public RangeMarker getRangeGuard(int start, int end) { return null; } @Override public void startGuardedBlockChecking() {} @Override public void stopGuardedBlockChecking() {} @Override public void setCyclicBufferSize(int bufferSize) {} @NotNull @Override public RangeMarker createRangeMarker(@NotNull TextRange textRange) { return null; } @Override public int getLineSeparatorLength(int line) { return 0; } @Override public int getModificationSequence() { return 0; } } }