@Override public void addProjectManagerListener( @NotNull Project project, @NotNull ProjectManagerListener listener) { List<ProjectManagerListener> listeners = project.getUserData(LISTENERS_IN_PROJECT_KEY); if (listeners == null) { listeners = ((UserDataHolderEx) project) .putUserDataIfAbsent( LISTENERS_IN_PROJECT_KEY, ContainerUtil.<ProjectManagerListener>createLockFreeCopyOnWriteList()); } listeners.add(listener); }
public void registerFix( @Nullable IntentionAction action, @Nullable List<IntentionAction> options, @Nullable String displayName, @Nullable TextRange fixRange, @Nullable HighlightDisplayKey key) { if (action == null) return; if (fixRange == null) fixRange = new TextRange(startOffset, endOffset); if (quickFixActionRanges == null) { quickFixActionRanges = ContainerUtil.createLockFreeCopyOnWriteList(); } IntentionActionDescriptor desc = new IntentionActionDescriptor(action, options, displayName, null, key, getProblemGroup()); quickFixActionRanges.add(Pair.create(desc, fixRange)); fixStartOffset = Math.min(fixStartOffset, fixRange.getStartOffset()); fixEndOffset = Math.max(fixEndOffset, fixRange.getEndOffset()); if (action instanceof HintAction) { setHint(true); } }
private static Set<String> calcDevPatternClassNames(@NotNull final Project project) { final List<String> roots = ContainerUtil.createLockFreeCopyOnWriteList(); JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); PsiClass beanClass = psiFacade.findClass(PatternClassBean.class.getName(), GlobalSearchScope.allScope(project)); if (beanClass != null) { GlobalSearchScope scope = GlobalSearchScope.getScopeRestrictedByFileTypes( GlobalSearchScope.allScope(project), StdFileTypes.XML); final TextOccurenceProcessor occurenceProcessor = new TextOccurenceProcessor() { @Override public boolean execute(@NotNull PsiElement element, int offsetInElement) { XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class); String className = tag == null ? null : tag.getAttributeValue("className"); if (StringUtil.isNotEmpty(className) && tag.getLocalName().endsWith("patternClass")) { roots.add(className); } return true; } }; final StringSearcher searcher = new StringSearcher("patternClass", true, true); CacheManager.SERVICE .getInstance(beanClass.getProject()) .processFilesWithWord( new Processor<PsiFile>() { @Override public boolean process(PsiFile psiFile) { LowLevelSearchUtil.processElementsContainingWordInElement( occurenceProcessor, psiFile, searcher, true, new EmptyProgressIndicator()); return true; } }, searcher.getPattern(), UsageSearchContext.IN_FOREIGN_LANGUAGES, scope, searcher.isCaseSensitive()); } return ContainerUtil.newHashSet(roots); }
/** @author Roman Chernyatchik */ public class StatisticsPanel implements DataProvider { public static final DataKey<StatisticsPanel> SM_TEST_RUNNER_STATISTICS = DataKey.create("SM_TEST_RUNNER_STATISTICS"); private TableView<SMTestProxy> myStatisticsTableView; private JPanel myContentPane; private final Storage.PropertiesComponentStorage myStorage = new Storage.PropertiesComponentStorage("sm_test_statistics_table_columns"); private final StatisticsTableModel myTableModel; private final List<PropagateSelectionHandler> myPropagateSelectionHandlers = ContainerUtil.createLockFreeCopyOnWriteList(); private final Project myProject; private final TestFrameworkRunningModel myFrameworkRunningModel; public StatisticsPanel(final Project project, final TestFrameworkRunningModel model) { myProject = project; myTableModel = new StatisticsTableModel(); myStatisticsTableView.setModelAndUpdateColumns(myTableModel); myFrameworkRunningModel = model; final Runnable gotoSuiteOrParentAction = createGotoSuiteOrParentAction(); new DoubleClickListener() { @Override protected boolean onDoubleClick(MouseEvent e) { gotoSuiteOrParentAction.run(); return true; } }.installOn(myStatisticsTableView); // Fire selection changed and move focus on SHIFT+ENTER final KeyStroke shiftEnterKey = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.SHIFT_MASK); SMRunnerUtil.registerAsAction( shiftEnterKey, "select-test-proxy-in-test-view", new Runnable() { public void run() { showSelectedProxyInTestsTree(); } }, myStatisticsTableView); // Expand selected or go to parent on ENTER final KeyStroke enterKey = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0); SMRunnerUtil.registerAsAction( enterKey, "go-to-selected-suite-or-parent", gotoSuiteOrParentAction, myStatisticsTableView); // Contex menu in Table PopupHandler.installPopupHandler( myStatisticsTableView, IdeActions.GROUP_TESTTREE_POPUP, ActionPlaces.TESTTREE_VIEW_POPUP); // set this statistic tab as dataprovider for test's table view DataManager.registerDataProvider(myStatisticsTableView, this); } public void addPropagateSelectionListener(final PropagateSelectionHandler handler) { myPropagateSelectionHandlers.add(handler); } public JPanel getContentPane() { return myContentPane; } public SMTRunnerEventsListener createTestEventsListener() { return new SMTRunnerEventsAdapter() { @Override public void onSuiteStarted(@NotNull final SMTestProxy suite) { if (myTableModel.shouldUpdateModelBySuite(suite)) { updateAndRestoreSelection(); } } @Override public void onSuiteFinished(@NotNull final SMTestProxy suite) { if (myTableModel.shouldUpdateModelBySuite(suite)) { updateAndRestoreSelection(); } } @Override public void onTestStarted(@NotNull final SMTestProxy test) { if (myTableModel.shouldUpdateModelByTest(test)) { updateAndRestoreSelection(); } } @Override public void onTestFinished(@NotNull final SMTestProxy test) { if (myTableModel.shouldUpdateModelByTest(test)) { updateAndRestoreSelection(); } } private void updateAndRestoreSelection() { SMRunnerUtil.addToInvokeLater( new Runnable() { public void run() { BaseTableView.restore(myStorage, myStatisticsTableView); // statisticsTableView can be null in JUnit tests final SMTestProxy oldSelection = myStatisticsTableView.getSelectedObject(); // update module myTableModel.updateModel(); // restore selection if it is possible if (oldSelection != null) { final int newRow = myTableModel.getIndexOf(oldSelection); if (newRow > -1) { myStatisticsTableView.setRowSelectionInterval(newRow, newRow); } } } }); } }; } public Object getData(@NonNls final String dataId) { if (SM_TEST_RUNNER_STATISTICS.is(dataId)) { return this; } return TestsUIUtil.getData(getSelectedItem(), dataId, myFrameworkRunningModel); } /** * On event - change selection and probably requests focus. Is used when we want navigate from * other component to this * * @return Listener */ public PropagateSelectionHandler createSelectMeListener() { return new PropagateSelectionHandler() { public void handlePropagateSelectionRequest( @Nullable final SMTestProxy selectedTestProxy, @NotNull final Object sender, final boolean requestFocus) { selectProxy(selectedTestProxy, sender, requestFocus); } }; } public void selectProxy( @Nullable final SMTestProxy selectedTestProxy, @NotNull final Object sender, final boolean requestFocus) { SMRunnerUtil.addToInvokeLater( new Runnable() { public void run() { // Select tab if focus was requested if (requestFocus) { IdeFocusManager.getInstance(myProject).requestFocus(myStatisticsTableView, true); } // Select proxy in table selectProxy(selectedTestProxy); } }); } protected void showSelectedProxyInTestsTree() { final Collection<SMTestProxy> proxies = myStatisticsTableView.getSelection(); if (proxies.isEmpty()) { return; } final SMTestProxy proxy = proxies.iterator().next(); myStatisticsTableView.clearSelection(); fireOnPropagateSelection(proxy); } protected Runnable createGotoSuiteOrParentAction() { // Expand selected or go to parent return new Runnable() { public void run() { final SMTestProxy selectedProxy = getSelectedItem(); if (selectedProxy == null) { return; } final int i = myStatisticsTableView.getSelectedRow(); assert i >= 0; // because something is selected // if selected element is suite - we should expand it if (selectedProxy.isSuite()) { // expand and select first (Total) row showInTableAndSelectRow(selectedProxy, selectedProxy); } } }; } protected void selectProxy(@Nullable final SMTestProxy selectedTestProxy) { // Send event to model myTableModel.updateModelOnProxySelected(selectedTestProxy); // Now we want to select proxy in table (if it is possible) if (selectedTestProxy != null) { findAndSelectInTable(selectedTestProxy); } } /** * Selects row in table * * @param rowIndex Row's index */ protected void selectRow(final int rowIndex) { SMRunnerUtil.addToInvokeLater( new Runnable() { public void run() { // updates model myStatisticsTableView.setRowSelectionInterval(rowIndex, rowIndex); // Scroll to visible TableUtil.scrollSelectionToVisible(myStatisticsTableView); } }); } /** Selects row in table */ protected void selectRowOf(final SMTestProxy proxy) { SMRunnerUtil.addToInvokeLater( new Runnable() { public void run() { final int rowIndex = myTableModel.getIndexOf(proxy); myStatisticsTableView.setRowSelectionInterval(rowIndex, rowIndex >= 0 ? rowIndex : 0); // Scroll to visible TableUtil.scrollSelectionToVisible(myStatisticsTableView); } }); } @Nullable protected SMTestProxy getSelectedItem() { return myStatisticsTableView.getSelectedObject(); } protected List<SMTestProxy> getTableItems() { return myTableModel.getItems(); } private void findAndSelectInTable(final SMTestProxy proxy) { SMRunnerUtil.addToInvokeLater( new Runnable() { public void run() { final int rowIndex = myTableModel.getIndexOf(proxy); if (rowIndex >= 0) { myStatisticsTableView.setRowSelectionInterval(rowIndex, rowIndex); } } }); } private void fireOnPropagateSelection(final SMTestProxy selectedTestProxy) { for (PropagateSelectionHandler handler : myPropagateSelectionHandlers) { handler.handlePropagateSelectionRequest(selectedTestProxy, this, true); } } private void createUIComponents() { myStatisticsTableView = new TableView<SMTestProxy>(); } private void showInTableAndSelectRow(final SMTestProxy suite, final SMTestProxy suiteProxy) { selectProxy(suite); selectRowOf(suiteProxy); } public void doDispose() { BaseTableView.store(myStorage, myStatisticsTableView); } }
public abstract class PsiDocumentManagerBase extends PsiDocumentManager implements ProjectComponent, DocumentListener { protected static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.PsiDocumentManagerImpl"); protected static final Key<PsiFile> HARD_REF_TO_PSI = new Key<PsiFile>("HARD_REFERENCE_TO_PSI"); protected static final Key<List<Runnable>> ACTION_AFTER_COMMIT = Key.create("ACTION_AFTER_COMMIT"); protected final Project myProject; protected final PsiManager myPsiManager; protected final DocumentCommitProcessor myDocumentCommitProcessor; protected final Set<Document> myUncommittedDocuments = Collections.synchronizedSet(new HashSet<Document>()); protected volatile boolean myIsCommitInProgress; protected final PsiToDocumentSynchronizer mySynchronizer; protected final List<Listener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); protected final SmartPointerManagerImpl mySmartPointerManager; public PsiDocumentManagerBase( @NotNull final Project project, @NotNull PsiManager psiManager, @NotNull SmartPointerManager smartPointerManager, @NotNull MessageBus bus, @NonNls @NotNull final DocumentCommitProcessor documentCommitProcessor) { myProject = project; myPsiManager = psiManager; myDocumentCommitProcessor = documentCommitProcessor; mySmartPointerManager = (SmartPointerManagerImpl) smartPointerManager; mySynchronizer = new PsiToDocumentSynchronizer(this, bus); myPsiManager.addPsiTreeChangeListener(mySynchronizer); } @Override public void projectOpened() {} @Override public void projectClosed() {} @Override @NotNull public String getComponentName() { return "PsiDocumentManager"; } @Override public void initComponent() {} @Override public void disposeComponent() {} @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, @NotNull 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 public FileViewProvider getCachedViewProvider(@NotNull Document document) { final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); if (virtualFile == null || !virtualFile.isValid()) return null; return ((PsiManagerEx) myPsiManager).getFileManager().findCachedViewProvider(virtualFile); } @Nullable protected PsiFile getCachedPsiFile(VirtualFile virtualFile) { return ((PsiManagerEx) myPsiManager).getFileManager().getCachedPsiFile(virtualFile); } @Nullable protected PsiFile getPsiFile(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) { cachePsi(document, file); } return document; } if (!file.getViewProvider().isEventSystemEnabled()) return null; document = FileDocumentManager.getInstance().getDocument(file.getViewProvider().getVirtualFile()); if (document != null) { if (document.getTextLength() != file.getTextLength()) { throw new AssertionError( "Modified PSI with no document: " + file + "; physical=" + file.getViewProvider().isPhysical()); } if (!file.getViewProvider().isPhysical()) { cachePsi(document, file); } } 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(); 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, null); } } 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)) { final InjectedLanguageManager injectedLanguageManager = InjectedLanguageManager.getInstance(myProject); if (injectedLanguageManager != null) injectedLanguageManager.startRunInjectors(document, synchronously); } // run after commit actions outside write action runAfterCommitActions(document); if (DebugUtil.DO_EXPENSIVE_CHECKS) { 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; } if (!success) { break; } } viewProvider.contentsSynchronized(); } } 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 + "'"); } } }); } } protected void doCommit(@NotNull final Document document, final PsiFile excludeFile) { 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) && excludeFile == null) return; myIsCommitInProgress = true; try { myDocumentCommitProcessor.commitSynchronously(document, myProject, excludeFile); } finally { myIsCommitInProgress = false; } assert !myUncommittedDocuments.contains(document) : "Document :" + document; } }); } public void commitOtherFilesAssociatedWithDocument( final Document document, final PsiFile psiFile) {} @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 commitAndRunReadAction(@NotNull final Runnable runnable) { final Application application = ApplicationManager.getApplication(); if (SwingUtilities.isEventDispatchThread()) { commitAllDocuments(); runnable.run(); } else { LOG.assertTrue( !ApplicationManager.getApplication().isReadAccessAllowed(), "Don't call commitAndRunReadAction inside ReadAction, it will cause a deadlock otherwise."); 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) { Runnable action = actionsWhenAllDocumentsAreCommitted.remove(key); myDocumentCommitProcessor.log("Running after commit runnable: ", null, false, key, action); action.run(); } } } @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) {} protected void fireDocumentCreated(@NotNull Document document, PsiFile file) { for (Listener listener : myListeners) { listener.documentCreated(document, file); } } private void fireFileCreated(Document document, PsiFile file) { for (Listener listener : myListeners) { listener.fileCreated(file, document); } } @Override @NotNull public Document[] getUncommittedDocuments() { ApplicationManager.getApplication().assertIsDispatchThread(); return myUncommittedDocuments.toArray(new Document[myUncommittedDocuments.size()]); } public Collection<Document> getUncommittedDocumentsUnsafe() { return myUncommittedDocuments; } @Override public boolean isUncommited(@NotNull Document document) { return !isCommitted(document); } @Override public boolean isCommitted(@NotNull Document document) { if (getSynchronizer().isInSynchronization(document)) return true; return !((DocumentEx) document).isInEventsHandling() && !myUncommittedDocuments.contains(document); } @Override public boolean hasUncommitedDocuments() { return !myIsCommitInProgress && !myUncommittedDocuments.isEmpty(); } @Override public void beforeDocumentChange(DocumentEvent event) { final Document document = event.getDocument(); final FileViewProvider viewProvider = getCachedViewProvider(document); if (viewProvider == null) return; if (!isRelevant(viewProvider)) return; VirtualFile virtualFile = viewProvider.getVirtualFile(); if (virtualFile.getFileType().isBinary()) return; final List<PsiFile> files = viewProvider.getAllFiles(); PsiFile psiCause = null; for (PsiFile file : files) { mySmartPointerManager.fastenBelts(file, event.getOffset(), null); if (TextBlock.get(file).isLocked()) { psiCause = file; } } if (psiCause == null) { beforeDocumentChangeOnUnlockedDocument(viewProvider); } ((SingleRootFileViewProvider) viewProvider).beforeDocumentChanged(psiCause); } protected void beforeDocumentChangeOnUnlockedDocument( @NotNull final FileViewProvider viewProvider) {} @Override public void documentChanged(DocumentEvent event) { final Document document = event.getDocument(); final FileViewProvider viewProvider = getCachedViewProvider(document); if (viewProvider == null) return; if (!isRelevant(viewProvider)) return; ApplicationManager.getApplication().assertWriteAccessAllowed(); final List<PsiFile> files = viewProvider.getAllFiles(); boolean commitNecessary = true; for (PsiFile file : files) { mySmartPointerManager.unfastenBelts(file, event.getOffset()); final TextBlock textBlock = TextBlock.get(file); if (textBlock.isLocked()) { commitNecessary = false; continue; } textBlock.documentChanged(event); 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()); // 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) { myUncommittedDocuments.add(document); if (forceCommit) { commitDocument(document); } else if (!((DocumentEx) document).isInBulkUpdate()) { myDocumentCommitProcessor.commitAsynchronously(myProject, document, event); } } } private boolean isRelevant(@NotNull FileViewProvider viewProvider) { VirtualFile virtualFile = viewProvider.getVirtualFile(); return !virtualFile.getFileType().isBinary() && viewProvider.getManager() == myPsiManager && !myPsiManager.getProject().isDisposed(); } public static boolean checkConsistency(PsiFile psiFile, 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() { myUncommittedDocuments.clear(); mySynchronizer.cleanupForNextTest(); } public PsiToDocumentSynchronizer getSynchronizer() { return mySynchronizer; } }
/** @author oleg */ public class PydevConsoleRunner extends AbstractConsoleRunnerWithHistory<PythonConsoleView> { private static final Logger LOG = Logger.getInstance(PydevConsoleRunner.class.getName()); @SuppressWarnings("SpellCheckingInspection") public static final String PYDEV_PYDEVCONSOLE_PY = "pydev/pydevconsole.py"; public static final int PORTS_WAITING_TIMEOUT = 20000; private Sdk mySdk; @NotNull private CommandLineArgumentsProvider myCommandLineArgumentsProvider; private int[] myPorts; private PydevConsoleCommunication myPydevConsoleCommunication; private PyConsoleProcessHandler myProcessHandler; private PydevConsoleExecuteActionHandler myConsoleExecuteActionHandler; private List<ConsoleListener> myConsoleListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private final PyConsoleType myConsoleType; private Map<String, String> myEnvironmentVariables; private String myCommandLine; private String[] myStatementsToExecute = ArrayUtil.EMPTY_STRING_ARRAY; private ConsoleHistoryController myHistoryController; public static Key<ConsoleCommunication> CONSOLE_KEY = new Key<ConsoleCommunication>("PYDEV_CONSOLE_KEY"); public static Key<Sdk> CONSOLE_SDK = new Key<Sdk>("PYDEV_CONSOLE_SDK_KEY"); private static final long APPROPRIATE_TO_WAIT = 60000; private PyRemoteSdkCredentials myRemoteCredentials; private ToolWindow myToolWindow; private String myConsoleTitle = null; protected PydevConsoleRunner( @NotNull final Project project, @NotNull Sdk sdk, @NotNull final PyConsoleType consoleType, @Nullable final String workingDir, Map<String, String> environmentVariables) { super(project, consoleType.getTitle(), workingDir); mySdk = sdk; myConsoleType = consoleType; myEnvironmentVariables = environmentVariables; } public void setStatementsToExecute(String... statementsToExecute) { myStatementsToExecute = statementsToExecute; } public static Map<String, String> addDefaultEnvironments(Sdk sdk, Map<String, String> envs) { Charset defaultCharset = EncodingManager.getInstance().getDefaultCharset(); final String encoding = defaultCharset != null ? defaultCharset.name() : "utf-8"; setPythonIOEncoding(setPythonUnbuffered(envs), encoding); PythonSdkFlavor.initPythonPath(envs, true, PythonCommandLineState.getAddedPaths(sdk)); return envs; } @Override protected List<AnAction> fillToolBarActions( final DefaultActionGroup toolbarActions, final Executor defaultExecutor, final RunContentDescriptor contentDescriptor) { AnAction backspaceHandlingAction = createBackspaceHandlingAction(); // toolbarActions.add(backspaceHandlingAction); AnAction interruptAction = createInterruptAction(); AnAction rerunAction = createRerunAction(); toolbarActions.add(rerunAction); List<AnAction> actions = super.fillToolBarActions(toolbarActions, defaultExecutor, contentDescriptor); actions.add(0, rerunAction); actions.add(backspaceHandlingAction); actions.add(interruptAction); actions.add(createSplitLineAction()); AnAction showVarsAction = new ShowVarsAction(); toolbarActions.add(showVarsAction); toolbarActions.add(myHistoryController.getBrowseHistory()); toolbarActions.add(new ConnectDebuggerAction()); return actions; } @NotNull public static PydevConsoleRunner createAndRun( @NotNull final Project project, @NotNull final Sdk sdk, @NotNull final PyConsoleType consoleType, @Nullable final String workingDirectory, @NotNull final Map<String, String> environmentVariables, @Nullable final ToolWindow toolWindow, final String... statements2execute) { final PydevConsoleRunner consoleRunner = create(project, sdk, consoleType, workingDirectory, environmentVariables); consoleRunner.setToolWindow(toolWindow); consoleRunner.setStatementsToExecute(statements2execute); consoleRunner.run(); return consoleRunner; } public void run() { UIUtil.invokeAndWaitIfNeeded( new Runnable() { @Override public void run() { FileDocumentManager.getInstance().saveAllDocuments(); } }); myPorts = findAvailablePorts(getProject(), myConsoleType); assert myPorts != null; myCommandLineArgumentsProvider = createCommandLineArgumentsProvider(mySdk, myEnvironmentVariables, myPorts); UIUtil.invokeLaterIfNeeded( new Runnable() { @Override public void run() { ProgressManager.getInstance() .run( new Task.Backgroundable(getProject(), "Connecting to console", false) { @Override public void run(@NotNull final ProgressIndicator indicator) { indicator.setText("Connecting to console..."); try { initAndRun(myStatementsToExecute); } catch (ExecutionException e) { LOG.warn("Error running console", e); assert myProject != null; ExecutionHelper.showErrors( myProject, Arrays.<Exception>asList(e), getTitle(), null); } } }); } }); } public static PydevConsoleRunner create( Project project, Sdk sdk, PyConsoleType consoleType, String workingDirectory) { return create(project, sdk, consoleType, workingDirectory, Maps.<String, String>newHashMap()); } @NotNull private static PydevConsoleRunner create( @NotNull final Project project, @NotNull final Sdk sdk, @NotNull final PyConsoleType consoleType, @Nullable final String workingDirectory, @NotNull final Map<String, String> environmentVariables) { return new PydevConsoleRunner( project, sdk, consoleType, workingDirectory, environmentVariables); } private static int[] findAvailablePorts(Project project, PyConsoleType consoleType) { final int[] ports; try { // File "pydev/console/pydevconsole.py", line 223, in <module> // port, client_port = sys.argv[1:3] ports = NetUtils.findAvailableSocketPorts(2); } catch (IOException e) { ExecutionHelper.showErrors( project, Arrays.<Exception>asList(e), consoleType.getTitle(), null); return null; } return ports; } private static CommandLineArgumentsProvider createCommandLineArgumentsProvider( final Sdk sdk, final Map<String, String> environmentVariables, int[] ports) { final ArrayList<String> args = new ArrayList<String>(); args.add(sdk.getHomePath()); final String versionString = sdk.getVersionString(); if (versionString == null || !versionString.toLowerCase().contains("jython")) { args.add("-u"); } args.add( FileUtil.toSystemDependentName(PythonHelpersLocator.getHelperPath(PYDEV_PYDEVCONSOLE_PY))); for (int port : ports) { args.add(String.valueOf(port)); } return new CommandLineArgumentsProvider() { @Override public String[] getArguments() { return ArrayUtil.toStringArray(args); } @Override public boolean passParentEnvs() { return false; } @Override public Map<String, String> getAdditionalEnvs() { return addDefaultEnvironments(sdk, environmentVariables); } }; } @Override protected PythonConsoleView createConsoleView() { PythonConsoleView consoleView = new PythonConsoleView(getProject(), getConsoleTitle(), mySdk); myPydevConsoleCommunication.setConsoleFile(consoleView.getConsoleVirtualFile()); consoleView.addMessageFilter(new PythonTracebackFilter(getProject())); return consoleView; } @Override protected Process createProcess() throws ExecutionException { if (PySdkUtil.isRemote(mySdk)) { PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance(); if (manager != null) { return createRemoteConsoleProcess( manager, myCommandLineArgumentsProvider.getArguments(), myCommandLineArgumentsProvider.getAdditionalEnvs()); } throw new PythonRemoteInterpreterManager.PyRemoteInterpreterExecutionException(); } else { myCommandLine = myCommandLineArgumentsProvider.getCommandLineString(); Map<String, String> envs = myCommandLineArgumentsProvider.getAdditionalEnvs(); if (envs != null) { EncodingEnvironmentUtil.fixDefaultEncodingIfMac(envs, getProject()); } final Process server = ProcessRunner.createProcess( getWorkingDir(), envs, myCommandLineArgumentsProvider.getArguments()); try { myPydevConsoleCommunication = new PydevConsoleCommunication(getProject(), myPorts[0], server, myPorts[1]); } catch (Exception e) { throw new ExecutionException(e.getMessage()); } return server; } } private Process createRemoteConsoleProcess( PythonRemoteInterpreterManager manager, String[] command, Map<String, String> env) throws ExecutionException { PyRemoteSdkAdditionalDataBase data = (PyRemoteSdkAdditionalDataBase) mySdk.getSdkAdditionalData(); assert data != null; GeneralCommandLine commandLine = new GeneralCommandLine(command); commandLine.getEnvironment().putAll(env); commandLine .getParametersList() .set( 1, PythonRemoteInterpreterManager.toSystemDependent( new File(data.getHelpersPath(), PYDEV_PYDEVCONSOLE_PY).getPath(), PySourcePosition.isWindowsPath(data.getInterpreterPath()))); commandLine.getParametersList().set(2, "0"); commandLine.getParametersList().set(3, "0"); myCommandLine = commandLine.getCommandLineString(); try { myRemoteCredentials = data.getRemoteSdkCredentials(true); PathMappingSettings mappings = manager.setupMappings(getProject(), data, null); RemoteSshProcess remoteProcess = manager.createRemoteProcess( getProject(), myRemoteCredentials, mappings, commandLine, true); Couple<Integer> remotePorts = getRemotePortsFromProcess(remoteProcess); remoteProcess.addLocalTunnel(myPorts[0], myRemoteCredentials.getHost(), remotePorts.first); remoteProcess.addRemoteTunnel(remotePorts.second, "localhost", myPorts[1]); myPydevConsoleCommunication = new PydevRemoteConsoleCommunication(getProject(), myPorts[0], remoteProcess, myPorts[1]); return remoteProcess; } catch (Exception e) { throw new ExecutionException(e.getMessage()); } } private static Couple<Integer> getRemotePortsFromProcess(RemoteSshProcess process) throws ExecutionException { Scanner s = new Scanner(process.getInputStream()); return Couple.of(readInt(s, process), readInt(s, process)); } private static int readInt(Scanner s, Process process) throws ExecutionException { long started = System.currentTimeMillis(); while (System.currentTimeMillis() - started < PORTS_WAITING_TIMEOUT) { if (s.hasNextLine()) { String line = s.nextLine(); try { return Integer.parseInt(line); } catch (NumberFormatException ignored) { continue; } } try { Thread.sleep(200); } catch (InterruptedException ignored) { } if (process.exitValue() != 0) { String error; try { error = "Console process terminated with error:\n" + StreamUtil.readText(process.getErrorStream()); } catch (Exception ignored) { error = "Console process terminated with exit code " + process.exitValue(); } throw new ExecutionException(error); } else { break; } } throw new ExecutionException("Couldn't read integer value from stream"); } @Override protected PyConsoleProcessHandler createProcessHandler(final Process process) { if (PySdkUtil.isRemote(mySdk)) { PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance(); if (manager != null) { PyRemoteSdkAdditionalDataBase data = (PyRemoteSdkAdditionalDataBase) mySdk.getSdkAdditionalData(); assert data != null; myProcessHandler = manager.createConsoleProcessHandler( process, myRemoteCredentials, getConsoleView(), myPydevConsoleCommunication, myCommandLine, CharsetToolkit.UTF8_CHARSET, manager.setupMappings(getProject(), data, null)); } else { LOG.error("Can't create remote console process handler"); } } else { myProcessHandler = new PyConsoleProcessHandler( process, getConsoleView(), myPydevConsoleCommunication, myCommandLine, CharsetToolkit.UTF8_CHARSET); } return myProcessHandler; } public void initAndRun(final String... statements2execute) throws ExecutionException { super.initAndRun(); if (handshake()) { ApplicationManager.getApplication() .invokeLater( new Runnable() { @Override public void run() { // Propagate console communication to language console final PythonConsoleView consoleView = getConsoleView(); consoleView.setConsoleCommunication(myPydevConsoleCommunication); consoleView.setSdk(mySdk); consoleView.setExecutionHandler(myConsoleExecuteActionHandler); myProcessHandler.addProcessListener( new ProcessAdapter() { @Override public void onTextAvailable(ProcessEvent event, Key outputType) { consoleView.print(event.getText(), outputType); } }); enableConsoleExecuteAction(); for (String statement : statements2execute) { consoleView.executeStatement(statement + "\n", ProcessOutputTypes.SYSTEM); } fireConsoleInitializedEvent(consoleView); } }); } else { getConsoleView().print("Couldn't connect to console process.", ProcessOutputTypes.STDERR); myProcessHandler.destroyProcess(); finishConsole(); } } @Override protected String constructConsoleTitle(@NotNull String consoleTitle) { if (myConsoleTitle == null) { myConsoleTitle = super.constructConsoleTitle(consoleTitle); } return myConsoleTitle; } @Override protected void showConsole(Executor defaultExecutor, RunContentDescriptor contentDescriptor) { PythonConsoleToolWindow terminalView = PythonConsoleToolWindow.getInstance(getProject()); terminalView.init(getToolWindow(), contentDescriptor); } protected AnAction createRerunAction() { return new RestartAction(this); } private AnAction createInterruptAction() { AnAction anAction = new AnAction() { @Override public void actionPerformed(final AnActionEvent e) { if (myPydevConsoleCommunication.isExecuting()) { getConsoleView().print("^C", ProcessOutputTypes.SYSTEM); } myPydevConsoleCommunication.interrupt(); } @Override public void update(final AnActionEvent e) { EditorEx consoleEditor = getConsoleView().getConsole().getConsoleEditor(); boolean enabled = IJSwingUtilities.hasFocus(consoleEditor.getComponent()) && !consoleEditor.getSelectionModel().hasSelection(); e.getPresentation().setEnabled(enabled); } }; anAction.registerCustomShortcutSet( KeyEvent.VK_C, InputEvent.CTRL_MASK, getConsoleView().getConsole().getConsoleEditor().getComponent()); anAction.getTemplatePresentation().setVisible(false); return anAction; } private AnAction createBackspaceHandlingAction() { final AnAction upAction = new AnAction() { @Override public void actionPerformed(final AnActionEvent e) { new WriteCommandAction( getLanguageConsole().getProject(), getLanguageConsole().getFile()) { @Override protected void run(@NotNull final Result result) throws Throwable { String text = getLanguageConsole().getEditorDocument().getText(); String newText = text.substring( 0, text.length() - myConsoleExecuteActionHandler.getPythonIndent()); getLanguageConsole().getEditorDocument().setText(newText); getLanguageConsole() .getConsoleEditor() .getCaretModel() .moveToOffset(newText.length()); } }.execute(); } @Override public void update(final AnActionEvent e) { e.getPresentation() .setEnabled( myConsoleExecuteActionHandler.getCurrentIndentSize() >= myConsoleExecuteActionHandler.getPythonIndent() && isIndentSubstring(getLanguageConsole().getEditorDocument().getText())); } }; upAction.registerCustomShortcutSet(KeyEvent.VK_BACK_SPACE, 0, null); upAction.getTemplatePresentation().setVisible(false); return upAction; } private boolean isIndentSubstring(String text) { int indentSize = myConsoleExecuteActionHandler.getPythonIndent(); return text.length() >= indentSize && CharMatcher.WHITESPACE.matchesAllOf(text.substring(text.length() - indentSize)); } private void enableConsoleExecuteAction() { myConsoleExecuteActionHandler.setEnabled(true); } private boolean handshake() { boolean res; long started = System.currentTimeMillis(); do { try { res = myPydevConsoleCommunication.handshake(); } catch (XmlRpcException ignored) { res = false; } if (res) { break; } else { long now = System.currentTimeMillis(); if (now - started > APPROPRIATE_TO_WAIT) { break; } else { try { Thread.sleep(100); } catch (InterruptedException ignored) { } } } } while (true); return res; } @Override protected AnAction createStopAction() { final AnAction generalStopAction = super.createStopAction(); return createConsoleStoppingAction(generalStopAction); } @Override protected AnAction createCloseAction( Executor defaultExecutor, final RunContentDescriptor descriptor) { final AnAction generalCloseAction = super.createCloseAction(defaultExecutor, descriptor); final AnAction stopAction = new DumbAwareAction() { @Override public void update(AnActionEvent e) { generalCloseAction.update(e); } @Override public void actionPerformed(AnActionEvent e) { e = stopConsole(e); clearContent(descriptor); generalCloseAction.actionPerformed(e); } }; stopAction.copyFrom(generalCloseAction); return stopAction; } private void clearContent(RunContentDescriptor descriptor) { Content content = getToolWindow().getContentManager().findContent(descriptor.getDisplayName()); assert content != null; getToolWindow().getContentManager().removeContent(content, true); } private AnAction createConsoleStoppingAction(final AnAction generalStopAction) { final AnAction stopAction = new DumbAwareAction() { @Override public void update(AnActionEvent e) { generalStopAction.update(e); } @Override public void actionPerformed(AnActionEvent e) { e = stopConsole(e); generalStopAction.actionPerformed(e); } }; stopAction.copyFrom(generalStopAction); return stopAction; } private AnActionEvent stopConsole(AnActionEvent e) { if (myPydevConsoleCommunication != null) { e = new AnActionEvent( e.getInputEvent(), e.getDataContext(), e.getPlace(), e.getPresentation(), e.getActionManager(), e.getModifiers()); try { closeCommunication(); // waiting for REPL communication before destroying process handler Thread.sleep(300); } catch (Exception ignored) { // Ignore } } return e; } protected AnAction createSplitLineAction() { class ConsoleSplitLineAction extends EditorAction { private static final String CONSOLE_SPLIT_LINE_ACTION_ID = "Console.SplitLine"; public ConsoleSplitLineAction() { super( new EditorWriteActionHandler() { private final SplitLineAction mySplitLineAction = new SplitLineAction(); @Override public boolean isEnabled(Editor editor, DataContext dataContext) { return mySplitLineAction.getHandler().isEnabled(editor, dataContext); } @Override public void executeWriteAction( Editor editor, @Nullable Caret caret, DataContext dataContext) { ((EditorWriteActionHandler) mySplitLineAction.getHandler()) .executeWriteAction(editor, caret, dataContext); editor.getCaretModel().getCurrentCaret().moveCaretRelatively(0, 1, false, true); } }); } public void setup() { EmptyAction.setupAction(this, CONSOLE_SPLIT_LINE_ACTION_ID, null); } } ConsoleSplitLineAction action = new ConsoleSplitLineAction(); action.setup(); return action; } private void closeCommunication() { if (!myProcessHandler.isProcessTerminated()) { myPydevConsoleCommunication.close(); } } @NotNull @Override protected ProcessBackedConsoleExecuteActionHandler createExecuteActionHandler() { myConsoleExecuteActionHandler = new PydevConsoleExecuteActionHandler( getConsoleView(), getProcessHandler(), myPydevConsoleCommunication); myConsoleExecuteActionHandler.setEnabled(false); myHistoryController = new ConsoleHistoryController( myConsoleType.getTypeId(), "", getLanguageConsole(), myConsoleExecuteActionHandler.getConsoleHistoryModel()); myHistoryController.install(); return myConsoleExecuteActionHandler; } public PydevConsoleCommunication getPydevConsoleCommunication() { return myPydevConsoleCommunication; } public static boolean isInPydevConsole(final PsiElement element) { return element instanceof PydevConsoleElement || getConsoleCommunication(element) != null; } public static boolean isInPydevConsole(final VirtualFile file) { return file.getName().contains("Python Console"); } public static boolean isPythonConsole(@Nullable final FileElement element) { return getPythonConsoleData(element) != null; } @Nullable public static PythonConsoleData getPythonConsoleData(@Nullable FileElement element) { if (element == null || element.getPsi() == null || element.getPsi().getContainingFile() == null) { return null; } VirtualFile file = getConsoleFile(element.getPsi().getContainingFile()); if (file == null) { return null; } return file.getUserData(PyConsoleUtil.PYTHON_CONSOLE_DATA); } private static VirtualFile getConsoleFile(PsiFile psiFile) { VirtualFile file = psiFile.getViewProvider().getVirtualFile(); if (file instanceof LightVirtualFile) { file = ((LightVirtualFile) file).getOriginalFile(); } return file; } @Nullable public static ConsoleCommunication getConsoleCommunication(final PsiElement element) { final PsiFile containingFile = element.getContainingFile(); return containingFile != null ? containingFile.getCopyableUserData(CONSOLE_KEY) : null; } @Nullable public static Sdk getConsoleSdk(final PsiElement element) { final PsiFile containingFile = element.getContainingFile(); return containingFile != null ? containingFile.getCopyableUserData(CONSOLE_SDK) : null; } @Override protected boolean shouldAddNumberToTitle() { return true; } public void addConsoleListener(ConsoleListener consoleListener) { myConsoleListeners.add(consoleListener); } public void removeConsoleListener(ConsoleListener consoleListener) { myConsoleListeners.remove(consoleListener); } private void fireConsoleInitializedEvent(LanguageConsoleView consoleView) { for (ConsoleListener listener : myConsoleListeners) { listener.handleConsoleInitialized(consoleView); } } public ToolWindow getToolWindow() { if (myToolWindow == null) { myToolWindow = ToolWindowManager.getInstance(getProject()) .getToolWindow(PythonConsoleToolWindowFactory.ID); } return myToolWindow; } public void setToolWindow(ToolWindow toolWindow) { myToolWindow = toolWindow; } public interface ConsoleListener { void handleConsoleInitialized(LanguageConsoleView consoleView); } private static class RestartAction extends AnAction { private PydevConsoleRunner myConsoleRunner; private RestartAction(PydevConsoleRunner runner) { copyFrom(ActionManager.getInstance().getAction(IdeActions.ACTION_RERUN)); getTemplatePresentation().setIcon(AllIcons.Actions.Restart); myConsoleRunner = runner; } @Override public void actionPerformed(AnActionEvent e) { myConsoleRunner.rerun(); } } private void rerun() { new Task.Backgroundable(getProject(), "Restarting console", true) { @Override public void run(@NotNull ProgressIndicator indicator) { UIUtil.invokeLaterIfNeeded( new Runnable() { @Override public void run() { closeCommunication(); } }); myProcessHandler.waitFor(); UIUtil.invokeLaterIfNeeded( new Runnable() { @Override public void run() { PydevConsoleRunner.this.run(); } }); } }.queue(); } private class ShowVarsAction extends ToggleAction implements DumbAware { private boolean mySelected = false; public ShowVarsAction() { super("Show Variables", "Shows active console variables", AllIcons.Debugger.Watches); } @Override public boolean isSelected(AnActionEvent e) { return mySelected; } @Override public void setSelected(AnActionEvent e, boolean state) { mySelected = state; if (mySelected) { getConsoleView().showVariables(myPydevConsoleCommunication); } else { getConsoleView().restoreWindow(); } } } private class ConnectDebuggerAction extends ToggleAction implements DumbAware { private boolean mySelected = false; private XDebugSession mySession = null; public ConnectDebuggerAction() { super( "Attach Debugger", "Enables tracing of code executed in console", AllIcons.Actions.StartDebugger); } @Override public boolean isSelected(AnActionEvent e) { return mySelected; } @Override public void update(AnActionEvent e) { if (mySession != null) { e.getPresentation().setEnabled(false); } else { e.getPresentation().setEnabled(true); } } @Override public void setSelected(AnActionEvent e, boolean state) { mySelected = state; if (mySelected) { try { mySession = connectToDebugger(); } catch (Exception e1) { LOG.error(e1); Messages.showErrorDialog("Can't connect to debugger", "Error Connecting Debugger"); } } else { // TODO: disable debugging } } } private XDebugSession connectToDebugger() throws ExecutionException { final ServerSocket serverSocket = PythonCommandLineState.createServerSocket(); final XDebugSession session = XDebuggerManager.getInstance(getProject()) .startSessionAndShowTab( "Python Console Debugger", PythonIcons.Python.Python, null, true, new XDebugProcessStarter() { @NotNull public XDebugProcess start(@NotNull final XDebugSession session) { PythonDebugLanguageConsoleView debugConsoleView = new PythonDebugLanguageConsoleView(getProject(), mySdk); PyConsoleDebugProcessHandler consoleDebugProcessHandler = new PyConsoleDebugProcessHandler(myProcessHandler); PyConsoleDebugProcess consoleDebugProcess = new PyConsoleDebugProcess( session, serverSocket, debugConsoleView, consoleDebugProcessHandler); PythonDebugConsoleCommunication communication = PyDebugRunner.initDebugConsoleView( getProject(), consoleDebugProcess, debugConsoleView, consoleDebugProcessHandler); myPydevConsoleCommunication.setDebugCommunication(communication); debugConsoleView.attachToProcess(consoleDebugProcessHandler); consoleDebugProcess.waitForNextConnection(); try { consoleDebugProcess.connect(myPydevConsoleCommunication); } catch (Exception e) { LOG.error(e); // TODO } myProcessHandler.notifyTextAvailable( "\nDebugger connected.\n", ProcessOutputTypes.STDERR); return consoleDebugProcess; } }); return session; } @Override protected List<String> getActiveConsoleNames(final String consoleTitle) { return FluentIterable.from( Lists.newArrayList( PythonConsoleToolWindow.getInstance(getProject()) .getToolWindow() .getContentManager() .getContents())) .transform( new Function<Content, String>() { @Override public String apply(Content input) { return input.getDisplayName(); } }) .filter( new Predicate<String>() { @Override public boolean apply(String input) { return input.contains(consoleTitle); } }) .toList(); } }
/** @author: Roman Chernyatchik */ public class SMTestRunnerResultsForm extends TestResultsPanel implements TestFrameworkRunningModel, TestResultsViewer, SMTRunnerEventsListener { @NonNls public static final String HISTORY_DATE_FORMAT = "yyyy.MM.dd 'at' HH'h' mm'm' ss's'"; @NonNls private static final String DEFAULT_SM_RUNNER_SPLITTER_PROPERTY = "SMTestRunner.Splitter.Proportion"; public static final Color DARK_YELLOW = JBColor.YELLOW.darker(); private static final Logger LOG = Logger.getInstance("#" + SMTestRunnerResultsForm.class.getName()); private SMTRunnerTestTreeView myTreeView; private TestsProgressAnimator myAnimator; /** Fake parent suite for all tests and suites */ private final SMTestProxy.SMRootTestProxy myTestsRootNode; private SMTRunnerTreeBuilder myTreeBuilder; private final List<EventsListener> myEventListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private PropagateSelectionHandler myShowStatisticForProxyHandler; private final Project myProject; private int myTotalTestCount = 0; private int myStartedTestCount = 0; private int myFinishedTestCount = 0; private int myFailedTestCount = 0; private int myIgnoredTestCount = 0; private long myStartTime; private long myEndTime; private StatisticsPanel myStatisticsPane; // custom progress private String myCurrentCustomProgressCategory; private final Set<String> myMentionedCategories = new LinkedHashSet<String>(); private boolean myTestsRunning = true; private AbstractTestProxy myLastSelected; private Alarm myUpdateQueue; private Set<Update> myRequests = Collections.synchronizedSet(new HashSet<Update>()); public SMTestRunnerResultsForm( @NotNull final JComponent console, final TestConsoleProperties consoleProperties) { this(console, AnAction.EMPTY_ARRAY, consoleProperties, null); } public SMTestRunnerResultsForm( @NotNull final JComponent console, AnAction[] consoleActions, final TestConsoleProperties consoleProperties, @Nullable String splitterPropertyName) { super( console, consoleActions, consoleProperties, StringUtil.notNullize(splitterPropertyName, DEFAULT_SM_RUNNER_SPLITTER_PROPERTY), 0.2f); myProject = consoleProperties.getProject(); // Create tests common suite root //noinspection HardCodedStringLiteral myTestsRootNode = new SMTestProxy.SMRootTestProxy(); // todo myTestsRootNode.setOutputFilePath(runConfiguration.getOutputFilePath()); // Fire selection changed and move focus on SHIFT+ENTER // TODO[romeo] improve /* final ArrayList<Component> components = new ArrayList<Component>(); components.add(myTreeView); components.add(myTabs.getComponent()); myContentPane.setFocusTraversalPolicy(new MyFocusTraversalPolicy(components)); myContentPane.setFocusCycleRoot(true); */ } @Override public void initUI() { super.initUI(); if (Registry.is("tests.view.old.statistics.panel")) { final KeyStroke shiftEnterKey = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.SHIFT_MASK); SMRunnerUtil.registerAsAction( shiftEnterKey, "show-statistics-for-test-proxy", new Runnable() { public void run() { showStatisticsForSelectedProxy(); } }, myTreeView); } } protected ToolbarPanel createToolbarPanel() { return new SMTRunnerToolbarPanel(myProperties, this, this); } protected JComponent createTestTreeView() { myTreeView = new SMTRunnerTestTreeView(); myTreeView.setLargeModel(true); myTreeView.attachToModel(this); myTreeView.setTestResultsViewer(this); if (Registry.is("tests.view.old.statistics.panel")) { addTestsTreeSelectionListener( new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent e) { AbstractTestProxy selectedTest = getTreeView().getSelectedTest(); if (selectedTest instanceof SMTestProxy) { myStatisticsPane.selectProxy(((SMTestProxy) selectedTest), this, false); } } }); } final SMTRunnerTreeStructure structure = new SMTRunnerTreeStructure(myProject, myTestsRootNode); myTreeBuilder = new SMTRunnerTreeBuilder(myTreeView, structure); myTreeBuilder.setTestsComparator(TestConsoleProperties.SORT_ALPHABETICALLY.value(myProperties)); Disposer.register(this, myTreeBuilder); myAnimator = new TestsProgressAnimator(myTreeBuilder); TrackRunningTestUtil.installStopListeners( myTreeView, myProperties, new Pass<AbstractTestProxy>() { @Override public void pass(AbstractTestProxy testProxy) { if (testProxy == null) return; // drill to the first leaf while (!testProxy.isLeaf()) { final List<? extends AbstractTestProxy> children = testProxy.getChildren(); if (!children.isEmpty()) { final AbstractTestProxy firstChild = children.get(0); if (firstChild != null) { testProxy = firstChild; continue; } } break; } // pretend the selection on the first leaf // so if test would be run, tracking would be restarted myLastSelected = testProxy; } }); // TODO always hide root node // myTreeView.setRootVisible(false); myUpdateQueue = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, this); return myTreeView; } protected JComponent createStatisticsPanel() { // Statistics tab final StatisticsPanel statisticsPane = new StatisticsPanel(myProject, this); // handler to select in results viewer by statistics pane events statisticsPane.addPropagateSelectionListener(createSelectMeListener()); // handler to select test statistics pane by result viewer events setShowStatisticForProxyHandler(statisticsPane.createSelectMeListener()); myStatisticsPane = statisticsPane; return myStatisticsPane.getContentPane(); } public StatisticsPanel getStatisticsPane() { return myStatisticsPane; } public void addTestsTreeSelectionListener(final TreeSelectionListener listener) { myTreeView.getSelectionModel().addTreeSelectionListener(listener); } /** * Is used for navigation from tree view to other UI components * * @param handler */ public void setShowStatisticForProxyHandler(final PropagateSelectionHandler handler) { myShowStatisticForProxyHandler = handler; } /** * Returns root node, fake parent suite for all tests and suites * * @param testsRoot * @return */ public void onTestingStarted(@NotNull SMTestProxy.SMRootTestProxy testsRoot) { myAnimator.setCurrentTestCase(myTestsRootNode); myTreeBuilder.updateFromRoot(); // Status line myStatusLine.setStatusColor(ColorProgressBar.GREEN); // Tests tree selectAndNotify(myTestsRootNode); myStartTime = System.currentTimeMillis(); boolean printTestingStartedTime = true; if (myProperties instanceof SMTRunnerConsoleProperties) { printTestingStartedTime = ((SMTRunnerConsoleProperties) myProperties).isPrintTestingStartedTime(); } if (printTestingStartedTime) { myTestsRootNode.addSystemOutput( "Testing started at " + DateFormatUtil.formatTime(myStartTime) + " ...\n"); } updateStatusLabel(false); // TODO : show info - "Loading..." msg fireOnTestingStarted(); } public void onTestingFinished(@NotNull SMTestProxy.SMRootTestProxy testsRoot) { myEndTime = System.currentTimeMillis(); if (myTotalTestCount == 0) { myTotalTestCount = myStartedTestCount; myStatusLine.setFraction(1); } updateStatusLabel(true); updateIconProgress(true); myAnimator.stopMovie(); myTreeBuilder.updateFromRoot(); LvcsHelper.addLabel(this); final Runnable onDone = new Runnable() { @Override public void run() { myTestsRunning = false; final boolean sortByDuration = TestConsoleProperties.SORT_BY_DURATION.value(myProperties); if (sortByDuration) { myTreeBuilder.setStatisticsComparator(myProperties, sortByDuration); } } }; if (myLastSelected == null) { selectAndNotify(myTestsRootNode, onDone); } else { onDone.run(); } fireOnTestingFinished(); if (testsRoot.wasTerminated() && myStatusLine.getStatusColor() == ColorProgressBar.GREEN) { myStatusLine.setStatusColor(JBColor.LIGHT_GRAY); } if (testsRoot.isEmptySuite() && testsRoot.isTestsReporterAttached() && myProperties instanceof SMTRunnerConsoleProperties && ((SMTRunnerConsoleProperties) myProperties).fixEmptySuite()) { return; } final TestsUIUtil.TestResultPresentation presentation = new TestsUIUtil.TestResultPresentation(testsRoot, myStartTime > 0, null) .getPresentation( myFailedTestCount, myFinishedTestCount - myFailedTestCount - myIgnoredTestCount, myTotalTestCount - myFinishedTestCount, myIgnoredTestCount); TestsUIUtil.notifyByBalloon(myProperties.getProject(), testsRoot, myProperties, presentation); addToHistory(testsRoot, myProperties, this); } private static void addToHistory( final SMTestProxy.SMRootTestProxy root, TestConsoleProperties consoleProperties, Disposable parentDisposable) { final RunProfile configuration = consoleProperties.getConfiguration(); if (configuration instanceof RunConfiguration && !(consoleProperties instanceof ImportedTestConsoleProperties)) { final MySaveHistoryTask backgroundable = new MySaveHistoryTask(consoleProperties, root, (RunConfiguration) configuration); final BackgroundableProcessIndicator processIndicator = new BackgroundableProcessIndicator(backgroundable); Disposer.register( parentDisposable, new Disposable() { @Override public void dispose() { processIndicator.cancel(); backgroundable.dispose(); } }); Disposer.register(parentDisposable, processIndicator); ProgressManager.getInstance() .runProcessWithProgressAsynchronously(backgroundable, processIndicator); } } public void onTestsCountInSuite(final int count) { updateCountersAndProgressOnTestCount(count, false); } /** * Adds test to tree and updates status line. Test proxy should be initialized, proxy parent must * be some suite (already added to tree) * * @param testProxy Proxy */ public void onTestStarted(@NotNull final SMTestProxy testProxy) { if (!testProxy.isConfig()) { updateOnTestStarted(false); } _addTestOrSuite(testProxy); fireOnTestNodeAdded(testProxy); } @Override public void onSuiteTreeNodeAdded(SMTestProxy testProxy) { myTotalTestCount++; } @Override public void onSuiteTreeStarted(SMTestProxy suite) {} public void onTestFailed(@NotNull final SMTestProxy test) { updateOnTestFailed(false); if (test.isConfig()) { myStartedTestCount++; myFinishedTestCount++; } updateIconProgress(false); // still expand failure when user selected another test if (myLastSelected != null && TestConsoleProperties.TRACK_RUNNING_TEST.value(myProperties) && TestConsoleProperties.HIDE_PASSED_TESTS.value(myProperties)) { myTreeBuilder.expand(test, null); } } public void onTestIgnored(@NotNull final SMTestProxy test) { updateOnTestIgnored(); } /** * Adds suite to tree Suite proxy should be initialized, proxy parent must be some suite (already * added to tree) If parent is null, then suite will be added to tests root. * * @param newSuite Tests suite */ public void onSuiteStarted(@NotNull final SMTestProxy newSuite) { _addTestOrSuite(newSuite); } public void onCustomProgressTestsCategory(@Nullable String categoryName, int testCount) { myCurrentCustomProgressCategory = categoryName; updateCountersAndProgressOnTestCount(testCount, true); } public void onCustomProgressTestStarted() { updateOnTestStarted(true); } public void onCustomProgressTestFailed() { updateOnTestFailed(true); } @Override public void onCustomProgressTestFinished() { updateOnTestFinished(true); } public void onTestFinished(@NotNull final SMTestProxy test) { if (!test.isConfig()) { updateOnTestFinished(false); } updateIconProgress(false); } public void onSuiteFinished(@NotNull final SMTestProxy suite) { // Do nothing } public SMTestProxy.SMRootTestProxy getTestsRootNode() { return myTestsRootNode; } public TestConsoleProperties getProperties() { return myProperties; } public void setFilter(final Filter filter) { // is used by Test Runner actions, e.g. hide passed, etc final SMTRunnerTreeStructure treeStructure = myTreeBuilder.getSMRunnerTreeStructure(); treeStructure.setFilter(filter); // TODO - show correct info if no children are available // (e.g no tests found or all tests passed, etc.) // treeStructure.getChildElements(treeStructure.getRootElement()).length == 0 myTreeBuilder.queueUpdate(); } public boolean isRunning() { return myTestsRunning; } public TestTreeView getTreeView() { return myTreeView; } @Override public SMTRunnerTreeBuilder getTreeBuilder() { return myTreeBuilder; } public boolean hasTestSuites() { return getRoot().getChildren().size() > 0; } @NotNull public AbstractTestProxy getRoot() { return myTestsRootNode; } /** * Manual test proxy selection in tests tree. E.g. do select root node on testing started or do * select current node if TRACK_RUNNING_TEST is enabled * * <p> * * <p>Will select proxy in Event Dispatch Thread. Invocation of this method may be not in event * dispatch thread * * @param testProxy Test or suite */ @Override public void selectAndNotify(AbstractTestProxy testProxy) { selectAndNotify(testProxy, null); } private void selectAndNotify( @Nullable final AbstractTestProxy testProxy, @Nullable Runnable onDone) { selectWithoutNotify(testProxy, onDone); // Is used by Statistic tab to differ use selection in tree // from manual selection from API (e.g. test runner events) if (Registry.is("tests.view.old.statistics.panel")) { showStatisticsForSelectedProxy(testProxy, false); } } public void addEventsListener(final EventsListener listener) { myEventListeners.add(listener); addTestsTreeSelectionListener( new TreeSelectionListener() { public void valueChanged(final TreeSelectionEvent e) { // We should fire event only if it was generated by this component, // e.g. it is focused. Otherwise it is side effect of selecting proxy in // try by other component // if (myTreeView.isFocusOwner()) { @Nullable final SMTestProxy selectedProxy = (SMTestProxy) getTreeView().getSelectedTest(); listener.onSelected( selectedProxy, SMTestRunnerResultsForm.this, SMTestRunnerResultsForm.this); // } } }); } public void dispose() { super.dispose(); myShowStatisticForProxyHandler = null; myEventListeners.clear(); myStatisticsPane.doDispose(); } public void showStatisticsForSelectedProxy() { TestConsoleProperties.SHOW_STATISTICS.set(myProperties, true); final AbstractTestProxy selectedProxy = myTreeView.getSelectedTest(); showStatisticsForSelectedProxy(selectedProxy, true); } private void showStatisticsForSelectedProxy( final AbstractTestProxy selectedProxy, final boolean requestFocus) { if (selectedProxy instanceof SMTestProxy && myShowStatisticForProxyHandler != null) { myShowStatisticForProxyHandler.handlePropagateSelectionRequest( (SMTestProxy) selectedProxy, this, requestFocus); } } protected int getTotalTestCount() { return myTotalTestCount; } protected int getStartedTestCount() { return myStartedTestCount; } protected int getFinishedTestCount() { return myFinishedTestCount; } protected int getFailedTestCount() { return myFailedTestCount; } protected int getIgnoredTestCount() { return myIgnoredTestCount; } protected Color getTestsStatusColor() { return myStatusLine.getStatusColor(); } public Set<String> getMentionedCategories() { return myMentionedCategories; } protected long getStartTime() { return myStartTime; } protected long getEndTime() { return myEndTime; } private void _addTestOrSuite(@NotNull final SMTestProxy newTestOrSuite) { final SMTestProxy parentSuite = newTestOrSuite.getParent(); assert parentSuite != null; // Tree final Update update = new Update(parentSuite) { @Override public void run() { myRequests.remove(this); myTreeBuilder.updateTestsSubtree(parentSuite); } }; if (ApplicationManager.getApplication().isUnitTestMode()) { update.run(); } else if (myRequests.add(update) && !myUpdateQueue.isDisposed()) { myUpdateQueue.addRequest(update, 100); } myTreeBuilder.repaintWithParents(newTestOrSuite); myAnimator.setCurrentTestCase(newTestOrSuite); if (TestConsoleProperties.TRACK_RUNNING_TEST.value(myProperties)) { if (myLastSelected == null || myLastSelected == newTestOrSuite) { myLastSelected = null; selectAndNotify(newTestOrSuite); } } } private void fireOnTestNodeAdded(final SMTestProxy test) { for (EventsListener eventListener : myEventListeners) { eventListener.onTestNodeAdded(this, test); } } private void fireOnTestingFinished() { for (EventsListener eventListener : myEventListeners) { eventListener.onTestingFinished(this); } } private void fireOnTestingStarted() { for (EventsListener eventListener : myEventListeners) { eventListener.onTestingStarted(this); } } private void selectWithoutNotify( final AbstractTestProxy testProxy, @Nullable final Runnable onDone) { if (testProxy == null) { return; } SMRunnerUtil.runInEventDispatchThread( new Runnable() { public void run() { if (myTreeBuilder.isDisposed()) { return; } myTreeBuilder.select(testProxy, onDone); } }, ModalityState.NON_MODAL); } private void updateStatusLabel(final boolean testingFinished) { if (myFailedTestCount > 0) { myStatusLine.setStatusColor(ColorProgressBar.RED); } if (testingFinished) { if (myTotalTestCount == 0) { myStatusLine.setStatusColor( myTestsRootNode.wasLaunched() || !myTestsRootNode.isTestsReporterAttached() ? JBColor.LIGHT_GRAY : ColorProgressBar.RED); } // else color will be according failed/passed tests } // launchedAndFinished - is launched and not in progress. If we remove "launched' that // onTestingStarted() before // initializing will be "launchedAndFinished" final boolean launchedAndFinished = myTestsRootNode.wasLaunched() && !myTestsRootNode.isInProgress(); if (!TestsPresentationUtil.hasNonDefaultCategories(myMentionedCategories)) { myStatusLine.formatTestMessage( myTotalTestCount, myFinishedTestCount, myFailedTestCount, myIgnoredTestCount, myTestsRootNode.getDuration(), myEndTime); } else { myStatusLine.setText( TestsPresentationUtil.getProgressStatus_Text( myStartTime, myEndTime, myTotalTestCount, myFinishedTestCount, myFailedTestCount, myMentionedCategories, launchedAndFinished)); } } /** for java unit tests */ public void performUpdate() { myTreeBuilder.performUpdate(); } private void updateIconProgress(boolean updateWithAttention) { final int totalTestCount, doneTestCount; if (myTotalTestCount == 0) { totalTestCount = 2; doneTestCount = 1; } else { totalTestCount = myTotalTestCount; doneTestCount = myFinishedTestCount; } TestsUIUtil.showIconProgress( myProject, doneTestCount, totalTestCount, myFailedTestCount, updateWithAttention); } /** * On event change selection and probably requests focus. Is used when we want navigate from other * component to this * * @return Listener */ public PropagateSelectionHandler createSelectMeListener() { return new PropagateSelectionHandler() { public void handlePropagateSelectionRequest( @Nullable final SMTestProxy selectedTestProxy, @NotNull final Object sender, final boolean requestFocus) { SMRunnerUtil.addToInvokeLater( new Runnable() { public void run() { selectWithoutNotify(selectedTestProxy, null); // Request focus if necessary if (requestFocus) { // myTreeView.requestFocusInWindow(); IdeFocusManager.getInstance(myProject).requestFocus(myTreeView, true); } } }); } }; } private void updateCountersAndProgressOnTestCount( final int count, final boolean isCustomMessage) { if (!isModeConsistent(isCustomMessage)) return; // This is for better support groups of TestSuites // Each group notifies about it's size myTotalTestCount += count; updateStatusLabel(false); } private void updateOnTestStarted(final boolean isCustomMessage) { if (!isModeConsistent(isCustomMessage)) return; // for mixed tests results : mention category only if it contained tests myMentionedCategories.add( myCurrentCustomProgressCategory != null ? myCurrentCustomProgressCategory : TestsPresentationUtil.DEFAULT_TESTS_CATEGORY); myStartedTestCount++; // fix total count if it is corrupted // but if test count wasn't set at all let's process such case separately if (myStartedTestCount > myTotalTestCount && myTotalTestCount != 0) { myTotalTestCount = myStartedTestCount; } updateStatusLabel(false); } private void updateProgressOnTestDone() { int doneTestCount = myFinishedTestCount; // update progress if (myTotalTestCount != 0) { // if total is set myStatusLine.setFraction((double) doneTestCount / myTotalTestCount); } else { // if at least one test was launcher than just set progress in the middle to show user that // tests are running myStatusLine.setFraction(doneTestCount > 0 ? 0.5 : 0); } } private void updateOnTestFailed(final boolean isCustomMessage) { if (!isModeConsistent(isCustomMessage)) return; myFailedTestCount++; updateProgressOnTestDone(); updateStatusLabel(false); } private void updateOnTestFinished(final boolean isCustomMessage) { if (!isModeConsistent(isCustomMessage)) return; myFinishedTestCount++; updateProgressOnTestDone(); } private void updateOnTestIgnored() { myIgnoredTestCount++; updateProgressOnTestDone(); updateStatusLabel(false); } private boolean isModeConsistent(boolean isCustomMessage) { // check that we are in consistent mode return isCustomMessage != (myCurrentCustomProgressCategory == null); } private static class MySaveHistoryTask extends Task.Backgroundable { private final TestConsoleProperties myConsoleProperties; private SMTestProxy.SMRootTestProxy myRoot; private RunConfiguration myConfiguration; private File myOutputFile; public MySaveHistoryTask( TestConsoleProperties consoleProperties, SMTestProxy.SMRootTestProxy root, RunConfiguration configuration) { super(consoleProperties.getProject(), "Save Test Results", true); myConsoleProperties = consoleProperties; myRoot = root; myConfiguration = configuration; } @Override public void run(@NotNull ProgressIndicator indicator) { try { SAXTransformerFactory transformerFactory = (SAXTransformerFactory) TransformerFactory.newInstance(); TransformerHandler handler = transformerFactory.newTransformerHandler(); handler.getTransformer().setOutputProperty(OutputKeys.INDENT, "yes"); handler .getTransformer() .setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); final String configurationNameIncludedDate = PathUtil.suggestFileName(myConfiguration.getName()) + " - " + new SimpleDateFormat(HISTORY_DATE_FORMAT).format(new Date()); myOutputFile = new File( AbstractImportTestsAction.getTestHistoryRoot(myProject), configurationNameIncludedDate + ".xml"); FileUtilRt.createParentDirs(myOutputFile); handler.setResult(new StreamResult(new FileWriter(myOutputFile))); final SMTestProxy.SMRootTestProxy root = myRoot; final RunConfiguration configuration = myConfiguration; if (root != null && configuration != null) { TestResultsXmlFormatter.execute(root, configuration, myConsoleProperties, handler); } } catch (ProcessCanceledException e) { throw e; } catch (Exception e) { LOG.info("Export to history failed", e); } } @Override public void onSuccess() { if (myOutputFile != null && myOutputFile.exists()) { AbstractImportTestsAction.adjustHistory(myProject); TestHistoryConfiguration.getInstance(myProject) .registerHistoryItem( myOutputFile.getName(), myConfiguration.getName(), myConfiguration.getType().getId()); } } public void dispose() { myConfiguration = null; myRoot = null; myOutputFile = null; } } }
public class PsiManagerImpl extends PsiManagerEx { private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.PsiManagerImpl"); private final Project myProject; private final FileIndexFacade myExcludedFileIndex; private final MessageBus myMessageBus; private final PsiModificationTracker myModificationTracker; private final FileManager myFileManager; private final List<PsiTreeChangePreprocessor> myTreeChangePreprocessors = ContainerUtil.createLockFreeCopyOnWriteList(); private final List<PsiTreeChangeListener> myTreeChangeListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private boolean myTreeChangeEventIsFiring = false; private boolean myIsDisposed; private VirtualFileFilter myAssertOnFileLoadingFilter = VirtualFileFilter.NONE; private final AtomicInteger myBatchFilesProcessingModeCount = new AtomicInteger(0); public static final Topic<AnyPsiChangeListener> ANY_PSI_CHANGE_TOPIC = Topic.create( "ANY_PSI_CHANGE_TOPIC", AnyPsiChangeListener.class, Topic.BroadcastDirection.TO_PARENT); public PsiManagerImpl( Project project, FileDocumentManager fileDocumentManager, PsiBuilderFactory psiBuilderFactory, FileIndexFacade excludedFileIndex, MessageBus messageBus, PsiModificationTracker modificationTracker) { myProject = project; myExcludedFileIndex = excludedFileIndex; myMessageBus = messageBus; myModificationTracker = modificationTracker; // We need to initialize PsiBuilderFactory service so it won't initialize under PsiLock from // ChameleonTransform @SuppressWarnings({"UnusedDeclaration", "UnnecessaryLocalVariable"}) Object used = psiBuilderFactory; boolean isProjectDefault = project.isDefault(); myFileManager = isProjectDefault ? new EmptyFileManager(this) : new FileManagerImpl(this, fileDocumentManager, excludedFileIndex); myTreeChangePreprocessors.add((PsiTreeChangePreprocessor) modificationTracker); Collections.addAll( myTreeChangePreprocessors, Extensions.getExtensions(PsiTreeChangePreprocessor.EP_NAME, myProject)); Disposer.register( project, new Disposable() { @Override public void dispose() { myIsDisposed = true; } }); } @Override public boolean isDisposed() { return myIsDisposed; } @Override public void dropResolveCaches() { FileManager fileManager = myFileManager; if (fileManager instanceof FileManagerImpl) { // mock tests ((FileManagerImpl) fileManager).processQueue(); } beforeChange(true); beforeChange(false); } @Override public boolean isInProject(@NotNull PsiElement element) { PsiFile file = element.getContainingFile(); if (file != null && file.isPhysical() && file.getViewProvider().getVirtualFile() instanceof LightVirtualFile) return true; if (element instanceof PsiDirectoryContainer) { PsiDirectory[] dirs = ((PsiDirectoryContainer) element).getDirectories(); for (PsiDirectory dir : dirs) { if (!isInProject(dir)) return false; } return true; } VirtualFile virtualFile = null; if (file != null) { virtualFile = file.getViewProvider().getVirtualFile(); } else if (element instanceof PsiFileSystemItem) { virtualFile = ((PsiFileSystemItem) element).getVirtualFile(); } if (virtualFile != null) { return myExcludedFileIndex.isInContent(virtualFile); } return false; } @TestOnly public void setAssertOnFileLoadingFilter( @NotNull VirtualFileFilter filter, @NotNull Disposable parentDisposable) { // Find something to ensure there's no changed files waiting to be processed in repository // indices. myAssertOnFileLoadingFilter = filter; Disposer.register( parentDisposable, new Disposable() { @Override public void dispose() { myAssertOnFileLoadingFilter = VirtualFileFilter.NONE; } }); } @Override public boolean isAssertOnFileLoading(@NotNull VirtualFile file) { return myAssertOnFileLoadingFilter.accept(file); } @Override @NotNull public Project getProject() { return myProject; } @Override @NotNull public FileManager getFileManager() { return myFileManager; } @Override public boolean areElementsEquivalent(PsiElement element1, PsiElement element2) { ProgressIndicatorProvider .checkCanceled(); // We hope this method is being called often enough to cancel daemon // processes smoothly if (element1 == element2) return true; if (element1 == null || element2 == null) { return false; } return element1.equals(element2) || element1.isEquivalentTo(element2) || element2.isEquivalentTo(element1); } @Override public PsiFile findFile(@NotNull VirtualFile file) { return myFileManager.findFile(file); } @Override @Nullable public FileViewProvider findViewProvider(@NotNull VirtualFile file) { return myFileManager.findViewProvider(file); } @TestOnly public void cleanupForNextTest() { myFileManager.cleanupForNextTest(); LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode()); } @Override public PsiDirectory findDirectory(@NotNull VirtualFile file) { ProgressIndicatorProvider.checkCanceled(); return myFileManager.findDirectory(file); } @Override public void reloadFromDisk(@NotNull PsiFile file) { myFileManager.reloadFromDisk(file); } @Override public void addPsiTreeChangeListener(@NotNull PsiTreeChangeListener listener) { myTreeChangeListeners.add(listener); } @Override public void addPsiTreeChangeListener( @NotNull final PsiTreeChangeListener listener, @NotNull Disposable parentDisposable) { addPsiTreeChangeListener(listener); Disposer.register( parentDisposable, new Disposable() { @Override public void dispose() { removePsiTreeChangeListener(listener); } }); } @Override public void removePsiTreeChangeListener(@NotNull PsiTreeChangeListener listener) { myTreeChangeListeners.remove(listener); } @Override public void beforeChildAddition(@NotNull PsiTreeChangeEventImpl event) { beforeChange(true); event.setCode(PsiTreeChangeEventImpl.PsiEventType.BEFORE_CHILD_ADDITION); if (LOG.isDebugEnabled()) { LOG.debug("beforeChildAddition: parent = " + event.getParent()); } fireEvent(event); } @Override public void beforeChildRemoval(@NotNull PsiTreeChangeEventImpl event) { beforeChange(true); event.setCode(PsiTreeChangeEventImpl.PsiEventType.BEFORE_CHILD_REMOVAL); if (LOG.isDebugEnabled()) { LOG.debug( "beforeChildRemoval: child = " + event.getChild() + ", parent = " + event.getParent()); } fireEvent(event); } @Override public void beforeChildReplacement(@NotNull PsiTreeChangeEventImpl event) { beforeChange(true); event.setCode(PsiTreeChangeEventImpl.PsiEventType.BEFORE_CHILD_REPLACEMENT); if (LOG.isDebugEnabled()) { LOG.debug( "beforeChildReplacement: oldChild = " + event.getOldChild() + ", parent = " + event.getParent()); } fireEvent(event); } public void beforeChildrenChange(@NotNull PsiTreeChangeEventImpl event) { beforeChange(true); event.setCode(PsiTreeChangeEventImpl.PsiEventType.BEFORE_CHILDREN_CHANGE); if (LOG.isDebugEnabled()) { LOG.debug("beforeChildrenChange: parent = " + event.getParent()); } fireEvent(event); } public void beforeChildMovement(@NotNull PsiTreeChangeEventImpl event) { beforeChange(true); event.setCode(PsiTreeChangeEventImpl.PsiEventType.BEFORE_CHILD_MOVEMENT); if (LOG.isDebugEnabled()) { LOG.debug( "beforeChildMovement: child = " + event.getChild() + ", oldParent = " + event.getOldParent() + ", newParent = " + event.getNewParent()); } fireEvent(event); } public void beforePropertyChange(@NotNull PsiTreeChangeEventImpl event) { beforeChange(true); event.setCode(PsiTreeChangeEventImpl.PsiEventType.BEFORE_PROPERTY_CHANGE); if (LOG.isDebugEnabled()) { LOG.debug( "beforePropertyChange: element = " + event.getElement() + ", propertyName = " + event.getPropertyName() + ", oldValue = " + event.getOldValue()); } fireEvent(event); } public void childAdded(@NotNull PsiTreeChangeEventImpl event) { event.setCode(PsiTreeChangeEventImpl.PsiEventType.CHILD_ADDED); if (LOG.isDebugEnabled()) { LOG.debug("childAdded: child = " + event.getChild() + ", parent = " + event.getParent()); } fireEvent(event); afterChange(true); } public void childRemoved(@NotNull PsiTreeChangeEventImpl event) { event.setCode(PsiTreeChangeEventImpl.PsiEventType.CHILD_REMOVED); if (LOG.isDebugEnabled()) { LOG.debug("childRemoved: child = " + event.getChild() + ", parent = " + event.getParent()); } fireEvent(event); afterChange(true); } public void childReplaced(@NotNull PsiTreeChangeEventImpl event) { event.setCode(PsiTreeChangeEventImpl.PsiEventType.CHILD_REPLACED); if (LOG.isDebugEnabled()) { LOG.debug( "childReplaced: oldChild = " + event.getOldChild() + ", newChild = " + event.getNewChild() + ", parent = " + event.getParent()); } fireEvent(event); afterChange(true); } public void childMoved(@NotNull PsiTreeChangeEventImpl event) { event.setCode(PsiTreeChangeEventImpl.PsiEventType.CHILD_MOVED); if (LOG.isDebugEnabled()) { LOG.debug( "childMoved: child = " + event.getChild() + ", oldParent = " + event.getOldParent() + ", newParent = " + event.getNewParent()); } fireEvent(event); afterChange(true); } public void childrenChanged(@NotNull PsiTreeChangeEventImpl event) { event.setCode(PsiTreeChangeEventImpl.PsiEventType.CHILDREN_CHANGED); if (LOG.isDebugEnabled()) { LOG.debug("childrenChanged: parent = " + event.getParent()); } fireEvent(event); afterChange(true); } public void propertyChanged(@NotNull PsiTreeChangeEventImpl event) { event.setCode(PsiTreeChangeEventImpl.PsiEventType.PROPERTY_CHANGED); if (LOG.isDebugEnabled()) { LOG.debug( "propertyChanged: element = " + event.getElement() + ", propertyName = " + event.getPropertyName() + ", oldValue = " + event.getOldValue() + ", newValue = " + event.getNewValue()); } fireEvent(event); afterChange(true); } public void addTreeChangePreprocessor(@NotNull PsiTreeChangePreprocessor preprocessor) { myTreeChangePreprocessors.add(preprocessor); } private void fireEvent(@NotNull PsiTreeChangeEventImpl event) { boolean isRealTreeChange = event.getCode() != PsiTreeChangeEventImpl.PsiEventType.PROPERTY_CHANGED && event.getCode() != PsiTreeChangeEventImpl.PsiEventType.BEFORE_PROPERTY_CHANGE; PsiFile file = event.getFile(); if (file == null || file.isPhysical()) { ApplicationManager.getApplication().assertWriteAccessAllowed(); } if (isRealTreeChange) { LOG.assertTrue( !myTreeChangeEventIsFiring, "Changes to PSI are not allowed inside event processing"); myTreeChangeEventIsFiring = true; } try { for (PsiTreeChangePreprocessor preprocessor : myTreeChangePreprocessors) { preprocessor.treeChanged(event); } for (PsiTreeChangeListener listener : myTreeChangeListeners) { try { switch (event.getCode()) { case BEFORE_CHILD_ADDITION: listener.beforeChildAddition(event); break; case BEFORE_CHILD_REMOVAL: listener.beforeChildRemoval(event); break; case BEFORE_CHILD_REPLACEMENT: listener.beforeChildReplacement(event); break; case BEFORE_CHILD_MOVEMENT: listener.beforeChildMovement(event); break; case BEFORE_CHILDREN_CHANGE: listener.beforeChildrenChange(event); break; case BEFORE_PROPERTY_CHANGE: listener.beforePropertyChange(event); break; case CHILD_ADDED: listener.childAdded(event); break; case CHILD_REMOVED: listener.childRemoved(event); break; case CHILD_REPLACED: listener.childReplaced(event); break; case CHILD_MOVED: listener.childMoved(event); break; case CHILDREN_CHANGED: listener.childrenChanged(event); break; case PROPERTY_CHANGED: listener.propertyChanged(event); break; } } catch (Exception e) { LOG.error(e); } } } finally { if (isRealTreeChange) { myTreeChangeEventIsFiring = false; } } } @Override public void registerRunnableToRunOnChange(@NotNull final Runnable runnable) { myMessageBus .connect() .subscribe( ANY_PSI_CHANGE_TOPIC, new AnyPsiChangeListener() { @Override public void beforePsiChanged(boolean isPhysical) { if (isPhysical) runnable.run(); } @Override public void afterPsiChanged(boolean isPhysical) {} }); } @Override public void registerRunnableToRunOnAnyChange( @NotNull final Runnable runnable) { // includes non-physical changes myMessageBus .connect() .subscribe( ANY_PSI_CHANGE_TOPIC, new AnyPsiChangeListener() { @Override public void beforePsiChanged(boolean isPhysical) { runnable.run(); } @Override public void afterPsiChanged(boolean isPhysical) {} }); } @Override public void registerRunnableToRunAfterAnyChange( @NotNull final Runnable runnable) { // includes non-physical changes myMessageBus .connect() .subscribe( ANY_PSI_CHANGE_TOPIC, new AnyPsiChangeListener() { @Override public void beforePsiChanged(boolean isPhysical) {} @Override public void afterPsiChanged(boolean isPhysical) { runnable.run(); } }); } @Override public void beforeChange(boolean isPhysical) { myMessageBus.syncPublisher(ANY_PSI_CHANGE_TOPIC).beforePsiChanged(isPhysical); } @Override public void afterChange(boolean isPhysical) { myMessageBus.syncPublisher(ANY_PSI_CHANGE_TOPIC).afterPsiChanged(isPhysical); } @Override @NotNull public PsiModificationTracker getModificationTracker() { return myModificationTracker; } @Override public void startBatchFilesProcessingMode() { myBatchFilesProcessingModeCount.incrementAndGet(); } @Override public void finishBatchFilesProcessingMode() { myBatchFilesProcessingModeCount.decrementAndGet(); LOG.assertTrue(myBatchFilesProcessingModeCount.get() >= 0); } @Override public boolean isBatchFilesProcessingMode() { return myBatchFilesProcessingModeCount.get() > 0; } }
@Override protected List<BaseInjection> create(final String key) { return ContainerUtil.createLockFreeCopyOnWriteList(); }
public class RefResolveServiceImpl extends RefResolveService implements Runnable, Disposable { private static final Logger LOG = Logger.getInstance(RefResolveServiceImpl.class); private final AtomicInteger fileCount = new AtomicInteger(); private final AtomicLong bytesSize = new AtomicLong(); private final AtomicLong refCount = new AtomicLong(); private final PersistentIntList storage; private final Deque<VirtualFile> filesToResolve = new ArrayDeque<VirtualFile>(); // guarded by filesToResolve private final ConcurrentBitSet fileIsInQueue = new ConcurrentBitSet(); private final ConcurrentBitSet fileIsResolved; private final ApplicationEx myApplication; private volatile boolean myDisposed; private volatile boolean upToDate; private final AtomicInteger enableVetoes = new AtomicInteger(); // number of disable() calls. To enable the service, there should be at // least corresponding number of enable() calls. private final FileWriter log; private final ProjectFileIndex myProjectFileIndex; public RefResolveServiceImpl( final Project project, final MessageBus messageBus, final PsiManager psiManager, StartupManager startupManager, ApplicationEx application, ProjectFileIndex projectFileIndex) throws IOException { super(project); ((FutureTask) resolveProcess).run(); myApplication = application; myProjectFileIndex = projectFileIndex; if (ENABLED) { log = new FileWriter(new File(getStorageDirectory(), "log.txt")); File dataFile = new File(getStorageDirectory(), "data"); fileIsResolved = ConcurrentBitSet.readFrom(new File(getStorageDirectory(), "bitSet")); log("Read resolved file bitset: " + fileIsResolved); int maxId = FSRecords.getMaxId(); PersistentIntList list = new PersistentIntList(dataFile, dataFile.exists() ? 0 : maxId); if (list.getSize() == maxId) { storage = list; } else { // just to be safe, re-resolve all if VFS files count changes since last restart list.dispose(); storage = new PersistentIntList(dataFile, maxId); log( "VFS maxId changed: was " + list.getSize() + "; now: " + maxId + "; re-resolving everything"); fileIsResolved.clear(); } Disposer.register(this, storage); if (!application.isUnitTestMode()) { startupManager.runWhenProjectIsInitialized( () -> { initListeners(messageBus, psiManager); startThread(); }); } Disposer.register( this, new Disposable() { @Override public void dispose() { try { save(); log.close(); } catch (IOException e) { LOG.error(e); } } }); } else { log = null; fileIsResolved = null; storage = null; } } @NotNull private static List<VirtualFile> toVf(@NotNull int[] ids) { List<VirtualFile> res = new ArrayList<VirtualFile>(); for (int id : ids) { VirtualFile file = PersistentFS.getInstance().findFileById(id); if (file != null) { res.add(file); } } return res; } @NotNull private static String toVfString(@NotNull int[] backIds) { List<VirtualFile> list = toVf(backIds); return toVfString(list); } @NotNull private static String toVfString(@NotNull Collection<VirtualFile> list) { List<VirtualFile> sub = new ArrayList<VirtualFile>(list).subList(0, Math.min(list.size(), 100)); return list.size() + " files: " + StringUtil.join(sub, file -> file.getName(), ", ") + (list.size() == sub.size() ? "" : "..."); } private void initListeners(@NotNull MessageBus messageBus, @NotNull PsiManager psiManager) { messageBus .connect() .subscribe( VirtualFileManager.VFS_CHANGES, new BulkFileListener.Adapter() { @Override public void after(@NotNull List<? extends VFileEvent> events) { fileCount.set(0); List<VirtualFile> files = ContainerUtil.mapNotNull( events, new Function<VFileEvent, VirtualFile>() { @Override public VirtualFile fun(VFileEvent event) { return event.getFile(); } }); queue(files, "VFS events " + events.size()); } }); psiManager.addPsiTreeChangeListener( new PsiTreeChangeAdapter() { @Override public void childrenChanged(@NotNull PsiTreeChangeEvent event) { PsiFile file = event.getFile(); VirtualFile virtualFile = PsiUtilCore.getVirtualFile(file); if (virtualFile != null) { queue(Collections.singletonList(virtualFile), event); } } @Override public void propertyChanged(@NotNull PsiTreeChangeEvent event) { childrenChanged(event); } }); messageBus .connect() .subscribe( DumbService.DUMB_MODE, new DumbService.DumbModeListener() { @Override public void enteredDumbMode() { disable(); } @Override public void exitDumbMode() { enable(); } }); messageBus .connect() .subscribe( PowerSaveMode.TOPIC, new PowerSaveMode.Listener() { @Override public void powerSaveStateChanged() { if (PowerSaveMode.isEnabled()) { enable(); } else { disable(); } } }); myApplication.addApplicationListener( new ApplicationAdapter() { @Override public void beforeWriteActionStart(@NotNull Object action) { disable(); } @Override public void writeActionFinished(@NotNull Object action) { enable(); } @Override public void applicationExiting() { disable(); } }, this); VirtualFileManager.getInstance() .addVirtualFileManagerListener( new VirtualFileManagerListener() { @Override public void beforeRefreshStart(boolean asynchronous) { disable(); } @Override public void afterRefreshFinish(boolean asynchronous) { enable(); } }, this); HeavyProcessLatch.INSTANCE.addListener( new HeavyProcessLatch.HeavyProcessListener() { @Override public void processStarted() {} @Override public void processFinished() { wakeUp(); } }, this); } // return true if file was added to queue private boolean queueIfNeeded(VirtualFile virtualFile, @NotNull Project project) { return toResolve(virtualFile, project) && queueUpdate(virtualFile); } private boolean toResolve(VirtualFile virtualFile, @NotNull Project project) { if (virtualFile != null && virtualFile.isValid() && project.isInitialized() && myProjectFileIndex.isInSourceContent(virtualFile) && isSupportedFileType(virtualFile)) { return true; } // else mark it as resolved so we will not have to check it again if (virtualFile instanceof VirtualFileWithId) { int id = getAbsId(virtualFile); fileIsResolved.set(id); } return false; } public static boolean isSupportedFileType(@NotNull VirtualFile virtualFile) { if (virtualFile.isDirectory()) return true; if (virtualFile.getFileType() == StdFileTypes.JAVA) return true; if (virtualFile.getFileType() == StdFileTypes.XML && !ProjectCoreUtil.isProjectOrWorkspaceFile(virtualFile)) return true; if ("groovy".equals(virtualFile.getExtension())) return true; return false; } @NotNull private File getStorageDirectory() { String dirName = myProject.getName() + "." + Integer.toHexString(myProject.getPresentableUrl().hashCode()); File dir = new File(PathManager.getSystemPath(), "refs/" + dirName); FileUtil.createDirectory(dir); return dir; } private void log(String m) { // System.out.println(m); logf(m); } private void logf(String m) { if (LOG.isDebugEnabled()) { try { log.write( DateFormat.getDateTimeInstance().format(new Date()) + " " + m + /*" ; gap="+storage.gap+*/ "\n"); } catch (IOException e) { LOG.error(e); } } } private void flushLog() { try { log.flush(); } catch (IOException e) { LOG.error(e); } } // return true if file was added to queue private boolean queueUpdate(@NotNull VirtualFile file) { synchronized (filesToResolve) { if (!(file instanceof VirtualFileWithId)) return false; int fileId = getAbsId(file); countAndMarkUnresolved(file, new LinkedHashSet<VirtualFile>(), true); boolean alreadyAdded = fileIsInQueue.set(fileId); if (!alreadyAdded) { filesToResolve.add(file); } upToDate = false; wakeUpUnderLock(); return !alreadyAdded; } } private void wakeUp() { synchronized (filesToResolve) { wakeUpUnderLock(); } } private void wakeUpUnderLock() { filesToResolve.notifyAll(); } private void waitForQueue() throws InterruptedException { synchronized (filesToResolve) { filesToResolve.wait(1000); } } private void startThread() { new Thread(this, "Ref resolve service").start(); upToDate = true; queueUnresolvedFilesSinceLastRestart(); } private void queueUnresolvedFilesSinceLastRestart() { PersistentFS fs = PersistentFS.getInstance(); int maxId = FSRecords.getMaxId(); TIntArrayList list = new TIntArrayList(); for (int id = fileIsResolved.nextClearBit(1); id >= 0 && id < maxId; id = fileIsResolved.nextClearBit(id + 1)) { int nextSetBit = fileIsResolved.nextSetBit(id); int endOfRun = Math.min(maxId, nextSetBit == -1 ? maxId : nextSetBit); do { VirtualFile virtualFile = fs.findFileById(id); if (queueIfNeeded(virtualFile, myProject)) { list.add(id); } else { fileIsResolved.set(id); } } while (++id < endOfRun); } log("Initially added to resolve " + toVfString(list.toNativeArray())); } @Override public void dispose() { myDisposed = true; } private void save() throws IOException { log("Saving resolved file bitset: " + fileIsResolved); fileIsResolved.writeTo(new File(getStorageDirectory(), "bitSet")); log("list.size = " + storage.getSize()); } private volatile Future<?> resolveProcess = new FutureTask<Object>(EmptyRunnable.getInstance(), null); // write from EDT only @Override public void run() { while (!myDisposed) { boolean isEmpty; synchronized (filesToResolve) { isEmpty = filesToResolve.isEmpty(); } if (enableVetoes.get() > 0 || isEmpty || !resolveProcess.isDone() || HeavyProcessLatch.INSTANCE.isRunning() || PsiDocumentManager.getInstance(myProject).hasUncommitedDocuments()) { try { waitForQueue(); } catch (InterruptedException e) { break; } continue; } final Set<VirtualFile> files = pollFilesToResolve(); if (files.isEmpty()) continue; upToDate = false; myApplication.invokeLater( () -> { if (!resolveProcess.isDone()) return; log("Started to resolve " + files.size() + " files"); Task.Backgroundable backgroundable = new Task.Backgroundable(myProject, "Resolving files...", false) { @Override public void run(@NotNull final ProgressIndicator indicator) { if (!myApplication.isDisposed()) { processBatch(indicator, files); } } }; ProgressIndicator indicator; if (files.size() > 1) { // show progress indicator = new BackgroundableProcessIndicator(backgroundable); } else { indicator = new MyProgress(); } resolveProcess = ((ProgressManagerImpl) ProgressManager.getInstance()) .runProcessWithProgressAsynchronously(backgroundable, indicator, null); }, myProject.getDisposed()); flushLog(); } } private volatile int resolvedInPreviousBatch; private void processBatch( @NotNull final ProgressIndicator indicator, @NotNull Set<VirtualFile> files) { assert !myApplication.isDispatchThread(); final int resolvedInPreviousBatch = this.resolvedInPreviousBatch; final int totalSize = files.size() + resolvedInPreviousBatch; final ConcurrentIntObjectMap<int[]> fileToForwardIds = ContainerUtil.createConcurrentIntObjectMap(); final Set<VirtualFile> toProcess = Collections.synchronizedSet(files); indicator.setIndeterminate(false); ProgressIndicatorUtils.forceWriteActionPriority(indicator, (Disposable) indicator); long start = System.currentTimeMillis(); Processor<VirtualFile> processor = file -> { double fraction = 1 - toProcess.size() * 1.0 / totalSize; indicator.setFraction(fraction); try { if (!file.isDirectory() && toResolve(file, myProject)) { int fileId = getAbsId(file); int i = totalSize - toProcess.size(); indicator.setText(i + "/" + totalSize + ": Resolving " + file.getPresentableUrl()); int[] forwardIds = processFile(file, fileId, indicator); if (forwardIds == null) { // queueUpdate(file); return false; } fileToForwardIds.put(fileId, forwardIds); } toProcess.remove(file); return true; } catch (RuntimeException e) { indicator.checkCanceled(); } return true; }; boolean success = true; try { success = processFilesConcurrently(files, indicator, processor); } finally { this.resolvedInPreviousBatch = toProcess.isEmpty() ? 0 : totalSize - toProcess.size(); queue(toProcess, "re-added after fail. success=" + success); storeIds(fileToForwardIds); long end = System.currentTimeMillis(); log( "Resolved batch of " + (totalSize - toProcess.size()) + " from " + totalSize + " files in " + ((end - start) / 1000) + "sec. (Gap: " + storage.gap + ")"); synchronized (filesToResolve) { upToDate = filesToResolve.isEmpty(); log("upToDate = " + upToDate); if (upToDate) { for (Listener listener : myListeners) { listener.allFilesResolved(); } } } } } private boolean processFilesConcurrently( @NotNull Set<VirtualFile> files, @NotNull final ProgressIndicator indicator, @NotNull final Processor<VirtualFile> processor) { final List<VirtualFile> fileList = new ArrayList<VirtualFile>(files); // fine but grabs all CPUs // return JobLauncher.getInstance().invokeConcurrentlyUnderProgress(fileList, indicator, false, // false, processor); int parallelism = CacheUpdateRunner.indexingThreadCount(); final Callable<Boolean> processFileFromSet = () -> { final boolean[] result = {true}; ProgressManager.getInstance() .executeProcessUnderProgress( () -> { while (true) { ProgressManager.checkCanceled(); VirtualFile file; synchronized (fileList) { file = fileList.isEmpty() ? null : fileList.remove(fileList.size() - 1); } if (file == null) { break; } if (!processor.process(file)) { result[0] = false; break; } } }, indicator); return result[0]; }; List<Future<Boolean>> futures = ContainerUtil.map( Collections.nCopies(parallelism, ""), s -> myApplication.executeOnPooledThread(processFileFromSet)); List<Boolean> results = ContainerUtil.map( futures, future -> { try { return future.get(); } catch (Exception e) { LOG.error(e); } return false; }); return !ContainerUtil.exists( results, result -> { return result != null && !result; // null means PCE }); } @NotNull private Set<VirtualFile> pollFilesToResolve() { Set<VirtualFile> set; synchronized (filesToResolve) { int queuedSize = filesToResolve.size(); set = new LinkedHashSet<VirtualFile>(queuedSize); // someone might have cleared this bit to mark file as processed for (VirtualFile file : filesToResolve) { if (fileIsInQueue.clear(getAbsId(file))) { set.add(file); } } filesToResolve.clear(); } return countAndMarkUnresolved(set, false); } private static int getAbsId(@NotNull VirtualFile file) { return Math.abs(((VirtualFileWithId) file).getId()); } @NotNull private Set<VirtualFile> countAndMarkUnresolved( @NotNull Collection<VirtualFile> files, boolean inDbOnly) { Set<VirtualFile> result = new LinkedHashSet<VirtualFile>(); for (VirtualFile file : files) { countAndMarkUnresolved(file, result, inDbOnly); } return result; } private void countAndMarkUnresolved( @NotNull VirtualFile file, @NotNull final Set<VirtualFile> result, final boolean inDbOnly) { if (file.isDirectory()) { VfsUtilCore.visitChildrenRecursively( file, new VirtualFileVisitor() { @Override public boolean visitFile(@NotNull VirtualFile file) { return doCountAndMarkUnresolved(file, result); } @Nullable @Override public Iterable<VirtualFile> getChildrenIterable(@NotNull VirtualFile file) { return inDbOnly ? ((NewVirtualFile) file).iterInDbChildren() : null; } }); } else { doCountAndMarkUnresolved(file, result); } } // return true if continue to process sub-directories of the {@code file}, false if the file is // already processed private boolean doCountAndMarkUnresolved( @NotNull VirtualFile file, @NotNull Set<VirtualFile> result) { if (file.isDirectory()) { fileIsResolved.set(getAbsId(file)); return result.add(file); } if (toResolve(file, myProject)) { result.add(file); fileIsResolved.clear(getAbsId(file)); } return true; } private void enable() { // decrement but only if it's positive int vetoes; do { vetoes = enableVetoes.get(); if (vetoes == 0) break; } while (!enableVetoes.compareAndSet(vetoes, vetoes - 1)); wakeUp(); } private void disable() { enableVetoes.incrementAndGet(); wakeUp(); } // returns list of resolved files if updated successfully, or null if write action or dumb mode // started private int[] processFile( @NotNull final VirtualFile file, int fileId, @NotNull final ProgressIndicator indicator) { final TIntHashSet forward; try { forward = calcForwardRefs(file, indicator); } catch (IndexNotReadyException e) { return null; } catch (ApplicationUtil.CannotRunReadActionException e) { return null; } catch (ProcessCanceledException e) { throw e; } catch (Exception e) { log(ExceptionUtil.getThrowableText(e)); flushLog(); return null; } int[] forwardIds = forward.toArray(); fileIsResolved.set(fileId); logf( " ---- " + file.getPresentableUrl() + " processed. forwardIds: " + toVfString(forwardIds)); for (Listener listener : myListeners) { listener.fileResolved(file); } return forwardIds; } private void storeIds(@NotNull ConcurrentIntObjectMap<int[]> fileToForwardIds) { int forwardSize = 0; int backwardSize = 0; final TIntObjectHashMap<TIntArrayList> fileToBackwardIds = new TIntObjectHashMap<TIntArrayList>(fileToForwardIds.size()); for (ConcurrentIntObjectMap.IntEntry<int[]> entry : fileToForwardIds.entries()) { int fileId = entry.getKey(); int[] forwardIds = entry.getValue(); forwardSize += forwardIds.length; for (int forwardId : forwardIds) { TIntArrayList backIds = fileToBackwardIds.get(forwardId); if (backIds == null) { backIds = new TIntArrayList(); fileToBackwardIds.put(forwardId, backIds); } backIds.add(fileId); backwardSize++; } } log("backwardSize = " + backwardSize); log("forwardSize = " + forwardSize); log("fileToForwardIds.size() = " + fileToForwardIds.size()); log("fileToBackwardIds.size() = " + fileToBackwardIds.size()); assert forwardSize == backwardSize; // wrap in read action so that sudden quit (in write action) would not interrupt us myApplication.runReadAction( () -> { if (!myApplication.isDisposed()) { fileToBackwardIds.forEachEntry( new TIntObjectProcedure<TIntArrayList>() { @Override public boolean execute(int fileId, TIntArrayList backIds) { storage.addAll(fileId, backIds.toNativeArray()); return true; } }); } }); } @NotNull private TIntHashSet calcForwardRefs( @NotNull final VirtualFile virtualFile, @NotNull final ProgressIndicator indicator) throws IndexNotReadyException, ApplicationUtil.CannotRunReadActionException { final TIntHashSet forward = new TIntHashSet(); final PsiFile psiFile = ApplicationUtil.tryRunReadAction( () -> { if (myProject.isDisposed()) throw new ProcessCanceledException(); if (fileCount.incrementAndGet() % 100 == 0) { PsiManager.getInstance(myProject).dropResolveCaches(); try { storage.flush(); log.flush(); } catch (IOException e) { LOG.error(e); } } return PsiManager.getInstance(myProject).findFile(virtualFile); }); final int fileId = getAbsId(virtualFile); if (psiFile != null) { bytesSize.addAndGet(virtualFile.getLength()); final Set<PsiElement> resolved = new THashSet<PsiElement>(); ApplicationUtil.tryRunReadAction( new Runnable() { @Override public void run() { indicator.checkCanceled(); if (psiFile instanceof PsiJavaFile) { psiFile.accept( new JavaRecursiveElementWalkingVisitor() { @Override public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { indicator.checkCanceled(); resolveReference(reference, resolved); super.visitReferenceElement(reference); } }); } else { psiFile.accept( new PsiRecursiveElementWalkingVisitor() { @Override public void visitElement(PsiElement element) { for (PsiReference reference : element.getReferences()) { indicator.checkCanceled(); resolveReference(reference, resolved); } super.visitElement(element); } }); } indicator.checkCanceled(); for (PsiElement element : resolved) { PsiFile file = element.getContainingFile(); addIdAndSuperClasses(file, forward); } } }); } forward.remove(fileId); return forward; } private void resolveReference( @NotNull PsiReference reference, @NotNull Set<PsiElement> resolved) { PsiElement element = reference.resolve(); if (element != null) { resolved.add(element); } refCount.incrementAndGet(); } private static void addIdAndSuperClasses(PsiFile file, @NotNull TIntHashSet forward) { if (file instanceof PsiJavaFile && file.getName().equals("Object.class") && ((PsiJavaFile) file).getPackageName().equals("java.lang")) { return; } VirtualFile virtualFile = PsiUtilCore.getVirtualFile(file); if (virtualFile instanceof VirtualFileWithId && forward.add(getAbsId(virtualFile)) && file instanceof PsiClassOwner) { for (PsiClass aClass : ((PsiClassOwner) file).getClasses()) { for (PsiClass superClass : aClass.getSupers()) { addIdAndSuperClasses(superClass.getContainingFile(), forward); } } } } @Override @Nullable public int[] getBackwardIds(@NotNull VirtualFileWithId file) { if (!isUpToDate()) return null; int fileId = getAbsId((VirtualFile) file); return storage.get(fileId); } private String prevLog = ""; private static final Set<JavaSourceRootType> SOURCE_ROOTS = ContainerUtil.newTroveSet(JavaSourceRootType.SOURCE, JavaSourceRootType.TEST_SOURCE); @NotNull @Override public GlobalSearchScope restrictByBackwardIds( @NotNull final VirtualFile virtualFile, @NotNull GlobalSearchScope scope) { final int[] backIds = RefResolveService.getInstance(myProject).getBackwardIds((VirtualFileWithId) virtualFile); if (backIds == null) { return scope; } String files = toVfString(backIds); String log = "Restricting scope of " + virtualFile.getName() + " to " + files; if (!log.equals(prevLog)) { log(log); flushLog(); prevLog = log; } GlobalSearchScope restrictedByBackwardIds = new GlobalSearchScope() { @Override public boolean contains(@NotNull VirtualFile file) { if (!(file instanceof VirtualFileWithId) || file.equals(virtualFile) || ArrayUtil.indexOf(backIds, getAbsId(file)) != -1) return true; return false & !myProjectFileIndex.isUnderSourceRootOfType( file, SOURCE_ROOTS); // filter out source file which we know for sure does not // reference the element } @Override public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) { return 0; } @Override public boolean isSearchInModuleContent(@NotNull Module aModule) { return true; } @Override public boolean isSearchInLibraries() { return false; } }; return scope.intersectWith(restrictedByBackwardIds); } @Override public boolean queue(@NotNull Collection<VirtualFile> files, @NotNull Object reason) { if (files.isEmpty()) { return false; } boolean queued = false; List<VirtualFile> added = new ArrayList<VirtualFile>(files.size()); for (VirtualFile file : files) { boolean wasAdded = queueIfNeeded(file, myProject); if (wasAdded) { added.add(file); } queued |= wasAdded; } if (queued) { log("Queued to resolve (from " + reason + "): " + toVfString(added)); flushLog(); } return queued; } @Override public boolean isUpToDate() { return ENABLED && !myDisposed && upToDate; } @Override public int getQueueSize() { synchronized (filesToResolve) { return filesToResolve.size(); } } private final List<Listener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); @Override public void addListener(@NotNull Disposable parent, @NotNull final Listener listener) { myListeners.add(listener); Disposer.register( parent, new Disposable() { @Override public void dispose() { myListeners.remove(listener); } }); } private static class MyProgress extends ProgressIndicatorBase implements Disposable { @Override public void dispose() {} } }
private static class ColorWheel extends JComponent { private static final int BORDER_SIZE = 5; private float myBrightness = 1f; private float myHue = 1f; private float mySaturation = 0f; private Image myImage; private Rectangle myWheel; private boolean myShouldInvalidate = true; private Color myColor; private final List<ColorListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private int myOpacity; private ColorWheel() { setOpaque(true); addComponentListener( new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { myShouldInvalidate = true; } }); addMouseMotionListener( new MouseAdapter() { @Override public void mouseDragged(MouseEvent e) { final int x = e.getX(); final int y = e.getY(); int mx = myWheel.x + myWheel.width / 2; int my = myWheel.y + myWheel.height / 2; double s; double h; s = Math.sqrt((double) ((x - mx) * (x - mx) + (y - my) * (y - my))) / (myWheel.height / 2); h = -Math.atan2((double) (y - my), (double) (x - mx)) / (2 * Math.PI); if (h < 0) h += 1.0; if (s > 1) s = 1.0; setHSBValue((float) h, (float) s, myBrightness, myOpacity); } }); addMouseListener( new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { final int x = e.getX(); final int y = e.getY(); int mx = myWheel.x + myWheel.width / 2; int my = myWheel.y + myWheel.height / 2; double s; double h; s = Math.sqrt((double) ((x - mx) * (x - mx) + (y - my) * (y - my))) / (myWheel.height / 2); h = -Math.atan2((double) (y - my), (double) (x - mx)) / (2 * Math.PI); if (h < 0) h += 1.0; if (s <= 1) { setHSBValue((float) h, (float) s, myBrightness, myOpacity); } } }); } private void setHSBValue(float h, float s, float b, int opacity) { //noinspection UseJBColor Color rgb = new Color(Color.HSBtoRGB(h, s, b)); setColor(ColorUtil.toAlpha(rgb, opacity), this); } private void setColor(Color color, Object source) { float[] hsb = new float[3]; Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), hsb); myColor = color; myHue = hsb[0]; mySaturation = hsb[1]; myBrightness = hsb[2]; myOpacity = color.getAlpha(); fireColorChanged(source); repaint(); } public void addListener(ColorListener listener) { myListeners.add(listener); } private void fireColorChanged(Object source) { for (ColorListener listener : myListeners) { listener.colorChanged(myColor, source); } } public void setBrightness(float brightness) { if (brightness != myBrightness) { myImage = null; setHSBValue(myHue, mySaturation, brightness, myOpacity); } } public void setOpacity(int opacity) { if (opacity != myOpacity) { setHSBValue(myHue, mySaturation, myBrightness, opacity); } } @Override public Dimension getPreferredSize() { return getMinimumSize(); } @Override public Dimension getMinimumSize() { return JBUI.size(300, 300); } @Override protected void paintComponent(Graphics g) { Graphics2D g2d = (Graphics2D) g; final Dimension size = getSize(); int _size = Math.min(size.width, size.height); _size = Math.min(_size, 600); if (myImage != null && myShouldInvalidate) { if (myImage.getWidth(null) != _size) { myImage = null; } } myShouldInvalidate = false; if (myImage == null) { myImage = createImage( new ColorWheelImageProducer( _size - BORDER_SIZE * 2, _size - BORDER_SIZE * 2, myBrightness)); myWheel = new Rectangle( BORDER_SIZE, BORDER_SIZE, _size - BORDER_SIZE * 2, _size - BORDER_SIZE * 2); } g.setColor(UIManager.getColor("Panel.background")); g.fillRect(0, 0, getWidth(), getHeight()); g2d.setComposite( AlphaComposite.getInstance(AlphaComposite.SRC_OVER, ((float) myOpacity) / 255f)); g.drawImage(myImage, myWheel.x, myWheel.y, null); g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 1.0f)); int mx = myWheel.x + myWheel.width / 2; int my = myWheel.y + myWheel.height / 2; //noinspection UseJBColor g.setColor(Color.WHITE); int arcw = (int) (myWheel.width * mySaturation / 2); int arch = (int) (myWheel.height * mySaturation / 2); double th = myHue * 2 * Math.PI; final int x = (int) (mx + arcw * Math.cos(th)); final int y = (int) (my - arch * Math.sin(th)); g.fillRect(x - 2, y - 2, 4, 4); //noinspection UseJBColor g.setColor(Color.BLACK); g.drawRect(x - 2, y - 2, 4, 4); } public void dropImage() { myImage = null; } }
public class ProjectManagerImpl extends ProjectManagerEx implements NamedJDOMExternalizable, ExportableApplicationComponent { private static final Logger LOG = Logger.getInstance("#com.intellij.project.impl.ProjectManagerImpl"); public static final int CURRENT_FORMAT_VERSION = 4; private static final Key<List<ProjectManagerListener>> LISTENERS_IN_PROJECT_KEY = Key.create("LISTENERS_IN_PROJECT_KEY"); @NonNls private static final String ELEMENT_DEFAULT_PROJECT = "defaultProject"; @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"}) private ProjectImpl myDefaultProject; // Only used asynchronously in save and dispose, which itself are // synchronized. @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"}) private Element myDefaultProjectRootElement; // Only used asynchronously in save and dispose, which itself are // synchronized. private final List<Project> myOpenProjects = new ArrayList<Project>(); private Project[] myOpenProjectsArrayCache = {}; private final List<ProjectManagerListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private final Set<Project> myTestProjects = new THashSet<Project>(); private final Map<VirtualFile, byte[]> mySavedCopies = new HashMap<VirtualFile, byte[]>(); private final TObjectLongHashMap<VirtualFile> mySavedTimestamps = new TObjectLongHashMap<VirtualFile>(); private final Map<Project, List<Pair<VirtualFile, StateStorage>>> myChangedProjectFiles = new HashMap<Project, List<Pair<VirtualFile, StateStorage>>>(); private final Alarm myChangedFilesAlarm = new Alarm(); private final List<Pair<VirtualFile, StateStorage>> myChangedApplicationFiles = new ArrayList<Pair<VirtualFile, StateStorage>>(); private final AtomicInteger myReloadBlockCount = new AtomicInteger(0); @SuppressWarnings("FieldCanBeLocal") private final Map<Project, String> myProjects = new WeakHashMap<Project, String>(); private static final int MAX_LEAKY_PROJECTS = 42; private final ProgressManager myProgressManager; private volatile boolean myDefaultProjectWasDisposed = false; @NotNull private static List<ProjectManagerListener> getListeners(Project project) { List<ProjectManagerListener> array = project.getUserData(LISTENERS_IN_PROJECT_KEY); if (array == null) return Collections.emptyList(); return array; } /** @noinspection UnusedParameters */ public ProjectManagerImpl( VirtualFileManager virtualFileManager, RecentProjectsManagerBase recentProjectsManager, ProgressManager progressManager) { myProgressManager = progressManager; Application app = ApplicationManager.getApplication(); MessageBus messageBus = app.getMessageBus(); MessageBusConnection connection = messageBus.connect(app); connection.subscribe( StateStorage.STORAGE_TOPIC, new StateStorage.Listener() { @Override public void storageFileChanged( @NotNull final VirtualFileEvent event, @NotNull final StateStorage storage) { VirtualFile file = event.getFile(); if (!file.isDirectory() && !(event.getRequestor() instanceof StateStorage.SaveSession)) { saveChangedProjectFile(file, null, storage); } } }); final ProjectManagerListener busPublisher = messageBus.syncPublisher(TOPIC); addProjectManagerListener( new ProjectManagerListener() { @Override public void projectOpened(final Project project) { MessageBus messageBus = project.getMessageBus(); MessageBusConnection connection = messageBus.connect(project); connection.subscribe( StateStorage.STORAGE_TOPIC, new StateStorage.Listener() { @Override public void storageFileChanged( @NotNull final VirtualFileEvent event, @NotNull final StateStorage storage) { VirtualFile file = event.getFile(); if (!file.isDirectory() && !(event.getRequestor() instanceof StateStorage.SaveSession)) { saveChangedProjectFile(file, project, storage); } } }); busPublisher.projectOpened(project); for (ProjectManagerListener listener : getListeners(project)) { listener.projectOpened(project); } } @Override public void projectClosed(Project project) { busPublisher.projectClosed(project); for (ProjectManagerListener listener : getListeners(project)) { listener.projectClosed(project); } } @Override public boolean canCloseProject(Project project) { for (ProjectManagerListener listener : getListeners(project)) { if (!listener.canCloseProject(project)) { return false; } } return true; } @Override public void projectClosing(Project project) { busPublisher.projectClosing(project); for (ProjectManagerListener listener : getListeners(project)) { listener.projectClosing(project); } } }); registerExternalProjectFileListener(virtualFileManager); } @Override public void disposeComponent() { ApplicationManager.getApplication().assertWriteAccessAllowed(); Disposer.dispose(myChangedFilesAlarm); if (myDefaultProject != null) { Disposer.dispose(myDefaultProject); myDefaultProject = null; myDefaultProjectWasDisposed = true; } } @Override public void initComponent() {} private static final boolean LOG_PROJECT_LEAKAGE_IN_TESTS = false; @Override @Nullable public Project newProject( final String projectName, @NotNull String filePath, boolean useDefaultProjectSettings, boolean isDummy) { filePath = toCanonicalName(filePath); //noinspection ConstantConditions if (LOG_PROJECT_LEAKAGE_IN_TESTS && ApplicationManager.getApplication().isUnitTestMode()) { for (int i = 0; i < 42; i++) { if (myProjects.size() < MAX_LEAKY_PROJECTS) break; System.gc(); TimeoutUtil.sleep(100); System.gc(); } if (myProjects.size() >= MAX_LEAKY_PROJECTS) { List<Project> copy = new ArrayList<Project>(myProjects.keySet()); myProjects.clear(); throw new TooManyProjectLeakedException(copy); } } ProjectImpl project = createProject( projectName, filePath, false, ApplicationManager.getApplication().isUnitTestMode()); try { initProject(project, useDefaultProjectSettings ? (ProjectImpl) getDefaultProject() : null); if (LOG_PROJECT_LEAKAGE_IN_TESTS) { myProjects.put(project, null); } return project; } catch (final Exception e) { LOG.info(e); Messages.showErrorDialog(message(e), ProjectBundle.message("project.load.default.error")); } return null; } @NonNls private static String message(Throwable e) { String message = e.getMessage(); if (message != null) return message; message = e.getLocalizedMessage(); if (message != null) return message; message = e.toString(); Throwable cause = e.getCause(); if (cause != null) { String causeMessage = message(cause); return message + " (cause: " + causeMessage + ")"; } return message; } private void initProject(@NotNull ProjectImpl project, @Nullable ProjectImpl template) throws IOException { final ProgressIndicator indicator = myProgressManager.getProgressIndicator(); if (indicator != null && !project.isDefault()) { indicator.setText(ProjectBundle.message("loading.components.for", project.getName())); indicator.setIndeterminate(true); } ApplicationManager.getApplication() .getMessageBus() .syncPublisher(ProjectLifecycleListener.TOPIC) .beforeProjectLoaded(project); try { if (template != null) { project.getStateStore().loadProjectFromTemplate(template); } else { project.getStateStore().load(); } project.loadProjectComponents(); project.init(); } catch (IOException e) { scheduleDispose(project); throw e; } catch (ProcessCanceledException e) { scheduleDispose(project); throw e; } } private ProjectImpl createProject( @Nullable String projectName, @NotNull String filePath, boolean isDefault, boolean isOptimiseTestLoadSpeed) { return isDefault ? new DefaultProject(this, "", isOptimiseTestLoadSpeed) : new ProjectImpl( this, new File(filePath).getAbsolutePath(), isOptimiseTestLoadSpeed, projectName); } private static void scheduleDispose(final ProjectImpl project) { if (project.isDefault()) { return; } ApplicationManager.getApplication() .invokeLater( new Runnable() { @Override public void run() { ApplicationManager.getApplication() .runWriteAction( new Runnable() { @Override public void run() { if (!project.isDisposed()) { Disposer.dispose(project); } } }); } }); } @Override @Nullable public Project loadProject(@NotNull String filePath) throws IOException, JDOMException, InvalidDataException { try { ProjectImpl project = createProject(null, filePath, false, false); initProject(project, null); return project; } catch (StateStorageException e) { throw new IOException(e.getMessage()); } } @NotNull private static String toCanonicalName(@NotNull final String filePath) { try { return FileUtil.resolveShortWindowsName(filePath); } catch (IOException e) { // OK. File does not yet exist so it's canonical path will be equal to its original path. } return filePath; } @TestOnly public synchronized boolean isDefaultProjectInitialized() { return myDefaultProject != null; } @Override @NotNull public synchronized Project getDefaultProject() { LOG.assertTrue(!myDefaultProjectWasDisposed, "Default project has been already disposed!"); if (myDefaultProject == null) { try { myDefaultProject = createProject(null, "", true, ApplicationManager.getApplication().isUnitTestMode()); initProject(myDefaultProject, null); myDefaultProjectRootElement = null; } catch (IOException e) { LOG.error(e); } catch (StateStorageException e) { LOG.error(e); } } return myDefaultProject; } public Element getDefaultProjectRootElement() { return myDefaultProjectRootElement; } @Override @NotNull public Project[] getOpenProjects() { synchronized (myOpenProjects) { if (myOpenProjectsArrayCache.length != myOpenProjects.size()) { LOG.error( "Open projects: " + myOpenProjects + "; cache: " + Arrays.asList(myOpenProjectsArrayCache)); } if (myOpenProjectsArrayCache.length > 0 && myOpenProjectsArrayCache[0] != myOpenProjects.get(0)) { LOG.error( "Open projects cache corrupted. Open projects: " + myOpenProjects + "; cache: " + Arrays.asList(myOpenProjectsArrayCache)); } if (ApplicationManager.getApplication().isUnitTestMode()) { Project[] testProjects = myTestProjects.toArray(new Project[myTestProjects.size()]); for (Project testProject : testProjects) { assert !testProject.isDisposed() : testProject; } return ArrayUtil.mergeArrays(myOpenProjectsArrayCache, testProjects); } return myOpenProjectsArrayCache; } } @Override public boolean isProjectOpened(Project project) { synchronized (myOpenProjects) { return ApplicationManager.getApplication().isUnitTestMode() && myTestProjects.contains(project) || myOpenProjects.contains(project); } } @Override public boolean openProject(final Project project) { if (isLight(project)) { throw new AssertionError("must not open light project"); } final Application application = ApplicationManager.getApplication(); if (!application.isUnitTestMode() && !((ProjectEx) project).getStateStore().checkVersion()) { return false; } synchronized (myOpenProjects) { if (myOpenProjects.contains(project)) { return false; } myOpenProjects.add(project); cacheOpenProjects(); } fireProjectOpened(project); final StartupManagerImpl startupManager = (StartupManagerImpl) StartupManager.getInstance(project); waitForFileWatcher(project); boolean ok = myProgressManager.runProcessWithProgressSynchronously( new Runnable() { @Override public void run() { startupManager.runStartupActivities(); // dumb mode should start before post-startup activities // only when startCacheUpdate is called from UI thread, we can guarantee that // when the method returns, the application has entered dumb mode UIUtil.invokeAndWaitIfNeeded( new Runnable() { @Override public void run() { startupManager.startCacheUpdate(); } }); startupManager.runPostStartupActivitiesFromExtensions(); UIUtil.invokeLaterIfNeeded( new Runnable() { @Override public void run() { startupManager.runPostStartupActivities(); } }); } }, ProjectBundle.message("project.load.progress"), true, project); if (!ok) { closeProject(project, false, false, true); notifyProjectOpenFailed(); return false; } if (!application.isHeadlessEnvironment() && !application.isUnitTestMode()) { // should be invoked last startupManager.runWhenProjectIsInitialized( new Runnable() { @Override public void run() { final TrackingPathMacroSubstitutor macroSubstitutor = ((ProjectEx) project) .getStateStore() .getStateStorageManager() .getMacroSubstitutor(); if (macroSubstitutor != null) { StorageUtil.notifyUnknownMacros(macroSubstitutor, project, null); } } }); } return true; } private void cacheOpenProjects() { myOpenProjectsArrayCache = myOpenProjects.toArray(new Project[myOpenProjects.size()]); } private void waitForFileWatcher(@NotNull Project project) { LocalFileSystem fs = LocalFileSystem.getInstance(); if (!(fs instanceof LocalFileSystemImpl)) return; final FileWatcher watcher = ((LocalFileSystemImpl) fs).getFileWatcher(); if (!watcher.isOperational() || !watcher.isSettingRoots()) return; LOG.info("FW/roots waiting started"); Task.Modal task = new Task.Modal(project, ProjectBundle.message("project.load.progress"), true) { @Override public void run(@NotNull ProgressIndicator indicator) { indicator.setIndeterminate(true); indicator.setText(ProjectBundle.message("project.load.waiting.watcher")); if (indicator instanceof ProgressWindow) { ((ProgressWindow) indicator).setCancelButtonText(CommonBundle.message("button.skip")); } while (watcher.isSettingRoots() && !indicator.isCanceled()) { TimeoutUtil.sleep(10); } LOG.info("FW/roots waiting finished"); } }; myProgressManager.run(task); } @Override public Project loadAndOpenProject(@NotNull final String filePath) throws IOException { final Project project = convertAndLoadProject(filePath); if (project == null) { WelcomeFrame.showIfNoProjectOpened(); return null; } // todo unify this logic with PlatformProjectOpenProcessor if (!openProject(project)) { WelcomeFrame.showIfNoProjectOpened(); ApplicationManager.getApplication() .runWriteAction( new Runnable() { @Override public void run() { Disposer.dispose(project); } }); } return project; } /** * Converts and loads the project at the specified path. * * @param filePath the path to open the project. * @return the project, or null if the user has cancelled opening the project. */ @Override @Nullable public Project convertAndLoadProject(String filePath) throws IOException { final String fp = toCanonicalName(filePath); final ConversionResult conversionResult = ConversionService.getInstance().convert(fp); if (conversionResult.openingIsCanceled()) { return null; } final Project project = loadProjectWithProgress(filePath); if (project == null) return null; if (!conversionResult.conversionNotNeeded()) { StartupManager.getInstance(project) .registerPostStartupActivity( new Runnable() { @Override public void run() { conversionResult.postStartupActivity(project); } }); } return project; } /** * Opens the project at the specified path. * * @param filePath the path to open the project. * @return the project, or null if the user has cancelled opening the project. */ @Nullable private Project loadProjectWithProgress(@NotNull final String filePath) throws IOException { final ProjectImpl project = createProject(null, toCanonicalName(filePath), false, false); try { myProgressManager.runProcessWithProgressSynchronously( new ThrowableComputable<Project, IOException>() { @Override @Nullable public Project compute() throws IOException { initProject(project, null); return project; } }, ProjectBundle.message("project.load.progress"), true, project); } catch (StateStorageException e) { throw new IOException(e); } catch (ProcessCanceledException ignore) { return null; } return project; } private static void notifyProjectOpenFailed() { ApplicationManager.getApplication() .getMessageBus() .syncPublisher(AppLifecycleListener.TOPIC) .projectOpenFailed(); WelcomeFrame.showIfNoProjectOpened(); } private void registerExternalProjectFileListener(VirtualFileManager virtualFileManager) { virtualFileManager.addVirtualFileManagerListener( new VirtualFileManagerListener() { @Override public void beforeRefreshStart(boolean asynchronous) {} @Override public void afterRefreshFinish(boolean asynchronous) { scheduleReloadApplicationAndProject(); } }); } private void askToReloadProjectIfConfigFilesChangedExternally() { LOG.debug("[RELOAD] myReloadBlockCount = " + myReloadBlockCount.get()); if (myReloadBlockCount.get() == 0) { Set<Project> projects; synchronized (myChangedProjectFiles) { if (myChangedProjectFiles.isEmpty()) return; projects = new HashSet<Project>(myChangedProjectFiles.keySet()); } List<Project> projectsToReload = new ArrayList<Project>(); for (Project project : projects) { if (shouldReloadProject(project)) { projectsToReload.add(project); } } for (final Project projectToReload : projectsToReload) { reloadProjectImpl(projectToReload, false); } } } private boolean tryToReloadApplication() { try { final Application app = ApplicationManager.getApplication(); if (app.isDisposed()) return false; final HashSet<Pair<VirtualFile, StateStorage>> causes = new HashSet<Pair<VirtualFile, StateStorage>>(myChangedApplicationFiles); if (causes.isEmpty()) return true; final boolean[] reloadOk = {false}; final LinkedHashSet<String> components = new LinkedHashSet<String>(); ApplicationManager.getApplication() .runWriteAction( new Runnable() { @Override public void run() { try { reloadOk[0] = ((ApplicationImpl) app).getStateStore().reload(causes, components); } catch (StateStorageException e) { Messages.showWarningDialog( ProjectBundle.message("project.reload.failed", e.getMessage()), ProjectBundle.message("project.reload.failed.title")); } catch (IOException e) { Messages.showWarningDialog( ProjectBundle.message("project.reload.failed", e.getMessage()), ProjectBundle.message("project.reload.failed.title")); } } }); if (!reloadOk[0] && !components.isEmpty()) { String message = "Application components were changed externally and cannot be reloaded:\n"; for (String component : components) { message += component + "\n"; } final boolean canRestart = ApplicationManager.getApplication().isRestartCapable(); message += "Would you like to " + (canRestart ? "restart " : "shutdown "); message += ApplicationNamesInfo.getInstance().getProductName() + "?"; if (Messages.showYesNoDialog( message, "Application Configuration Reload", Messages.getQuestionIcon()) == Messages.YES) { for (Pair<VirtualFile, StateStorage> cause : causes) { StateStorage stateStorage = cause.getSecond(); if (stateStorage instanceof XmlElementStorage) { ((XmlElementStorage) stateStorage).disableSaving(); } } ApplicationManagerEx.getApplicationEx().restart(true); } } return reloadOk[0]; } finally { myChangedApplicationFiles.clear(); } } private boolean shouldReloadProject(final Project project) { if (project.isDisposed()) return false; final HashSet<Pair<VirtualFile, StateStorage>> causes = new HashSet<Pair<VirtualFile, StateStorage>>(); synchronized (myChangedProjectFiles) { final List<Pair<VirtualFile, StateStorage>> changes = myChangedProjectFiles.remove(project); if (changes != null) { causes.addAll(changes); } if (causes.isEmpty()) return false; } final boolean[] reloadOk = {false}; ApplicationManager.getApplication() .runWriteAction( new Runnable() { @Override public void run() { try { LOG.debug("[RELOAD] Reloading project/components..."); reloadOk[0] = ((ProjectEx) project).getStateStore().reload(causes); } catch (StateStorageException e) { Messages.showWarningDialog( ProjectBundle.message("project.reload.failed", e.getMessage()), ProjectBundle.message("project.reload.failed.title")); } catch (IOException e) { Messages.showWarningDialog( ProjectBundle.message("project.reload.failed", e.getMessage()), ProjectBundle.message("project.reload.failed.title")); } } }); if (reloadOk[0]) return false; String message; if (causes.size() == 1) { message = ProjectBundle.message( "project.reload.external.change.single", causes.iterator().next().first.getPresentableUrl()); } else { StringBuilder filesBuilder = new StringBuilder(); boolean first = true; Set<String> alreadyShown = new HashSet<String>(); for (Pair<VirtualFile, StateStorage> cause : causes) { String url = cause.first.getPresentableUrl(); if (!alreadyShown.contains(url)) { if (alreadyShown.size() > 10) { filesBuilder .append("\n" + "and ") .append(causes.size() - alreadyShown.size()) .append(" more"); break; } if (!first) filesBuilder.append("\n"); first = false; filesBuilder.append(url); alreadyShown.add(url); } } message = ProjectBundle.message("project.reload.external.change.multiple", filesBuilder.toString()); } return Messages.showTwoStepConfirmationDialog( message, ProjectBundle.message("project.reload.external.change.title"), "Reload project", Messages.getQuestionIcon()) == 0; } @Override public boolean isFileSavedToBeReloaded(VirtualFile candidate) { return mySavedCopies.containsKey(candidate); } @Override public void blockReloadingProjectOnExternalChanges() { myReloadBlockCount.incrementAndGet(); } @Override public void unblockReloadingProjectOnExternalChanges() { if (myReloadBlockCount.decrementAndGet() == 0) scheduleReloadApplicationAndProject(); } private void scheduleReloadApplicationAndProject() { // todo: commented due to "IDEA-61938 Libraries configuration is kept if switching branches" // because of save which may happen _before_ project reload ;( // ApplicationManager.getApplication().invokeLater(new Runnable() { // public void run() { // IdeEventQueue.getInstance().addIdleListener(new Runnable() { // @Override // public void run() { // IdeEventQueue.getInstance().removeIdleListener(this); ApplicationManager.getApplication() .invokeLater( new Runnable() { @Override public void run() { if (!tryToReloadApplication()) return; askToReloadProjectIfConfigFilesChangedExternally(); } }, ModalityState.NON_MODAL); // } // }, 2000); // } // }, ModalityState.NON_MODAL); } @Override public void openTestProject(@NotNull final Project project) { synchronized (myOpenProjects) { assert ApplicationManager.getApplication().isUnitTestMode(); assert !project.isDisposed() : "Must not open already disposed project"; myTestProjects.add(project); } } @Override public Collection<Project> closeTestProject(@NotNull Project project) { synchronized (myOpenProjects) { assert ApplicationManager.getApplication().isUnitTestMode(); myTestProjects.remove(project); return myTestProjects; } } @Override public void saveChangedProjectFile(final VirtualFile file, final Project project) { if (file.exists()) { copyToTemp(file); } registerProjectToReload(project, file, null); } private void saveChangedProjectFile( final VirtualFile file, @Nullable final Project project, final StateStorage storage) { if (file.exists()) { copyToTemp(file); } registerProjectToReload(project, file, storage); } private void registerProjectToReload( @Nullable final Project project, final VirtualFile cause, @Nullable final StateStorage storage) { if (LOG.isDebugEnabled()) { LOG.debug("[RELOAD] Registering project to reload: " + cause, new Exception()); } if (project != null) { synchronized (myChangedProjectFiles) { List<Pair<VirtualFile, StateStorage>> changedProjectFiles = myChangedProjectFiles.get(project); if (changedProjectFiles == null) { changedProjectFiles = new ArrayList<Pair<VirtualFile, StateStorage>>(); myChangedProjectFiles.put(project, changedProjectFiles); } changedProjectFiles.add(new Pair<VirtualFile, StateStorage>(cause, storage)); } } else { myChangedApplicationFiles.add(new Pair<VirtualFile, StateStorage>(cause, storage)); } myChangedFilesAlarm.cancelAllRequests(); myChangedFilesAlarm.addRequest( new Runnable() { @Override public void run() { LOG.debug( "[RELOAD] Scheduling reload application & project, myReloadBlockCount = " + myReloadBlockCount); if (myReloadBlockCount.get() == 0) { scheduleReloadApplicationAndProject(); } } }, 444); } private void copyToTemp(VirtualFile file) { try { final byte[] bytes = file.contentsToByteArray(); mySavedCopies.put(file, bytes); mySavedTimestamps.put(file, file.getTimeStamp()); } catch (IOException e) { LOG.error(e); } } private void restoreCopy(VirtualFile file) { try { if (file == null) return; // Externally deleted actually. if (!file.isWritable()) return; // IDEA was unable to save it as well. So no need to restore. final byte[] bytes = mySavedCopies.get(file); if (bytes != null) { try { file.setBinaryContent(bytes, -1, mySavedTimestamps.get(file)); } catch (IOException e) { Messages.showWarningDialog( ProjectBundle.message("project.reload.write.failed", file.getPresentableUrl()), ProjectBundle.message("project.reload.write.failed.title")); } } } finally { mySavedCopies.remove(file); mySavedTimestamps.remove(file); } } @Override public void reloadProject(@NotNull final Project p) { reloadProjectImpl(p, true); } public void reloadProjectImpl(@NotNull final Project p, final boolean clearCopyToRestore) { if (clearCopyToRestore) { mySavedCopies.clear(); mySavedTimestamps.clear(); } final Project[] project = {p}; ProjectReloadState.getInstance(project[0]).onBeforeAutomaticProjectReload(); final Application application = ApplicationManager.getApplication(); application.invokeLater( new Runnable() { @Override public void run() { LOG.debug("Reloading project."); ProjectImpl projectImpl = (ProjectImpl) project[0]; if (projectImpl.isDisposed()) return; IProjectStore projectStore = projectImpl.getStateStore(); final String location = projectImpl.getPresentableUrl(); final List<IFile> original; try { final IComponentStore.SaveSession saveSession = projectStore.startSave(); original = saveSession.getAllStorageFiles(true); saveSession.finishSave(); } catch (IOException e) { LOG.error(e); return; } if (project[0].isDisposed() || ProjectUtil.closeAndDispose(project[0])) { application.runWriteAction( new Runnable() { @Override public void run() { for (final IFile originalFile : original) { restoreCopy( LocalFileSystem.getInstance().refreshAndFindFileByIoFile(originalFile)); } } }); project[0] = null; // Let it go. ProjectUtil.openProject(location, null, true); } } }, ModalityState.NON_MODAL); } @Override public boolean closeProject(@NotNull final Project project) { return closeProject(project, true, false, true); } public boolean closeProject( @NotNull final Project project, final boolean save, final boolean dispose, boolean checkCanClose) { if (isLight(project)) { throw new AssertionError("must not close light project"); } if (!isProjectOpened(project)) return true; if (checkCanClose && !canClose(project)) return false; final ShutDownTracker shutDownTracker = ShutDownTracker.getInstance(); shutDownTracker.registerStopperThread(Thread.currentThread()); try { if (save) { FileDocumentManager.getInstance().saveAllDocuments(); project.save(); } if (checkCanClose && !ensureCouldCloseIfUnableToSave(project)) { return false; } fireProjectClosing(project); // somebody can start progress here, do not wrap in write action ApplicationManager.getApplication() .runWriteAction( new Runnable() { @Override public void run() { synchronized (myOpenProjects) { myOpenProjects.remove(project); cacheOpenProjects(); myTestProjects.remove(project); } myChangedProjectFiles.remove(project); fireProjectClosed(project); if (dispose) { Disposer.dispose(project); } } }); } finally { shutDownTracker.unregisterStopperThread(Thread.currentThread()); } return true; } public static boolean isLight(@NotNull Project project) { return ApplicationManager.getApplication().isUnitTestMode() && project.toString().contains("light_temp_"); } @Override public boolean closeAndDispose(@NotNull final Project project) { return closeProject(project, true, true, true); } private void fireProjectClosing(Project project) { if (LOG.isDebugEnabled()) { LOG.debug("enter: fireProjectClosing()"); } for (ProjectManagerListener listener : myListeners) { try { listener.projectClosing(project); } catch (Exception e) { LOG.error(e); } } } @Override public void addProjectManagerListener(@NotNull ProjectManagerListener listener) { myListeners.add(listener); } @Override public void addProjectManagerListener( @NotNull final ProjectManagerListener listener, @NotNull Disposable parentDisposable) { addProjectManagerListener(listener); Disposer.register( parentDisposable, new Disposable() { @Override public void dispose() { removeProjectManagerListener(listener); } }); } @Override public void removeProjectManagerListener(@NotNull ProjectManagerListener listener) { boolean removed = myListeners.remove(listener); LOG.assertTrue(removed); } @Override public void addProjectManagerListener( @NotNull Project project, @NotNull ProjectManagerListener listener) { List<ProjectManagerListener> listeners = project.getUserData(LISTENERS_IN_PROJECT_KEY); if (listeners == null) { listeners = ((UserDataHolderEx) project) .putUserDataIfAbsent( LISTENERS_IN_PROJECT_KEY, ContainerUtil.<ProjectManagerListener>createLockFreeCopyOnWriteList()); } listeners.add(listener); } @Override public void removeProjectManagerListener( @NotNull Project project, @NotNull ProjectManagerListener listener) { List<ProjectManagerListener> listeners = project.getUserData(LISTENERS_IN_PROJECT_KEY); LOG.assertTrue(listeners != null); boolean removed = listeners.remove(listener); LOG.assertTrue(removed); } private void fireProjectOpened(Project project) { if (LOG.isDebugEnabled()) { LOG.debug("projectOpened"); } for (ProjectManagerListener listener : myListeners) { try { listener.projectOpened(project); } catch (Exception e) { LOG.error(e); } } } private void fireProjectClosed(Project project) { if (LOG.isDebugEnabled()) { LOG.debug("projectClosed"); } for (ProjectManagerListener listener : myListeners) { try { listener.projectClosed(project); } catch (Exception e) { LOG.error(e); } } } @Override public boolean canClose(Project project) { if (LOG.isDebugEnabled()) { LOG.debug("enter: canClose()"); } for (ProjectManagerListener listener : myListeners) { try { if (!listener.canCloseProject(project)) return false; } catch (Throwable e) { LOG.warn(e); // DO NOT LET ANY PLUGIN to prevent closing due to exception } } return true; } private static boolean ensureCouldCloseIfUnableToSave(@NotNull final Project project) { final ProjectImpl.UnableToSaveProjectNotification[] notifications = NotificationsManager.getNotificationsManager() .getNotificationsOfType(ProjectImpl.UnableToSaveProjectNotification.class, project); if (notifications.length == 0) return true; final String fileNames = StringUtil.join(notifications[0].getFileNames(), "\n"); final String msg = String.format( "%s was unable to save some project files,\nare you sure you want to close this project anyway?", ApplicationNamesInfo.getInstance().getProductName()); return Messages.showDialog( project, msg, "Unsaved Project", "Read-only files:\n\n" + fileNames, new String[] {"Yes", "No"}, 0, 1, Messages.getWarningIcon()) == 0; } @Override public void writeExternal(Element parentNode) throws WriteExternalException { if (myDefaultProject != null) { myDefaultProject.save(); } if (myDefaultProjectRootElement == null) { // read external isn't called if config folder is absent myDefaultProjectRootElement = new Element(ELEMENT_DEFAULT_PROJECT); } myDefaultProjectRootElement.detach(); parentNode.addContent(myDefaultProjectRootElement); } public void setDefaultProjectRootElement(final Element defaultProjectRootElement) { myDefaultProjectRootElement = defaultProjectRootElement; } @Override public void readExternal(Element parentNode) throws InvalidDataException { myDefaultProjectRootElement = parentNode.getChild(ELEMENT_DEFAULT_PROJECT); if (myDefaultProjectRootElement == null) { myDefaultProjectRootElement = new Element(ELEMENT_DEFAULT_PROJECT); } myDefaultProjectRootElement.detach(); } @Override public String getExternalFileName() { return "project.default"; } @Override @NotNull public String getComponentName() { return "ProjectManager"; } @Override @NotNull public File[] getExportFiles() { return new File[] {PathManager.getOptionsFile(this)}; } @Override @NotNull public String getPresentableName() { return ProjectBundle.message("project.default.settings"); } }
/** @author anna Date: 04-Dec-2007 */ public class DetectedPluginsPanel extends OrderPanel<PluginDownloader> { private final List<Listener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private JEditorPane myDescriptionPanel = new JEditorPane(); private PluginHeaderPanel myHeader; public DetectedPluginsPanel() { super(PluginDownloader.class); final JTable entryTable = getEntryTable(); myHeader = new PluginHeaderPanel(null, entryTable); entryTable.setTableHeader(null); entryTable.setDefaultRenderer( PluginDownloader.class, new ColoredTableCellRenderer() { protected void customizeCellRenderer( final JTable table, final Object value, final boolean selected, final boolean hasFocus, final int row, final int column) { setBorder(null); final PluginDownloader downloader = (PluginDownloader) value; if (downloader != null) { final String pluginName = downloader.getPluginName(); append(pluginName, SimpleTextAttributes.REGULAR_ATTRIBUTES); final IdeaPluginDescriptor ideaPluginDescriptor = PluginManager.getPlugin(PluginId.getId(downloader.getPluginId())); if (ideaPluginDescriptor != null) { final String oldPluginName = ideaPluginDescriptor.getName(); if (!Comparing.strEqual(pluginName, oldPluginName)) { append(" - " + oldPluginName, SimpleTextAttributes.REGULAR_ATTRIBUTES); } } final String loadedVersion = downloader.getPluginVersion(); if (loadedVersion != null || (ideaPluginDescriptor != null && ideaPluginDescriptor.getVersion() != null)) { final String installedVersion = ideaPluginDescriptor != null && ideaPluginDescriptor.getVersion() != null ? "v. " + ideaPluginDescriptor.getVersion() + (loadedVersion != null ? " -> " : "") : ""; final String availableVersion = loadedVersion != null ? loadedVersion : ""; append( " (" + installedVersion + availableVersion + ")", SimpleTextAttributes.GRAY_ATTRIBUTES); } } } }); entryTable .getSelectionModel() .addListSelectionListener( new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { final int selectedRow = entryTable.getSelectedRow(); if (selectedRow != -1) { final PluginDownloader selection = getValueAt(selectedRow); final IdeaPluginDescriptor descriptor = selection.getDescriptor(); if (descriptor != null) { PluginManagerMain.pluginInfoUpdate( descriptor, null, myDescriptionPanel, myHeader, null); } } } }); setCheckboxColumnName(""); myDescriptionPanel.setPreferredSize(new Dimension(400, -1)); myDescriptionPanel.setEditable(false); myDescriptionPanel.setContentType(UIUtil.HTML_MIME); myDescriptionPanel.addHyperlinkListener(new PluginManagerMain.MyHyperlinkListener()); removeAll(); final Splitter splitter = new Splitter(false); splitter.setFirstComponent(ScrollPaneFactory.createScrollPane(entryTable)); splitter.setSecondComponent(ScrollPaneFactory.createScrollPane(myDescriptionPanel)); add(splitter, BorderLayout.CENTER); } public String getCheckboxColumnName() { return ""; } public boolean isCheckable(final PluginDownloader downloader) { return true; } public boolean isChecked(final PluginDownloader downloader) { return !getSkippedPlugins().contains(downloader.getPluginId()); } public void setChecked(final PluginDownloader downloader, final boolean checked) { if (checked) { getSkippedPlugins().remove(downloader.getPluginId()); } else { getSkippedPlugins().add(downloader.getPluginId()); } for (Listener listener : myListeners) { listener.stateChanged(); } } protected Set<String> getSkippedPlugins() { return UpdateChecker.getDisabledToUpdatePlugins(); } public void addStateListener(Listener l) { myListeners.add(l); } public interface Listener { void stateChanged(); } }
private static class MyPsiElementVisitor extends PsiElementVisitor { private final boolean highlightErrorElements; private final boolean runAnnotators; final List<Pair<PsiFile, HighlightInfo>> result = ContainerUtil.createLockFreeCopyOnWriteList(); public MyPsiElementVisitor(boolean highlightErrorElements, boolean runAnnotators) { this.highlightErrorElements = highlightErrorElements; this.runAnnotators = runAnnotators; } @Override public void visitFile(PsiFile file) { final VirtualFile virtualFile = file.getVirtualFile(); if (virtualFile == null) { return; } final Project project = file.getProject(); Document document = PsiDocumentManager.getInstance(project).getDocument(file); if (document == null) return; final HighlightInfoFilter[] filters = ApplicationManager.getApplication() .getExtensions(HighlightInfoFilter.EXTENSION_POINT_NAME); GeneralHighlightingPass pass = new GeneralHighlightingPass(project, file, document, 0, file.getTextLength(), true) { @NotNull @Override protected HighlightVisitor[] createHighlightVisitors() { return new HighlightVisitor[] { new DefaultHighlightVisitor(project, highlightErrorElements, runAnnotators, true) }; } @Override protected HighlightInfoHolder createInfoHolder(final PsiFile file) { return new HighlightInfoHolder(file, getColorsScheme(), filters) { @Override public boolean add(@Nullable HighlightInfo info) { if (info == null) return true; if (info.type == HighlightInfoType.INJECTED_LANGUAGE_FRAGMENT) return true; if (info.getSeverity() == HighlightSeverity.INFORMATION) return true; result.add(Pair.create(file, info)); return true; } }; } @Override protected void killAbandonedHighlightsUnder( @NotNull TextRange range, @Nullable List<HighlightInfo> holder, @NotNull ProgressIndicator progress) { // do not mess with real editor highlights } @Override protected boolean isFailFastOnAcquireReadAction() { return false; } }; DaemonProgressIndicator progress = new DaemonProgressIndicator(); progress.start(); pass.collectInformation(progress); } }
@State( name = "DebuggerManager", storages = {@Storage(file = StoragePathMacros.WORKSPACE_FILE)}) public class DebuggerManagerImpl extends DebuggerManagerEx implements PersistentStateComponent<Element> { private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.impl.DebuggerManagerImpl"); private final Project myProject; private final HashMap<ProcessHandler, DebuggerSession> mySessions = new HashMap<ProcessHandler, DebuggerSession>(); private final BreakpointManager myBreakpointManager; private final List<NameMapper> myNameMappers = ContainerUtil.createLockFreeCopyOnWriteList(); private final List<Function<DebugProcess, PositionManager>> myCustomPositionManagerFactories = new ArrayList<Function<DebugProcess, PositionManager>>(); private final EventDispatcher<DebuggerManagerListener> myDispatcher = EventDispatcher.create(DebuggerManagerListener.class); private final MyDebuggerStateManager myDebuggerStateManager = new MyDebuggerStateManager(); private final DebuggerContextListener mySessionListener = new DebuggerContextListener() { @Override public void changeEvent(DebuggerContextImpl newContext, DebuggerSession.Event event) { final DebuggerSession session = newContext.getDebuggerSession(); if (event == DebuggerSession.Event.PAUSE && myDebuggerStateManager.myDebuggerSession != session) { // if paused in non-active session; switch current session myDebuggerStateManager.setState( newContext, session != null ? session.getState() : DebuggerSession.State.DISPOSED, event, null); return; } if (myDebuggerStateManager.myDebuggerSession == session) { myDebuggerStateManager.fireStateChanged(newContext, event); } if (event == DebuggerSession.Event.ATTACHED) { myDispatcher.getMulticaster().sessionAttached(session); } else if (event == DebuggerSession.Event.DETACHED) { myDispatcher.getMulticaster().sessionDetached(session); } else if (event == DebuggerSession.Event.DISPOSE) { dispose(session); if (myDebuggerStateManager.myDebuggerSession == session) { myDebuggerStateManager.setState( DebuggerContextImpl.EMPTY_CONTEXT, DebuggerSession.State.DISPOSED, DebuggerSession.Event.DISPOSE, null); } } } }; @NonNls private static final String DEBUG_KEY_NAME = "idea.xdebug.key"; @Override public void addClassNameMapper(final NameMapper mapper) { myNameMappers.add(mapper); } @Override public void removeClassNameMapper(final NameMapper mapper) { myNameMappers.remove(mapper); } @Override public String getVMClassQualifiedName(@NotNull final PsiClass aClass) { for (NameMapper nameMapper : myNameMappers) { final String qName = nameMapper.getQualifiedName(aClass); if (qName != null) { return qName; } } return aClass.getQualifiedName(); } @Override public void addDebuggerManagerListener(DebuggerManagerListener listener) { myDispatcher.addListener(listener); } @Override public void removeDebuggerManagerListener(DebuggerManagerListener listener) { myDispatcher.removeListener(listener); } public DebuggerManagerImpl( Project project, StartupManager startupManager, EditorColorsManager colorsManager) { myProject = project; myBreakpointManager = new BreakpointManager(myProject, startupManager, this); if (!project.isDefault()) { colorsManager.addEditorColorsListener( new EditorColorsListener() { @Override public void globalSchemeChange(EditorColorsScheme scheme) { getBreakpointManager().updateBreakpointsUI(); } }, project); } } @Override public DebuggerSession getSession(DebugProcess process) { ApplicationManager.getApplication().assertIsDispatchThread(); for (final DebuggerSession debuggerSession : getSessions()) { if (process == debuggerSession.getProcess()) return debuggerSession; } return null; } @Override public Collection<DebuggerSession> getSessions() { synchronized (mySessions) { final Collection<DebuggerSession> values = mySessions.values(); return values.isEmpty() ? Collections.<DebuggerSession>emptyList() : new ArrayList<DebuggerSession>(values); } } @Override public void disposeComponent() {} @Override public void initComponent() {} @Override public void projectClosed() {} @Override public void projectOpened() { myBreakpointManager.init(); } @Nullable @Override public Element getState() { Element state = new Element("state"); myBreakpointManager.writeExternal(state); return state; } @Override public void loadState(Element state) { myBreakpointManager.readExternal(state); } public void writeExternal(Element element) throws WriteExternalException { myBreakpointManager.writeExternal(element); } @Override @Nullable public DebuggerSession attachVirtualMachine(@NotNull DebugEnvironment environment) throws ExecutionException { ApplicationManager.getApplication().assertIsDispatchThread(); final DebugProcessEvents debugProcess = new DebugProcessEvents(myProject); debugProcess.addDebugProcessListener( new DebugProcessAdapter() { @Override public void processAttached(final DebugProcess process) { process.removeDebugProcessListener(this); for (Function<DebugProcess, PositionManager> factory : myCustomPositionManagerFactories) { final PositionManager positionManager = factory.fun(process); if (positionManager != null) { process.appendPositionManager(positionManager); } } for (PositionManagerFactory factory : Extensions.getExtensions(PositionManagerFactory.EP_NAME, myProject)) { final PositionManager manager = factory.createPositionManager(debugProcess); if (manager != null) { process.appendPositionManager(manager); } } } @Override public void processDetached(final DebugProcess process, final boolean closedByUser) { debugProcess.removeDebugProcessListener(this); } @Override public void attachException( final RunProfileState state, final ExecutionException exception, final RemoteConnection remoteConnection) { debugProcess.removeDebugProcessListener(this); } }); DebuggerSession session = DebuggerSession.create(environment.getSessionName(), debugProcess, environment); ExecutionResult executionResult = session.getProcess().getExecutionResult(); if (executionResult == null) { return null; } session.getContextManager().addListener(mySessionListener); getContextManager() .setState( DebuggerContextUtil.createDebuggerContext( session, session.getContextManager().getContext().getSuspendContext()), session.getState(), DebuggerSession.Event.CONTEXT, null); final ProcessHandler processHandler = executionResult.getProcessHandler(); synchronized (mySessions) { mySessions.put(processHandler, session); } if (!(processHandler instanceof RemoteDebugProcessHandler)) { // add listener only to non-remote process handler: // on Unix systems destroying process does not cause VMDeathEvent to be generated, // so we need to call debugProcess.stop() explicitly for graceful termination. // RemoteProcessHandler on the other hand will call debugProcess.stop() as a part of // destroyProcess() and detachProcess() implementation, // so we shouldn't add the listener to avoid calling stop() twice processHandler.addProcessListener( new ProcessAdapter() { @Override public void processWillTerminate(ProcessEvent event, boolean willBeDestroyed) { final DebugProcessImpl debugProcess = getDebugProcess(event.getProcessHandler()); if (debugProcess != null) { // if current thread is a "debugger manager thread", stop will execute synchronously // it is KillableColoredProcessHandler responsibility to terminate VM debugProcess.stop( willBeDestroyed && !(event.getProcessHandler() instanceof KillableColoredProcessHandler)); // wait at most 10 seconds: the problem is that debugProcess.stop() can hang if // there are troubles in the debuggee // if processWillTerminate() is called from AWT thread debugProcess.waitFor() will // block it and the whole app will hang if (!DebuggerManagerThreadImpl.isManagerThread()) { if (SwingUtilities.isEventDispatchThread()) { ProgressManager.getInstance() .runProcessWithProgressSynchronously( new Runnable() { @Override public void run() { ProgressManager.getInstance() .getProgressIndicator() .setIndeterminate(true); debugProcess.waitFor(10000); } }, "Waiting For Debugger Response", false, debugProcess.getProject()); } else { debugProcess.waitFor(10000); } } } } }); } myDispatcher.getMulticaster().sessionCreated(session); return session; } @Override public DebugProcessImpl getDebugProcess(final ProcessHandler processHandler) { synchronized (mySessions) { DebuggerSession session = mySessions.get(processHandler); return session != null ? session.getProcess() : null; } } @SuppressWarnings("UnusedDeclaration") @Nullable public DebuggerSession getDebugSession(final ProcessHandler processHandler) { synchronized (mySessions) { return mySessions.get(processHandler); } } @Override public void addDebugProcessListener( final ProcessHandler processHandler, final DebugProcessListener listener) { DebugProcessImpl debugProcess = getDebugProcess(processHandler); if (debugProcess != null) { debugProcess.addDebugProcessListener(listener); } else { processHandler.addProcessListener( new ProcessAdapter() { @Override public void startNotified(ProcessEvent event) { DebugProcessImpl debugProcess = getDebugProcess(processHandler); if (debugProcess != null) { debugProcess.addDebugProcessListener(listener); } processHandler.removeProcessListener(this); } }); } } @Override public void removeDebugProcessListener( final ProcessHandler processHandler, final DebugProcessListener listener) { DebugProcessImpl debugProcess = getDebugProcess(processHandler); if (debugProcess != null) { debugProcess.removeDebugProcessListener(listener); } else { processHandler.addProcessListener( new ProcessAdapter() { @Override public void startNotified(ProcessEvent event) { DebugProcessImpl debugProcess = getDebugProcess(processHandler); if (debugProcess != null) { debugProcess.removeDebugProcessListener(listener); } processHandler.removeProcessListener(this); } }); } } @Override public boolean isDebuggerManagerThread() { return DebuggerManagerThreadImpl.isManagerThread(); } @Override @NotNull public String getComponentName() { return "DebuggerManager"; } @Override public BreakpointManager getBreakpointManager() { return myBreakpointManager; } @Override public DebuggerContextImpl getContext() { return getContextManager().getContext(); } @Override public DebuggerStateManager getContextManager() { return myDebuggerStateManager; } @Override public void registerPositionManagerFactory( final Function<DebugProcess, PositionManager> factory) { myCustomPositionManagerFactories.add(factory); } @Override public void unregisterPositionManagerFactory( final Function<DebugProcess, PositionManager> factory) { myCustomPositionManagerFactories.remove(factory); } private static boolean hasWhitespace(String string) { int length = string.length(); for (int i = 0; i < length; i++) { if (Character.isWhitespace(string.charAt(i))) { return true; } } return false; } /* Remoting */ private static void checkTargetJPDAInstalled(JavaParameters parameters) throws ExecutionException { final Sdk jdk = parameters.getJdk(); if (jdk == null) { throw new ExecutionException(DebuggerBundle.message("error.jdk.not.specified")); } final JavaSdkVersion version = JavaSdk.getInstance().getVersion(jdk); String versionString = jdk.getVersionString(); if (version == JavaSdkVersion.JDK_1_0 || version == JavaSdkVersion.JDK_1_1) { throw new ExecutionException( DebuggerBundle.message("error.unsupported.jdk.version", versionString)); } if (SystemInfo.isWindows && version == JavaSdkVersion.JDK_1_2) { final VirtualFile homeDirectory = jdk.getHomeDirectory(); if (homeDirectory == null || !homeDirectory.isValid()) { throw new ExecutionException( DebuggerBundle.message("error.invalid.jdk.home", versionString)); } //noinspection HardCodedStringLiteral File dllFile = new File( homeDirectory.getPath().replace('/', File.separatorChar) + File.separator + "bin" + File.separator + "jdwp.dll"); if (!dllFile.exists()) { GetJPDADialog dialog = new GetJPDADialog(); dialog.show(); throw new ExecutionException(DebuggerBundle.message("error.debug.libraries.missing")); } } } /** for Target JDKs versions 1.2.x - 1.3.0 the Classic VM should be used for debugging */ private static boolean shouldForceClassicVM(Sdk jdk) { if (SystemInfo.isMac) { return false; } if (jdk == null) return false; String version = JdkUtil.getJdkMainAttribute(jdk, Attributes.Name.IMPLEMENTATION_VERSION); if (version != null) { if (version.compareTo("1.4") >= 0) { return false; } if (version.startsWith("1.2") && SystemInfo.isWindows) { return true; } version += ".0"; if (version.startsWith("1.3.0") && SystemInfo.isWindows) { return true; } if ((version.startsWith("1.3.1_07") || version.startsWith("1.3.1_08")) && SystemInfo.isWindows) { return false; // fixes bug for these JDKs that it cannot start with -classic option } } return DebuggerSettings.getInstance().FORCE_CLASSIC_VM; } @SuppressWarnings({"HardCodedStringLiteral"}) public static RemoteConnection createDebugParameters( final JavaParameters parameters, final boolean debuggerInServerMode, int transport, final String debugPort, boolean checkValidity) throws ExecutionException { if (checkValidity) { checkTargetJPDAInstalled(parameters); } final boolean useSockets = transport == DebuggerSettings.SOCKET_TRANSPORT; String address = ""; if (StringUtil.isEmptyOrSpaces(debugPort)) { try { address = DebuggerUtils.getInstance().findAvailableDebugAddress(useSockets); } catch (ExecutionException e) { if (checkValidity) { throw e; } } } else { address = debugPort; } final TransportServiceWrapper transportService = TransportServiceWrapper.getTransportService(useSockets); final String debugAddress = debuggerInServerMode && useSockets ? "127.0.0.1:" + address : address; String debuggeeRunProperties = "transport=" + transportService.transportId() + ",address=" + debugAddress; if (debuggerInServerMode) { debuggeeRunProperties += ",suspend=y,server=n"; } else { debuggeeRunProperties += ",suspend=n,server=y"; } if (hasWhitespace(debuggeeRunProperties)) { debuggeeRunProperties = "\"" + debuggeeRunProperties + "\""; } final String _debuggeeRunProperties = debuggeeRunProperties; ApplicationManager.getApplication() .runReadAction( new Runnable() { @Override @SuppressWarnings({"HardCodedStringLiteral"}) public void run() { JavaSdkUtil.addRtJar(parameters.getClassPath()); final Sdk jdk = parameters.getJdk(); final boolean forceClassicVM = shouldForceClassicVM(jdk); final boolean forceNoJIT = shouldForceNoJIT(jdk); final String debugKey = System.getProperty(DEBUG_KEY_NAME, "-Xdebug"); final boolean needDebugKey = shouldAddXdebugKey(jdk) || !"-Xdebug".equals(debugKey) /*the key is non-standard*/; if (forceClassicVM || forceNoJIT || needDebugKey || !isJVMTIAvailable(jdk)) { parameters .getVMParametersList() .replaceOrPrepend("-Xrunjdwp:", "-Xrunjdwp:" + _debuggeeRunProperties); } else { // use newer JVMTI if available parameters.getVMParametersList().replaceOrPrepend("-Xrunjdwp:", ""); parameters .getVMParametersList() .replaceOrPrepend( "-agentlib:jdwp=", "-agentlib:jdwp=" + _debuggeeRunProperties); } if (forceNoJIT) { parameters .getVMParametersList() .replaceOrPrepend("-Djava.compiler=", "-Djava.compiler=NONE"); parameters.getVMParametersList().replaceOrPrepend("-Xnoagent", "-Xnoagent"); } if (needDebugKey) { parameters.getVMParametersList().replaceOrPrepend(debugKey, debugKey); } else { // deliberately skip outdated parameter because it can disable full-speed // debugging for some jdk builds // see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6272174 parameters.getVMParametersList().replaceOrPrepend("-Xdebug", ""); } parameters .getVMParametersList() .replaceOrPrepend("-classic", forceClassicVM ? "-classic" : ""); } }); return new RemoteConnection(useSockets, "127.0.0.1", address, debuggerInServerMode); } private static boolean shouldForceNoJIT(Sdk jdk) { if (DebuggerSettings.getInstance().DISABLE_JIT) { return true; } if (jdk != null) { final String version = JdkUtil.getJdkMainAttribute(jdk, Attributes.Name.IMPLEMENTATION_VERSION); if (version != null && (version.startsWith("1.2") || version.startsWith("1.3"))) { return true; } } return false; } private static boolean shouldAddXdebugKey(Sdk jdk) { if (jdk == null) { return true; // conservative choice } if (DebuggerSettings.getInstance().DISABLE_JIT) { return true; } // if (ApplicationManager.getApplication().isUnitTestMode()) { // need this in unit tests to avoid false alarms when comparing actual output with expected // output // return true; // } final String version = JdkUtil.getJdkMainAttribute(jdk, Attributes.Name.IMPLEMENTATION_VERSION); return version == null || // version.startsWith("1.5") || version.startsWith("1.4") || version.startsWith("1.3") || version.startsWith("1.2") || version.startsWith("1.1") || version.startsWith("1.0"); } private static boolean isJVMTIAvailable(Sdk jdk) { if (jdk == null) { return false; // conservative choice } final String version = JdkUtil.getJdkMainAttribute(jdk, Attributes.Name.IMPLEMENTATION_VERSION); if (version == null) { return false; } return !(version.startsWith("1.4") || version.startsWith("1.3") || version.startsWith("1.2") || version.startsWith("1.1") || version.startsWith("1.0")); } public static RemoteConnection createDebugParameters( final JavaParameters parameters, GenericDebuggerRunnerSettings settings, boolean checkValidity) throws ExecutionException { return createDebugParameters( parameters, settings.LOCAL, settings.getTransport(), settings.getDebugPort(), checkValidity); } private static class MyDebuggerStateManager extends DebuggerStateManager { private DebuggerSession myDebuggerSession; @Override public DebuggerContextImpl getContext() { return myDebuggerSession == null ? DebuggerContextImpl.EMPTY_CONTEXT : myDebuggerSession.getContextManager().getContext(); } @Override public void setState( final DebuggerContextImpl context, DebuggerSession.State state, DebuggerSession.Event event, String description) { ApplicationManager.getApplication().assertIsDispatchThread(); myDebuggerSession = context.getDebuggerSession(); if (myDebuggerSession != null) { myDebuggerSession.getContextManager().setState(context, state, event, description); } else { fireStateChanged(context, event); } } } private void dispose(DebuggerSession session) { ProcessHandler processHandler = session.getProcess().getProcessHandler(); synchronized (mySessions) { DebuggerSession removed = mySessions.remove(processHandler); LOG.assertTrue(removed != null); myDispatcher.getMulticaster().sessionRemoved(session); } } }
public class ExecutionManagerImpl extends ExecutionManager implements Disposable { public static final Key<Object> EXECUTION_SESSION_ID_KEY = Key.create("EXECUTION_SESSION_ID_KEY"); public static final Key<Boolean> EXECUTION_SKIP_RUN = Key.create("EXECUTION_SKIP_RUN"); private static final Logger LOG = Logger.getInstance(ExecutionManagerImpl.class); private static final ProcessHandler[] EMPTY_PROCESS_HANDLERS = new ProcessHandler[0]; private final Project myProject; private final Alarm myAwaitingTerminationAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD); private final Map<RunProfile, ExecutionEnvironment> myAwaitingRunProfiles = ContainerUtil.newHashMap(); private final List<Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor>> myRunningConfigurations = ContainerUtil.createLockFreeCopyOnWriteList(); private RunContentManagerImpl myContentManager; private volatile boolean myForceCompilationInTests; @SuppressWarnings("MethodOverridesStaticMethodOfSuperclass") @NotNull public static ExecutionManagerImpl getInstance(@NotNull Project project) { return (ExecutionManagerImpl) ServiceManager.getService(project, ExecutionManager.class); } protected ExecutionManagerImpl(@NotNull Project project) { myProject = project; } @NotNull private static ExecutionEnvironmentBuilder createEnvironmentBuilder( @NotNull Project project, @NotNull Executor executor, @Nullable RunnerAndConfigurationSettings configuration) { ExecutionEnvironmentBuilder builder = new ExecutionEnvironmentBuilder(project, executor); ProgramRunner runner = RunnerRegistry.getInstance() .getRunner( executor.getId(), configuration != null ? configuration.getConfiguration() : null); if (runner == null && configuration != null) { LOG.error("Cannot find runner for " + configuration.getName()); } else if (runner != null) { assert configuration != null; builder.runnerAndSettings(runner, configuration); } return builder; } public static boolean isProcessRunning(@Nullable RunContentDescriptor descriptor) { ProcessHandler processHandler = descriptor == null ? null : descriptor.getProcessHandler(); return processHandler != null && !processHandler.isProcessTerminated(); } private static void start(@NotNull ExecutionEnvironment environment) { RunnerAndConfigurationSettings settings = environment.getRunnerAndConfigurationSettings(); ProgramRunnerUtil.executeConfiguration( environment, settings != null && settings.isEditBeforeRun(), true); } private static boolean userApprovesStopForSameTypeConfigurations( Project project, String configName, int instancesCount) { RunManagerImpl runManager = RunManagerImpl.getInstanceImpl(project); final RunManagerConfig config = runManager.getConfig(); if (!config.isRestartRequiresConfirmation()) return true; DialogWrapper.DoNotAskOption option = new DialogWrapper.DoNotAskOption() { @Override public boolean isToBeShown() { return config.isRestartRequiresConfirmation(); } @Override public void setToBeShown(boolean value, int exitCode) { config.setRestartRequiresConfirmation(value); } @Override public boolean canBeHidden() { return true; } @Override public boolean shouldSaveOptionsOnCancel() { return false; } @NotNull @Override public String getDoNotShowMessage() { return CommonBundle.message("dialog.options.do.not.show"); } }; return Messages.showOkCancelDialog( project, ExecutionBundle.message( "rerun.singleton.confirmation.message", configName, instancesCount), ExecutionBundle.message("process.is.running.dialog.title", configName), ExecutionBundle.message("rerun.confirmation.button.text"), CommonBundle.message("button.cancel"), Messages.getQuestionIcon(), option) == Messages.OK; } private static boolean userApprovesStopForIncompatibleConfigurations( Project project, String configName, List<RunContentDescriptor> runningIncompatibleDescriptors) { RunManagerImpl runManager = RunManagerImpl.getInstanceImpl(project); final RunManagerConfig config = runManager.getConfig(); if (!config.isStopIncompatibleRequiresConfirmation()) return true; DialogWrapper.DoNotAskOption option = new DialogWrapper.DoNotAskOption() { @Override public boolean isToBeShown() { return config.isStopIncompatibleRequiresConfirmation(); } @Override public void setToBeShown(boolean value, int exitCode) { config.setStopIncompatibleRequiresConfirmation(value); } @Override public boolean canBeHidden() { return true; } @Override public boolean shouldSaveOptionsOnCancel() { return false; } @NotNull @Override public String getDoNotShowMessage() { return CommonBundle.message("dialog.options.do.not.show"); } }; final StringBuilder names = new StringBuilder(); for (final RunContentDescriptor descriptor : runningIncompatibleDescriptors) { String name = descriptor.getDisplayName(); if (names.length() > 0) { names.append(", "); } names.append( StringUtil.isEmpty(name) ? ExecutionBundle.message("run.configuration.no.name") : String.format("'%s'", name)); } //noinspection DialogTitleCapitalization return Messages.showOkCancelDialog( project, ExecutionBundle.message( "stop.incompatible.confirmation.message", configName, names.toString(), runningIncompatibleDescriptors.size()), ExecutionBundle.message( "incompatible.configuration.is.running.dialog.title", runningIncompatibleDescriptors.size()), ExecutionBundle.message("stop.incompatible.confirmation.button.text"), CommonBundle.message("button.cancel"), Messages.getQuestionIcon(), option) == Messages.OK; } private static void stop(@Nullable RunContentDescriptor descriptor) { ProcessHandler processHandler = descriptor != null ? descriptor.getProcessHandler() : null; if (processHandler == null) { return; } if (processHandler instanceof KillableProcess && processHandler.isProcessTerminating()) { ((KillableProcess) processHandler).killProcess(); return; } if (!processHandler.isProcessTerminated()) { if (processHandler.detachIsDefault()) { processHandler.detachProcess(); } else { processHandler.destroyProcess(); } } } @Override public void dispose() { for (Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor> trinity : myRunningConfigurations) { Disposer.dispose(trinity.first); } myRunningConfigurations.clear(); } @NotNull @Override public RunContentManager getContentManager() { if (myContentManager == null) { myContentManager = new RunContentManagerImpl(myProject, DockManager.getInstance(myProject)); Disposer.register(myProject, myContentManager); } return myContentManager; } @NotNull @Override public ProcessHandler[] getRunningProcesses() { if (myContentManager == null) return EMPTY_PROCESS_HANDLERS; List<ProcessHandler> handlers = null; for (RunContentDescriptor descriptor : getContentManager().getAllDescriptors()) { ProcessHandler processHandler = descriptor.getProcessHandler(); if (processHandler != null) { if (handlers == null) { handlers = new SmartList<>(); } handlers.add(processHandler); } } return handlers == null ? EMPTY_PROCESS_HANDLERS : handlers.toArray(new ProcessHandler[handlers.size()]); } @Override public void compileAndRun( @NotNull final Runnable startRunnable, @NotNull final ExecutionEnvironment environment, @Nullable final RunProfileState state, @Nullable final Runnable onCancelRunnable) { long id = environment.getExecutionId(); if (id == 0) { id = environment.assignNewExecutionId(); } RunProfile profile = environment.getRunProfile(); if (!(profile instanceof RunConfiguration)) { startRunnable.run(); return; } final RunConfiguration runConfiguration = (RunConfiguration) profile; final List<BeforeRunTask> beforeRunTasks = RunManagerEx.getInstanceEx(myProject).getBeforeRunTasks(runConfiguration); if (beforeRunTasks.isEmpty()) { startRunnable.run(); } else { DataContext context = environment.getDataContext(); final DataContext projectContext = context != null ? context : SimpleDataContext.getProjectContext(myProject); final long finalId = id; final Long executionSessionId = new Long(id); ApplicationManager.getApplication() .executeOnPooledThread( () -> { for (BeforeRunTask task : beforeRunTasks) { if (myProject.isDisposed()) { return; } @SuppressWarnings("unchecked") BeforeRunTaskProvider<BeforeRunTask> provider = BeforeRunTaskProvider.getProvider(myProject, task.getProviderId()); if (provider == null) { LOG.warn( "Cannot find BeforeRunTaskProvider for id='" + task.getProviderId() + "'"); continue; } ExecutionEnvironment taskEnvironment = new ExecutionEnvironmentBuilder(environment).contentToReuse(null).build(); taskEnvironment.setExecutionId(finalId); EXECUTION_SESSION_ID_KEY.set(taskEnvironment, executionSessionId); if (!provider.executeTask( projectContext, runConfiguration, taskEnvironment, task)) { if (onCancelRunnable != null) { SwingUtilities.invokeLater(onCancelRunnable); } return; } } doRun(environment, startRunnable); }); } } protected void doRun( @NotNull final ExecutionEnvironment environment, @NotNull final Runnable startRunnable) { Boolean allowSkipRun = environment.getUserData(EXECUTION_SKIP_RUN); if (allowSkipRun != null && allowSkipRun) { environment .getProject() .getMessageBus() .syncPublisher(EXECUTION_TOPIC) .processNotStarted(environment.getExecutor().getId(), environment); } else { // important! Do not use DumbService.smartInvokeLater here because it depends on modality // state // and execution of startRunnable could be skipped if modality state check fails //noinspection SSBasedInspection SwingUtilities.invokeLater( () -> { if (!myProject.isDisposed()) { if (!Registry.is("dumb.aware.run.configurations")) { DumbService.getInstance(myProject).runWhenSmart(startRunnable); } else { try { DumbService.getInstance(myProject).setAlternativeResolveEnabled(true); startRunnable.run(); } catch (IndexNotReadyException ignored) { ExecutionUtil.handleExecutionError( environment, new ExecutionException("cannot start while indexing is in progress.")); } finally { DumbService.getInstance(myProject).setAlternativeResolveEnabled(false); } } } }); } } @Override public void startRunProfile( @NotNull final RunProfileStarter starter, @NotNull final RunProfileState state, @NotNull final ExecutionEnvironment environment) { final Project project = environment.getProject(); RunContentDescriptor reuseContent = getContentManager().getReuseContent(environment); if (reuseContent != null) { reuseContent.setExecutionId(environment.getExecutionId()); environment.setContentToReuse(reuseContent); } final Executor executor = environment.getExecutor(); project .getMessageBus() .syncPublisher(EXECUTION_TOPIC) .processStartScheduled(executor.getId(), environment); Runnable startRunnable; startRunnable = () -> { if (project.isDisposed()) { return; } RunProfile profile = environment.getRunProfile(); boolean started = false; try { project .getMessageBus() .syncPublisher(EXECUTION_TOPIC) .processStarting(executor.getId(), environment); final RunContentDescriptor descriptor = starter.execute(state, environment); if (descriptor != null) { final Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor> trinity = Trinity.create( descriptor, environment.getRunnerAndConfigurationSettings(), executor); myRunningConfigurations.add(trinity); Disposer.register(descriptor, () -> myRunningConfigurations.remove(trinity)); getContentManager() .showRunContent(executor, descriptor, environment.getContentToReuse()); final ProcessHandler processHandler = descriptor.getProcessHandler(); if (processHandler != null) { if (!processHandler.isStartNotified()) { processHandler.startNotify(); } project .getMessageBus() .syncPublisher(EXECUTION_TOPIC) .processStarted(executor.getId(), environment, processHandler); started = true; ProcessExecutionListener listener = new ProcessExecutionListener( project, executor.getId(), environment, processHandler, descriptor); processHandler.addProcessListener(listener); // Since we cannot guarantee that the listener is added before process handled is // start notified, // we have to make sure the process termination events are delivered to the clients. // Here we check the current process state and manually deliver events, while // the ProcessExecutionListener guarantees each such event is only delivered once // either by this code, or by the ProcessHandler. boolean terminating = processHandler.isProcessTerminating(); boolean terminated = processHandler.isProcessTerminated(); if (terminating || terminated) { listener.processWillTerminate( new ProcessEvent(processHandler), false /*doesn't matter*/); if (terminated) { //noinspection ConstantConditions int exitCode = processHandler.getExitCode(); listener.processTerminated(new ProcessEvent(processHandler, exitCode)); } } } environment.setContentToReuse(descriptor); } } catch (ProcessCanceledException e) { LOG.info(e); } catch (ExecutionException e) { ExecutionUtil.handleExecutionError(project, executor.getToolWindowId(), profile, e); LOG.info(e); } finally { if (!started) { project .getMessageBus() .syncPublisher(EXECUTION_TOPIC) .processNotStarted(executor.getId(), environment); } } }; if (ApplicationManager.getApplication().isUnitTestMode() && !myForceCompilationInTests) { startRunnable.run(); } else { compileAndRun( () -> TransactionGuard.submitTransaction(project, startRunnable), environment, state, () -> { if (!project.isDisposed()) { project .getMessageBus() .syncPublisher(EXECUTION_TOPIC) .processNotStarted(executor.getId(), environment); } }); } } @Override public void restartRunProfile( @NotNull Project project, @NotNull Executor executor, @NotNull ExecutionTarget target, @Nullable RunnerAndConfigurationSettings configuration, @Nullable ProcessHandler processHandler) { ExecutionEnvironmentBuilder builder = createEnvironmentBuilder(project, executor, configuration); if (processHandler != null) { for (RunContentDescriptor descriptor : getContentManager().getAllDescriptors()) { if (descriptor.getProcessHandler() == processHandler) { builder.contentToReuse(descriptor); break; } } } restartRunProfile(builder.target(target).build()); } @Override public void restartRunProfile(@NotNull final ExecutionEnvironment environment) { RunnerAndConfigurationSettings configuration = environment.getRunnerAndConfigurationSettings(); List<RunContentDescriptor> runningIncompatible; if (configuration == null) { runningIncompatible = Collections.emptyList(); } else { runningIncompatible = getIncompatibleRunningDescriptors(configuration); } RunContentDescriptor contentToReuse = environment.getContentToReuse(); final List<RunContentDescriptor> runningOfTheSameType = new SmartList<>(); if (configuration != null && configuration.isSingleton()) { runningOfTheSameType.addAll(getRunningDescriptorsOfTheSameConfigType(configuration)); } else if (isProcessRunning(contentToReuse)) { runningOfTheSameType.add(contentToReuse); } List<RunContentDescriptor> runningToStop = ContainerUtil.concat(runningOfTheSameType, runningIncompatible); if (!runningToStop.isEmpty()) { if (configuration != null) { if (!runningOfTheSameType.isEmpty() && (runningOfTheSameType.size() > 1 || contentToReuse == null || runningOfTheSameType.get(0) != contentToReuse) && !userApprovesStopForSameTypeConfigurations( environment.getProject(), configuration.getName(), runningOfTheSameType.size())) { return; } if (!runningIncompatible.isEmpty() && !userApprovesStopForIncompatibleConfigurations( myProject, configuration.getName(), runningIncompatible)) { return; } } for (RunContentDescriptor descriptor : runningToStop) { stop(descriptor); } } if (myAwaitingRunProfiles.get(environment.getRunProfile()) == environment) { // defense from rerunning exactly the same ExecutionEnvironment return; } myAwaitingRunProfiles.put(environment.getRunProfile(), environment); awaitTermination( new Runnable() { @Override public void run() { if (myAwaitingRunProfiles.get(environment.getRunProfile()) != environment) { // a new rerun has been requested before starting this one, ignore this rerun return; } if ((DumbService.getInstance(myProject).isDumb() && !Registry.is("dumb.aware.run.configurations")) || ExecutorRegistry.getInstance().isStarting(environment)) { awaitTermination(this, 100); return; } for (RunContentDescriptor descriptor : runningOfTheSameType) { ProcessHandler processHandler = descriptor.getProcessHandler(); if (processHandler != null && !processHandler.isProcessTerminated()) { awaitTermination(this, 100); return; } } myAwaitingRunProfiles.remove(environment.getRunProfile()); start(environment); } }, 50); } private void awaitTermination(@NotNull Runnable request, long delayMillis) { if (ApplicationManager.getApplication().isUnitTestMode()) { ApplicationManager.getApplication().invokeLater(request, ModalityState.any()); } else { myAwaitingTerminationAlarm.addRequest(request, delayMillis); } } @TestOnly public void setForceCompilationInTests(boolean forceCompilationInTests) { myForceCompilationInTests = forceCompilationInTests; } @NotNull private List<RunContentDescriptor> getRunningDescriptorsOfTheSameConfigType( @NotNull final RunnerAndConfigurationSettings configurationAndSettings) { return getRunningDescriptors( runningConfigurationAndSettings -> configurationAndSettings == runningConfigurationAndSettings); } @NotNull private List<RunContentDescriptor> getIncompatibleRunningDescriptors( @NotNull RunnerAndConfigurationSettings configurationAndSettings) { final RunConfiguration configurationToCheckCompatibility = configurationAndSettings.getConfiguration(); return getRunningDescriptors( runningConfigurationAndSettings -> { RunConfiguration runningConfiguration = runningConfigurationAndSettings == null ? null : runningConfigurationAndSettings.getConfiguration(); if (runningConfiguration == null || !(runningConfiguration instanceof CompatibilityAwareRunProfile)) { return false; } return ((CompatibilityAwareRunProfile) runningConfiguration) .mustBeStoppedToRun(configurationToCheckCompatibility); }); } @NotNull public List<RunContentDescriptor> getRunningDescriptors( @NotNull Condition<RunnerAndConfigurationSettings> condition) { List<RunContentDescriptor> result = new SmartList<>(); for (Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor> trinity : myRunningConfigurations) { if (trinity.getSecond() != null && condition.value(trinity.getSecond())) { ProcessHandler processHandler = trinity.getFirst().getProcessHandler(); if (processHandler != null /*&& !processHandler.isProcessTerminating()*/ && !processHandler.isProcessTerminated()) { result.add(trinity.getFirst()); } } } return result; } @NotNull public Set<Executor> getExecutors(RunContentDescriptor descriptor) { Set<Executor> result = new HashSet<>(); for (Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor> trinity : myRunningConfigurations) { if (descriptor == trinity.first) result.add(trinity.third); } return result; } private static class ProcessExecutionListener extends ProcessAdapter { @NotNull private final Project myProject; @NotNull private final String myExecutorId; @NotNull private final ExecutionEnvironment myEnvironment; @NotNull private final ProcessHandler myProcessHandler; @NotNull private final RunContentDescriptor myDescriptor; @NotNull private final AtomicBoolean myWillTerminateNotified = new AtomicBoolean(); @NotNull private final AtomicBoolean myTerminateNotified = new AtomicBoolean(); public ProcessExecutionListener( @NotNull Project project, @NotNull String executorId, @NotNull ExecutionEnvironment environment, @NotNull ProcessHandler processHandler, @NotNull RunContentDescriptor descriptor) { myProject = project; myExecutorId = executorId; myEnvironment = environment; myProcessHandler = processHandler; myDescriptor = descriptor; } @Override public void processTerminated(ProcessEvent event) { if (myProject.isDisposed()) return; if (!myTerminateNotified.compareAndSet(false, true)) return; ApplicationManager.getApplication() .invokeLater( () -> { RunnerLayoutUi ui = myDescriptor.getRunnerLayoutUi(); if (ui != null && !ui.isDisposed()) { ui.updateActionsNow(); } }, ModalityState.any()); myProject .getMessageBus() .syncPublisher(EXECUTION_TOPIC) .processTerminated(myExecutorId, myEnvironment, myProcessHandler, event.getExitCode()); SaveAndSyncHandler saveAndSyncHandler = SaveAndSyncHandler.getInstance(); if (saveAndSyncHandler != null) { saveAndSyncHandler.scheduleRefresh(); } } @Override public void processWillTerminate(ProcessEvent event, boolean shouldNotBeUsed) { if (myProject.isDisposed()) return; if (!myWillTerminateNotified.compareAndSet(false, true)) return; myProject .getMessageBus() .syncPublisher(EXECUTION_TOPIC) .processTerminating(myExecutorId, myEnvironment, myProcessHandler); } } }
/** @author dsl */ public class VirtualFilePointerContainerImpl extends TraceableDisposable implements VirtualFilePointerContainer, Disposable { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.pointers.VirtualFilePointerContainer"); @NotNull private final List<VirtualFilePointer> myList = ContainerUtil.createLockFreeCopyOnWriteList(); @NotNull private final VirtualFilePointerManager myVirtualFilePointerManager; @NotNull private final Disposable myParent; private final VirtualFilePointerListener myListener; private volatile Trinity<String[], VirtualFile[], VirtualFile[]> myCachedThings; private volatile long myTimeStampOfCachedThings = -1; @NonNls public static final String URL_ATTR = "url"; private boolean myDisposed; private static final boolean TRACE_CREATION = LOG.isDebugEnabled() || ApplicationManager.getApplication().isUnitTestMode(); public VirtualFilePointerContainerImpl( @NotNull VirtualFilePointerManager manager, @NotNull Disposable parentDisposable, @Nullable VirtualFilePointerListener listener) { //noinspection HardCodedStringLiteral super( TRACE_CREATION && !ApplicationInfoImpl.isInPerformanceTest() ? new Throwable( "parent = '" + parentDisposable + "' (" + parentDisposable.getClass() + "); listener=" + listener) : null); myVirtualFilePointerManager = manager; myParent = parentDisposable; myListener = listener; } @Override public void readExternal(@NotNull final Element rootChild, @NotNull final String childElements) throws InvalidDataException { final List urls = rootChild.getChildren(childElements); for (Object url : urls) { Element pathElement = (Element) url; final String urlAttribute = pathElement.getAttributeValue(URL_ATTR); if (urlAttribute == null) throw new InvalidDataException("path element without url"); add(urlAttribute); } } @Override public void writeExternal( @NotNull final Element element, @NotNull final String childElementName) { for (VirtualFilePointer pointer : myList) { String url = pointer.getUrl(); final Element rootPathElement = new Element(childElementName); rootPathElement.setAttribute(URL_ATTR, url); element.addContent(rootPathElement); } } @Override public void moveUp(@NotNull String url) { int index = indexOf(url); if (index <= 0) return; dropCaches(); ContainerUtil.swapElements(myList, index - 1, index); } @Override public void moveDown(@NotNull String url) { int index = indexOf(url); if (index < 0 || index + 1 >= myList.size()) return; dropCaches(); ContainerUtil.swapElements(myList, index, index + 1); } private int indexOf(@NotNull final String url) { for (int i = 0; i < myList.size(); i++) { final VirtualFilePointer pointer = myList.get(i); if (url.equals(pointer.getUrl())) { return i; } } return -1; } @Override public void killAll() { myList.clear(); } @Override public void add(@NotNull VirtualFile file) { assert !myDisposed; dropCaches(); final VirtualFilePointer pointer = create(file); myList.add(pointer); } @Override public void add(@NotNull String url) { assert !myDisposed; dropCaches(); final VirtualFilePointer pointer = create(url); myList.add(pointer); } @Override public void remove(@NotNull VirtualFilePointer pointer) { assert !myDisposed; dropCaches(); final boolean result = myList.remove(pointer); LOG.assertTrue(result); } @Override @NotNull public List<VirtualFilePointer> getList() { assert !myDisposed; return Collections.unmodifiableList(myList); } @Override public void addAll(@NotNull VirtualFilePointerContainer that) { assert !myDisposed; dropCaches(); List<VirtualFilePointer> thatList = ((VirtualFilePointerContainerImpl) that).myList; for (final VirtualFilePointer pointer : thatList) { myList.add(duplicate(pointer)); } } private void dropCaches() { myTimeStampOfCachedThings = -1; // make it never equal to myVirtualFilePointerManager.getModificationCount() myCachedThings = EMPTY; } @Override @NotNull public String[] getUrls() { return getOrCache().first; } @NotNull private Trinity<String[], VirtualFile[], VirtualFile[]> getOrCache() { assert !myDisposed; long timeStamp = myTimeStampOfCachedThings; Trinity<String[], VirtualFile[], VirtualFile[]> cached = myCachedThings; return timeStamp == myVirtualFilePointerManager.getModificationCount() ? cached : cacheThings(); } private static final Trinity<String[], VirtualFile[], VirtualFile[]> EMPTY = Trinity.create( ArrayUtil.EMPTY_STRING_ARRAY, VirtualFile.EMPTY_ARRAY, VirtualFile.EMPTY_ARRAY); @NotNull private Trinity<String[], VirtualFile[], VirtualFile[]> cacheThings() { Trinity<String[], VirtualFile[], VirtualFile[]> result; if (myList.isEmpty()) { result = EMPTY; } else { VirtualFilePointer[] vf = myList.toArray(new VirtualFilePointer[myList.size()]); List<VirtualFile> cachedFiles = new ArrayList<VirtualFile>(vf.length); List<String> cachedUrls = new ArrayList<String>(vf.length); List<VirtualFile> cachedDirectories = new ArrayList<VirtualFile>(vf.length / 3); boolean allFilesAreDirs = true; for (VirtualFilePointer v : vf) { VirtualFile file = v.getFile(); String url = v.getUrl(); cachedUrls.add(url); if (file != null) { cachedFiles.add(file); if (file.isDirectory()) { cachedDirectories.add(file); } else { allFilesAreDirs = false; } } } VirtualFile[] directories = VfsUtilCore.toVirtualFileArray(cachedDirectories); VirtualFile[] files = allFilesAreDirs ? directories : VfsUtilCore.toVirtualFileArray(cachedFiles); String[] urlsArray = ArrayUtil.toStringArray(cachedUrls); result = Trinity.create(urlsArray, files, directories); } myCachedThings = result; myTimeStampOfCachedThings = myVirtualFilePointerManager.getModificationCount(); return result; } @Override @NotNull public VirtualFile[] getFiles() { return getOrCache().second; } @Override @NotNull public VirtualFile[] getDirectories() { return getOrCache().third; } @Override @Nullable public VirtualFilePointer findByUrl(@NotNull String url) { assert !myDisposed; for (VirtualFilePointer pointer : myList) { if (url.equals(pointer.getUrl())) return pointer; } return null; } @Override public void clear() { dropCaches(); killAll(); } @Override public int size() { return myList.size(); } public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof VirtualFilePointerContainerImpl)) return false; final VirtualFilePointerContainerImpl virtualFilePointerContainer = (VirtualFilePointerContainerImpl) o; return myList.equals(virtualFilePointerContainer.myList); } public int hashCode() { return myList.hashCode(); } protected VirtualFilePointer create(@NotNull VirtualFile file) { return myVirtualFilePointerManager.create(file, myParent, myListener); } protected VirtualFilePointer create(@NotNull String url) { return myVirtualFilePointerManager.create(url, myParent, myListener); } protected VirtualFilePointer duplicate(@NotNull VirtualFilePointer virtualFilePointer) { return myVirtualFilePointerManager.duplicate(virtualFilePointer, myParent, myListener); } @NotNull @NonNls @Override public String toString() { return "VFPContainer: " + myList /*+"; parent:"+myParent*/; } @Override @NotNull public VirtualFilePointerContainer clone(@NotNull Disposable parent) { return clone(parent, null); } @Override @NotNull public VirtualFilePointerContainer clone( @NotNull Disposable parent, @Nullable VirtualFilePointerListener listener) { assert !myDisposed; VirtualFilePointerContainer clone = myVirtualFilePointerManager.createContainer(parent, listener); for (VirtualFilePointer pointer : myList) { clone.add(pointer.getUrl()); } return clone; } @Override public void dispose() { assert !myDisposed; myDisposed = true; kill(null); } }
/** @author ven */ public class CoverageDataManagerImpl extends CoverageDataManager { private static final String REPLACE_ACTIVE_SUITES = "&Replace active suites"; private static final String ADD_TO_ACTIVE_SUITES = "&Add to active suites"; private static final String DO_NOT_APPLY_COLLECTED_COVERAGE = "Do not apply &collected coverage"; private final List<CoverageSuiteListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private static final Logger LOG = Logger.getInstance("#" + CoverageDataManagerImpl.class.getName()); @NonNls private static final String SUITE = "SUITE"; private final Project myProject; private final Set<CoverageSuite> myCoverageSuites = new HashSet<CoverageSuite>(); private boolean myIsProjectClosing = false; private final Object myLock = new Object(); private boolean mySubCoverageIsActive; public CoverageSuitesBundle getCurrentSuitesBundle() { return myCurrentSuitesBundle; } private CoverageSuitesBundle myCurrentSuitesBundle; private final Object ANNOTATORS_LOCK = new Object(); private final Map<Editor, SrcFileAnnotator> myAnnotators = new HashMap<Editor, SrcFileAnnotator>(); public CoverageDataManagerImpl(final Project project) { myProject = project; EditorColorsManager.getInstance() .addEditorColorsListener( new EditorColorsAdapter() { @Override public void globalSchemeChange(EditorColorsScheme scheme) { chooseSuitesBundle(myCurrentSuitesBundle); } }, project); addSuiteListener(new CoverageViewSuiteListener(this, myProject), myProject); } @NotNull @NonNls public String getComponentName() { return "CoverageDataManager"; } public void initComponent() {} public void disposeComponent() {} public void readExternal(Element element) throws InvalidDataException { //noinspection unchecked for (Element suiteElement : element.getChildren(SUITE)) { final CoverageRunner coverageRunner = BaseCoverageSuite.readRunnerAttribute(suiteElement); // skip unknown runners if (coverageRunner == null) { // collect gc final CoverageFileProvider fileProvider = BaseCoverageSuite.readDataFileProviderAttribute(suiteElement); if (fileProvider.isValid()) { // deleteCachedCoverage(fileProvider.getCoverageDataFilePath()); } continue; } CoverageSuite suite = null; for (CoverageEngine engine : CoverageEngine.EP_NAME.getExtensions()) { if (coverageRunner.acceptsCoverageEngine(engine)) { suite = engine.createEmptyCoverageSuite(coverageRunner); if (suite != null) { break; } } } if (suite != null) { try { suite.readExternal(suiteElement); myCoverageSuites.add(suite); } catch (NumberFormatException e) { // try next suite } } } } public void writeExternal(final Element element) throws WriteExternalException { for (CoverageSuite coverageSuite : myCoverageSuites) { final Element suiteElement = new Element(SUITE); element.addContent(suiteElement); coverageSuite.writeExternal(suiteElement); } } public CoverageSuite addCoverageSuite( final String name, final CoverageFileProvider fileProvider, final String[] filters, final long lastCoverageTimeStamp, @Nullable final String suiteToMergeWith, final CoverageRunner coverageRunner, final boolean collectLineInfo, final boolean tracingEnabled) { final CoverageSuite suite = createCoverageSuite( coverageRunner, name, fileProvider, filters, lastCoverageTimeStamp, suiteToMergeWith, collectLineInfo, tracingEnabled); if (suiteToMergeWith == null || !name.equals(suiteToMergeWith)) { removeCoverageSuite(suite); } myCoverageSuites.remove(suite); // remove previous instance myCoverageSuites.add(suite); // add new instance return suite; } @Override public CoverageSuite addExternalCoverageSuite( String selectedFileName, long timeStamp, CoverageRunner coverageRunner, CoverageFileProvider fileProvider) { final CoverageSuite suite = createCoverageSuite( coverageRunner, selectedFileName, fileProvider, ArrayUtil.EMPTY_STRING_ARRAY, timeStamp, null, false, false); myCoverageSuites.add(suite); return suite; } @Override public CoverageSuite addCoverageSuite(final CoverageEnabledConfiguration config) { final String name = config.getName() + " Coverage Results"; final String covFilePath = config.getCoverageFilePath(); assert covFilePath != null; // Shouldn't be null here! final CoverageRunner coverageRunner = config.getCoverageRunner(); LOG.assertTrue(coverageRunner != null, "Coverage runner id = " + config.getRunnerId()); final DefaultCoverageFileProvider fileProvider = new DefaultCoverageFileProvider(new File(covFilePath)); final CoverageSuite suite = createCoverageSuite(config, name, coverageRunner, fileProvider); // remove previous instance removeCoverageSuite(suite); // add new instance myCoverageSuites.add(suite); return suite; } public void removeCoverageSuite(final CoverageSuite suite) { final String fileName = suite.getCoverageDataFileName(); boolean deleteTraces = suite.isTracingEnabled(); if (!FileUtil.isAncestor(PathManager.getSystemPath(), fileName, false)) { String message = "Would you like to delete file \'" + fileName + "\' "; if (deleteTraces) { message += "and traces directory \'" + FileUtil.getNameWithoutExtension(new File(fileName)) + "\' "; } message += "on disk?"; if (Messages.showYesNoDialog( myProject, message, CommonBundle.getWarningTitle(), Messages.getWarningIcon()) == Messages.YES) { deleteCachedCoverage(fileName, deleteTraces); } } else { deleteCachedCoverage(fileName, deleteTraces); } myCoverageSuites.remove(suite); if (myCurrentSuitesBundle != null && myCurrentSuitesBundle.contains(suite)) { CoverageSuite[] suites = myCurrentSuitesBundle.getSuites(); suites = ArrayUtil.remove(suites, suite); chooseSuitesBundle(suites.length > 0 ? new CoverageSuitesBundle(suites) : null); } } private void deleteCachedCoverage(String coverageDataFileName, boolean deleteTraces) { FileUtil.delete(new File(coverageDataFileName)); if (deleteTraces) { FileUtil.delete(getTracesDirectory(coverageDataFileName)); } } public CoverageSuite[] getSuites() { return myCoverageSuites.toArray(new CoverageSuite[myCoverageSuites.size()]); } public void chooseSuitesBundle(final CoverageSuitesBundle suite) { if (myCurrentSuitesBundle == suite && suite == null) { return; } LOG.assertTrue(!myProject.isDefault()); fireBeforeSuiteChosen(); mySubCoverageIsActive = false; if (myCurrentSuitesBundle != null) { myCurrentSuitesBundle .getCoverageEngine() .getCoverageAnnotator(myProject) .onSuiteChosen(suite); } myCurrentSuitesBundle = suite; disposeAnnotators(); if (suite == null) { triggerPresentationUpdate(); return; } for (CoverageSuite coverageSuite : myCurrentSuitesBundle.getSuites()) { final boolean suiteFileExists = coverageSuite.getCoverageDataFileProvider().ensureFileExists(); if (!suiteFileExists) { chooseSuitesBundle(null); return; } } renewCoverageData(suite); fireAfterSuiteChosen(); } public void coverageGathered(@NotNull final CoverageSuite suite) { ApplicationManager.getApplication() .invokeLater( new Runnable() { public void run() { if (myProject.isDisposed()) return; if (myCurrentSuitesBundle != null) { final String message = CodeInsightBundle.message( "display.coverage.prompt", suite.getPresentableName()); final CoverageOptionsProvider coverageOptionsProvider = CoverageOptionsProvider.getInstance(myProject); final DialogWrapper.DoNotAskOption doNotAskOption = new DialogWrapper.DoNotAskOption() { @Override public boolean isToBeShown() { return coverageOptionsProvider.getOptionToReplace() == 3; } @Override public void setToBeShown(boolean value, int exitCode) { coverageOptionsProvider.setOptionsToReplace(value ? 3 : exitCode); } @Override public boolean canBeHidden() { return true; } @Override public boolean shouldSaveOptionsOnCancel() { return true; } @NotNull @Override public String getDoNotShowMessage() { return CommonBundle.message("dialog.options.do.not.show"); } }; final String[] options = myCurrentSuitesBundle.getCoverageEngine() == suite.getCoverageEngine() ? new String[] { REPLACE_ACTIVE_SUITES, ADD_TO_ACTIVE_SUITES, DO_NOT_APPLY_COLLECTED_COVERAGE } : new String[] {REPLACE_ACTIVE_SUITES, DO_NOT_APPLY_COLLECTED_COVERAGE}; final int answer = doNotAskOption.isToBeShown() ? Messages.showDialog( message, CodeInsightBundle.message("code.coverage"), options, 1, Messages.getQuestionIcon(), doNotAskOption) : coverageOptionsProvider.getOptionToReplace(); if (answer == DialogWrapper.OK_EXIT_CODE) { chooseSuitesBundle(new CoverageSuitesBundle(suite)); } else if (answer == 1) { chooseSuitesBundle( new CoverageSuitesBundle( ArrayUtil.append(myCurrentSuitesBundle.getSuites(), suite))); } } else { chooseSuitesBundle(new CoverageSuitesBundle(suite)); } } }); } public void triggerPresentationUpdate() { renewInformationInEditors(); UIUtil.invokeLaterIfNeeded( new Runnable() { public void run() { if (myProject.isDisposed()) return; ProjectView.getInstance(myProject).refresh(); CoverageViewManager.getInstance(myProject).setReady(true); } }); } public void attachToProcess( @NotNull final ProcessHandler handler, @NotNull final RunConfigurationBase configuration, final RunnerSettings runnerSettings) { handler.addProcessListener( new ProcessAdapter() { public void processTerminated(final ProcessEvent event) { processGatheredCoverage(configuration, runnerSettings); } }); } @Override public void processGatheredCoverage( @NotNull RunConfigurationBase configuration, RunnerSettings runnerSettings) { if (runnerSettings instanceof CoverageRunnerData) { processGatheredCoverage(configuration); } } public static void processGatheredCoverage(RunConfigurationBase configuration) { final Project project = configuration.getProject(); if (project.isDisposed()) return; final CoverageDataManager coverageDataManager = CoverageDataManager.getInstance(project); final CoverageEnabledConfiguration coverageEnabledConfiguration = CoverageEnabledConfiguration.getOrCreate(configuration); //noinspection ConstantConditions final CoverageSuite coverageSuite = coverageEnabledConfiguration.getCurrentCoverageSuite(); if (coverageSuite != null) { ((BaseCoverageSuite) coverageSuite).setConfiguration(configuration); coverageDataManager.coverageGathered(coverageSuite); } } protected void renewCoverageData(@NotNull final CoverageSuitesBundle suite) { if (myCurrentSuitesBundle != null) { myCurrentSuitesBundle .getCoverageEngine() .getCoverageAnnotator(myProject) .renewCoverageData(suite, this); } } private void renewInformationInEditors() { final FileEditorManager fileEditorManager = FileEditorManager.getInstance(myProject); final VirtualFile[] openFiles = fileEditorManager.getOpenFiles(); for (VirtualFile openFile : openFiles) { final FileEditor[] allEditors = fileEditorManager.getAllEditors(openFile); applyInformationToEditor(allEditors, openFile); } } private void applyInformationToEditor(FileEditor[] editors, final VirtualFile file) { final PsiFile psiFile = doInReadActionIfProjectOpen( new Computable<PsiFile>() { @Nullable @Override public PsiFile compute() { return PsiManager.getInstance(myProject).findFile(file); } }); if (psiFile != null && myCurrentSuitesBundle != null && psiFile.isPhysical()) { final CoverageEngine engine = myCurrentSuitesBundle.getCoverageEngine(); if (!engine.coverageEditorHighlightingApplicableTo(psiFile)) { return; } for (FileEditor editor : editors) { if (editor instanceof TextEditor) { final Editor textEditor = ((TextEditor) editor).getEditor(); SrcFileAnnotator annotator; synchronized (ANNOTATORS_LOCK) { annotator = myAnnotators.remove(textEditor); } if (annotator != null) { Disposer.dispose(annotator); } break; } } for (FileEditor editor : editors) { if (editor instanceof TextEditor) { final Editor textEditor = ((TextEditor) editor).getEditor(); SrcFileAnnotator annotator = getAnnotator(textEditor); if (annotator == null) { annotator = new SrcFileAnnotator(psiFile, textEditor); synchronized (ANNOTATORS_LOCK) { myAnnotators.put(textEditor, annotator); } } if (myCurrentSuitesBundle != null && engine.acceptedByFilters(psiFile, myCurrentSuitesBundle)) { annotator.showCoverageInformation(myCurrentSuitesBundle); } } } } } public void projectOpened() { EditorFactory.getInstance() .addEditorFactoryListener(new CoverageEditorFactoryListener(), myProject); ProjectManagerAdapter projectManagerListener = new ProjectManagerAdapter() { public void projectClosing(Project project) { synchronized (myLock) { myIsProjectClosing = true; } } }; ProjectManager.getInstance().addProjectManagerListener(myProject, projectManagerListener); } public void projectClosed() {} public <T> T doInReadActionIfProjectOpen(Computable<T> computation) { synchronized (myLock) { if (myIsProjectClosing) return null; } return ApplicationManager.getApplication().runReadAction(computation); } public void selectSubCoverage( @NotNull final CoverageSuitesBundle suite, final List<String> testNames) { suite.restoreCoverageData(); final ProjectData data = suite.getCoverageData(); if (data == null) return; mySubCoverageIsActive = true; final Map<String, Set<Integer>> executionTrace = new HashMap<String, Set<Integer>>(); for (CoverageSuite coverageSuite : suite.getSuites()) { final String fileName = coverageSuite.getCoverageDataFileName(); final File tracesDir = getTracesDirectory(fileName); for (String testName : testNames) { final File file = new File(tracesDir, FileUtil.sanitizeFileName(testName) + ".tr"); if (file.exists()) { DataInputStream in = null; try { in = new DataInputStream(new FileInputStream(file)); int traceSize = in.readInt(); for (int i = 0; i < traceSize; i++) { final String className = in.readUTF(); final int linesSize = in.readInt(); Set<Integer> lines = executionTrace.get(className); if (lines == null) { lines = new HashSet<Integer>(); executionTrace.put(className, lines); } for (int l = 0; l < linesSize; l++) { lines.add(in.readInt()); } } } catch (Exception e) { LOG.error(e); } finally { try { in.close(); } catch (IOException e) { LOG.error(e); } } } } } final ProjectData projectData = new ProjectData(); for (String className : executionTrace.keySet()) { ClassData loadedClassData = projectData.getClassData(className); if (loadedClassData == null) { loadedClassData = projectData.getOrCreateClassData(className); } final Set<Integer> lineNumbers = executionTrace.get(className); final ClassData oldData = data.getClassData(className); LOG.assertTrue(oldData != null, "missed className: \"" + className + "\""); final Object[] oldLines = oldData.getLines(); LOG.assertTrue(oldLines != null); int maxNumber = oldLines.length; for (Integer lineNumber : lineNumbers) { if (lineNumber >= maxNumber) { maxNumber = lineNumber + 1; } } final LineData[] lines = new LineData[maxNumber]; for (Integer line : lineNumbers) { final int lineIdx = line.intValue() - 1; String methodSig = null; if (lineIdx < oldData.getLines().length) { final LineData oldLineData = oldData.getLineData(lineIdx); if (oldLineData != null) { methodSig = oldLineData.getMethodSignature(); } } final LineData lineData = new LineData(lineIdx, methodSig); if (methodSig != null) { loadedClassData.registerMethodSignature(lineData); } lineData.setStatus(LineCoverage.FULL); lines[lineIdx] = lineData; } loadedClassData.setLines(lines); } suite.setCoverageData(projectData); renewCoverageData(suite); } private File getTracesDirectory(final String fileName) { return new File( new File(fileName).getParentFile(), FileUtil.getNameWithoutExtension(new File(fileName))); } public void restoreMergedCoverage(@NotNull final CoverageSuitesBundle suite) { mySubCoverageIsActive = false; suite.restoreCoverageData(); renewCoverageData(suite); } @Override public void addSuiteListener(final CoverageSuiteListener listener, Disposable parentDisposable) { myListeners.add(listener); Disposer.register( parentDisposable, new Disposable() { public void dispose() { myListeners.remove(listener); } }); } public void fireBeforeSuiteChosen() { for (CoverageSuiteListener listener : myListeners) { listener.beforeSuiteChosen(); } } public void fireAfterSuiteChosen() { for (CoverageSuiteListener listener : myListeners) { listener.afterSuiteChosen(); } } public boolean isSubCoverageActive() { return mySubCoverageIsActive; } @Nullable public SrcFileAnnotator getAnnotator(Editor editor) { synchronized (ANNOTATORS_LOCK) { return myAnnotators.get(editor); } } public void disposeAnnotators() { synchronized (ANNOTATORS_LOCK) { for (SrcFileAnnotator annotator : myAnnotators.values()) { if (annotator != null) { Disposer.dispose(annotator); } } myAnnotators.clear(); } } @NotNull private CoverageSuite createCoverageSuite( final CoverageEnabledConfiguration config, final String name, final CoverageRunner coverageRunner, final DefaultCoverageFileProvider fileProvider) { CoverageSuite suite = null; for (CoverageEngine engine : CoverageEngine.EP_NAME.getExtensions()) { if (coverageRunner.acceptsCoverageEngine(engine) && engine.isApplicableTo(config.getConfiguration())) { suite = engine.createCoverageSuite(coverageRunner, name, fileProvider, config); if (suite != null) { break; } } } LOG.assertTrue( suite != null, "Cannot create coverage suite for runner: " + coverageRunner.getPresentableName()); return suite; } @NotNull private CoverageSuite createCoverageSuite( final CoverageRunner coverageRunner, final String name, final CoverageFileProvider fileProvider, final String[] filters, final long lastCoverageTimeStamp, final String suiteToMergeWith, final boolean collectLineInfo, final boolean tracingEnabled) { CoverageSuite suite = null; for (CoverageEngine engine : CoverageEngine.EP_NAME.getExtensions()) { if (coverageRunner.acceptsCoverageEngine(engine)) { suite = engine.createCoverageSuite( coverageRunner, name, fileProvider, filters, lastCoverageTimeStamp, suiteToMergeWith, collectLineInfo, tracingEnabled, false, myProject); if (suite != null) { break; } } } LOG.assertTrue( suite != null, "Cannot create coverage suite for runner: " + coverageRunner.getPresentableName()); return suite; } private class CoverageEditorFactoryListener implements EditorFactoryListener { private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.OWN_THREAD, myProject); private final Map<Editor, Runnable> myCurrentEditors = new HashMap<Editor, Runnable>(); public void editorCreated(@NotNull EditorFactoryEvent event) { synchronized (myLock) { if (myIsProjectClosing) return; } final Editor editor = event.getEditor(); if (editor.getProject() != myProject) return; final PsiFile psiFile = ApplicationManager.getApplication() .runReadAction( new Computable<PsiFile>() { @Nullable @Override public PsiFile compute() { if (myProject.isDisposed()) return null; final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); final Document document = editor.getDocument(); return documentManager.getPsiFile(document); } }); if (psiFile != null && myCurrentSuitesBundle != null && psiFile.isPhysical()) { final CoverageEngine engine = myCurrentSuitesBundle.getCoverageEngine(); if (!engine.coverageEditorHighlightingApplicableTo(psiFile)) { return; } SrcFileAnnotator annotator = getAnnotator(editor); if (annotator == null) { annotator = new SrcFileAnnotator(psiFile, editor); } final SrcFileAnnotator finalAnnotator = annotator; synchronized (ANNOTATORS_LOCK) { myAnnotators.put(editor, finalAnnotator); } final Runnable request = new Runnable() { @Override public void run() { if (myProject.isDisposed()) return; if (myCurrentSuitesBundle != null) { if (engine.acceptedByFilters(psiFile, myCurrentSuitesBundle)) { finalAnnotator.showCoverageInformation(myCurrentSuitesBundle); } } } }; myCurrentEditors.put(editor, request); myAlarm.addRequest(request, 100); } } public void editorReleased(@NotNull EditorFactoryEvent event) { final Editor editor = event.getEditor(); if (editor.getProject() != myProject) return; try { final SrcFileAnnotator fileAnnotator; synchronized (ANNOTATORS_LOCK) { fileAnnotator = myAnnotators.remove(editor); } if (fileAnnotator != null) { Disposer.dispose(fileAnnotator); } } finally { final Runnable request = myCurrentEditors.remove(editor); if (request != null) { myAlarm.cancelRequest(request); } } } } }
/** @author Konstantin Kolosovsky. */ public class TerminalProcessHandler extends SvnProcessHandler { private final List<InteractiveCommandListener> myInteractiveListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private final CapturingProcessAdapter terminalOutputCapturer = new CapturingProcessAdapter(); private final StringBuilder outputLine = new StringBuilder(); private final StringBuilder errorLine = new StringBuilder(); public TerminalProcessHandler(@NotNull Process process, boolean forceUtf8, boolean forceBinary) { super(process, forceUtf8, forceBinary); } public void addInteractiveListener(@NotNull InteractiveCommandListener listener) { myInteractiveListeners.add(listener); } @Override protected boolean processHasSeparateErrorStream() { return false; } @Override protected void destroyProcessImpl() { final Process process = getProcess(); process.destroy(); } @Override protected boolean useNonBlockingRead() { return false; } @Override public void notifyTextAvailable(String text, Key outputType) { terminalOutputCapturer.onTextAvailable(new ProcessEvent(this, text), outputType); text = filterText(text); if (!StringUtil.isEmpty(text)) { StringBuilder lastLine = getLastLineFor(outputType); String currentLine = lastLine.append(text).toString(); lastLine.setLength(0); currentLine = filterCombinedText(currentLine); // check if current line presents some interactive output boolean handled = handlePrompt(currentLine, outputType); if (!handled) { notify(currentLine, outputType, lastLine); } } } protected boolean handlePrompt(String text, Key outputType) { // if process has separate output and error streams => try to handle prompts only from error // stream output boolean shouldHandleWithListeners = !processHasSeparateErrorStream() || ProcessOutputTypes.STDERR.equals(outputType); return shouldHandleWithListeners && handlePromptWithListeners(text, outputType); } private boolean handlePromptWithListeners(String text, Key outputType) { boolean result = false; for (InteractiveCommandListener listener : myInteractiveListeners) { result |= listener.handlePrompt(text, outputType); } return result; } @NotNull protected String filterCombinedText(@NotNull String currentLine) { return currentLine; } @NotNull protected String filterText(@NotNull String text) { return text; } private void notify( @NotNull String text, @NotNull Key outputType, @NotNull StringBuilder lastLine) { // text is not more than one line - either one line or part of the line if (StringUtil.endsWith(text, "\n")) { // we have full line - notify listeners super.notifyTextAvailable(text, resolveOutputType(text, outputType)); } else { // save line part to lastLine lastLine.append(text); } } @NotNull protected Key resolveOutputType(@NotNull String line, @NotNull Key outputType) { Key result = outputType; if (!ProcessOutputTypes.SYSTEM.equals(outputType)) { Matcher errorMatcher = SvnUtil.ERROR_PATTERN.matcher(line); Matcher warningMatcher = SvnUtil.WARNING_PATTERN.matcher(line); result = errorMatcher.find() || warningMatcher.find() ? ProcessOutputTypes.STDERR : ProcessOutputTypes.STDOUT; } return result; } @NotNull private StringBuilder getLastLineFor(Key outputType) { if (ProcessOutputTypes.STDERR.equals(outputType)) { return errorLine; } else if (ProcessOutputTypes.STDOUT.equals(outputType)) { return outputLine; } else { throw new IllegalArgumentException("Unknown process output type " + outputType); } } public String getTerminalOutput() { return terminalOutputCapturer.getOutput().getStdout(); } }
class DocumentFoldingInfo implements JDOMExternalizable, CodeFoldingState { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.folding.impl.DocumentFoldingInfo"); private static final Key<FoldingInfo> FOLDING_INFO_KEY = Key.create("FOLDING_INFO"); @NotNull private final Project myProject; private final VirtualFile myFile; private static class SerializedPsiElement { private final String mySerializedElement; private final FoldingInfo myFoldingInfo; public SerializedPsiElement(@NotNull String serialized, @NotNull FoldingInfo foldingInfo) { mySerializedElement = serialized; myFoldingInfo = foldingInfo; } } @NotNull private final List<SmartPsiElementPointer<PsiElement>> myPsiElements = ContainerUtil.createLockFreeCopyOnWriteList(); @NotNull private final List<SerializedPsiElement> mySerializedElements = ContainerUtil.createLockFreeCopyOnWriteList(); @NotNull private final List<RangeMarker> myRangeMarkers = ContainerUtil.createLockFreeCopyOnWriteList(); private static final String DEFAULT_PLACEHOLDER = "..."; @NonNls private static final String ELEMENT_TAG = "element"; @NonNls private static final String SIGNATURE_ATT = "signature"; @NonNls private static final String EXPANDED_ATT = "expanded"; @NonNls private static final String MARKER_TAG = "marker"; @NonNls private static final String DATE_ATT = "date"; @NonNls private static final String PLACEHOLDER_ATT = "placeholder"; DocumentFoldingInfo(@NotNull Project project, @NotNull Document document) { myProject = project; myFile = FileDocumentManager.getInstance().getFile(document); } void loadFromEditor(@NotNull Editor editor) { assertDispatchThread(); LOG.assertTrue(!editor.isDisposed()); clear(); PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); documentManager.commitDocument(editor.getDocument()); PsiFile file = documentManager.getPsiFile(editor.getDocument()); SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(myProject); EditorFoldingInfo info = EditorFoldingInfo.get(editor); FoldRegion[] foldRegions = editor.getFoldingModel().getAllFoldRegions(); for (FoldRegion region : foldRegions) { if (!region.isValid()) continue; PsiElement element = info.getPsiElement(region); boolean expanded = region.isExpanded(); boolean collapseByDefault = element != null && FoldingPolicy.isCollapseByDefault(element) && !FoldingUtil.caretInsideRange(editor, TextRange.create(region)); if (collapseByDefault == expanded || element == null) { FoldingInfo fi = new FoldingInfo(region.getPlaceholderText(), expanded); if (element != null) { myPsiElements.add(smartPointerManager.createSmartPsiElementPointer(element, file)); element.putUserData(FOLDING_INFO_KEY, fi); } else if (region.isValid()) { myRangeMarkers.add(region); region.putUserData(FOLDING_INFO_KEY, fi); } } } } private static void assertDispatchThread() { ApplicationManagerEx.getApplicationEx().assertIsDispatchThread(); } @Override public void setToEditor(@NotNull final Editor editor) { assertDispatchThread(); final PsiManager psiManager = PsiManager.getInstance(myProject); if (psiManager.isDisposed()) return; if (!myFile.isValid()) return; final PsiFile psiFile = psiManager.findFile(myFile); if (psiFile == null) return; if (!mySerializedElements.isEmpty()) { // Restore postponed state assert myPsiElements.isEmpty() : "Sequential deserialization"; for (SerializedPsiElement entry : mySerializedElements) { PsiElement restoredElement = FoldingPolicy.restoreBySignature(psiFile, entry.mySerializedElement); if (restoredElement != null && restoredElement.isValid()) { myPsiElements.add( SmartPointerManager.getInstance(myProject) .createSmartPsiElementPointer(restoredElement)); restoredElement.putUserData(FOLDING_INFO_KEY, entry.myFoldingInfo); } } mySerializedElements.clear(); } Map<PsiElement, FoldingDescriptor> ranges = null; for (SmartPsiElementPointer<PsiElement> ptr : myPsiElements) { PsiElement element = ptr.getElement(); if (element == null || !element.isValid()) { continue; } if (ranges == null) { ranges = buildRanges(editor, psiFile); } FoldingDescriptor descriptor = ranges.get(element); if (descriptor == null) { continue; } TextRange range = descriptor.getRange(); FoldRegion region = FoldingUtil.findFoldRegion(editor, range.getStartOffset(), range.getEndOffset()); if (region != null) { FoldingInfo fi = element.getUserData(FOLDING_INFO_KEY); boolean state = fi != null && fi.expanded; region.setExpanded(state); } } for (RangeMarker marker : myRangeMarkers) { if (!marker.isValid()) { continue; } FoldRegion region = FoldingUtil.findFoldRegion(editor, marker.getStartOffset(), marker.getEndOffset()); FoldingInfo info = marker.getUserData(FOLDING_INFO_KEY); if (region == null) { if (info != null) { region = editor .getFoldingModel() .addFoldRegion(marker.getStartOffset(), marker.getEndOffset(), info.placeHolder); } if (region == null) { return; } } boolean state = info != null && info.expanded; region.setExpanded(state); } } @NotNull private static Map<PsiElement, FoldingDescriptor> buildRanges( @NotNull Editor editor, @NotNull PsiFile psiFile) { final FoldingBuilder foldingBuilder = LanguageFolding.INSTANCE.forLanguage(psiFile.getLanguage()); final ASTNode node = psiFile.getNode(); if (node == null) return Collections.emptyMap(); final FoldingDescriptor[] descriptors = LanguageFolding.buildFoldingDescriptors( foldingBuilder, psiFile, editor.getDocument(), true); Map<PsiElement, FoldingDescriptor> ranges = new HashMap<PsiElement, FoldingDescriptor>(); for (FoldingDescriptor descriptor : descriptors) { final ASTNode ast = descriptor.getElement(); final PsiElement psi = ast.getPsi(); if (psi != null) { ranges.put(psi, descriptor); } } return ranges; } void clear() { myPsiElements.clear(); for (RangeMarker marker : myRangeMarkers) { if (!(marker instanceof FoldRegion)) marker.dispose(); } myRangeMarkers.clear(); mySerializedElements.clear(); } @Override public void writeExternal(Element element) throws WriteExternalException { PsiDocumentManager.getInstance(myProject).commitAllDocuments(); if (myPsiElements.isEmpty() && myRangeMarkers.isEmpty() && mySerializedElements.isEmpty()) { throw new WriteExternalException(); } if (mySerializedElements.isEmpty()) { for (SmartPsiElementPointer<PsiElement> ptr : myPsiElements) { PsiElement psiElement = ptr.getElement(); if (psiElement == null || !psiElement.isValid()) { continue; } FoldingInfo fi = psiElement.getUserData(FOLDING_INFO_KEY); boolean state = fi != null && fi.expanded; String signature = FoldingPolicy.getSignature(psiElement); if (signature == null) { continue; } PsiFile containingFile = psiElement.getContainingFile(); PsiElement restoredElement = FoldingPolicy.restoreBySignature(containingFile, signature); if (!psiElement.equals(restoredElement)) { StringBuilder trace = new StringBuilder(); PsiElement restoredAgain = FoldingPolicy.restoreBySignature(containingFile, signature, trace); LOG.error( "element: " + psiElement + "(" + psiElement.getText() + "); restoredElement: " + restoredElement + "; signature: '" + signature + "'; file: " + containingFile + "; injected: " + InjectedLanguageManager.getInstance(myProject) .isInjectedFragment(containingFile) + "; languages: " + containingFile.getViewProvider().getLanguages() + "; restored again: " + restoredAgain + "; restore produces same results: " + (restoredAgain == restoredElement) + "; trace:\n" + trace); } Element e = new Element(ELEMENT_TAG); e.setAttribute(SIGNATURE_ATT, signature); e.setAttribute(EXPANDED_ATT, Boolean.toString(state)); element.addContent(e); } } else { // get back postponed state (before folding initialization) for (SerializedPsiElement entry : mySerializedElements) { Element e = new Element(ELEMENT_TAG); e.setAttribute(SIGNATURE_ATT, entry.mySerializedElement); e.setAttribute(EXPANDED_ATT, Boolean.toString(entry.myFoldingInfo.getExpanded())); element.addContent(e); } } String date = null; for (RangeMarker marker : myRangeMarkers) { FoldingInfo fi = marker.getUserData(FOLDING_INFO_KEY); boolean state = fi != null && fi.expanded; Element e = new Element(MARKER_TAG); if (date == null) { date = getTimeStamp(); } if (date.isEmpty()) { continue; } e.setAttribute(DATE_ATT, date); e.setAttribute(EXPANDED_ATT, Boolean.toString(state)); String signature = Integer.valueOf(marker.getStartOffset()) + ":" + Integer.valueOf(marker.getEndOffset()); e.setAttribute(SIGNATURE_ATT, signature); String placeHolderText = fi == null ? DEFAULT_PLACEHOLDER : fi.placeHolder; e.setAttribute(PLACEHOLDER_ATT, placeHolderText); element.addContent(e); } } @Override public void readExternal(final Element element) { ApplicationManager.getApplication() .runReadAction( new Runnable() { @Override public void run() { clear(); if (!myFile.isValid()) return; final Document document = FileDocumentManager.getInstance().getDocument(myFile); if (document == null) return; PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(document); if (psiFile == null || !psiFile.getViewProvider().isPhysical()) return; String date = null; boolean canRestoreElement = !DumbService.getInstance(myProject).isDumb() || FoldingUpdate.supportsDumbModeFolding(psiFile); for (final Object o : element.getChildren()) { Element e = (Element) o; Boolean expanded = Boolean.valueOf(e.getAttributeValue(EXPANDED_ATT)); if (ELEMENT_TAG.equals(e.getName())) { String signature = e.getAttributeValue(SIGNATURE_ATT); if (signature == null) { continue; } FoldingInfo fi = new FoldingInfo(DEFAULT_PLACEHOLDER, expanded); if (canRestoreElement) { PsiElement restoredElement = FoldingPolicy.restoreBySignature(psiFile, signature); if (restoredElement != null && restoredElement.isValid()) { myPsiElements.add( SmartPointerManager.getInstance(myProject) .createSmartPsiElementPointer(restoredElement)); restoredElement.putUserData(FOLDING_INFO_KEY, fi); } } else { // Postponed initialization mySerializedElements.add(new SerializedPsiElement(signature, fi)); } } else if (MARKER_TAG.equals(e.getName())) { if (date == null) { date = getTimeStamp(); } if (date.isEmpty()) continue; if (!date.equals(e.getAttributeValue(DATE_ATT)) || FileDocumentManager.getInstance().isDocumentUnsaved(document)) continue; StringTokenizer tokenizer = new StringTokenizer(e.getAttributeValue(SIGNATURE_ATT), ":"); try { int start = Integer.valueOf(tokenizer.nextToken()).intValue(); int end = Integer.valueOf(tokenizer.nextToken()).intValue(); if (start < 0 || end >= document.getTextLength() || start > end) continue; RangeMarker marker = document.createRangeMarker(start, end); myRangeMarkers.add(marker); String placeHolderText = e.getAttributeValue(PLACEHOLDER_ATT); if (placeHolderText == null) placeHolderText = DEFAULT_PLACEHOLDER; FoldingInfo fi = new FoldingInfo(placeHolderText, expanded); marker.putUserData(FOLDING_INFO_KEY, fi); } catch (NoSuchElementException exc) { LOG.error(exc); } } else { throw new IllegalStateException("unknown tag: " + e.getName()); } } } }); } private String getTimeStamp() { if (!myFile.isValid()) return ""; return Long.toString(myFile.getTimeStamp()); } @Override public int hashCode() { int result = myProject.hashCode(); result = 31 * result + (myFile != null ? myFile.hashCode() : 0); result = 31 * result + myPsiElements.hashCode(); result = 31 * result + myRangeMarkers.hashCode(); result = 31 * result + mySerializedElements.hashCode(); return result; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } DocumentFoldingInfo info = (DocumentFoldingInfo) o; if (myFile != null ? !myFile.equals(info.myFile) : info.myFile != null) { return false; } if (!myProject.equals(info.myProject) || !myPsiElements.equals(info.myPsiElements) || !mySerializedElements.equals(info.mySerializedElements)) { return false; } if (myRangeMarkers.size() != info.myRangeMarkers.size()) return false; for (int i = 0; i < myRangeMarkers.size(); i++) { RangeMarker marker = myRangeMarkers.get(i); RangeMarker other = info.myRangeMarkers.get(i); if (marker == other || !marker.isValid() || !other.isValid()) { continue; } if (!TextRange.areSegmentsEqual(marker, other)) return false; FoldingInfo fi = marker.getUserData(FOLDING_INFO_KEY); FoldingInfo ofi = other.getUserData(FOLDING_INFO_KEY); if (!Comparing.equal(fi, ofi)) return false; } return true; } private static class FoldingInfo { private final String placeHolder; private final boolean expanded; private FoldingInfo(@NotNull String placeHolder, boolean expanded) { this.placeHolder = placeHolder; this.expanded = expanded; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } FoldingInfo info = (FoldingInfo) o; return expanded == info.expanded && placeHolder.equals(info.placeHolder); } @Override public int hashCode() { int result = placeHolder.hashCode(); result = 31 * result + (expanded ? 1 : 0); return result; } public boolean getExpanded() { return expanded; } } }
/** * @author anna * @since 29-May-2006 */ public abstract class MasterDetailsComponent implements Configurable, DetailsComponent.Facade, MasterDetails { protected static final Logger LOG = Logger.getInstance("#com.intellij.openapi.ui.MasterDetailsComponent"); protected static final Icon COPY_ICON = PlatformIcons.COPY_ICON; protected NamedConfigurable myCurrentConfigurable; private final JBSplitter mySplitter; @NonNls public static final String TREE_OBJECT = "treeObject"; @NonNls public static final String TREE_NAME = "treeName"; protected History myHistory = new History( new Place.Navigator() { public void setHistory(final History history) { myHistory = history; } @Nullable public ActionCallback navigateTo( @Nullable final Place place, final boolean requestFocus) { return null; } public void queryPlace(@NotNull final Place place) {} }); private JComponent myMaster; public void setHistory(final History history) { myHistory = history; } protected final MasterDetailsState myState; protected Runnable TREE_UPDATER; { TREE_UPDATER = new Runnable() { public void run() { final TreePath selectionPath = myTree.getSelectionPath(); if (selectionPath == null) return; MyNode node = (MyNode) selectionPath.getLastPathComponent(); if (node == null) return; myState.setLastEditedConfigurable(getNodePathString(node)); // survive after rename; myDetails.setText(node.getConfigurable().getBannerSlogan()); ((DefaultTreeModel) myTree.getModel()).reload(node); fireItemsChangedExternally(); } }; } protected MyNode myRoot = new MyRootNode(); protected Tree myTree = new Tree(); private final DetailsComponent myDetails = new DetailsComponent( !Registry.is("ide.new.project.settings"), !Registry.is("ide.new.project.settings")); protected JPanel myWholePanel; public JPanel myNorthPanel = new JPanel(new BorderLayout()); private final List<ItemsChangeListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private final Set<NamedConfigurable> myInitializedConfigurables = new HashSet<NamedConfigurable>(); private boolean myHasDeletedItems; protected AutoScrollToSourceHandler myAutoScrollHandler; protected boolean myToReInitWholePanel = true; protected MasterDetailsComponent() { this(new MasterDetailsState()); } protected MasterDetailsComponent(MasterDetailsState state) { myState = state; mySplitter = isNewProjectSettings() ? new OnePixelSplitter(false, .2f) : new JBSplitter(false, .2f); mySplitter.setSplitterProportionKey("ProjectStructure.SecondLevelElements"); mySplitter.setHonorComponentsMinimumSize(true); installAutoScroll(); reInitWholePanelIfNeeded(); } private boolean isNewProjectSettings() { if (!Registry.is("ide.new.project.settings")) { return false; } try { // assume that only project structure dialog uses the following base class for details: String name = "com.intellij.openapi.roots.ui.configuration.projectRoot.BaseStructureConfigurable"; return Class.forName(name).isAssignableFrom(getClass()); } catch (ClassNotFoundException ignored) { return false; } } protected void reInitWholePanelIfNeeded() { if (!myToReInitWholePanel) return; myWholePanel = new JPanel(new BorderLayout()) { public void addNotify() { super.addNotify(); MasterDetailsComponent.this.addNotify(); TreeModel m = myTree.getModel(); if (m instanceof DefaultTreeModel) { DefaultTreeModel model = (DefaultTreeModel) m; for (int eachRow = 0; eachRow < myTree.getRowCount(); eachRow++) { TreePath eachPath = myTree.getPathForRow(eachRow); Object component = eachPath.getLastPathComponent(); if (component instanceof TreeNode) { model.nodeChanged((TreeNode) component); } } } } }; mySplitter.setHonorComponentsMinimumSize(true); myWholePanel.add(mySplitter, BorderLayout.CENTER); JPanel left = new JPanel(new BorderLayout()) { public Dimension getMinimumSize() { final Dimension original = super.getMinimumSize(); return new Dimension(Math.max(original.width, 100), original.height); } }; if (isNewProjectSettings()) { ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myTree); DefaultActionGroup group = createToolbarActionGroup(); if (group != null) { decorator.setActionGroup(group); } // left.add(myNorthPanel, BorderLayout.NORTH); myMaster = decorator.setAsUsualTopToolbar().setPanelBorder(JBUI.Borders.empty()).createPanel(); myNorthPanel.setVisible(false); } else { left.add(myNorthPanel, BorderLayout.NORTH); myMaster = ScrollPaneFactory.createScrollPane(myTree); } left.add(myMaster, BorderLayout.CENTER); mySplitter.setFirstComponent(left); final JPanel right = new JPanel(new BorderLayout()); right.add(myDetails.getComponent(), BorderLayout.CENTER); if (!isNewProjectSettings()) { myWholePanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); } mySplitter.setSecondComponent(right); GuiUtils.replaceJSplitPaneWithIDEASplitter(myWholePanel); myToReInitWholePanel = false; } private void installAutoScroll() { myAutoScrollHandler = new AutoScrollToSourceHandler() { protected boolean isAutoScrollMode() { return isAutoScrollEnabled(); } protected void setAutoScrollMode(boolean state) { // do nothing } protected void scrollToSource(Component tree) { updateSelectionFromTree(); } protected boolean needToCheckFocus() { return false; } }; myAutoScrollHandler.install(myTree); } protected void addNotify() { updateSelectionFromTree(); } private void updateSelectionFromTree() { TreePath[] treePaths = myTree.getSelectionPaths(); if (treePaths != null) { List<NamedConfigurable> selectedConfigurables = new ArrayList<NamedConfigurable>(); for (TreePath path : treePaths) { Object lastPathComponent = path.getLastPathComponent(); if (lastPathComponent instanceof MyNode) { selectedConfigurables.add(((MyNode) lastPathComponent).getConfigurable()); } } if (selectedConfigurables.size() > 1 && updateMultiSelection(selectedConfigurables)) { return; } } final TreePath path = myTree.getSelectionPath(); if (path != null) { final Object lastPathComp = path.getLastPathComponent(); if (!(lastPathComp instanceof MyNode)) return; final MyNode node = (MyNode) lastPathComp; setSelectedNode(node); } else { setSelectedNode(null); } } protected boolean updateMultiSelection(final List<NamedConfigurable> selectedConfigurables) { return false; } public DetailsComponent getDetailsComponent() { return myDetails; } public Splitter getSplitter() { return mySplitter; } protected boolean isAutoScrollEnabled() { return myHistory == null || !myHistory.isNavigatingNow(); } protected DefaultActionGroup createToolbarActionGroup() { final ArrayList<AnAction> actions = createActions(false); if (actions != null) { final DefaultActionGroup group = new DefaultActionGroup(); for (AnAction action : actions) { if (action instanceof ActionGroupWithPreselection) { group.add(new MyActionGroupWrapper((ActionGroupWithPreselection) action)); } else { group.add(action); } } return group; } return null; } private void initToolbar() { if (isNewProjectSettings()) return; DefaultActionGroup group = createToolbarActionGroup(); if (group != null) { final JComponent component = ActionManager.getInstance() .createActionToolbar(ActionPlaces.UNKNOWN, group, true) .getComponent(); myNorthPanel.add(component, BorderLayout.NORTH); } } public void addItemsChangeListener(ItemsChangeListener l) { myListeners.add(l); } protected Dimension getPanelPreferredSize() { return JBUI.size(800, 600); } @NotNull public JComponent createComponent() { myTree.updateUI(); reInitWholePanelIfNeeded(); updateSelectionFromTree(); final JPanel panel = new JPanel(new BorderLayout()) { public Dimension getPreferredSize() { return getPanelPreferredSize(); } }; panel.add(myWholePanel, BorderLayout.CENTER); return panel; } public boolean isModified() { if (myHasDeletedItems) return true; final boolean[] modified = new boolean[1]; TreeUtil.traverseDepth( myRoot, new TreeUtil.Traverse() { public boolean accept(Object node) { if (node instanceof MyNode) { final NamedConfigurable configurable = ((MyNode) node).getConfigurable(); if (isInitialized(configurable) && configurable.isModified()) { modified[0] = true; return false; } } return true; } }); return modified[0]; } protected boolean isInitialized(final NamedConfigurable configurable) { return myInitializedConfigurables.contains(configurable); } public void apply() throws ConfigurationException { processRemovedItems(); final ConfigurationException[] ex = new ConfigurationException[1]; TreeUtil.traverse( myRoot, new TreeUtil.Traverse() { public boolean accept(Object node) { if (node instanceof MyNode) { try { final NamedConfigurable configurable = ((MyNode) node).getConfigurable(); if (isInitialized(configurable) && configurable.isModified()) { configurable.apply(); } } catch (ConfigurationException e) { ex[0] = e; return false; } } return true; } }); if (ex[0] != null) { throw ex[0]; } myHasDeletedItems = false; } protected abstract void processRemovedItems(); protected abstract boolean wasObjectStored(Object editableObject); public void reset() { loadComponentState(); myHasDeletedItems = false; ((DefaultTreeModel) myTree.getModel()).reload(); // myTree.requestFocus(); myState.getProportions().restoreSplitterProportions(myWholePanel); final Enumeration enumeration = myRoot.breadthFirstEnumeration(); boolean selected = false; while (enumeration.hasMoreElements()) { final MyNode node = (MyNode) enumeration.nextElement(); if (node instanceof MyRootNode) continue; final String path = getNodePathString(node); if (!selected && Comparing.strEqual(path, myState.getLastEditedConfigurable())) { TreeUtil.selectInTree(node, false, myTree); selected = true; } } if (!selected) { TreeUtil.selectFirstNode(myTree); } updateSelectionFromTree(); } protected void loadComponentState() { final String key = getComponentStateKey(); final MasterDetailsStateService stateService = getStateService(); if (key != null && stateService != null) { final MasterDetailsState state = stateService.getComponentState(key, myState.getClass()); if (state != null) { loadState(state); } } } private static String getNodePathString(final MyNode node) { StringBuilder path = new StringBuilder(); MyNode current = node; while (current != null) { final Object userObject = current.getUserObject(); if (!(userObject instanceof NamedConfigurable)) break; final String displayName = current.getDisplayName(); if (StringUtil.isEmptyOrSpaces(displayName)) break; if (path.length() > 0) { path.append('|'); } path.append(displayName); final TreeNode parent = current.getParent(); if (!(parent instanceof MyNode)) break; current = (MyNode) parent; } return path.toString(); } @Nullable @NonNls protected String getComponentStateKey() { return null; } @Nullable protected MasterDetailsStateService getStateService() { return null; } protected MasterDetailsState getState() { return myState; } protected void loadState(final MasterDetailsState object) { XmlSerializerUtil.copyBean(object, myState); } public void disposeUIResources() { myState.getProportions().saveSplitterProportions(myWholePanel); myAutoScrollHandler.cancelAllRequests(); myDetails.disposeUIResources(); myInitializedConfigurables.clear(); clearChildren(); final String key = getComponentStateKey(); final MasterDetailsStateService stateService = getStateService(); if (key != null && stateService != null) { stateService.setComponentState(key, getState()); } myCurrentConfigurable = null; } protected void clearChildren() { TreeUtil.traverseDepth( myRoot, new TreeUtil.Traverse() { public boolean accept(Object node) { if (node instanceof MyNode) { final MyNode treeNode = ((MyNode) node); treeNode.getConfigurable().disposeUIResources(); if (!(treeNode instanceof MyRootNode)) { treeNode.setUserObject(null); } } return true; } }); myRoot.removeAllChildren(); } @Nullable protected ArrayList<AnAction> createActions(final boolean fromPopup) { return null; } protected void initTree() { ((DefaultTreeModel) myTree.getModel()).setRoot(myRoot); myTree.setRootVisible(false); myTree.setShowsRootHandles(true); UIUtil.setLineStyleAngled(myTree); TreeUtil.installActions(myTree); myTree.setCellRenderer( new ColoredTreeCellRenderer() { public void customizeCellRenderer( JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { if (value instanceof MyNode) { final MyNode node = ((MyNode) value); setIcon(node.getIcon(expanded)); final Font font = UIUtil.getTreeFont(); if (node.isDisplayInBold()) { setFont(font.deriveFont(Font.BOLD)); } else { setFont(font.deriveFont(Font.PLAIN)); } append( node.getDisplayName(), node.isDisplayInBold() ? SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES : SimpleTextAttributes.REGULAR_ATTRIBUTES); } } }); initToolbar(); ArrayList<AnAction> actions = createActions(true); if (actions != null) { final DefaultActionGroup group = new DefaultActionGroup(); for (AnAction action : actions) { group.add(action); } actions = getAdditionalActions(); if (actions != null) { group.addSeparator(); for (AnAction action : actions) { group.add(action); } } PopupHandler.installPopupHandler( myTree, group, ActionPlaces.UNKNOWN, ActionManager.getInstance()); // popup should follow the selection } } @Nullable protected ArrayList<AnAction> getAdditionalActions() { return null; } public void fireItemsChangeListener(final Object editableObject) { for (ItemsChangeListener listener : myListeners) { listener.itemChanged(editableObject); } } private void fireItemsChangedExternally() { for (ItemsChangeListener listener : myListeners) { listener.itemsExternallyChanged(); } } private void createUIComponents() { myTree = new Tree() { public Dimension getPreferredScrollableViewportSize() { Dimension size = super.getPreferredScrollableViewportSize(); size = new Dimension(size.width + 20, size.height); return size; } @SuppressWarnings({"NonStaticInitializer"}) public JToolTip createToolTip() { final JToolTip toolTip = new JToolTip() { { setUI(new MultiLineTooltipUI()); } }; toolTip.setComponent(this); return toolTip; } }; } protected void addNode(MyNode nodeToAdd, MyNode parent) { int i = TreeUtil.indexedBinarySearch(parent, nodeToAdd, getNodeComparator()); int insertionPoint = i >= 0 ? i : -i - 1; ((DefaultTreeModel) myTree.getModel()).insertNodeInto(nodeToAdd, parent, insertionPoint); } protected void sortDescendants(MyNode root) { TreeUtil.sort(root, getNodeComparator()); ((DefaultTreeModel) myTree.getModel()).reload(root); } protected Comparator<MyNode> getNodeComparator() { return new Comparator<MyNode>() { public int compare(final MyNode o1, final MyNode o2) { return StringUtil.naturalCompare(o1.getDisplayName(), o2.getDisplayName()); } }; } public ActionCallback selectNodeInTree(final DefaultMutableTreeNode nodeToSelect) { return selectNodeInTree(nodeToSelect, true, false); } public ActionCallback selectNodeInTree( final DefaultMutableTreeNode nodeToSelect, boolean requestFocus) { return selectNodeInTree(nodeToSelect, true, requestFocus); } public ActionCallback selectNodeInTree( final DefaultMutableTreeNode nodeToSelect, boolean center, final boolean requestFocus) { if (requestFocus) { myTree.requestFocus(); } if (nodeToSelect != null) { return TreeUtil.selectInTree(nodeToSelect, requestFocus, myTree, center); } else { return TreeUtil.selectFirstNode(myTree); } } @Nullable public Object getSelectedObject() { final TreePath selectionPath = myTree.getSelectionPath(); if (selectionPath != null && selectionPath.getLastPathComponent() instanceof MyNode) { MyNode node = (MyNode) selectionPath.getLastPathComponent(); final NamedConfigurable configurable = node.getConfigurable(); LOG.assertTrue(configurable != null, "already disposed"); return configurable.getEditableObject(); } return null; } @Nullable public NamedConfigurable getSelectedConfigurable() { final TreePath selectionPath = myTree.getSelectionPath(); if (selectionPath != null) { MyNode node = (MyNode) selectionPath.getLastPathComponent(); final NamedConfigurable configurable = node.getConfigurable(); LOG.assertTrue(configurable != null, "already disposed"); return configurable; } return null; } public void selectNodeInTree(String displayName) { final MyNode nodeByName = findNodeByName(myRoot, displayName); selectNodeInTree(nodeByName, true); } public void selectNodeInTree(final Object object) { selectNodeInTree(findNodeByObject(myRoot, object), true); } @Nullable protected static MyNode findNodeByName(final TreeNode root, final String profileName) { if (profileName == null) return null; // do not suggest root node return findNodeByCondition( root, new Condition<NamedConfigurable>() { public boolean value(final NamedConfigurable configurable) { return Comparing.strEqual(profileName, configurable.getDisplayName()); } }); } @Nullable public static MyNode findNodeByObject(final TreeNode root, final Object editableObject) { if (editableObject == null) return null; // do not suggest root node return findNodeByCondition( root, new Condition<NamedConfigurable>() { public boolean value(final NamedConfigurable configurable) { return Comparing.equal(editableObject, configurable.getEditableObject()); } }); } protected static MyNode findNodeByCondition( final TreeNode root, final Condition<NamedConfigurable> condition) { final MyNode[] nodeToSelect = new MyNode[1]; TreeUtil.traverseDepth( root, new TreeUtil.Traverse() { public boolean accept(Object node) { if (condition.value(((MyNode) node).getConfigurable())) { nodeToSelect[0] = (MyNode) node; return false; } return true; } }); return nodeToSelect[0]; } protected void setSelectedNode(@Nullable MyNode node) { if (node != null) { myState.setLastEditedConfigurable(getNodePathString(node)); } updateSelection(node != null ? node.getConfigurable() : null); } protected void updateSelection(@Nullable NamedConfigurable configurable) { myDetails.setText(configurable != null ? configurable.getBannerSlogan() : null); myCurrentConfigurable = configurable; if (configurable != null) { final JComponent comp = configurable.createComponent(); if (comp == null) { setEmpty(); LOG.error("createComponent() returned null. configurable=" + configurable); } else { myDetails.setContent(comp); ensureInitialized(configurable); myHistory.pushPlaceForElement(TREE_OBJECT, configurable.getEditableObject()); } } else { setEmpty(); } } public void ensureInitialized(NamedConfigurable configurable) { if (!isInitialized(configurable)) { configurable.reset(); initializeConfigurable(configurable); } } private void setEmpty() { myDetails.setContent(null); myDetails.setEmptyContentText(getEmptySelectionString()); } public String getHelpTopic() { if (myCurrentConfigurable != null) { return myCurrentConfigurable.getHelpTopic(); } return null; } protected @Nullable String getEmptySelectionString() { return null; } protected void initializeConfigurable(final NamedConfigurable configurable) { myInitializedConfigurables.add(configurable); } /** @deprecated use {@link #checkForEmptyAndDuplicatedNames(String, String, Class} instead */ protected void checkApply(Set<MyNode> rootNodes, String prefix, String title) throws ConfigurationException { for (MyNode rootNode : rootNodes) { checkForEmptyAndDuplicatedNames(rootNode, prefix, title, NamedConfigurable.class, false); } } protected final void checkForEmptyAndDuplicatedNames( String prefix, String title, Class<? extends NamedConfigurable> configurableClass) throws ConfigurationException { checkForEmptyAndDuplicatedNames(myRoot, prefix, title, configurableClass, true); } private void checkForEmptyAndDuplicatedNames( MyNode rootNode, String prefix, String title, Class<? extends NamedConfigurable> configurableClass, boolean recursively) throws ConfigurationException { final Set<String> names = new HashSet<String>(); for (int i = 0; i < rootNode.getChildCount(); i++) { final MyNode node = (MyNode) rootNode.getChildAt(i); final NamedConfigurable scopeConfigurable = node.getConfigurable(); if (configurableClass.isInstance(scopeConfigurable)) { final String name = scopeConfigurable.getDisplayName(); if (name.trim().length() == 0) { selectNodeInTree(node); throw new ConfigurationException("Name should contain non-space characters"); } if (names.contains(name)) { final NamedConfigurable selectedConfigurable = getSelectedConfigurable(); if (selectedConfigurable == null || !Comparing.strEqual(selectedConfigurable.getDisplayName(), name)) { selectNodeInTree(node); } throw new ConfigurationException( CommonBundle.message("smth.already.exist.error.message", prefix, name), title); } names.add(name); } if (recursively) { checkForEmptyAndDuplicatedNames(node, prefix, title, configurableClass, true); } } } public Tree getTree() { return myTree; } protected void removePaths(final TreePath... paths) { MyNode parentNode = null; int idx = -1; for (TreePath path : paths) { final MyNode node = (MyNode) path.getLastPathComponent(); final NamedConfigurable namedConfigurable = node.getConfigurable(); final Object editableObject = namedConfigurable.getEditableObject(); parentNode = (MyNode) node.getParent(); idx = parentNode.getIndex(node); ((DefaultTreeModel) myTree.getModel()).removeNodeFromParent(node); myHasDeletedItems |= wasObjectStored(editableObject); fireItemsChangeListener(editableObject); onItemDeleted(editableObject); namedConfigurable.disposeUIResources(); } if (paths.length > 0) { if (parentNode != null && idx != -1) { DefaultMutableTreeNode toSelect = null; if (idx < parentNode.getChildCount()) { toSelect = (DefaultMutableTreeNode) parentNode.getChildAt(idx); } else { if (idx > 0 && parentNode.getChildCount() > 0) { if (idx - 1 < parentNode.getChildCount()) { toSelect = (DefaultMutableTreeNode) parentNode.getChildAt(idx - 1); } else { toSelect = (DefaultMutableTreeNode) parentNode.getFirstChild(); } } else { if (parentNode.isRoot() && myTree.isRootVisible()) { toSelect = parentNode; } else if (parentNode.getChildCount() > 0) { toSelect = (DefaultMutableTreeNode) parentNode.getFirstChild(); } } } if (toSelect != null) { TreeUtil.selectInTree(toSelect, true, myTree); } } else { TreeUtil.selectFirstNode(myTree); } } } protected void onItemDeleted(Object item) {} protected class MyDeleteAction extends AnAction implements DumbAware { private final Condition<Object[]> myCondition; public MyDeleteAction() { this(Conditions.<Object[]>alwaysTrue()); } public MyDeleteAction(Condition<Object[]> availableCondition) { super( CommonBundle.message("button.delete"), CommonBundle.message("button.delete"), PlatformIcons.DELETE_ICON); registerCustomShortcutSet(CommonShortcuts.getDelete(), myTree); myCondition = availableCondition; } public void update(AnActionEvent e) { final Presentation presentation = e.getPresentation(); presentation.setEnabled(false); final TreePath[] selectionPath = myTree.getSelectionPaths(); if (selectionPath != null) { Object[] nodes = ContainerUtil.map2Array( selectionPath, new Function<TreePath, Object>() { @Override public Object fun(TreePath treePath) { return treePath.getLastPathComponent(); } }); if (!myCondition.value(nodes)) return; presentation.setEnabled(true); } } public void actionPerformed(AnActionEvent e) { removePaths(myTree.getSelectionPaths()); } } protected static Condition<Object[]> forAll(final Condition<Object> condition) { return new Condition<Object[]>() { @Override public boolean value(Object[] objects) { for (Object object : objects) { if (!condition.value(object)) return false; } return true; } }; } public static class MyNode extends DefaultMutableTreeNode { private boolean myDisplayInBold; public MyNode(@NotNull NamedConfigurable userObject) { super(userObject); } public MyNode(@NotNull NamedConfigurable userObject, boolean displayInBold) { super(userObject); myDisplayInBold = displayInBold; } @NotNull public String getDisplayName() { final NamedConfigurable configurable = ((NamedConfigurable) getUserObject()); LOG.assertTrue(configurable != null, "Tree was already disposed"); return configurable.getDisplayName(); } public NamedConfigurable getConfigurable() { return (NamedConfigurable) getUserObject(); } public boolean isDisplayInBold() { return myDisplayInBold; } public void setDisplayInBold(boolean displayInBold) { myDisplayInBold = displayInBold; } @Nullable public Icon getIcon(boolean expanded) { // thanks to invokeLater() in TreeUtil.showAndSelect(), we can get calls to getIcon() after // the tree has been disposed final NamedConfigurable configurable = getConfigurable(); if (configurable != null) { return configurable.getIcon(expanded); } return null; } } @SuppressWarnings({"ConstantConditions"}) protected static class MyRootNode extends MyNode { public MyRootNode() { super( new NamedConfigurable(false, null) { public void setDisplayName(String name) {} public Object getEditableObject() { return null; } public String getBannerSlogan() { return null; } public String getDisplayName() { return ""; } @Nullable @NonNls public String getHelpTopic() { return null; } public JComponent createOptionsPanel() { return null; } public boolean isModified() { return false; } public void apply() throws ConfigurationException {} public void reset() {} public void disposeUIResources() {} }, false); } } protected interface ItemsChangeListener { void itemChanged(@Nullable Object deletedItem); void itemsExternallyChanged(); } public interface ActionGroupWithPreselection { ActionGroup getActionGroup(); int getDefaultIndex(); } protected class MyActionGroupWrapper extends AnAction implements DumbAware { private ActionGroup myActionGroup; private ActionGroupWithPreselection myPreselection; public MyActionGroupWrapper(final ActionGroupWithPreselection actionGroup) { this(actionGroup.getActionGroup()); myPreselection = actionGroup; } public MyActionGroupWrapper(final ActionGroup actionGroup) { super( actionGroup.getTemplatePresentation().getText(), actionGroup.getTemplatePresentation().getDescription(), actionGroup.getTemplatePresentation().getIcon()); myActionGroup = actionGroup; registerCustomShortcutSet(actionGroup.getShortcutSet(), myTree); } public void actionPerformed(AnActionEvent e) { final JBPopupFactory popupFactory = JBPopupFactory.getInstance(); final ListPopupStep step = popupFactory.createActionsStep( myActionGroup, e.getDataContext(), false, false, myActionGroup.getTemplatePresentation().getText(), myTree, true, myPreselection != null ? myPreselection.getDefaultIndex() : 0, true); final ListPopup listPopup = popupFactory.createListPopup(step); listPopup.setHandleAutoSelectionBeforeShow(true); if (e instanceof AnActionButton.AnActionEventWrapper) { ((AnActionButton.AnActionEventWrapper) e).showPopup(listPopup); } else { listPopup.showUnderneathOf(myNorthPanel); } } } public JComponent getToolbar() { myToReInitWholePanel = true; return myNorthPanel; } public JComponent getMaster() { myToReInitWholePanel = true; return myMaster; } public DetailsComponent getDetails() { myToReInitWholePanel = true; return myDetails; } public void initUi() { createComponent(); } }
public class WebServer { private static final String START_TIME_PATH = "/startTime"; private final List<ChannelFutureListener> closingListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private final ChannelGroup openChannels = new DefaultChannelGroup("web-server"); private static final Logger LOG = Logger.getInstance(WebServer.class); @NonNls private static final String PROPERTY_ONLY_ANY_HOST = "rpc.onlyAnyHost"; private final NioServerSocketChannelFactory channelFactory; public WebServer() { Executor pooledThreadExecutor = new PooledThreadExecutor(); channelFactory = new NioServerSocketChannelFactory(pooledThreadExecutor, pooledThreadExecutor, 1); } public boolean isRunning() { return !openChannels.isEmpty(); } public void start(int port, Consumer<ChannelPipeline>... pipelineConsumers) { start( port, new Computable.PredefinedValueComputable<Consumer<ChannelPipeline>[]>(pipelineConsumers)); } public void start(int port, int portsCount, Consumer<ChannelPipeline>... pipelineConsumers) { start( port, portsCount, false, new Computable.PredefinedValueComputable<Consumer<ChannelPipeline>[]>(pipelineConsumers)); } public void start(int port, Computable<Consumer<ChannelPipeline>[]> pipelineConsumers) { start(port, 1, false, pipelineConsumers); } public int start( int firstPort, int portsCount, boolean tryAnyPort, Computable<Consumer<ChannelPipeline>[]> pipelineConsumers) { if (isRunning()) { throw new IllegalStateException("server already started"); } ServerBootstrap bootstrap = new ServerBootstrap(channelFactory); bootstrap.setOption("child.tcpNoDelay", true); bootstrap.setPipelineFactory( new ChannelPipelineFactoryImpl(pipelineConsumers, new DefaultHandler(openChannels))); return bind(firstPort, portsCount, tryAnyPort, bootstrap); } private static boolean checkPort(final InetSocketAddress remoteAddress) { final ClientBootstrap bootstrap = new ClientBootstrap(new OioClientSocketChannelFactory(new PooledThreadExecutor())); bootstrap.setOption("child.tcpNoDelay", true); final AtomicBoolean result = new AtomicBoolean(false); final Semaphore semaphore = new Semaphore(); semaphore.down(); // must call to down() here to ensure that down was called _before_ up() bootstrap.setPipeline( pipeline( new HttpResponseDecoder(), new HttpChunkAggregator(1048576), new HttpRequestEncoder(), new SimpleChannelUpstreamHandler() { @Override public void messageReceived(ChannelHandlerContext context, MessageEvent e) throws Exception { try { if (e.getMessage() instanceof HttpResponse) { HttpResponse response = (HttpResponse) e.getMessage(); if (response.getStatus().equals(OK) && response .getContent() .toString(CharsetUtil.US_ASCII) .equals(getApplicationStartTime())) { LOG.info("port check: current OS must be marked as normal"); result.set(true); } } } finally { semaphore.up(); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { try { LOG.error(e.getCause()); } finally { semaphore.up(); } } })); ChannelFuture connectFuture = null; try { connectFuture = bootstrap.connect(remoteAddress); if (!waitComplete(connectFuture, "connect")) { return false; } ChannelFuture writeFuture = connectFuture .getChannel() .write(new DefaultHttpRequest(HTTP_1_1, HttpMethod.GET, START_TIME_PATH)); if (!waitComplete(writeFuture, "write")) { return false; } try { // yes, 30 seconds. I always get timeout in Linux in Parallels if I set to 2 seconds. // In any case all work is done in pooled thread (IDE init time isn't affected) if (!semaphore.waitForUnsafe(30000)) { LOG.info("port check: semaphore down timeout"); } } catch (InterruptedException e) { LOG.info("port check: semaphore interrupted", e); } } finally { if (connectFuture != null) { connectFuture.getChannel().close().awaitUninterruptibly(); } bootstrap.releaseExternalResources(); } return result.get(); } private static boolean waitComplete(ChannelFuture writeFuture, String failedMessage) { if (!writeFuture.awaitUninterruptibly(500) || !writeFuture.isSuccess()) { LOG.info("port check: " + failedMessage + ", " + writeFuture.isSuccess()); return false; } return true; } private static String getApplicationStartTime() { return Long.toString(ApplicationManager.getApplication().getStartTime()); } // IDEA-91436 idea <121 binds to 127.0.0.1, but >=121 must be available not only from localhost // but if we bind only to any local port (0.0.0.0), instance of idea <121 can bind to our ports // and any request to us will be intercepted // so, we bind to 127.0.0.1 and 0.0.0.0 private int bind(int firstPort, int portsCount, boolean tryAnyPort, ServerBootstrap bootstrap) { String property = System.getProperty(PROPERTY_ONLY_ANY_HOST); boolean onlyAnyHost = property == null ? (SystemInfo.isLinux || SystemInfo.isWindows && !SystemInfo.isWinVistaOrNewer) : (property.isEmpty() || Boolean.valueOf(property)); boolean portChecked = false; for (int i = 0; i < portsCount; i++) { int port = firstPort + i; ChannelException channelException = null; try { openChannels.add(bootstrap.bind(new InetSocketAddress(port))); if (!onlyAnyHost) { InetSocketAddress localAddress = null; try { localAddress = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), port); openChannels.add(bootstrap.bind(localAddress)); } catch (UnknownHostException e) { return port; } catch (ChannelException e) { channelException = e; if (!portChecked) { portChecked = true; assert localAddress != null; if (checkPortSafe(localAddress)) { return port; } } } } } catch (ChannelException e) { channelException = e; } if (channelException == null) { return port; } else { if (!openChannels.isEmpty()) { openChannels.close(); openChannels.clear(); } if (portsCount == 1) { throw channelException; } else if (!tryAnyPort && i == (portsCount - 1)) { LOG.error(channelException); } } } if (tryAnyPort) { LOG.info("We cannot bind to our default range, so, try to bind to any free port"); try { Channel channel = bootstrap.bind(new InetSocketAddress(0)); openChannels.add(channel); return ((InetSocketAddress) channel.getLocalAddress()).getPort(); } catch (ChannelException e) { LOG.error(e); } } return -1; } private static boolean checkPortSafe(@NotNull InetSocketAddress localAddress) { LOG.info( "We have tried to bind to 127.0.0.1 host but have got exception (" + SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION + "), " + "so, try to check - are we really need to bind to 127.0.0.1"); try { return checkPort(localAddress); } catch (Throwable innerE) { LOG.error(innerE); return false; } } public void stop() { try { for (ChannelFutureListener listener : closingListeners) { try { listener.operationComplete(null); } catch (Exception e) { LOG.error(e); } } } finally { try { openChannels.close().awaitUninterruptibly(); } finally { channelFactory.releaseExternalResources(); } } } public void addClosingListener(ChannelFutureListener listener) { closingListeners.add(listener); } public Runnable createShutdownTask() { return new Runnable() { @Override public void run() { if (isRunning()) { stop(); } } }; } public void addShutdownHook() { ShutDownTracker.getInstance().registerShutdownTask(createShutdownTask()); } public static void removePluggableHandlers(ChannelPipeline pipeline) { for (String name : pipeline.getNames()) { if (name.startsWith("pluggable_")) { pipeline.remove(name); } } } private static class ChannelPipelineFactoryImpl implements ChannelPipelineFactory { private final Computable<Consumer<ChannelPipeline>[]> pipelineConsumers; private final DefaultHandler defaultHandler; public ChannelPipelineFactoryImpl( Computable<Consumer<ChannelPipeline>[]> pipelineConsumers, DefaultHandler defaultHandler) { this.pipelineConsumers = pipelineConsumers; this.defaultHandler = defaultHandler; } @Override public ChannelPipeline getPipeline() throws Exception { ChannelPipeline pipeline = pipeline( new HttpRequestDecoder(), new HttpChunkAggregator(1048576), new HttpResponseEncoder()); for (Consumer<ChannelPipeline> consumer : pipelineConsumers.compute()) { try { consumer.consume(pipeline); } catch (Throwable e) { LOG.error(e); } } pipeline.addLast("defaultHandler", defaultHandler); return pipeline; } } @ChannelHandler.Sharable private static class DefaultHandler extends SimpleChannelUpstreamHandler { private final ChannelGroup openChannels; public DefaultHandler(ChannelGroup openChannels) { this.openChannels = openChannels; } @Override public void channelOpen(ChannelHandlerContext context, ChannelStateEvent e) { openChannels.add(e.getChannel()); } @Override public void messageReceived(ChannelHandlerContext context, MessageEvent e) throws Exception { if (e.getMessage() instanceof HttpRequest) { HttpRequest message = (HttpRequest) e.getMessage(); HttpResponse response; if (new QueryStringDecoder(message.getUri()).getPath().equals(START_TIME_PATH)) { response = new DefaultHttpResponse(HTTP_1_1, OK); response.setHeader("Access-Control-Allow-Origin", "*"); response.setContent( ChannelBuffers.copiedBuffer(getApplicationStartTime(), CharsetUtil.US_ASCII)); } else { response = new DefaultHttpResponse(HTTP_1_1, NOT_FOUND); } context.getChannel().write(response).addListener(ChannelFutureListener.CLOSE); } } } }
public class ChangeList { private static final Logger LOG = Logger.getInstance(ChangeList.class); public static final Comparator<Change> CHANGE_ORDER = new SimpleChange.ChangeOrder(FragmentSide.SIDE1); private final Project myProject; private final Document[] myDocuments = new Document[2]; private final List<Listener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private ArrayList<Change> myChanges; private ArrayList<Change> myAppliedChanges; public ChangeList(@NotNull Document base, @NotNull Document version, @Nullable Project project) { myDocuments[0] = base; myDocuments[1] = version; myProject = project; } public void addListener(Listener listener) { myListeners.add(listener); } public void removeListener(Listener listener) { LOG.assertTrue(myListeners.remove(listener)); } public void setChanges(@NotNull ArrayList<Change> changes) { if (myChanges != null) { HashSet<Change> newChanges = new HashSet<>(changes); LOG.assertTrue(newChanges.size() == changes.size()); for (Iterator<Change> iterator = myChanges.iterator(); iterator.hasNext(); ) { Change oldChange = iterator.next(); if (!newChanges.contains(oldChange)) { iterator.remove(); oldChange.onRemovedFromList(); } } } for (Change change : changes) { LOG.assertTrue(change.isValid()); } myChanges = new ArrayList<>(changes); myAppliedChanges = new ArrayList<>(); } @Nullable public Project getProject() { return myProject; } @NotNull public List<Change> getChanges() { return new ArrayList<>(myChanges); } public static ChangeList build( @NotNull Document base, @NotNull Document version, @NotNull Project project) throws FilesTooBigForDiffException { ChangeList result = new ChangeList(base, version, project); ArrayList<Change> changes = result.buildChanges(); Collections.sort(changes, CHANGE_ORDER); result.setChanges(changes); return result; } public void setMarkup(final Editor base, final Editor version) { Editor[] editors = {base, version}; for (Change change : myChanges) { change.addMarkup(editors); } } public void updateMarkup() { for (Change change : myChanges) { change.updateMarkup(); } } @NotNull public Document getDocument(@NotNull FragmentSide side) { return myDocuments[side.getIndex()]; } private ArrayList<Change> buildChanges() throws FilesTooBigForDiffException { Document base = getDocument(FragmentSide.SIDE1); DiffString[] baseLines = DiffUtil.convertToLines(base.getText()); Document version = getDocument(FragmentSide.SIDE2); DiffString[] versionLines = DiffUtil.convertToLines(version.getText()); DiffFragment[] fragments = ComparisonPolicy.DEFAULT.buildDiffFragmentsFromLines(baseLines, versionLines); final ArrayList<Change> result = new ArrayList<>(); new DiffFragmentsEnumerator(fragments) { @Override protected void process(DiffFragment fragment) { if (fragment.isEqual()) return; Context context = getContext(); TextRange range1 = context.createRange(FragmentSide.SIDE1); TextRange range2 = context.createRange(FragmentSide.SIDE2); result.add( new SimpleChange( ChangeType.fromDiffFragment(context.getFragment()), range1, range2, ChangeList.this)); } }.execute(); return result; } public Change getChange(int index) { return myChanges.get(index); } private abstract static class DiffFragmentsEnumerator { @NotNull private final DiffFragment[] myFragments; @NotNull private final Context myContext; private DiffFragmentsEnumerator(@NotNull DiffFragment[] fragments) { myContext = new Context(); myFragments = fragments; } public void execute() { for (DiffFragment fragment : myFragments) { myContext.myFragment = fragment; process(fragment); DiffString text1 = fragment.getText1(); DiffString text2 = fragment.getText2(); myContext.myStarts[0] += StringUtil.length(text1); myContext.myStarts[1] += StringUtil.length(text2); myContext.myLines[0] += countLines(text1); myContext.myLines[1] += countLines(text2); } } private static int countLines(@Nullable DiffString text) { if (text == null) return 0; return StringUtil.countNewLines(text); } @NotNull protected Context getContext() { return myContext; } protected abstract void process(DiffFragment fragment); } public static class Context { private DiffFragment myFragment; private final int[] myStarts = {0, 0}; private final int[] myLines = {0, 0}; public DiffFragment getFragment() { return myFragment; } public int getStart(@NotNull FragmentSide side) { return myStarts[side.getIndex()]; } public int getEnd(@NotNull FragmentSide side) { return getStart(side) + StringUtil.length(side.getText(myFragment)); } @NotNull public TextRange createRange(@NotNull FragmentSide side) { return new TextRange(getStart(side), getEnd(side)); } } public int getCount() { return myChanges.size(); } public LineBlocks getNonAppliedLineBlocks() { ArrayList<Change> changes = new ArrayList<>(myChanges); return LineBlocks.fromChanges(changes); } public LineBlocks getLineBlocks() { ArrayList<Change> changes = new ArrayList<>(myChanges); changes.addAll(myAppliedChanges); return LineBlocks.fromChanges(changes); } public void remove(@NotNull Change change) { if (change.getType().isApplied()) { LOG.assertTrue(myAppliedChanges.remove(change), change); } else { LOG.assertTrue(myChanges.remove(change), change); } change.onRemovedFromList(); fireOnChangeRemoved(); } public void apply(@NotNull Change change) { LOG.assertTrue(myChanges.remove(change), change); myAppliedChanges.add(change); fireOnChangeApplied(); } private void fireOnChangeRemoved() { for (Listener listener : myListeners) { listener.onChangeRemoved(this); } } void fireOnChangeApplied() { for (Listener listener : myListeners) { listener.onChangeApplied(this); } } public interface Listener { void onChangeRemoved(ChangeList source); void onChangeApplied(ChangeList source); } }
public class LookupImpl extends LightweightHint implements LookupEx, Disposable, WeighingContext { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.lookup.impl.LookupImpl"); private final LookupOffsets myOffsets; private final Project myProject; private final Editor myEditor; private final JBList myList = new JBList(new CollectionListModel<LookupElement>()) { @Override protected void processKeyEvent(@NotNull final KeyEvent e) { final char keyChar = e.getKeyChar(); if (keyChar == KeyEvent.VK_ENTER || keyChar == KeyEvent.VK_TAB) { IdeFocusManager.getInstance(myProject) .requestFocus(myEditor.getContentComponent(), true) .doWhenDone( new Runnable() { @Override public void run() { IdeEventQueue.getInstance().getKeyEventDispatcher().dispatchKeyEvent(e); } }); return; } super.processKeyEvent(e); } @NotNull @Override protected ExpandableItemsHandler<Integer> createExpandableItemsHandler() { return new CompletionExtender(this); } }; final LookupCellRenderer myCellRenderer; private final List<LookupListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private long myStampShown = 0; private boolean myShown = false; private boolean myDisposed = false; private boolean myHidden = false; private boolean mySelectionTouched; private FocusDegree myFocusDegree = FocusDegree.FOCUSED; private volatile boolean myCalculating; private final Advertiser myAdComponent; volatile int myLookupTextWidth = 50; private boolean myChangeGuard; private volatile LookupArranger myArranger; private LookupArranger myPresentableArranger; private final Map<LookupElement, PrefixMatcher> myMatchers = ContainerUtil.newConcurrentMap(ContainerUtil.<LookupElement>identityStrategy()); private final Map<LookupElement, Font> myCustomFonts = ContainerUtil.createConcurrentWeakMap( 10, 0.75f, Runtime.getRuntime().availableProcessors(), ContainerUtil.<LookupElement>identityStrategy()); private boolean myStartCompletionWhenNothingMatches; boolean myResizePending; private boolean myFinishing; boolean myUpdating; private LookupUi myUi; public LookupImpl(Project project, Editor editor, @NotNull LookupArranger arranger) { super(new JPanel(new BorderLayout())); setForceShowAsPopup(true); setCancelOnClickOutside(false); setResizable(true); AbstractPopup.suppressMacCornerFor(getComponent()); myProject = project; myEditor = editor; myArranger = arranger; myPresentableArranger = arranger; myCellRenderer = new LookupCellRenderer(this); myList.setCellRenderer(myCellRenderer); myList.setFocusable(false); myList.setFixedCellWidth(50); myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); myList.setBackground(LookupCellRenderer.BACKGROUND_COLOR); myList.getExpandableItemsHandler(); myAdComponent = new Advertiser(); myOffsets = new LookupOffsets(editor); final CollectionListModel<LookupElement> model = getListModel(); addEmptyItem(model); updateListHeight(model); addListeners(); } private CollectionListModel<LookupElement> getListModel() { //noinspection unchecked return (CollectionListModel<LookupElement>) myList.getModel(); } public void setArranger(LookupArranger arranger) { myArranger = arranger; } public FocusDegree getFocusDegree() { return myFocusDegree; } @Override public boolean isFocused() { return getFocusDegree() == FocusDegree.FOCUSED; } public void setFocusDegree(FocusDegree focusDegree) { myFocusDegree = focusDegree; } public boolean isCalculating() { return myCalculating; } public void setCalculating(final boolean calculating) { myCalculating = calculating; if (myUi != null) { myUi.setCalculating(calculating); } } public void markSelectionTouched() { if (!ApplicationManager.getApplication().isUnitTestMode()) { ApplicationManager.getApplication().assertIsDispatchThread(); } mySelectionTouched = true; myList.repaint(); } @TestOnly public void setSelectionTouched(boolean selectionTouched) { mySelectionTouched = selectionTouched; } public void resort(boolean addAgain) { final List<LookupElement> items = getItems(); synchronized (myList) { myPresentableArranger.prefixChanged(this); getListModel().removeAll(); } if (addAgain) { for (final LookupElement item : items) { addItem(item, itemMatcher(item)); } } refreshUi(true, true); } public boolean addItem(LookupElement item, PrefixMatcher matcher) { LookupElementPresentation presentation = renderItemApproximately(item); if (containsDummyIdentifier(presentation.getItemText()) || containsDummyIdentifier(presentation.getTailText()) || containsDummyIdentifier(presentation.getTypeText())) { return false; } myMatchers.put(item, matcher); updateLookupWidth(item, presentation); synchronized (myList) { myArranger.addElement(this, item, presentation); } return true; } private static boolean containsDummyIdentifier(@Nullable final String s) { return s != null && s.contains(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED); } public void updateLookupWidth(LookupElement item) { updateLookupWidth(item, renderItemApproximately(item)); } private void updateLookupWidth(LookupElement item, LookupElementPresentation presentation) { final Font customFont = myCellRenderer.getFontAbleToDisplay(presentation); if (customFont != null) { myCustomFonts.put(item, customFont); } int maxWidth = myCellRenderer.updateMaximumWidth(presentation, item); myLookupTextWidth = Math.max(maxWidth, myLookupTextWidth); } @Nullable public Font getCustomFont(LookupElement item, boolean bold) { Font font = myCustomFonts.get(item); return font == null ? null : bold ? font.deriveFont(Font.BOLD) : font; } public void requestResize() { ApplicationManager.getApplication().assertIsDispatchThread(); myResizePending = true; } public Collection<LookupElementAction> getActionsFor(LookupElement element) { final CollectConsumer<LookupElementAction> consumer = new CollectConsumer<LookupElementAction>(); for (LookupActionProvider provider : LookupActionProvider.EP_NAME.getExtensions()) { provider.fillActions(element, this, consumer); } if (!consumer.getResult().isEmpty()) { consumer.consume(new ShowHideIntentionIconLookupAction()); } return consumer.getResult(); } public JList getList() { return myList; } @Override public List<LookupElement> getItems() { synchronized (myList) { return ContainerUtil.findAll( getListModel().toList(), new Condition<LookupElement>() { @Override public boolean value(LookupElement element) { return !(element instanceof EmptyLookupItem); } }); } } public String getAdditionalPrefix() { return myOffsets.getAdditionalPrefix(); } void appendPrefix(char c) { checkValid(); myOffsets.appendPrefix(c); synchronized (myList) { myPresentableArranger.prefixChanged(this); } requestResize(); refreshUi(false, true); ensureSelectionVisible(true); } public void setStartCompletionWhenNothingMatches(boolean startCompletionWhenNothingMatches) { myStartCompletionWhenNothingMatches = startCompletionWhenNothingMatches; } public boolean isStartCompletionWhenNothingMatches() { return myStartCompletionWhenNothingMatches; } public void ensureSelectionVisible(boolean forceTopSelection) { if (isSelectionVisible() && !forceTopSelection) { return; } if (!forceTopSelection) { ListScrollingUtil.ensureIndexIsVisible(myList, myList.getSelectedIndex(), 1); return; } // selected item should be at the top of the visible list int top = myList.getSelectedIndex(); if (top > 0) { top--; // show one element above the selected one to give the hint that there are more // available via scrolling } int firstVisibleIndex = myList.getFirstVisibleIndex(); if (firstVisibleIndex == top) { return; } ListScrollingUtil.ensureRangeIsVisible( myList, top, top + myList.getLastVisibleIndex() - firstVisibleIndex); } boolean truncatePrefix(boolean preserveSelection) { if (!myOffsets.truncatePrefix()) { return false; } if (preserveSelection) { markSelectionTouched(); } boolean shouldUpdate; synchronized (myList) { shouldUpdate = myPresentableArranger == myArranger; myPresentableArranger.prefixChanged(this); } requestResize(); if (shouldUpdate) { refreshUi(false, true); ensureSelectionVisible(true); } return true; } private boolean updateList(boolean onExplicitAction, boolean reused) { if (!ApplicationManager.getApplication().isUnitTestMode()) { ApplicationManager.getApplication().assertIsDispatchThread(); } checkValid(); CollectionListModel<LookupElement> listModel = getListModel(); Pair<List<LookupElement>, Integer> pair; synchronized (myList) { pair = myPresentableArranger.arrangeItems(this, onExplicitAction || reused); } List<LookupElement> items = pair.first; Integer toSelect = pair.second; if (toSelect == null || toSelect < 0 || items.size() > 0 && toSelect >= items.size()) { LOG.error( "Arranger " + myPresentableArranger + " returned invalid selection index=" + toSelect + "; items=" + items); toSelect = 0; } myOffsets.checkMinPrefixLengthChanges(items, this); List<LookupElement> oldModel = listModel.toList(); listModel.removeAll(); if (!items.isEmpty()) { listModel.add(items); } else { addEmptyItem(listModel); } updateListHeight(listModel); myList.setSelectedIndex(toSelect); return !ContainerUtil.equalsIdentity(oldModel, items); } private boolean isSelectionVisible() { return ListScrollingUtil.isIndexFullyVisible(myList, myList.getSelectedIndex()); } private boolean checkReused() { synchronized (myList) { if (myPresentableArranger != myArranger) { myPresentableArranger = myArranger; myOffsets.clearAdditionalPrefix(); myPresentableArranger.prefixChanged(this); return true; } return false; } } private void updateListHeight(ListModel model) { myList.setFixedCellHeight( myCellRenderer .getListCellRendererComponent(myList, model.getElementAt(0), 0, false, false) .getPreferredSize() .height); myList.setVisibleRowCount( Math.min(model.getSize(), UISettings.getInstance().MAX_LOOKUP_LIST_HEIGHT)); } private void addEmptyItem(CollectionListModel<LookupElement> model) { LookupItem<String> item = new EmptyLookupItem( myCalculating ? " " : LangBundle.message("completion.no.suggestions"), false); myMatchers.put(item, new CamelHumpMatcher("")); model.add(item); updateLookupWidth(item); requestResize(); } private static LookupElementPresentation renderItemApproximately(LookupElement item) { final LookupElementPresentation p = new LookupElementPresentation(); item.renderElement(p); return p; } @NotNull @Override public String itemPattern(@NotNull LookupElement element) { String prefix = itemMatcher(element).getPrefix(); String additionalPrefix = getAdditionalPrefix(); return additionalPrefix.isEmpty() ? prefix : prefix + additionalPrefix; } @Override @NotNull public PrefixMatcher itemMatcher(@NotNull LookupElement item) { PrefixMatcher matcher = itemMatcherNullable(item); if (matcher == null) { throw new AssertionError("Item not in lookup: item=" + item + "; lookup items=" + getItems()); } return matcher; } public PrefixMatcher itemMatcherNullable(LookupElement item) { return myMatchers.get(item); } public void finishLookup(final char completionChar) { finishLookup(completionChar, (LookupElement) myList.getSelectedValue()); } public void finishLookup(char completionChar, @Nullable final LookupElement item) { //noinspection deprecation,unchecked if (item == null || item instanceof EmptyLookupItem || item.getObject() instanceof DeferredUserLookupValue && item.as(LookupItem.CLASS_CONDITION_KEY) != null && !((DeferredUserLookupValue) item.getObject()) .handleUserSelection(item.as(LookupItem.CLASS_CONDITION_KEY), myProject)) { doHide(false, true); fireItemSelected(null, completionChar); return; } if (myDisposed) { // DeferredUserLookupValue could close us in any way return; } final PsiFile file = getPsiFile(); boolean writableOk = file == null || FileModificationService.getInstance().prepareFileForWrite(file); if (myDisposed) { // ensureFilesWritable could close us by showing a dialog return; } if (!writableOk) { doHide(false, true); fireItemSelected(null, completionChar); return; } final String prefix = itemPattern(item); boolean plainMatch = ContainerUtil.or( item.getAllLookupStrings(), new Condition<String>() { @Override public boolean value(String s) { return StringUtil.containsIgnoreCase(s, prefix); } }); if (!plainMatch) { FeatureUsageTracker.getInstance() .triggerFeatureUsed(CodeCompletionFeatures.EDITING_COMPLETION_CAMEL_HUMPS); } myFinishing = true; ApplicationManager.getApplication() .runWriteAction( new Runnable() { public void run() { myEditor.getDocument().startGuardedBlockChecking(); try { insertLookupString(item, getPrefixLength(item)); } finally { myEditor.getDocument().stopGuardedBlockChecking(); } } }); if (myDisposed) { // any document listeners could close us return; } doHide(false, true); fireItemSelected(item, completionChar); } public int getPrefixLength(LookupElement item) { return myOffsets.getPrefixLength(item, this); } private void insertLookupString(LookupElement item, final int prefix) { final String lookupString = getCaseCorrectedLookupString(item); final Editor hostEditor = InjectedLanguageUtil.getTopLevelEditor(myEditor); hostEditor .getCaretModel() .runForEachCaret( new CaretAction() { @Override public void perform(Caret caret) { EditorModificationUtil.deleteSelectedText(hostEditor); final int caretOffset = hostEditor.getCaretModel().getOffset(); int lookupStart = Math.max(caretOffset - prefix, 0); int len = hostEditor.getDocument().getTextLength(); LOG.assertTrue( lookupStart >= 0 && lookupStart <= len, "ls: " + lookupStart + " caret: " + caretOffset + " prefix:" + prefix + " doc: " + len); LOG.assertTrue( caretOffset >= 0 && caretOffset <= len, "co: " + caretOffset + " doc: " + len); hostEditor.getDocument().replaceString(lookupStart, caretOffset, lookupString); int offset = lookupStart + lookupString.length(); hostEditor.getCaretModel().moveToOffset(offset); hostEditor.getSelectionModel().removeSelection(); } }); myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); } private String getCaseCorrectedLookupString(LookupElement item) { String lookupString = item.getLookupString(); if (item.isCaseSensitive()) { return lookupString; } final String prefix = itemPattern(item); final int length = prefix.length(); if (length == 0 || !itemMatcher(item).prefixMatches(prefix)) return lookupString; boolean isAllLower = true; boolean isAllUpper = true; boolean sameCase = true; for (int i = 0; i < length && (isAllLower || isAllUpper || sameCase); i++) { final char c = prefix.charAt(i); boolean isLower = Character.isLowerCase(c); boolean isUpper = Character.isUpperCase(c); // do not take this kind of symbols into account ('_', '@', etc.) if (!isLower && !isUpper) continue; isAllLower = isAllLower && isLower; isAllUpper = isAllUpper && isUpper; sameCase = sameCase && isLower == Character.isLowerCase(lookupString.charAt(i)); } if (sameCase) return lookupString; if (isAllLower) return lookupString.toLowerCase(); if (isAllUpper) return StringUtil.toUpperCase(lookupString); return lookupString; } @Override public int getLookupStart() { return myOffsets.getLookupStart(disposeTrace); } public int getLookupOriginalStart() { return myOffsets.getLookupOriginalStart(); } public boolean performGuardedChange(Runnable change) { checkValid(); assert !myChangeGuard : "already in change"; myEditor.getDocument().startGuardedBlockChecking(); myChangeGuard = true; boolean result; try { result = myOffsets.performGuardedChange(change); } finally { myEditor.getDocument().stopGuardedBlockChecking(); myChangeGuard = false; } if (!result || myDisposed) { hide(); return false; } if (isVisible()) { HintManagerImpl.updateLocation(this, myEditor, myUi.calculatePosition().getLocation()); } checkValid(); return true; } @Override public boolean vetoesHiding() { return myChangeGuard; } public boolean isAvailableToUser() { if (ApplicationManager.getApplication().isUnitTestMode()) { return myShown; } return isVisible(); } public boolean isShown() { if (!ApplicationManager.getApplication().isUnitTestMode()) { ApplicationManager.getApplication().assertIsDispatchThread(); } return myShown; } public boolean showLookup() { ApplicationManager.getApplication().assertIsDispatchThread(); checkValid(); LOG.assertTrue(!myShown); myShown = true; myStampShown = System.currentTimeMillis(); if (ApplicationManager.getApplication().isUnitTestMode()) return true; if (!myEditor.getContentComponent().isShowing()) { hide(); return false; } myAdComponent.showRandomText(); myUi = new LookupUi(this, myAdComponent, myList, myProject); myUi.setCalculating(myCalculating); Point p = myUi.calculatePosition().getLocation(); try { HintManagerImpl.getInstanceImpl() .showEditorHint( this, myEditor, p, HintManager.HIDE_BY_ESCAPE | HintManager.UPDATE_BY_SCROLLING, 0, false, HintManagerImpl.createHintHint(myEditor, p, this, HintManager.UNDER) .setAwtTooltip(false)); } catch (Exception e) { LOG.error(e); } if (!isVisible() || !myList.isShowing()) { hide(); return false; } DaemonCodeAnalyzer.getInstance(myProject).disableUpdateByTimer(this); return true; } public Advertiser getAdvertiser() { return myAdComponent; } public boolean mayBeNoticed() { return myStampShown > 0 && System.currentTimeMillis() - myStampShown > 300; } private void addListeners() { myEditor .getDocument() .addDocumentListener( new DocumentAdapter() { @Override public void documentChanged(DocumentEvent e) { if (!myChangeGuard && !myFinishing) { hide(); } } }, this); final CaretListener caretListener = new CaretAdapter() { @Override public void caretPositionChanged(CaretEvent e) { if (!myChangeGuard && !myFinishing) { hide(); } } }; final SelectionListener selectionListener = new SelectionListener() { @Override public void selectionChanged(final SelectionEvent e) { if (!myChangeGuard && !myFinishing) { hide(); } } }; final EditorMouseListener mouseListener = new EditorMouseAdapter() { @Override public void mouseClicked(EditorMouseEvent e) { e.consume(); hide(); } }; myEditor.getCaretModel().addCaretListener(caretListener); myEditor.getSelectionModel().addSelectionListener(selectionListener); myEditor.addEditorMouseListener(mouseListener); Disposer.register( this, new Disposable() { @Override public void dispose() { myEditor.getCaretModel().removeCaretListener(caretListener); myEditor.getSelectionModel().removeSelectionListener(selectionListener); myEditor.removeEditorMouseListener(mouseListener); } }); JComponent editorComponent = myEditor.getContentComponent(); if (editorComponent.isShowing()) { Disposer.register( this, new UiNotifyConnector( editorComponent, new Activatable() { @Override public void showNotify() {} @Override public void hideNotify() { hideLookup(false); } })); } myList.addListSelectionListener( new ListSelectionListener() { private LookupElement oldItem = null; @Override public void valueChanged(@NotNull ListSelectionEvent e) { if (!myUpdating) { final LookupElement item = getCurrentItem(); fireCurrentItemChanged(oldItem, item); oldItem = item; } } }); new ClickListener() { @Override public boolean onClick(@NotNull MouseEvent e, int clickCount) { setFocusDegree(FocusDegree.FOCUSED); markSelectionTouched(); if (clickCount == 2) { CommandProcessor.getInstance() .executeCommand( myProject, new Runnable() { @Override public void run() { finishLookup(NORMAL_SELECT_CHAR); } }, "", null); } return true; } }.installOn(myList); } @Override @Nullable public LookupElement getCurrentItem() { LookupElement item = (LookupElement) myList.getSelectedValue(); return item instanceof EmptyLookupItem ? null : item; } @Override public void setCurrentItem(LookupElement item) { markSelectionTouched(); myList.setSelectedValue(item, false); } @Override public void addLookupListener(LookupListener listener) { myListeners.add(listener); } @Override public void removeLookupListener(LookupListener listener) { myListeners.remove(listener); } @Override public Rectangle getCurrentItemBounds() { int index = myList.getSelectedIndex(); if (index < 0) { LOG.error("No selected element, size=" + getListModel().getSize() + "; items" + getItems()); } Rectangle itmBounds = myList.getCellBounds(index, index); if (itmBounds == null) { LOG.error("No bounds for " + index + "; size=" + getListModel().getSize()); return null; } Point layeredPanePoint = SwingUtilities.convertPoint(myList, itmBounds.x, itmBounds.y, getComponent()); itmBounds.x = layeredPanePoint.x; itmBounds.y = layeredPanePoint.y; return itmBounds; } public void fireItemSelected(@Nullable final LookupElement item, char completionChar) { PsiDocumentManager.getInstance(myProject).commitAllDocuments(); if (!myListeners.isEmpty()) { LookupEvent event = new LookupEvent(this, item, completionChar); for (LookupListener listener : myListeners) { try { listener.itemSelected(event); } catch (Throwable e) { LOG.error(e); } } } } private void fireLookupCanceled(final boolean explicitly) { if (!myListeners.isEmpty()) { LookupEvent event = new LookupEvent(this, explicitly); for (LookupListener listener : myListeners) { try { listener.lookupCanceled(event); } catch (Throwable e) { LOG.error(e); } } } } private void fireCurrentItemChanged( @Nullable LookupElement oldItem, @Nullable LookupElement currentItem) { if (oldItem != currentItem && !myListeners.isEmpty()) { LookupEvent event = new LookupEvent(this, currentItem, (char) 0); for (LookupListener listener : myListeners) { listener.currentItemChanged(event); } } } public boolean fillInCommonPrefix(boolean explicitlyInvoked) { if (explicitlyInvoked) { setFocusDegree(FocusDegree.FOCUSED); } if (explicitlyInvoked && myCalculating) return false; if (!explicitlyInvoked && mySelectionTouched) return false; ListModel listModel = getListModel(); if (listModel.getSize() <= 1) return false; if (listModel.getSize() == 0) return false; final LookupElement firstItem = (LookupElement) listModel.getElementAt(0); if (listModel.getSize() == 1 && firstItem instanceof EmptyLookupItem) return false; final PrefixMatcher firstItemMatcher = itemMatcher(firstItem); final String oldPrefix = firstItemMatcher.getPrefix(); final String presentPrefix = oldPrefix + getAdditionalPrefix(); String commonPrefix = getCaseCorrectedLookupString(firstItem); for (int i = 1; i < listModel.getSize(); i++) { LookupElement item = (LookupElement) listModel.getElementAt(i); if (item instanceof EmptyLookupItem) return false; if (!oldPrefix.equals(itemMatcher(item).getPrefix())) return false; final String lookupString = getCaseCorrectedLookupString(item); final int length = Math.min(commonPrefix.length(), lookupString.length()); if (length < commonPrefix.length()) { commonPrefix = commonPrefix.substring(0, length); } for (int j = 0; j < length; j++) { if (commonPrefix.charAt(j) != lookupString.charAt(j)) { commonPrefix = lookupString.substring(0, j); break; } } if (commonPrefix.length() == 0 || commonPrefix.length() < presentPrefix.length()) { return false; } } if (commonPrefix.equals(presentPrefix)) { return false; } for (int i = 0; i < listModel.getSize(); i++) { LookupElement item = (LookupElement) listModel.getElementAt(i); if (!itemMatcher(item).cloneWithPrefix(commonPrefix).prefixMatches(item)) { return false; } } myOffsets.setInitialPrefix(presentPrefix, explicitlyInvoked); replacePrefix(presentPrefix, commonPrefix); return true; } public void replacePrefix(final String presentPrefix, final String newPrefix) { if (!performGuardedChange( new Runnable() { @Override public void run() { EditorModificationUtil.deleteSelectedText(myEditor); int offset = myEditor.getCaretModel().getOffset(); final int start = offset - presentPrefix.length(); myEditor.getDocument().replaceString(start, offset, newPrefix); Map<LookupElement, PrefixMatcher> newMatchers = new HashMap<LookupElement, PrefixMatcher>(); for (LookupElement item : getItems()) { if (item.isValid()) { PrefixMatcher matcher = itemMatcher(item).cloneWithPrefix(newPrefix); if (matcher.prefixMatches(item)) { newMatchers.put(item, matcher); } } } myMatchers.clear(); myMatchers.putAll(newMatchers); myOffsets.clearAdditionalPrefix(); myEditor.getCaretModel().moveToOffset(start + newPrefix.length()); } })) { return; } synchronized (myList) { myPresentableArranger.prefixChanged(this); } refreshUi(true, true); } @Override @Nullable public PsiFile getPsiFile() { return PsiDocumentManager.getInstance(myProject).getPsiFile(myEditor.getDocument()); } @Override public boolean isCompletion() { return myArranger instanceof CompletionLookupArranger; } @Override public PsiElement getPsiElement() { PsiFile file = getPsiFile(); if (file == null) return null; int offset = getLookupStart(); if (offset > 0) return file.findElementAt(offset - 1); return file.findElementAt(0); } @Override public Editor getEditor() { return myEditor; } @Override public boolean isPositionedAboveCaret() { return myUi != null && myUi.isPositionedAboveCaret(); } @Override public boolean isSelectionTouched() { return mySelectionTouched; } @Override public List<String> getAdvertisements() { return myAdComponent.getAdvertisements(); } @Override public void hide() { hideLookup(true); } public void hideLookup(boolean explicitly) { ApplicationManager.getApplication().assertIsDispatchThread(); if (myHidden) return; doHide(true, explicitly); } private void doHide(final boolean fireCanceled, final boolean explicitly) { if (myDisposed) { LOG.error(disposeTrace); } else { myHidden = true; try { super.hide(); Disposer.dispose(this); assert myDisposed; } catch (Throwable e) { LOG.error(e); } } if (fireCanceled) { fireLookupCanceled(explicitly); } } public void restorePrefix() { myOffsets.restorePrefix(); } private static String staticDisposeTrace = null; private String disposeTrace = null; public static String getLastLookupDisposeTrace() { return staticDisposeTrace; } @Override public void dispose() { assert ApplicationManager.getApplication().isDispatchThread(); assert myHidden; if (myDisposed) { LOG.error(disposeTrace); return; } myOffsets.disposeMarkers(); myDisposed = true; disposeTrace = DebugUtil.currentStackTrace() + "\n============"; //noinspection AssignmentToStaticFieldFromInstanceMethod staticDisposeTrace = disposeTrace; } public void refreshUi(boolean mayCheckReused, boolean onExplicitAction) { assert !myUpdating; LookupElement prevItem = getCurrentItem(); myUpdating = true; try { final boolean reused = mayCheckReused && checkReused(); boolean selectionVisible = isSelectionVisible(); boolean itemsChanged = updateList(onExplicitAction, reused); if (isVisible()) { LOG.assertTrue(!ApplicationManager.getApplication().isUnitTestMode()); myUi.refreshUi(selectionVisible, itemsChanged, reused, onExplicitAction); } } finally { myUpdating = false; fireCurrentItemChanged(prevItem, getCurrentItem()); } } public void markReused() { synchronized (myList) { myArranger = myArranger.createEmptyCopy(); } requestResize(); } public void addAdvertisement(@NotNull final String text, final @Nullable Color bgColor) { if (containsDummyIdentifier(text)) { return; } myAdComponent.addAdvertisement(text, bgColor); requestResize(); } public boolean isLookupDisposed() { return myDisposed; } public void checkValid() { if (myDisposed) { throw new AssertionError("Disposed at: " + disposeTrace); } } @Override public void showItemPopup(JBPopup hint) { final Rectangle bounds = getCurrentItemBounds(); hint.show(new RelativePoint(getComponent(), new Point(bounds.x + bounds.width, bounds.y))); } @Override public boolean showElementActions() { if (!isVisible()) return false; final LookupElement element = getCurrentItem(); if (element == null) { return false; } final Collection<LookupElementAction> actions = getActionsFor(element); if (actions.isEmpty()) { return false; } showItemPopup( JBPopupFactory.getInstance() .createListPopup(new LookupActionsStep(actions, this, element))); return true; } public Map<LookupElement, StringBuilder> getRelevanceStrings() { synchronized (myList) { return myPresentableArranger.getRelevanceStrings(); } } public enum FocusDegree { FOCUSED, SEMI_FOCUSED, UNFOCUSED } }
public class FileTemplateConfigurable implements Configurable, Configurable.NoScroll { private static final Logger LOG = Logger.getInstance("#com.intellij.ide.fileTemplates.impl.FileTemplateConfigurable"); @NonNls private static final String EMPTY_HTML = "<html></html>"; @NonNls private static final String CONTENT_TYPE_PLAIN = "text/plain"; private JPanel myMainPanel; private FileTemplate myTemplate; private PsiFile myFile; private Editor myTemplateEditor; private JTextField myNameField; private JTextField myExtensionField; private JCheckBox myAdjustBox; private JPanel myTopPanel; private JEditorPane myDescriptionComponent; private boolean myModified = false; private URL myDefaultDescriptionUrl; private final Project myProject; private final List<ChangeListener> myChangeListeners = ContainerUtil.createLockFreeCopyOnWriteList();; private Splitter mySplitter; private final FileType myVelocityFileType = FileTypeManager.getInstance().getFileTypeByExtension("ft"); private JPanel myDescriptionPanel; public FileTemplateConfigurable() { Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext()); myProject = project != null ? project : ProjectManager.getInstance().getDefaultProject(); } public FileTemplate getTemplate() { return myTemplate; } public void setTemplate(FileTemplate template, URL defaultDescription) { myDefaultDescriptionUrl = defaultDescription; myTemplate = template; if (myMainPanel != null) { reset(); myNameField.selectAll(); myExtensionField.selectAll(); } } public void setShowInternalMessage(String message) { if (message == null) { myTopPanel.removeAll(); myTopPanel.add( new JLabel(IdeBundle.message("label.name")), new GridBagConstraints( 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 2), 0, 0)); myTopPanel.add( myNameField, new GridBagConstraints( 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 2, 0, 2), 0, 0)); myTopPanel.add( new JLabel(IdeBundle.message("label.extension")), new GridBagConstraints( 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 2, 0, 2), 0, 0)); myTopPanel.add( myExtensionField, new GridBagConstraints( 3, 0, 1, 1, .3, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 2, 0, 0), 0, 0)); myExtensionField.setColumns(7); } else { myTopPanel.removeAll(); myTopPanel.add( new JLabel(message), new GridBagConstraints( 0, 0, 4, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); myTopPanel.add( Box.createVerticalStrut(myNameField.getPreferredSize().height), new GridBagConstraints( 4, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); } myMainPanel.revalidate(); myTopPanel.repaint(); } public void setShowAdjustCheckBox(boolean show) { myAdjustBox.setEnabled(show); } @Override public String getDisplayName() { return IdeBundle.message("title.file.templates"); } @Override public String getHelpTopic() { return null; } @Override public JComponent createComponent() { myMainPanel = new JPanel(new GridBagLayout()); myNameField = new JTextField(); myExtensionField = new JTextField(); mySplitter = new Splitter(true, 0.4f); myTemplateEditor = createEditor(); myDescriptionComponent = new JEditorPane(UIUtil.HTML_MIME, EMPTY_HTML); myDescriptionComponent.setEditable(false); myAdjustBox = new JCheckBox(IdeBundle.message("checkbox.reformat.according.to.style")); myTopPanel = new JPanel(new GridBagLayout()); myDescriptionPanel = new JPanel(new GridBagLayout()); myDescriptionPanel.add( SeparatorFactory.createSeparator(IdeBundle.message("label.description"), null), new GridBagConstraints( 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 2, 0), 0, 0)); myDescriptionPanel.add( ScrollPaneFactory.createScrollPane(myDescriptionComponent), new GridBagConstraints( 0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(2, 0, 0, 0), 0, 0)); myMainPanel.add( myTopPanel, new GridBagConstraints( 0, 0, 4, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 2, 0), 0, 0)); myMainPanel.add( myAdjustBox, new GridBagConstraints( 0, 1, 4, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(2, 0, 2, 0), 0, 0)); myMainPanel.add( mySplitter, new GridBagConstraints( 0, 2, 4, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(2, 0, 0, 0), 0, 0)); mySplitter.setSecondComponent(myDescriptionPanel); setShowInternalMessage(null); myNameField.addFocusListener( new FocusAdapter() { @Override public void focusLost(FocusEvent e) { onNameChanged(); } }); myExtensionField.addFocusListener( new FocusAdapter() { @Override public void focusLost(FocusEvent e) { onNameChanged(); } }); myMainPanel.setPreferredSize(new Dimension(400, 300)); return myMainPanel; } private Editor createEditor() { EditorFactory editorFactory = EditorFactory.getInstance(); Document doc = myFile == null ? editorFactory.createDocument(myTemplate == null ? "" : myTemplate.getText()) : PsiDocumentManager.getInstance(myFile.getProject()).getDocument(myFile); Editor editor = myProject == null ? editorFactory.createEditor(doc) : editorFactory.createEditor(doc, myProject); EditorSettings editorSettings = editor.getSettings(); editorSettings.setVirtualSpace(false); editorSettings.setLineMarkerAreaShown(false); editorSettings.setIndentGuidesShown(false); editorSettings.setLineNumbersShown(false); editorSettings.setFoldingOutlineShown(false); editorSettings.setAdditionalColumnsCount(3); editorSettings.setAdditionalLinesCount(3); EditorColorsScheme scheme = editor.getColorsScheme(); scheme.setColor(EditorColors.CARET_ROW_COLOR, null); editor .getDocument() .addDocumentListener( new DocumentAdapter() { @Override public void documentChanged(DocumentEvent e) { onTextChanged(); } }); ((EditorEx) editor).setHighlighter(createHighlighter()); mySplitter.setFirstComponent(editor.getComponent()); return editor; } private void onTextChanged() { myModified = true; } public String getNameValue() { return myNameField.getText(); } public String getExtensionValue() { return myExtensionField.getText(); } private void onNameChanged() { ChangeEvent event = new ChangeEvent(this); for (ChangeListener changeListener : myChangeListeners) { changeListener.stateChanged(event); } } public void addChangeListener(ChangeListener listener) { if (!myChangeListeners.contains(listener)) { myChangeListeners.add(listener); } } public void removeChangeListener(ChangeListener listener) { myChangeListeners.remove(listener); } @Override public boolean isModified() { if (myModified) { return true; } String name = (myTemplate == null) ? "" : myTemplate.getName(); String extension = (myTemplate == null) ? "" : myTemplate.getExtension(); if (!Comparing.equal(name, myNameField.getText())) { return true; } if (!Comparing.equal(extension, myExtensionField.getText())) { return true; } if (myTemplate != null) { if (myTemplate.isReformatCode() != myAdjustBox.isSelected()) { return true; } } return false; } @Override public void apply() throws ConfigurationException { if (myTemplate != null) { myTemplate.setText(myTemplateEditor.getDocument().getText()); String name = myNameField.getText(); String extension = myExtensionField.getText(); int lastDotIndex = extension.lastIndexOf("."); if (lastDotIndex >= 0) { name += extension.substring(0, lastDotIndex + 1); extension = extension.substring(lastDotIndex + 1); } if (name.length() == 0 || !isValidFilename(name + "." + extension)) { throw new ConfigurationException( IdeBundle.message("error.invalid.template.file.name.or.extension")); } myTemplate.setName(name); myTemplate.setExtension(extension); myTemplate.setReformatCode(myAdjustBox.isSelected()); } myModified = false; } // TODO: needs to be generalized someday for other profiles private static boolean isValidFilename(final String filename) { if (filename.contains("/") || filename.contains("\\") || filename.contains(":")) { return false; } final File tempFile = new File(FileUtil.getTempDirectory() + File.separator + filename); return FileUtil.ensureCanCreateFile(tempFile); } @Override public void reset() { final String text = (myTemplate == null) ? "" : myTemplate.getText(); String name = (myTemplate == null) ? "" : myTemplate.getName(); String extension = (myTemplate == null) ? "" : myTemplate.getExtension(); String description = (myTemplate == null) ? "" : myTemplate.getDescription(); if ((description.length() == 0) && (myDefaultDescriptionUrl != null)) { try { description = UrlUtil.loadText(myDefaultDescriptionUrl); } catch (IOException e) { LOG.error(e); } } EditorFactory.getInstance().releaseEditor(myTemplateEditor); myFile = createFile(text, name); myTemplateEditor = createEditor(); boolean adjust = (myTemplate != null) && myTemplate.isReformatCode(); myNameField.setText(name); myExtensionField.setText(extension); myAdjustBox.setSelected(adjust); String desc = description.length() > 0 ? description : EMPTY_HTML; // [myakovlev] do not delete these stupid lines! Or you get Exception! myDescriptionComponent.setContentType(CONTENT_TYPE_PLAIN); myDescriptionComponent.setEditable(true); myDescriptionComponent.setText(desc); myDescriptionComponent.setContentType(UIUtil.HTML_MIME); myDescriptionComponent.setText(desc); myDescriptionComponent.setCaretPosition(0); myDescriptionComponent.setEditable(false); myDescriptionPanel.setVisible(StringUtil.isNotEmpty(description)); myNameField.setEditable((myTemplate != null) && (!myTemplate.isDefault())); myExtensionField.setEditable((myTemplate != null) && (!myTemplate.isDefault())); myModified = false; } @Nullable private PsiFile createFile(final String text, final String name) { if (myTemplate == null || myProject == null) return null; final FileType fileType = myVelocityFileType; if (fileType == FileTypes.UNKNOWN) return null; final PsiFile file = PsiFileFactory.getInstance(myProject) .createFileFromText(name + ".txt.ft", fileType, text, 0, true); file.getViewProvider() .putUserData( FileTemplateManager.DEFAULT_TEMPLATE_PROPERTIES, FileTemplateManager.getInstance().getDefaultProperties(myProject)); return file; } @Override public void disposeUIResources() { myMainPanel = null; if (myTemplateEditor != null) { EditorFactory.getInstance().releaseEditor(myTemplateEditor); myTemplateEditor = null; } myFile = null; } private EditorHighlighter createHighlighter() { if (myTemplate != null && myProject != null && myVelocityFileType != FileTypes.UNKNOWN) { return EditorHighlighterFactory.getInstance() .createEditorHighlighter( myProject, new LightVirtualFile("aaa." + myTemplate.getExtension() + ".ft")); } FileType fileType = null; if (myTemplate != null) { fileType = FileTypeManager.getInstance().getFileTypeByExtension(myTemplate.getExtension()); } if (fileType == null) { fileType = FileTypes.PLAIN_TEXT; } SyntaxHighlighter originalHighlighter = SyntaxHighlighterFactory.getSyntaxHighlighter(fileType, null, null); if (originalHighlighter == null) originalHighlighter = new PlainSyntaxHighlighter(); return new LexerEditorHighlighter( new TemplateHighlighter(originalHighlighter), EditorColorsManager.getInstance().getGlobalScheme()); } private static final TokenSet TOKENS_TO_MERGE = TokenSet.create(FileTemplateTokenType.TEXT); private static class TemplateHighlighter extends SyntaxHighlighterBase { private final Lexer myLexer; private final SyntaxHighlighter myOriginalHighlighter; public TemplateHighlighter(SyntaxHighlighter original) { myOriginalHighlighter = original; Lexer originalLexer = original.getHighlightingLexer(); Lexer templateLexer = new FlexAdapter(new FileTemplateTextLexer()); templateLexer = new MergingLexerAdapter(templateLexer, TOKENS_TO_MERGE); myLexer = new CompositeLexer(originalLexer, templateLexer) { @Override protected IElementType getCompositeTokenType(IElementType type1, IElementType type2) { if (type2 == FileTemplateTokenType.MACRO || type2 == FileTemplateTokenType.DIRECTIVE) { return type2; } else { return type1; } } }; } @Override @NotNull public Lexer getHighlightingLexer() { return myLexer; } @Override @NotNull public TextAttributesKey[] getTokenHighlights(IElementType tokenType) { if (tokenType == FileTemplateTokenType.MACRO) { return pack( myOriginalHighlighter.getTokenHighlights(tokenType), TemplateColors.TEMPLATE_VARIABLE_ATTRIBUTES); } else if (tokenType == FileTemplateTokenType.DIRECTIVE) { return pack( myOriginalHighlighter.getTokenHighlights(tokenType), TemplateColors.TEMPLATE_VARIABLE_ATTRIBUTES); } return myOriginalHighlighter.getTokenHighlights(tokenType); } } public void focusToNameField() { myNameField.selectAll(); myNameField.requestFocus(); } public void focusToExtensionField() { myExtensionField.selectAll(); myExtensionField.requestFocus(); } }
class Browser extends JPanel { private static final String UNDER_CONSTRUCTION = InspectionsBundle.message("inspection.tool.description.under.construction.text"); private final List<ClickListener> myClickListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private RefEntity myCurrentEntity; private JEditorPane myHTMLViewer; private final InspectionResultsView myView; private final HyperlinkListener myHyperLinkListener; private CommonProblemDescriptor myCurrentDescriptor; public static class ClickEvent { public static final int REF_ELEMENT = 1; public static final int FILE_OFFSET = 2; private final VirtualFile myFile; private final int myStartPosition; private final int myEndPosition; private final RefElement refElement; private final int myEventType; public ClickEvent(VirtualFile myFile, int myStartPosition, int myEndPosition) { this.myFile = myFile; this.myStartPosition = myStartPosition; this.myEndPosition = myEndPosition; myEventType = FILE_OFFSET; refElement = null; } public int getEventType() { return myEventType; } public VirtualFile getFile() { return myFile; } public int getStartOffset() { return myStartPosition; } public int getEndOffset() { return myEndPosition; } public RefElement getClickedElement() { return refElement; } } public void dispose() { removeAll(); if (myHTMLViewer != null) { myHTMLViewer.removeHyperlinkListener(myHyperLinkListener); myHTMLViewer = null; } myClickListeners.clear(); } public interface ClickListener { void referenceClicked(ClickEvent e); } private void showPageFromHistory(RefEntity newEntity) { InspectionTool tool = getTool(newEntity); try { if (tool instanceof DescriptorProviderInspection && !(tool instanceof CommonInspectionToolWrapper)) { showEmpty(); } else { try { String html = generateHTML(newEntity, tool); myHTMLViewer.read(new StringReader(html), null); setupStyle(); myHTMLViewer.setCaretPosition(0); } catch (Exception e) { showEmpty(); } } } finally { myCurrentEntity = newEntity; myCurrentDescriptor = null; } } public void showPageFor(RefEntity refEntity, CommonProblemDescriptor descriptor) { try { String html = generateHTML(refEntity, descriptor); myHTMLViewer.read(new StringReader(html), null); setupStyle(); myHTMLViewer.setCaretPosition(0); } catch (Exception e) { showEmpty(); } finally { myCurrentEntity = refEntity; myCurrentDescriptor = descriptor; } } public void showPageFor(RefEntity newEntity) { if (newEntity == null) { showEmpty(); return; } // multiple problems for one entity -> refresh browser showPageFromHistory(newEntity.getRefManager().getRefinedElement(newEntity)); } public Browser(InspectionResultsView view) { super(new BorderLayout()); myView = view; myCurrentEntity = null; myCurrentDescriptor = null; myHTMLViewer = new JEditorPane( UIUtil.HTML_MIME, InspectionsBundle.message("inspection.offline.view.empty.browser.text")); myHTMLViewer.setEditable(false); myHyperLinkListener = new HyperlinkListener() { @Override public void hyperlinkUpdate(HyperlinkEvent e) { if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { JEditorPane pane = (JEditorPane) e.getSource(); if (e instanceof HTMLFrameHyperlinkEvent) { HTMLFrameHyperlinkEvent evt = (HTMLFrameHyperlinkEvent) e; HTMLDocument doc = (HTMLDocument) pane.getDocument(); doc.processHTMLFrameHyperlinkEvent(evt); } else { try { URL url = e.getURL(); @NonNls String ref = url.getRef(); if (ref.startsWith("pos:")) { int delimeterPos = ref.indexOf(':', "pos:".length() + 1); String startPosition = ref.substring("pos:".length(), delimeterPos); String endPosition = ref.substring(delimeterPos + 1); Integer textStartOffset = new Integer(startPosition); Integer textEndOffset = new Integer(endPosition); String fileURL = url.toExternalForm(); fileURL = fileURL.substring(0, fileURL.indexOf('#')); VirtualFile vFile = VirtualFileManager.getInstance().findFileByUrl(fileURL); if (vFile != null) { fireClickEvent(vFile, textStartOffset.intValue(), textEndOffset.intValue()); } } else if (ref.startsWith("descr:")) { if (myCurrentDescriptor instanceof ProblemDescriptor) { PsiElement psiElement = ((ProblemDescriptor) myCurrentDescriptor).getPsiElement(); if (psiElement == null) return; VirtualFile vFile = psiElement.getContainingFile().getVirtualFile(); if (vFile != null) { TextRange range = ((ProblemDescriptorBase) myCurrentDescriptor).getTextRange(); fireClickEvent(vFile, range.getStartOffset(), range.getEndOffset()); } } } else if (ref.startsWith("invoke:")) { int actionNumber = Integer.parseInt(ref.substring("invoke:".length())); getTool() .getQuickFixes(new RefElement[] {(RefElement) myCurrentEntity})[ actionNumber] .doApplyFix(new RefElement[] {(RefElement) myCurrentEntity}, myView); } else if (ref.startsWith("invokelocal:")) { int actionNumber = Integer.parseInt(ref.substring("invokelocal:".length())); if (actionNumber > -1) { invokeLocalFix(actionNumber); } } else if (ref.startsWith("suppress:")) { final SuppressActionWrapper.SuppressTreeAction[] suppressTreeActions = new SuppressActionWrapper( myView.getProject(), getTool(), myView.getTree().getSelectionPaths()) .getChildren(null); final List<AnAction> activeActions = new ArrayList<AnAction>(); for (SuppressActionWrapper.SuppressTreeAction suppressTreeAction : suppressTreeActions) { if (suppressTreeAction.isAvailable()) activeActions.add(suppressTreeAction); } if (!activeActions.isEmpty()) { int actionNumber = Integer.parseInt(ref.substring("suppress:".length())); if (actionNumber > -1 && activeActions.size() > actionNumber) { activeActions.get(actionNumber).actionPerformed(null); } } } else { int offset = Integer.parseInt(ref); String fileURL = url.toExternalForm(); fileURL = fileURL.substring(0, fileURL.indexOf('#')); VirtualFile vFile = VirtualFileManager.getInstance().findFileByUrl(fileURL); if (vFile == null) { vFile = VfsUtil.findFileByURL(url); } if (vFile != null) { fireClickEvent(vFile, offset, offset); } } } catch (Throwable t) { // ??? } } } } }; myHTMLViewer.addHyperlinkListener(myHyperLinkListener); final JScrollPane pane = ScrollPaneFactory.createScrollPane(myHTMLViewer); pane.setBorder(null); add(pane, BorderLayout.CENTER); setupStyle(); } private void setupStyle() { Document document = myHTMLViewer.getDocument(); if (!(document instanceof StyledDocument)) { return; } StyledDocument styledDocument = (StyledDocument) document; EditorColorsManager colorsManager = EditorColorsManager.getInstance(); EditorColorsScheme scheme = colorsManager.getGlobalScheme(); Style style = styledDocument.addStyle("active", null); StyleConstants.setFontFamily(style, scheme.getEditorFontName()); StyleConstants.setFontSize(style, scheme.getEditorFontSize()); styledDocument.setCharacterAttributes(0, document.getLength(), style, false); } public void addClickListener(ClickListener listener) { myClickListeners.add(listener); } private void fireClickEvent(VirtualFile file, int startPosition, int endPosition) { ClickEvent e = new ClickEvent(file, startPosition, endPosition); for (ClickListener listener : myClickListeners) { listener.referenceClicked(e); } } private String generateHTML(final RefEntity refEntity, final InspectionTool tool) { final StringBuffer buf = new StringBuffer(); if (refEntity instanceof RefElement) { final Runnable action = new Runnable() { @Override public void run() { tool.getComposer().compose(buf, refEntity); } }; ApplicationManager.getApplication().runReadAction(action); } else { tool.getComposer().compose(buf, refEntity); } uppercaseFirstLetter(buf); if (refEntity instanceof RefElement) { appendSuppressSection(buf); } insertHeaderFooter(buf); return buf.toString(); } @SuppressWarnings({"HardCodedStringLiteral"}) private static void insertHeaderFooter(final StringBuffer buf) { buf.insert(0, "<HTML><BODY>"); buf.append("</BODY></HTML>"); } private String generateHTML(final RefEntity refEntity, final CommonProblemDescriptor descriptor) { final StringBuffer buf = new StringBuffer(); final Runnable action = new Runnable() { @Override public void run() { InspectionTool tool = getTool(refEntity); tool.getComposer().compose(buf, refEntity, descriptor); } }; ApplicationManager.getApplication().runReadAction(action); uppercaseFirstLetter(buf); if (refEntity instanceof RefElement) { appendSuppressSection(buf); } insertHeaderFooter(buf); return buf.toString(); } private InspectionTool getTool(final RefEntity refEntity) { InspectionTool tool = getTool(); assert tool != null; final GlobalInspectionContextImpl manager = tool.getContext(); if (manager == null) return tool; if (refEntity instanceof RefElement) { PsiElement element = ((RefElement) refEntity).getElement(); if (element == null) return tool; final InspectionProfileWrapper profileWrapper = InspectionProjectProfileManagerImpl.getInstanceImpl(manager.getProject()) .getProfileWrapper(); if (profileWrapper == null) return tool; tool = profileWrapper.getInspectionTool(tool.getShortName(), element); } return tool; } private void appendSuppressSection(final StringBuffer buf) { final InspectionTool tool = getTool(); if (tool != null) { final HighlightDisplayKey key = HighlightDisplayKey.find(tool.getShortName()); if (key != null) { // dummy entry points final SuppressActionWrapper.SuppressTreeAction[] suppressActions = new SuppressActionWrapper( myView.getProject(), tool, myView.getTree().getSelectionPaths()) .getChildren(null); if (suppressActions.length > 0) { final List<AnAction> activeSuppressActions = new ArrayList<AnAction>(); for (SuppressActionWrapper.SuppressTreeAction suppressAction : suppressActions) { if (suppressAction.isAvailable()) { activeSuppressActions.add(suppressAction); } } if (!activeSuppressActions.isEmpty()) { int idx = 0; @NonNls final String br = "<br>"; buf.append(br); HTMLComposerImpl.appendHeading( buf, InspectionsBundle.message("inspection.export.results.suppress")); for (AnAction suppressAction : activeSuppressActions) { buf.append(br); if (idx == activeSuppressActions.size() - 1) { buf.append(br); } HTMLComposer.appendAfterHeaderIndention(buf); @NonNls final String href = "<a HREF=\"file://bred.txt#suppress:" + idx + "\">" + suppressAction.getTemplatePresentation().getText() + "</a>"; buf.append(href); idx++; } } } } } } private static void uppercaseFirstLetter(final StringBuffer buf) { if (buf.length() > 1) { char[] firstLetter = new char[1]; buf.getChars(0, 1, firstLetter, 0); buf.setCharAt(0, Character.toUpperCase(firstLetter[0])); } } @SuppressWarnings({"HardCodedStringLiteral"}) public void showEmpty() { myCurrentEntity = null; try { myHTMLViewer.read(new StringReader("<html><body></body></html>"), null); } catch (IOException e) { // can't be } } public void showDescription(InspectionTool tool) { if (tool.getShortName().length() == 0) { showEmpty(); return; } @NonNls StringBuffer page = new StringBuffer(); page.append("<table border='0' cellspacing='0' cellpadding='0' width='100%'>"); page.append("<tr><td colspan='2'>"); HTMLComposer.appendHeading( page, InspectionsBundle.message("inspection.tool.in.browser.id.title")); page.append("</td></tr>"); page.append("<tr><td width='37'></td>" + "<td>"); page.append(tool.getShortName()); page.append("</td></tr>"); page.append("<tr height='10'></tr>"); page.append("<tr><td colspan='2'>"); HTMLComposer.appendHeading( page, InspectionsBundle.message("inspection.tool.in.browser.description.title")); page.append("</td></tr>"); page.append("<tr><td width='37'></td>" + "<td>"); @NonNls final String underConstruction = "<b>" + UNDER_CONSTRUCTION + "</b></html>"; try { @NonNls String description = tool.loadDescription(); if (description == null) { description = underConstruction; } page.append(UIUtil.getHtmlBody(description)); page.append("</td></tr></table>"); myHTMLViewer.setText(XmlStringUtil.wrapInHtml(page)); setupStyle(); } finally { myCurrentEntity = null; } } @Nullable private InspectionTool getTool() { if (myView != null) { return myView.getTree().getSelectedTool(); } return null; } public void invokeLocalFix(int idx) { if (myView.getTree().getSelectionCount() != 1) return; final InspectionTreeNode node = (InspectionTreeNode) myView.getTree().getSelectionPath().getLastPathComponent(); if (node instanceof ProblemDescriptionNode) { final ProblemDescriptionNode problemNode = (ProblemDescriptionNode) node; final CommonProblemDescriptor descriptor = problemNode.getDescriptor(); final RefEntity element = problemNode.getElement(); invokeFix(element, descriptor, idx); } else if (node instanceof RefElementNode) { RefElementNode elementNode = (RefElementNode) node; RefEntity element = elementNode.getElement(); CommonProblemDescriptor descriptor = elementNode.getProblem(); if (descriptor != null) { invokeFix(element, descriptor, idx); } } } private void invokeFix( final RefEntity element, final CommonProblemDescriptor descriptor, final int idx) { final QuickFix[] fixes = descriptor.getFixes(); if (fixes != null && fixes.length > idx && fixes[idx] != null) { if (element instanceof RefElement) { PsiElement psiElement = ((RefElement) element).getElement(); if (psiElement != null && psiElement.isValid()) { if (!FileModificationService.getInstance().preparePsiElementForWrite(psiElement)) return; performFix(element, descriptor, idx, fixes[idx]); } } else { performFix(element, descriptor, idx, fixes[idx]); } } } private void performFix( final RefEntity element, final CommonProblemDescriptor descriptor, final int idx, final QuickFix fix) { final Runnable command = new Runnable() { @Override public void run() { ApplicationManager.getApplication() .runWriteAction( new Runnable() { @Override public void run() { final PsiModificationTracker tracker = PsiManager.getInstance(myView.getProject()).getModificationTracker(); final long startCount = tracker.getModificationCount(); CommandProcessor.getInstance() .markCurrentCommandAsGlobal(myView.getProject()); // CCE here means QuickFix was incorrectly inherited fix.applyFix(myView.getProject(), descriptor); if (startCount != tracker.getModificationCount()) { final DescriptorProviderInspection tool = ((DescriptorProviderInspection) myView.getTree().getSelectedTool()); if (tool != null) { tool.ignoreProblem(element, descriptor, idx); } myView.updateView(false); } } }); } }; CommandProcessor.getInstance() .executeCommand(myView.getProject(), command, fix.getName(), null); } }
public class FoldingModelImpl implements FoldingModelEx, PrioritizedDocumentListener, Dumpable { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorFoldingModelImpl"); private static final Key<LogicalPosition> SAVED_CARET_POSITION = Key.create("saved.position.before.folding"); private final List<FoldingListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private boolean myIsFoldingEnabled; private final EditorImpl myEditor; private final FoldRegionsTree myFoldTree; private TextAttributes myFoldTextAttributes; private boolean myIsBatchFoldingProcessing; private boolean myDoNotCollapseCaret; private boolean myFoldRegionsProcessed; private int mySavedCaretShift; private final MultiMap<FoldingGroup, FoldRegion> myGroups = new MultiMap<FoldingGroup, FoldRegion>(); private boolean myDocumentChangeProcessed = true; public FoldingModelImpl(EditorImpl editor) { myEditor = editor; myIsFoldingEnabled = true; myIsBatchFoldingProcessing = false; myDoNotCollapseCaret = false; myFoldTree = new FoldRegionsTree() { @Override protected boolean isFoldingEnabled() { return FoldingModelImpl.this.isFoldingEnabled(); } }; myFoldRegionsProcessed = false; refreshSettings(); } @NotNull public List<FoldRegion> getGroupedRegions(@NotNull FoldingGroup group) { return (List<FoldRegion>) myGroups.get(group); } @NotNull public FoldRegion getFirstRegion(@NotNull FoldingGroup group, FoldRegion child) { final List<FoldRegion> regions = getGroupedRegions(group); if (regions.isEmpty()) { final boolean inAll = Arrays.asList(getAllFoldRegions()).contains(child); throw new AssertionError( "Folding group without children; the known child is in all: " + inAll); } FoldRegion main = regions.get(0); for (int i = 1; i < regions.size(); i++) { FoldRegion region = regions.get(i); if (main.getStartOffset() > region.getStartOffset()) { main = region; } } return main; } public int getEndOffset(@NotNull FoldingGroup group) { final List<FoldRegion> regions = getGroupedRegions(group); int endOffset = 0; for (FoldRegion region : regions) { if (region.isValid()) { endOffset = Math.max(endOffset, region.getEndOffset()); } } return endOffset; } public void refreshSettings() { myFoldTextAttributes = myEditor.getColorsScheme().getAttributes(EditorColors.FOLDED_TEXT_ATTRIBUTES); } @Override public boolean isFoldingEnabled() { return myIsFoldingEnabled; } @Override public boolean isOffsetCollapsed(int offset) { assertReadAccess(); return getCollapsedRegionAtOffset(offset) != null; } private boolean isOffsetInsideCollapsedRegion(int offset) { assertReadAccess(); FoldRegion region = getCollapsedRegionAtOffset(offset); return region != null && region.getStartOffset() < offset; } private static void assertIsDispatchThreadForEditor() { ApplicationManagerEx.getApplicationEx().assertIsDispatchThread(); } private static void assertReadAccess() { ApplicationManagerEx.getApplicationEx().assertReadAccessAllowed(); } @Override public void setFoldingEnabled(boolean isEnabled) { assertIsDispatchThreadForEditor(); myIsFoldingEnabled = isEnabled; } @Override public FoldRegion addFoldRegion(int startOffset, int endOffset, @NotNull String placeholderText) { FoldRegion region = createFoldRegion(startOffset, endOffset, placeholderText, null, false); if (region == null) return null; if (!addFoldRegion(region)) { region.dispose(); return null; } return region; } @Override public boolean addFoldRegion(@NotNull final FoldRegion region) { assertIsDispatchThreadForEditor(); if (!isFoldingEnabled()) { return false; } if (!myIsBatchFoldingProcessing) { LOG.error("Fold regions must be added or removed inside batchFoldProcessing() only."); return false; } myFoldRegionsProcessed = true; if (myFoldTree.addRegion(region)) { final FoldingGroup group = region.getGroup(); if (group != null) { myGroups.putValue(group, region); } for (FoldingListener listener : myListeners) { listener.onFoldRegionStateChange(region); } return true; } return false; } @Override public void runBatchFoldingOperation(@NotNull Runnable operation) { runBatchFoldingOperation(operation, false, true); } @Override public void runBatchFoldingOperation(@NotNull Runnable operation, boolean moveCaret) { runBatchFoldingOperation(operation, false, moveCaret); } private void runBatchFoldingOperation( final Runnable operation, final boolean dontCollapseCaret, final boolean moveCaret) { assertIsDispatchThreadForEditor(); boolean oldDontCollapseCaret = myDoNotCollapseCaret; myDoNotCollapseCaret |= dontCollapseCaret; boolean oldBatchFlag = myIsBatchFoldingProcessing; if (!oldBatchFlag) { ((ScrollingModelImpl) myEditor.getScrollingModel()).finishAnimation(); mySavedCaretShift = myEditor.visibleLineToY(myEditor.getCaretModel().getVisualPosition().line) - myEditor.getScrollingModel().getVerticalScrollOffset(); } myIsBatchFoldingProcessing = true; try { operation.run(); } finally { if (!oldBatchFlag) { if (myFoldRegionsProcessed) { notifyBatchFoldingProcessingDone(moveCaret); myFoldRegionsProcessed = false; } myIsBatchFoldingProcessing = false; } myDoNotCollapseCaret = oldDontCollapseCaret; } } @Override public void runBatchFoldingOperationDoNotCollapseCaret(@NotNull final Runnable operation) { runBatchFoldingOperation(operation, true, true); } /** * Disables caret position adjustment after batch folding operation is finished. Should be called * from inside batch operation runnable. */ public void flushCaretShift() { mySavedCaretShift = -1; } @Override @NotNull public FoldRegion[] getAllFoldRegions() { assertReadAccess(); return myFoldTree.fetchAllRegions(); } @Override @Nullable public FoldRegion getCollapsedRegionAtOffset(int offset) { return myFoldTree.fetchOutermost(offset); } @Nullable @Override public FoldRegion getFoldRegion(int startOffset, int endOffset) { assertReadAccess(); return myFoldTree.getRegionAt(startOffset, endOffset); } @Override @Nullable public FoldRegion getFoldingPlaceholderAt(Point p) { assertReadAccess(); LogicalPosition pos = myEditor.xyToLogicalPosition(p); int line = pos.line; if (line >= myEditor.getDocument().getLineCount()) return null; int offset = myEditor.logicalPositionToOffset(pos); return myFoldTree.fetchOutermost(offset); } @Override public void removeFoldRegion(@NotNull final FoldRegion region) { assertIsDispatchThreadForEditor(); if (!myIsBatchFoldingProcessing) { LOG.error("Fold regions must be added or removed inside batchFoldProcessing() only."); } region.setExpanded(true); final FoldingGroup group = region.getGroup(); if (group != null) { myGroups.remove(group, region); } myFoldTree.removeRegion(region); myFoldRegionsProcessed = true; region.dispose(); } public void dispose() { doClearFoldRegions(); } @Override public void clearFoldRegions() { if (!myIsBatchFoldingProcessing) { LOG.error("Fold regions must be added or removed inside batchFoldProcessing() only."); return; } doClearFoldRegions(); } public void doClearFoldRegions() { myGroups.clear(); myFoldTree.clear(); } public void expandFoldRegion(FoldRegion region) { assertIsDispatchThreadForEditor(); if (region.isExpanded() || region.shouldNeverExpand()) return; if (!myIsBatchFoldingProcessing) { LOG.error("Fold regions must be collapsed or expanded inside batchFoldProcessing() only."); } for (Caret caret : myEditor.getCaretModel().getAllCarets()) { LogicalPosition savedPosition = caret.getUserData(SAVED_CARET_POSITION); if (savedPosition != null) { int savedOffset = myEditor.logicalPositionToOffset(savedPosition); FoldRegion[] allCollapsed = myFoldTree.fetchCollapsedAt(savedOffset); if (allCollapsed.length == 1 && allCollapsed[0] == region) { caret.moveToLogicalPosition(savedPosition); } } } myFoldRegionsProcessed = true; ((FoldRegionImpl) region).setExpandedInternal(true); notifyListenersOnFoldRegionStateChange(region); } public void collapseFoldRegion(FoldRegion region) { assertIsDispatchThreadForEditor(); if (!region.isExpanded()) return; if (!myIsBatchFoldingProcessing) { LOG.error("Fold regions must be collapsed or expanded inside batchFoldProcessing() only."); } List<Caret> carets = myEditor.getCaretModel().getAllCarets(); for (Caret caret : carets) { LogicalPosition caretPosition = caret.getLogicalPosition(); int caretOffset = myEditor.logicalPositionToOffset(caretPosition); if (FoldRegionsTree.contains(region, caretOffset)) { if (myDoNotCollapseCaret) return; } } for (Caret caret : carets) { LogicalPosition caretPosition = caret.getLogicalPosition(); int caretOffset = myEditor.logicalPositionToOffset(caretPosition); if (FoldRegionsTree.contains(region, caretOffset)) { if (caret.getUserData(SAVED_CARET_POSITION) == null) { caret.putUserData(SAVED_CARET_POSITION, caretPosition.withoutVisualPositionInfo()); } } } myFoldRegionsProcessed = true; ((FoldRegionImpl) region).setExpandedInternal(false); notifyListenersOnFoldRegionStateChange(region); } private void notifyBatchFoldingProcessingDone(final boolean moveCaretFromCollapsedRegion) { rebuild(); for (FoldingListener listener : myListeners) { listener.onFoldProcessingEnd(); } myEditor.updateCaretCursor(); myEditor.recalculateSizeAndRepaint(); myEditor.getGutterComponentEx().updateSize(); myEditor.getGutterComponentEx().repaint(); for (Caret caret : myEditor.getCaretModel().getAllCarets()) { // There is a possible case that caret position is already visual position aware. But visual // position depends on number of folded // logical lines as well, hence, we can't be sure that target logical position defines correct // visual position because fold // regions have just changed. Hence, we use 'raw' logical position instead. LogicalPosition caretPosition = caret.getLogicalPosition().withoutVisualPositionInfo(); int caretOffset = myEditor.logicalPositionToOffset(caretPosition); int selectionStart = caret.getSelectionStart(); int selectionEnd = caret.getSelectionEnd(); LogicalPosition positionToUse = null; int offsetToUse = -1; FoldRegion collapsed = myFoldTree.fetchOutermost(caretOffset); LogicalPosition savedPosition = caret.getUserData(SAVED_CARET_POSITION); if (savedPosition != null) { int savedOffset = myEditor.logicalPositionToOffset(savedPosition); FoldRegion collapsedAtSaved = myFoldTree.fetchOutermost(savedOffset); if (collapsedAtSaved == null) { positionToUse = savedPosition; } else { offsetToUse = collapsedAtSaved.getStartOffset(); } } if (collapsed != null && positionToUse == null) { positionToUse = myEditor.offsetToLogicalPosition(collapsed.getStartOffset()); } if (moveCaretFromCollapsedRegion && caret.isUpToDate()) { if (offsetToUse >= 0) { caret.moveToOffset(offsetToUse); } else if (positionToUse != null) { caret.moveToLogicalPosition(positionToUse); } else { caret.moveToLogicalPosition(caretPosition); } } caret.putUserData(SAVED_CARET_POSITION, savedPosition); if (isOffsetInsideCollapsedRegion(selectionStart) || isOffsetInsideCollapsedRegion(selectionEnd)) { caret.removeSelection(); } else if (selectionStart < myEditor.getDocument().getTextLength()) { caret.setSelection(selectionStart, selectionEnd); } } if (mySavedCaretShift > 0) { final ScrollingModel scrollingModel = myEditor.getScrollingModel(); scrollingModel.disableAnimation(); scrollingModel.scrollVertically( myEditor.visibleLineToY(myEditor.getCaretModel().getVisualPosition().line) - mySavedCaretShift); scrollingModel.enableAnimation(); } } @Override public void rebuild() { if (!myEditor.getDocument().isInBulkUpdate()) { myFoldTree.rebuild(); } } private void updateCachedOffsets() { myFoldTree.updateCachedOffsets(); } public int getFoldedLinesCountBefore(int offset) { if (!myDocumentChangeProcessed && myEditor.getDocument().isInEventsHandling()) { // There is a possible case that this method is called on document update before fold regions // are recalculated. // We return zero in such situations then. return 0; } return myFoldTree.getFoldedLinesCountBefore(offset); } @Override @Nullable public FoldRegion[] fetchTopLevel() { return myFoldTree.fetchTopLevel(); } @Override @Nullable public FoldRegion fetchOutermost(int offset) { return myFoldTree.fetchOutermost(offset); } public FoldRegion[] fetchCollapsedAt(int offset) { return myFoldTree.fetchCollapsedAt(offset); } @Override public boolean intersectsRegion(int startOffset, int endOffset) { return myFoldTree.intersectsRegion(startOffset, endOffset); } public FoldRegion[] fetchVisible() { return myFoldTree.fetchVisible(); } @Override public int getLastCollapsedRegionBefore(int offset) { return myFoldTree.getLastTopLevelIndexBefore(offset); } @Override public TextAttributes getPlaceholderAttributes() { return myFoldTextAttributes; } public void flushCaretPosition() { for (Caret caret : myEditor.getCaretModel().getAllCarets()) { caret.putUserData(SAVED_CARET_POSITION, null); } } void onBulkDocumentUpdateStarted() { myFoldTree.clearCachedValues(); } void onBulkDocumentUpdateFinished() { myFoldTree.rebuild(); } @Override public void beforeDocumentChange(DocumentEvent event) { myDocumentChangeProcessed = false; } @Override public void documentChanged(DocumentEvent event) { try { if (!((DocumentEx) event.getDocument()).isInBulkUpdate()) { updateCachedOffsets(); } } finally { myDocumentChangeProcessed = true; } } @Override public int getPriority() { return EditorDocumentPriorities.FOLD_MODEL; } @Override public FoldRegion createFoldRegion( int startOffset, int endOffset, @NotNull String placeholder, @Nullable FoldingGroup group, boolean neverExpands) { FoldRegionImpl region = new FoldRegionImpl(myEditor, startOffset, endOffset, placeholder, group, neverExpands); LOG.assertTrue(region.isValid()); return region; } @Override public void addListener( @NotNull final FoldingListener listener, @NotNull Disposable parentDisposable) { myListeners.add(listener); Disposer.register( parentDisposable, new Disposable() { @Override public void dispose() { myListeners.remove(listener); } }); } private void notifyListenersOnFoldRegionStateChange(@NotNull FoldRegion foldRegion) { for (FoldingListener listener : myListeners) { listener.onFoldRegionStateChange(foldRegion); } } @NotNull @Override public String dumpState() { return Arrays.toString(myFoldTree.fetchTopLevel()); } @Override public String toString() { return dumpState(); } }
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); } }
/** @author nik */ public class XDebuggerTree extends DnDAwareTree implements DataProvider, Disposable { private final TransferToEDTQueue<Runnable> myLaterInvocator = TransferToEDTQueue.createRunnableMerger("XDebuggerTree later invocator", 100); private final ComponentListener myMoveListener = new ComponentAdapter() { @Override public void componentMoved(ComponentEvent e) { repaint(); // needed to repaint links in cell renderer on horizontal scrolling } }; private static final DataKey<XDebuggerTree> XDEBUGGER_TREE_KEY = DataKey.create("xdebugger.tree"); private final SingleAlarm myAlarm = new SingleAlarm( new Runnable() { @Override public void run() { final Editor editor = FileEditorManager.getInstance(myProject).getSelectedTextEditor(); if (editor != null) { editor.getContentComponent().revalidate(); editor.getContentComponent().repaint(); } } }, 100, this); private static final Convertor<TreePath, String> SPEED_SEARCH_CONVERTER = new Convertor<TreePath, String>() { @Override public String convert(TreePath o) { String text = null; if (o != null) { final Object node = o.getLastPathComponent(); if (node instanceof XDebuggerTreeNode) { text = ((XDebuggerTreeNode) node).getText().toString(); } } return StringUtil.notNullize(text); } }; private static final TransferHandler DEFAULT_TRANSFER_HANDLER = new TransferHandler() { @Override protected Transferable createTransferable(JComponent c) { if (!(c instanceof XDebuggerTree)) { return null; } XDebuggerTree tree = (XDebuggerTree) c; //noinspection deprecation TreePath[] selectedPaths = tree.getSelectionPaths(); if (selectedPaths == null || selectedPaths.length == 0) { return null; } StringBuilder plainBuf = new StringBuilder(); StringBuilder htmlBuf = new StringBuilder(); htmlBuf.append("<html>\n<body>\n<ul>\n"); TextTransferable.ColoredStringBuilder coloredTextContainer = new TextTransferable.ColoredStringBuilder(); for (TreePath path : selectedPaths) { htmlBuf.append(" <li>"); Object node = path.getLastPathComponent(); if (node != null) { if (node instanceof XDebuggerTreeNode) { ((XDebuggerTreeNode) node).appendToComponent(coloredTextContainer); coloredTextContainer.appendTo(plainBuf, htmlBuf); } else { String text = node.toString(); plainBuf.append(text); htmlBuf.append(text); } } plainBuf.append('\n'); htmlBuf.append("</li>\n"); } // remove the last newline plainBuf.setLength(plainBuf.length() - 1); htmlBuf.append("</ul>\n</body>\n</html>"); return new TextTransferable(htmlBuf.toString(), plainBuf.toString()); } @Override public int getSourceActions(JComponent c) { return COPY; } }; private final DefaultTreeModel myTreeModel; private final Project myProject; private final XDebuggerEditorsProvider myEditorsProvider; private XSourcePosition mySourcePosition; private final List<XDebuggerTreeListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private final XValueMarkers<?, ?> myValueMarkers; public XDebuggerTree( final @NotNull Project project, final @NotNull XDebuggerEditorsProvider editorsProvider, final @Nullable XSourcePosition sourcePosition, final @NotNull String popupActionGroupId, @Nullable XValueMarkers<?, ?> valueMarkers) { myValueMarkers = valueMarkers; myProject = project; myEditorsProvider = editorsProvider; mySourcePosition = sourcePosition; myTreeModel = new DefaultTreeModel(null); setModel(myTreeModel); setCellRenderer(new XDebuggerTreeRenderer()); new TreeLinkMouseListener(new XDebuggerTreeRenderer()) { @Override protected boolean doCacheLastNode() { return false; } @Override protected void handleTagClick(@Nullable Object tag, @NotNull MouseEvent event) { if (tag instanceof XDebuggerTreeNodeHyperlink) { ((XDebuggerTreeNodeHyperlink) tag).onClick(event); } } }.installOn(this); setRootVisible(false); setShowsRootHandles(true); new DoubleClickListener() { @Override protected boolean onDoubleClick(MouseEvent e) { return expandIfEllipsis(); } }.installOn(this); addKeyListener( new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { int key = e.getKeyCode(); if (key == KeyEvent.VK_ENTER || key == KeyEvent.VK_SPACE || key == KeyEvent.VK_RIGHT) { expandIfEllipsis(); } } }); if (Boolean.valueOf(System.getProperty("xdebugger.variablesView.rss"))) { new XDebuggerTreeSpeedSearch(this, SPEED_SEARCH_CONVERTER); } else { new TreeSpeedSearch(this, SPEED_SEARCH_CONVERTER); } final ActionManager actionManager = ActionManager.getInstance(); addMouseListener( new PopupHandler() { @Override public void invokePopup(final Component comp, final int x, final int y) { ActionGroup group = (ActionGroup) actionManager.getAction(popupActionGroupId); actionManager .createActionPopupMenu(ActionPlaces.UNKNOWN, group) .getComponent() .show(comp, x, y); } }); registerShortcuts(); setTransferHandler(DEFAULT_TRANSFER_HANDLER); addComponentListener(myMoveListener); } public void updateEditor() { myAlarm.cancelAndRequest(); } public boolean isUnderRemoteDebug() { // FIXME [VISTALL] we can access to RemoteRunProfile from platform - java specific // DataContext context = DataManager.getInstance().getDataContext(this); // ExecutionEnvironment env = LangDataKeys.EXECUTION_ENVIRONMENT.getData(context); // if (env != null && env.getRunProfile() instanceof RemoteRunProfile) { // return true; // } return false; } private boolean expandIfEllipsis() { MessageTreeNode[] treeNodes = getSelectedNodes(MessageTreeNode.class, null); if (treeNodes.length == 1) { MessageTreeNode node = treeNodes[0]; if (node.isEllipsis()) { TreeNode parent = node.getParent(); if (parent instanceof XValueContainerNode) { ((XValueContainerNode) parent).startComputingChildren(); return true; } } } return false; } public void addTreeListener(@NotNull XDebuggerTreeListener listener) { myListeners.add(listener); } public void removeTreeListener(@NotNull XDebuggerTreeListener listener) { myListeners.remove(listener); } public void setRoot(XDebuggerTreeNode root, final boolean rootVisible) { setRootVisible(rootVisible); myTreeModel.setRoot(root); } public XDebuggerTreeNode getRoot() { return (XDebuggerTreeNode) myTreeModel.getRoot(); } @Nullable public XSourcePosition getSourcePosition() { return mySourcePosition; } public void setSourcePosition(final @Nullable XSourcePosition sourcePosition) { mySourcePosition = sourcePosition; } @NotNull public XDebuggerEditorsProvider getEditorsProvider() { return myEditorsProvider; } @NotNull public Project getProject() { return myProject; } @Nullable public XValueMarkers<?, ?> getValueMarkers() { return myValueMarkers; } public DefaultTreeModel getTreeModel() { return myTreeModel; } @Override @Nullable public Object getData(@NonNls final String dataId) { if (XDEBUGGER_TREE_KEY.is(dataId)) { return this; } if (PlatformDataKeys.PREDEFINED_TEXT.is(dataId)) { XValueNodeImpl[] selectedNodes = getSelectedNodes(XValueNodeImpl.class, null); if (selectedNodes.length == 1 && selectedNodes[0].getFullValueEvaluator() == null) { return DebuggerUIUtil.getNodeRawValue(selectedNodes[0]); } } return null; } public void rebuildAndRestore(final XDebuggerTreeState treeState) { Object rootNode = myTreeModel.getRoot(); if (rootNode instanceof XDebuggerTreeNode) { ((XDebuggerTreeNode) rootNode).clearChildren(); treeState.restoreState(this); repaint(); } } public void childrenLoaded( final @NotNull XDebuggerTreeNode node, final @NotNull List<XValueContainerNode<?>> children, final boolean last) { for (XDebuggerTreeListener listener : myListeners) { listener.childrenLoaded(node, children, last); } } public void nodeLoaded(final @NotNull RestorableStateNode node, final @NotNull String name) { for (XDebuggerTreeListener listener : myListeners) { listener.nodeLoaded(node, name); } } public void markNodesObsolete() { Object root = myTreeModel.getRoot(); if (root instanceof XValueContainerNode<?>) { markNodesObsolete((XValueContainerNode<?>) root); } } @Override public void dispose() { ActionManager actionManager = ActionManager.getInstance(); actionManager.getAction(XDebuggerActions.SET_VALUE).unregisterCustomShortcutSet(this); actionManager.getAction(XDebuggerActions.COPY_VALUE).unregisterCustomShortcutSet(this); actionManager.getAction(XDebuggerActions.JUMP_TO_SOURCE).unregisterCustomShortcutSet(this); actionManager.getAction(XDebuggerActions.JUMP_TO_TYPE_SOURCE).unregisterCustomShortcutSet(this); actionManager.getAction(XDebuggerActions.MARK_OBJECT).unregisterCustomShortcutSet(this); // clear all possible inner fields that may still have links to debugger objects myTreeModel.setRoot(null); setCellRenderer(null); UIUtil.dispose(this); setLeadSelectionPath(null); setAnchorSelectionPath(null); removeComponentListener(myMoveListener); } private void registerShortcuts() { ActionManager actionManager = ActionManager.getInstance(); actionManager .getAction(XDebuggerActions.SET_VALUE) .registerCustomShortcutSet( new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0)), this); actionManager .getAction(XDebuggerActions.COPY_VALUE) .registerCustomShortcutSet(CommonShortcuts.getCopy(), this); actionManager .getAction(XDebuggerActions.JUMP_TO_SOURCE) .registerCustomShortcutSet(CommonShortcuts.getEditSource(), this); Shortcut[] editTypeShortcuts = KeymapManager.getInstance() .getActiveKeymap() .getShortcuts(XDebuggerActions.EDIT_TYPE_SOURCE); actionManager .getAction(XDebuggerActions.JUMP_TO_TYPE_SOURCE) .registerCustomShortcutSet(new CustomShortcutSet(editTypeShortcuts), this); actionManager .getAction(XDebuggerActions.MARK_OBJECT) .registerCustomShortcutSet( new CustomShortcutSet( KeymapManager.getInstance().getActiveKeymap().getShortcuts("ToggleBookmark")), this); } private static void markNodesObsolete(final XValueContainerNode<?> node) { node.setObsolete(); List<? extends XValueContainerNode<?>> loadedChildren = node.getLoadedChildren(); if (loadedChildren != null) { for (XValueContainerNode<?> child : loadedChildren) { markNodesObsolete(child); } } } @Nullable public static XDebuggerTree getTree(final AnActionEvent e) { return e.getData(XDEBUGGER_TREE_KEY); } @Nullable public static XDebuggerTree getTree(DataContext context) { return XDEBUGGER_TREE_KEY.getData(context); } public TransferToEDTQueue<Runnable> getLaterInvocator() { return myLaterInvocator; } public void selectNodeOnLoad(final Condition<TreeNode> nodeFilter) { addTreeListener( new XDebuggerTreeListener() { @Override public void nodeLoaded(@NotNull RestorableStateNode node, String name) { if (nodeFilter.value(node)) { setSelectionPath(node.getPath()); } } @Override public void childrenLoaded( @NotNull XDebuggerTreeNode node, @NotNull List<XValueContainerNode<?>> children, boolean last) {} }); } public void expandNodesOnLoad(final Condition<TreeNode> nodeFilter) { addTreeListener( new XDebuggerTreeListener() { @Override public void nodeLoaded(@NotNull RestorableStateNode node, String name) { if (nodeFilter.value(node) && !node.isLeaf()) { // cause children computing node.getChildCount(); } } @Override public void childrenLoaded( @NotNull XDebuggerTreeNode node, @NotNull List<XValueContainerNode<?>> children, boolean last) { if (nodeFilter.value(node)) { expandPath(node.getPath()); } } }); } }
/** @author Eugene.Kudelevsky */ public class MyDeviceChooser implements Disposable { private static final String[] COLUMN_TITLES = new String[] {"Device", "Serial Number", "State", "Compatible"}; private static final int DEVICE_NAME_COLUMN_INDEX = 0; private static final int SERIAL_COLUMN_INDEX = 1; private static final int DEVICE_STATE_COLUMN_INDEX = 2; private static final int COMPATIBILITY_COLUMN_INDEX = 3; private static final int REFRESH_INTERVAL_MS = 500; public static final IDevice[] EMPTY_DEVICE_ARRAY = new IDevice[0]; private final List<DeviceChooserListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private final Alarm myRefreshingAlarm; private final AndroidDebugBridge myBridge; private volatile boolean myProcessSelectionFlag = true; /** The current list of devices that is displayed in the table. */ private IDevice[] myDisplayedDevices = EMPTY_DEVICE_ARRAY; /** * The current list of devices obtained from the debug bridge. This is updated in a background * thread. If it is different than {@link #myDisplayedDevices}, then a {@link #refreshTable} * invocation in the EDT thread will update the displayed list to match the detected list. */ private AtomicReference<IDevice[]> myDetectedDevicesRef = new AtomicReference<IDevice[]>(EMPTY_DEVICE_ARRAY); private JComponent myPanel; private JBTable myDeviceTable; private final AndroidFacet myFacet; private final Condition<IDevice> myFilter; private final AndroidVersion myMinSdkVersion; private final IAndroidTarget myProjectTarget; private final EnumSet<IDevice.HardwareFeature> myRequiredHardwareFeatures; private int[] mySelectedRows; private boolean hadUserInteraction = false; @Nullable private String[] previouslySelectedSerials; public MyDeviceChooser( boolean multipleSelection, @NotNull final Action okAction, @NotNull AndroidFacet facet, @NotNull IAndroidTarget projectTarget, @Nullable Condition<IDevice> filter) { myFacet = facet; myFilter = filter; myMinSdkVersion = AndroidModuleInfo.get(facet).getRuntimeMinSdkVersion(); myProjectTarget = projectTarget; if (new IsWatchFeatureRequiredCompat(facet).get()) { myRequiredHardwareFeatures = EnumSet.of(IDevice.HardwareFeature.WATCH); } else { myRequiredHardwareFeatures = EnumSet.noneOf(IDevice.HardwareFeature.class); } myDeviceTable = new JBTable(); myPanel = ScrollPaneFactory.createScrollPane(myDeviceTable); myPanel.setPreferredSize(new Dimension(450, 220)); myDeviceTable.setModel(new MyDeviceTableModel(EMPTY_DEVICE_ARRAY)); myDeviceTable.setSelectionMode( multipleSelection ? ListSelectionModel.MULTIPLE_INTERVAL_SELECTION : ListSelectionModel.SINGLE_SELECTION); myDeviceTable .getSelectionModel() .addListSelectionListener( new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { if (myProcessSelectionFlag) { hadUserInteraction = true; fireSelectedDevicesChanged(); } } }); new DoubleClickListener() { @Override protected boolean onDoubleClick(MouseEvent e) { if (myDeviceTable.isEnabled() && okAction.isEnabled()) { okAction.actionPerformed(null); return true; } return false; } }.installOn(myDeviceTable); myDeviceTable.setDefaultRenderer(LaunchCompatibility.class, new LaunchCompatibilityRenderer()); myDeviceTable.setDefaultRenderer( IDevice.class, new DeviceRenderer.DeviceNameRenderer(facet.getAvdManagerSilently())); myDeviceTable.addKeyListener( new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER && okAction.isEnabled()) { okAction.actionPerformed(null); } } }); setColumnWidth( myDeviceTable, DEVICE_NAME_COLUMN_INDEX, "Samsung Galaxy Nexus Android 4.1 (API 17)"); setColumnWidth(myDeviceTable, SERIAL_COLUMN_INDEX, "0000-0000-00000"); setColumnWidth(myDeviceTable, DEVICE_STATE_COLUMN_INDEX, "offline"); setColumnWidth(myDeviceTable, COMPATIBILITY_COLUMN_INDEX, "yes"); // Do not recreate columns on every model update - this should help maintain the column sizes // set above myDeviceTable.setAutoCreateColumnsFromModel(false); // Allow sorting by columns (in lexicographic order) myDeviceTable.setAutoCreateRowSorter(true); myRefreshingAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, this); myBridge = AndroidSdkUtils.getDebugBridge(myFacet.getModule().getProject()); } private static EnumSet<IDevice.HardwareFeature> getRequiredHardwareFeatures( List<UsesFeature> requiredFeatures) { // Currently, this method is hardcoded to only search if the list of required features includes // a watch. // We may not want to search the device for every possible feature, but only a small subset of // important // features, starting with hardware type watch.. for (UsesFeature feature : requiredFeatures) { AndroidAttributeValue<String> name = feature.getName(); if (name != null && UsesFeature.HARDWARE_TYPE_WATCH.equals(name.getStringValue())) { return EnumSet.of(IDevice.HardwareFeature.WATCH); } } return EnumSet.noneOf(IDevice.HardwareFeature.class); } private void setColumnWidth(JBTable deviceTable, int columnIndex, String sampleText) { int width = getWidth(deviceTable, sampleText); deviceTable.getColumnModel().getColumn(columnIndex).setPreferredWidth(width); } private int getWidth(JBTable deviceTable, String sampleText) { FontMetrics metrics = deviceTable.getFontMetrics(deviceTable.getFont()); return metrics.stringWidth(sampleText); } public void init(@Nullable String[] selectedSerials) { previouslySelectedSerials = selectedSerials; updateTable(); addUpdatingRequest(); } public void init(List<String> selectedSerials) { init(selectedSerials.stream().toArray(String[]::new)); } private void updatePreviouslySelectedSerials() { if (previouslySelectedSerials != null && !hadUserInteraction) { resetSelection(previouslySelectedSerials); } } private final Runnable myUpdateRequest = new Runnable() { @Override public void run() { updateTable(); addUpdatingRequest(); } }; private void addUpdatingRequest() { if (myRefreshingAlarm.isDisposed()) { return; } myRefreshingAlarm.cancelAllRequests(); myRefreshingAlarm.addRequest(myUpdateRequest, REFRESH_INTERVAL_MS); } private void resetSelection(@NotNull String[] selectedSerials) { MyDeviceTableModel model = (MyDeviceTableModel) myDeviceTable.getModel(); Set<String> selectedSerialsSet = new HashSet<String>(); Collections.addAll(selectedSerialsSet, selectedSerials); IDevice[] myDevices = model.myDevices; ListSelectionModel selectionModel = myDeviceTable.getSelectionModel(); boolean cleared = false; for (int i = 0, n = myDevices.length; i < n; i++) { String serialNumber = myDevices[i].getSerialNumber(); if (selectedSerialsSet.contains(serialNumber)) { if (!cleared) { selectionModel.clearSelection(); cleared = true; } selectionModel.addSelectionInterval(i, i); } } } void updateTable() { IDevice[] devices = myBridge != null ? getFilteredDevices(myBridge) : EMPTY_DEVICE_ARRAY; if (devices.length > 1) { // sort by API level Arrays.sort( devices, new Comparator<IDevice>() { @Override public int compare(IDevice device1, IDevice device2) { int apiLevel1 = safeGetApiLevel(device1); int apiLevel2 = safeGetApiLevel(device2); return apiLevel2 - apiLevel1; } private int safeGetApiLevel(IDevice device) { try { String s = device.getProperty(IDevice.PROP_BUILD_API_LEVEL); return StringUtil.isNotEmpty(s) ? Integer.parseInt(s) : 0; } catch (Exception e) { return 0; } } }); } if (!Arrays.equals(myDisplayedDevices, devices)) { myDetectedDevicesRef.set(devices); ApplicationManager.getApplication() .invokeLater( new Runnable() { @Override public void run() { refreshTable(); } }, ModalityState.stateForComponent(myDeviceTable)); } } private void refreshTable() { IDevice[] devices = myDetectedDevicesRef.get(); myDisplayedDevices = devices; final IDevice[] selectedDevices = getSelectedDevices(); final TIntArrayList selectedRows = new TIntArrayList(); for (int i = 0; i < devices.length; i++) { if (ArrayUtil.indexOf(selectedDevices, devices[i]) >= 0) { selectedRows.add(i); } } myProcessSelectionFlag = false; myDeviceTable.setModel(new MyDeviceTableModel(devices)); if (selectedRows.size() == 0 && devices.length > 0) { myDeviceTable.getSelectionModel().setSelectionInterval(0, 0); } for (int selectedRow : selectedRows.toNativeArray()) { if (selectedRow < devices.length) { myDeviceTable.getSelectionModel().addSelectionInterval(selectedRow, selectedRow); } } fireSelectedDevicesChanged(); myProcessSelectionFlag = true; updatePreviouslySelectedSerials(); } public boolean hasDevices() { return myDetectedDevicesRef.get().length > 0; } public JComponent getPreferredFocusComponent() { return myDeviceTable; } @Nullable public JComponent getPanel() { return myPanel; } @NotNull public IDevice[] getSelectedDevices() { int[] rows = mySelectedRows != null ? mySelectedRows : myDeviceTable.getSelectedRows(); List<IDevice> result = new ArrayList<IDevice>(); for (int row : rows) { if (row >= 0) { Object serial = myDeviceTable.getValueAt(row, SERIAL_COLUMN_INDEX); final AndroidDebugBridge bridge = AndroidSdkUtils.getDebugBridge(myFacet.getModule().getProject()); if (bridge == null) { return EMPTY_DEVICE_ARRAY; } IDevice[] devices = getFilteredDevices(bridge); for (IDevice device : devices) { if (device.getSerialNumber().equals(serial.toString())) { result.add(device); break; } } } } return result.toArray(new IDevice[result.size()]); } @NotNull private IDevice[] getFilteredDevices(AndroidDebugBridge bridge) { final List<IDevice> filteredDevices = new ArrayList<IDevice>(); for (IDevice device : bridge.getDevices()) { if (myFilter == null || myFilter.value(device)) { filteredDevices.add(device); } } // Do not filter launching cloud devices as they are just unselectable progress markers // that are replaced with the actual cloud devices as soon as they are up and the actual cloud // devices will be filtered above. return filteredDevices.toArray(new IDevice[filteredDevices.size()]); } public void finish() { mySelectedRows = myDeviceTable.getSelectedRows(); } @Override public void dispose() {} public void setEnabled(boolean enabled) { myDeviceTable.setEnabled(enabled); } @NotNull private static String getDeviceState(@NotNull IDevice device) { IDevice.DeviceState state = device.getState(); return state != null ? capitalize(state.name().toLowerCase()) : ""; } public void fireSelectedDevicesChanged() { for (DeviceChooserListener listener : myListeners) { listener.selectedDevicesChanged(); } } public void addListener(@NotNull DeviceChooserListener listener) { myListeners.add(listener); } private class MyDeviceTableModel extends AbstractTableModel { private final IDevice[] myDevices; public MyDeviceTableModel(IDevice[] devices) { myDevices = devices; } @Override public String getColumnName(int column) { return COLUMN_TITLES[column]; } @Override public int getRowCount() { return myDevices.length; } @Override public int getColumnCount() { return COLUMN_TITLES.length; } @Override @Nullable public Object getValueAt(int rowIndex, int columnIndex) { if (rowIndex >= myDevices.length) { return null; } IDevice device = myDevices[rowIndex]; switch (columnIndex) { case DEVICE_NAME_COLUMN_INDEX: return device; case SERIAL_COLUMN_INDEX: return device.getSerialNumber(); case DEVICE_STATE_COLUMN_INDEX: return getDeviceState(device); case COMPATIBILITY_COLUMN_INDEX: return new CanRunOnDeviceCompat( myFacet, myMinSdkVersion, myProjectTarget, myRequiredHardwareFeatures, device) .get(); } return null; } @Override public Class<?> getColumnClass(int columnIndex) { if (columnIndex == COMPATIBILITY_COLUMN_INDEX) { return LaunchCompatibility.class; } else if (columnIndex == DEVICE_NAME_COLUMN_INDEX) { return IDevice.class; } else { return String.class; } } } private static class LaunchCompatibilityRenderer extends ColoredTableCellRenderer { @Override protected void customizeCellRenderer( JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) { if (!(value instanceof LaunchCompatibility)) { return; } LaunchCompatibility compatibility = (LaunchCompatibility) value; ThreeState compatible = compatibility.isCompatible(); if (compatible == ThreeState.YES) { append("Yes"); } else { if (compatible == ThreeState.NO) { append("No", SimpleTextAttributes.ERROR_ATTRIBUTES); } else { append("Maybe"); } String reason = compatibility.getReason(); if (reason != null) { append(", "); append(reason); } } } } }