/** User: catherine */ @State( name = "PyPackageService", storages = {@Storage(file = StoragePathMacros.APP_CONFIG + "/packages.xml")}) public class PyPackageService implements PersistentStateComponent<PyPackageService> { public Map<String, Boolean> sdkToUsersite = new HashMap<String, Boolean>(); public List<String> additionalRepositories = new ArrayList<String>(); public Map<String, String> PY_PACKAGES = ContainerUtil.newConcurrentMap(); public String virtualEnvBasePath; public Boolean PYPI_REMOVED = false; public long LAST_TIME_CHECKED = 0; @Override public PyPackageService getState() { return this; } @Override public void loadState(PyPackageService state) { XmlSerializerUtil.copyBean(state, this); } public void addSdkToUserSite(String sdk, boolean useUsersite) { sdkToUsersite.put(sdk, useUsersite); } public void addRepository(String repository) { if (repository == null) return; if (repository.startsWith(PyPIPackageUtil.PYPI_HOST)) { PYPI_REMOVED = false; } else { if (!repository.endsWith("/")) repository += "/"; additionalRepositories.add(repository); } } public void removeRepository(final String repository) { if (additionalRepositories.contains(repository)) additionalRepositories.remove(repository); else if (repository.startsWith(PyPIPackageUtil.PYPI_HOST)) { PYPI_REMOVED = true; } } public boolean useUserSite(String sdk) { if (sdkToUsersite.containsKey(sdk)) return sdkToUsersite.get(sdk); return false; } public static PyPackageService getInstance() { return ServiceManager.getService(PyPackageService.class); } public String getVirtualEnvBasePath() { return virtualEnvBasePath; } public void setVirtualEnvBasePath(String virtualEnvBasePath) { this.virtualEnvBasePath = virtualEnvBasePath; } }
@NotNull private InspectionTreeNode getToolParentNode( @NotNull String groupName, HighlightDisplayLevel errorLevel, boolean groupedBySeverity) { if (groupName.isEmpty()) { return getRelativeRootNode(groupedBySeverity, errorLevel); } ConcurrentMap<String, InspectionGroupNode> map = myGroups.get(errorLevel); if (map == null) { map = ConcurrencyUtil.cacheOrGet( myGroups, errorLevel, ContainerUtil.<String, InspectionGroupNode>newConcurrentMap()); } InspectionGroupNode group; if (groupedBySeverity) { group = map.get(groupName); } else { group = null; for (Map<String, InspectionGroupNode> groupMap : myGroups.values()) { if ((group = groupMap.get(groupName)) != null) break; } } if (group == null) { group = ConcurrencyUtil.cacheOrGet(map, groupName, new InspectionGroupNode(groupName)); addChildNodeInEDT(getRelativeRootNode(groupedBySeverity, errorLevel), group); } return group; }
public GradleResourceCompilerConfigurationGenerator(@NotNull final Project project) { myProject = project; myModulesConfigurationHash = ContainerUtil.newConcurrentMap(); myExternalProjectDataService = (ExternalProjectDataService) ServiceManager.getService(ProjectDataManager.class) .getDataService(ExternalProjectDataService.KEY); assert myExternalProjectDataService != null; project .getMessageBus() .connect(project) .subscribe( ProjectTopics.MODULES, new ModuleAdapter() { public void moduleRemoved(@NotNull Project project, @NotNull Module module) { myModulesConfigurationHash.remove(module.getName()); } @Override public void modulesRenamed( @NotNull Project project, @NotNull List<Module> modules, @NotNull Function<Module, String> oldNameProvider) { for (Module module : modules) { moduleRemoved(project, module); } } }); }
public V getCachedValue(T key) { SofterReference<ConcurrentMap<T, V>> ref = myCache; ConcurrentMap<T, V> map = ref == null ? null : ref.get(); if (map == null) { myCache = new SofterReference<ConcurrentMap<T, V>>(map = ContainerUtil.newConcurrentMap()); } V value = map.get(key); if (value == null) { map.put(key, value = myValueProvider.fun(key)); } return value; }
private static void setContinuation( CompileContext context, ModuleChunk chunk, @Nullable GroovycContinuation continuation) { clearContinuation(context, chunk); if (continuation != null) { if (Utils.IS_TEST_MODE || LOG.isDebugEnabled()) { LOG.info("registering continuation for " + chunk); } Map<ModuleChunk, GroovycContinuation> map = CONTINUATIONS.get(context); if (map == null) CONTINUATIONS.set(context, map = ContainerUtil.newConcurrentMap()); map.put(chunk, continuation); } }
private void updateSelfDependentDtdDescriptors( XmlDocumentImpl copy, HashMap<String, CachedValue<XmlNSDescriptor>> cacheStrict, HashMap<String, CachedValue<XmlNSDescriptor>> cacheNotStrict) { copy.myDefaultDescriptorsCacheNotStrict = ContainerUtil.newConcurrentMap(); copy.myDefaultDescriptorsCacheStrict = ContainerUtil.newConcurrentMap(); for (Map.Entry<String, CachedValue<XmlNSDescriptor>> e : cacheStrict.entrySet()) { if (e.getValue().hasUpToDateValue()) { final XmlNSDescriptor nsDescriptor = e.getValue().getValue(); if (!isGeneratedFromDtd(nsDescriptor)) copy.myDefaultDescriptorsCacheStrict.put(e.getKey(), e.getValue()); } } for (Map.Entry<String, CachedValue<XmlNSDescriptor>> e : cacheNotStrict.entrySet()) { if (e.getValue().hasUpToDateValue()) { final XmlNSDescriptor nsDescriptor = e.getValue().getValue(); if (!isGeneratedFromDtd(nsDescriptor)) copy.myDefaultDescriptorsCacheNotStrict.put(e.getKey(), e.getValue()); } } }
private static class DelegatingDisposable implements Disposable { private static final ConcurrentMap<Disposable, DelegatingDisposable> ourInstances = ContainerUtil.newConcurrentMap(ContainerUtil.<Disposable>identityStrategy()); private final TObjectIntHashMap<VirtualFilePointerImpl> myCounts = new TObjectIntHashMap<VirtualFilePointerImpl>(); private final Disposable myParent; private DelegatingDisposable(@NotNull Disposable parent) { myParent = parent; } static void registerDisposable( @NotNull Disposable parentDisposable, @NotNull VirtualFilePointerImpl pointer) { DelegatingDisposable result = ourInstances.get(parentDisposable); if (result == null) { DelegatingDisposable newDisposable = new DelegatingDisposable(parentDisposable); result = ConcurrencyUtil.cacheOrGet(ourInstances, parentDisposable, newDisposable); if (result == newDisposable) { Disposer.register(parentDisposable, result); } } synchronized (result) { result.myCounts.put(pointer, result.myCounts.get(pointer) + 1); } } @Override public void dispose() { ourInstances.remove(myParent); synchronized (this) { myCounts.forEachEntry( new TObjectIntProcedure<VirtualFilePointerImpl>() { @Override public boolean execute(VirtualFilePointerImpl pointer, int disposeCount) { int after = pointer.myNode.incrementUsageCount(-disposeCount + 1); LOG.assertTrue(after > 0, after); pointer.dispose(); return true; } }); } } }
/** @author Mike */ public class XmlDocumentImpl extends XmlElementImpl implements XmlDocument { private static final Key<Boolean> AUTO_GENERATED = Key.create("auto-generated xml schema"); public static boolean isAutoGeneratedSchema(XmlFile file) { return file.getUserData(AUTO_GENERATED) != null; } private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.xml.XmlDocumentImpl"); private volatile XmlProlog myProlog; private volatile XmlTag myRootTag; private volatile long myExtResourcesModCount = -1; public XmlDocumentImpl() { this(XmlElementType.XML_DOCUMENT); } protected XmlDocumentImpl(IElementType type) { super(type); } @Override public void accept(@NotNull PsiElementVisitor visitor) { if (visitor instanceof XmlElementVisitor) { ((XmlElementVisitor) visitor).visitXmlDocument(this); } else { visitor.visitElement(this); } } @Override public int getChildRole(ASTNode child) { LOG.assertTrue(child.getTreeParent() == this); IElementType i = child.getElementType(); if (i == XmlElementType.XML_PROLOG) { return XmlChildRole.XML_PROLOG; } else if (i == XmlElementType.XML_TAG) { return XmlChildRole.XML_TAG; } else { return ChildRoleBase.NONE; } } @Override public XmlProlog getProlog() { XmlProlog prolog = myProlog; if (prolog == null) { synchronized (PsiLock.LOCK) { prolog = myProlog; if (prolog == null) { prolog = (XmlProlog) findElementByTokenType(XmlElementType.XML_PROLOG); myProlog = prolog; } } } return myProlog; } @Override public XmlTag getRootTag() { XmlTag rootTag = myRootTag; if (rootTag == null) { synchronized (PsiLock.LOCK) { rootTag = myRootTag; if (rootTag == null) { rootTag = (XmlTag) findElementByTokenType(XmlElementType.XML_TAG); myRootTag = rootTag; } } } return myRootTag; } @Override @SuppressWarnings("ConstantConditions") public XmlNSDescriptor getRootTagNSDescriptor() { XmlTag rootTag = getRootTag(); return rootTag != null ? rootTag.getNSDescriptor(rootTag.getNamespace(), false) : null; } private ConcurrentMap<String, CachedValue<XmlNSDescriptor>> myDefaultDescriptorsCacheStrict = ContainerUtil.newConcurrentMap(); private ConcurrentMap<String, CachedValue<XmlNSDescriptor>> myDefaultDescriptorsCacheNotStrict = ContainerUtil.newConcurrentMap(); @Override public void clearCaches() { myDefaultDescriptorsCacheStrict.clear(); myDefaultDescriptorsCacheNotStrict.clear(); myProlog = null; myRootTag = null; super.clearCaches(); } @Override public XmlNSDescriptor getDefaultNSDescriptor(final String namespace, final boolean strict) { long curExtResourcesModCount = ExternalResourceManagerEx.getInstanceEx().getModificationCount(getProject()); if (myExtResourcesModCount != curExtResourcesModCount) { myDefaultDescriptorsCacheNotStrict.clear(); myDefaultDescriptorsCacheStrict.clear(); myExtResourcesModCount = curExtResourcesModCount; } final ConcurrentMap<String, CachedValue<XmlNSDescriptor>> defaultDescriptorsCache; if (strict) { defaultDescriptorsCache = myDefaultDescriptorsCacheStrict; } else { defaultDescriptorsCache = myDefaultDescriptorsCacheNotStrict; } CachedValue<XmlNSDescriptor> cachedValue = defaultDescriptorsCache.get(namespace); if (cachedValue == null) { defaultDescriptorsCache.put( namespace, cachedValue = new PsiCachedValueImpl<XmlNSDescriptor>( getManager(), new CachedValueProvider<XmlNSDescriptor>() { @Override public Result<XmlNSDescriptor> compute() { final XmlNSDescriptor defaultNSDescriptorInner = getDefaultNSDescriptorInner(namespace, strict); if (isGeneratedFromDtd(defaultNSDescriptorInner)) { return new Result<XmlNSDescriptor>( defaultNSDescriptorInner, XmlDocumentImpl.this, ExternalResourceManager.getInstance()); } return new Result<XmlNSDescriptor>( defaultNSDescriptorInner, defaultNSDescriptorInner != null ? defaultNSDescriptorInner.getDependences() : ExternalResourceManager.getInstance()); } })); } return cachedValue.getValue(); } private boolean isGeneratedFromDtd(XmlNSDescriptor defaultNSDescriptorInner) { if (defaultNSDescriptorInner == null) { return false; } XmlFile descriptorFile = defaultNSDescriptorInner.getDescriptorFile(); if (descriptorFile == null) { return false; } @NonNls String otherName = XmlUtil.getContainingFile(this).getName() + ".dtd"; return descriptorFile.getName().equals(otherName); } private XmlNSDescriptor getDefaultNSDescriptorInner( final String namespace, final boolean strict) { final XmlFile containingFile = XmlUtil.getContainingFile(this); if (containingFile == null) return null; final XmlProlog prolog = getProlog(); final XmlDoctype doctype = prolog != null ? prolog.getDoctype() : null; boolean dtdUriFromDocTypeIsNamespace = false; if (XmlUtil.HTML_URI.equals(namespace)) { XmlNSDescriptor nsDescriptor = doctype != null ? getNsDescriptorFormDocType(doctype, containingFile, true) : null; if (doctype != null) { LOG.debug( "Descriptor from doctype " + doctype + " is " + (nsDescriptor != null ? nsDescriptor.getClass().getCanonicalName() : "NULL")); } if (nsDescriptor == null) { String htmlns = ExternalResourceManagerEx.getInstanceEx().getDefaultHtmlDoctype(getProject()); if (htmlns.isEmpty()) { htmlns = Html5SchemaProvider.getHtml5SchemaLocation(); } nsDescriptor = getDefaultNSDescriptor(htmlns, false); } return new HtmlNSDescriptorImpl(nsDescriptor); } else if (XmlUtil.XHTML_URI.equals(namespace)) { String xhtmlNamespace = XmlUtil.getDefaultXhtmlNamespace(getProject()); if (xhtmlNamespace == null || xhtmlNamespace.isEmpty()) { xhtmlNamespace = Html5SchemaProvider.getXhtml5SchemaLocation(); } return getDefaultNSDescriptor(xhtmlNamespace, false); } else if (namespace != null && namespace != XmlUtil.EMPTY_URI) { if (doctype == null || !namespace.equals(XmlUtil.getDtdUri(doctype))) { boolean documentIsSchemaThatDefinesNs = namespace.equals(XmlUtil.getTargetSchemaNsFromTag(getRootTag())); final XmlFile xmlFile = documentIsSchemaThatDefinesNs ? containingFile : XmlUtil.findNamespace(containingFile, namespace); if (xmlFile != null) { final XmlDocument document = xmlFile.getDocument(); if (document != null) { return (XmlNSDescriptor) document.getMetaData(); } } } else { dtdUriFromDocTypeIsNamespace = true; } } if (strict && !dtdUriFromDocTypeIsNamespace) return null; if (doctype != null) { XmlNSDescriptor descr = getNsDescriptorFormDocType(doctype, containingFile, false); if (descr != null) { return XmlExtension.getExtension(containingFile) .getDescriptorFromDoctype(containingFile, descr); } } if (strict) return null; if (namespace == XmlUtil.EMPTY_URI) { final XmlFile xmlFile = XmlUtil.findNamespace(containingFile, namespace); if (xmlFile != null) { return (XmlNSDescriptor) xmlFile.getDocument().getMetaData(); } } try { final PsiFile fileFromText = PsiFileFactory.getInstance(getProject()) .createFileFromText( containingFile.getName() + ".dtd", DTDLanguage.INSTANCE, XmlUtil.generateDocumentDTD(this, false), false, false); if (fileFromText instanceof XmlFile) { fileFromText.putUserData(AUTO_GENERATED, Boolean.TRUE); return (XmlNSDescriptor) ((XmlFile) fileFromText).getDocument().getMetaData(); } } catch (ProcessCanceledException ex) { throw ex; } catch (RuntimeException ignored) { } // e.g. dtd isn't mapped to xml type return null; } @NotNull private static String getFilePathForLogging(@Nullable PsiFile file) { if (file == null) { return "NULL"; } final VirtualFile vFile = file.getVirtualFile(); return vFile != null ? vFile.getPath() : "NULL_VFILE"; } @Nullable private XmlNSDescriptor getNsDescriptorFormDocType( final XmlDoctype doctype, final XmlFile containingFile, final boolean forHtml) { XmlNSDescriptor descriptor = getNSDescriptorFromMetaData(doctype.getMarkupDecl(), true); final String filePath = getFilePathForLogging(containingFile); final String dtdUri = XmlUtil.getDtdUri(doctype); LOG.debug( "DTD url for doctype " + doctype.getText() + " in file " + filePath + " is " + dtdUri); if (dtdUri != null && !dtdUri.isEmpty()) { XmlFile xmlFile = XmlUtil.findNamespace(containingFile, dtdUri); if (xmlFile == null) { // try to auto-detect it xmlFile = XmlNamespaceIndex.guessDtd(dtdUri, containingFile); } final String schemaFilePath = getFilePathForLogging(xmlFile); LOG.debug("Schema file for " + filePath + " is " + schemaFilePath); XmlNSDescriptor descriptorFromDtd = getNSDescriptorFromMetaData(xmlFile == null ? null : xmlFile.getDocument(), forHtml); LOG.debug( "Descriptor from meta data for schema file " + schemaFilePath + " is " + (descriptorFromDtd != null ? descriptorFromDtd.getClass().getCanonicalName() : "NULL")); if (descriptor != null && descriptorFromDtd != null) { descriptor = new XmlNSDescriptorSequence(new XmlNSDescriptor[] {descriptor, descriptorFromDtd}); } else if (descriptorFromDtd != null) { descriptor = descriptorFromDtd; } } return descriptor; } @Nullable private XmlNSDescriptor getNSDescriptorFromMetaData( @Nullable PsiMetaOwner metaOwner, boolean nonEmpty) { if (metaOwner == null) return null; XmlNSDescriptor descriptor = (XmlNSDescriptor) metaOwner.getMetaData(); if (descriptor == null) return null; if (nonEmpty && descriptor.getRootElementsDescriptors(this).length == 0) { return null; } return descriptor; } @Override public CompositePsiElement clone() { HashMap<String, CachedValue<XmlNSDescriptor>> cacheStrict = new HashMap<String, CachedValue<XmlNSDescriptor>>(myDefaultDescriptorsCacheStrict); HashMap<String, CachedValue<XmlNSDescriptor>> cacheNotStrict = new HashMap<String, CachedValue<XmlNSDescriptor>>(myDefaultDescriptorsCacheNotStrict); final XmlDocumentImpl copy = (XmlDocumentImpl) super.clone(); updateSelfDependentDtdDescriptors(copy, cacheStrict, cacheNotStrict); return copy; } @Override public PsiElement copy() { HashMap<String, CachedValue<XmlNSDescriptor>> cacheStrict = new HashMap<String, CachedValue<XmlNSDescriptor>>(myDefaultDescriptorsCacheStrict); HashMap<String, CachedValue<XmlNSDescriptor>> cacheNotStrict = new HashMap<String, CachedValue<XmlNSDescriptor>>(myDefaultDescriptorsCacheNotStrict); final XmlDocumentImpl copy = (XmlDocumentImpl) super.copy(); updateSelfDependentDtdDescriptors(copy, cacheStrict, cacheNotStrict); return copy; } private void updateSelfDependentDtdDescriptors( XmlDocumentImpl copy, HashMap<String, CachedValue<XmlNSDescriptor>> cacheStrict, HashMap<String, CachedValue<XmlNSDescriptor>> cacheNotStrict) { copy.myDefaultDescriptorsCacheNotStrict = ContainerUtil.newConcurrentMap(); copy.myDefaultDescriptorsCacheStrict = ContainerUtil.newConcurrentMap(); for (Map.Entry<String, CachedValue<XmlNSDescriptor>> e : cacheStrict.entrySet()) { if (e.getValue().hasUpToDateValue()) { final XmlNSDescriptor nsDescriptor = e.getValue().getValue(); if (!isGeneratedFromDtd(nsDescriptor)) copy.myDefaultDescriptorsCacheStrict.put(e.getKey(), e.getValue()); } } for (Map.Entry<String, CachedValue<XmlNSDescriptor>> e : cacheNotStrict.entrySet()) { if (e.getValue().hasUpToDateValue()) { final XmlNSDescriptor nsDescriptor = e.getValue().getValue(); if (!isGeneratedFromDtd(nsDescriptor)) copy.myDefaultDescriptorsCacheNotStrict.put(e.getKey(), e.getValue()); } } } @Override public PsiMetaData getMetaData() { return MetaRegistry.getMeta(this); } @SuppressWarnings({"HardCodedStringLiteral"}) public void dumpStatistics() { System.out.println("Statistics:"); final TObjectIntHashMap<Object> map = new TObjectIntHashMap<Object>(); final PsiElementVisitor psiRecursiveElementVisitor = new XmlRecursiveElementVisitor() { @NonNls private static final String TOKENS_KEY = "Tokens"; @NonNls private static final String ELEMENTS_KEY = "Elements"; @Override public void visitXmlToken(XmlToken token) { inc(TOKENS_KEY); } @Override public void visitElement(PsiElement element) { inc(ELEMENTS_KEY); super.visitElement(element); } private void inc(final String key) { map.put(key, map.get(key) + 1); } }; accept(psiRecursiveElementVisitor); final Object[] keys = map.keys(); for (final Object key : keys) { System.out.println(key + ": " + map.get(key)); } } @Override public TreeElement addInternal( final TreeElement first, final ASTNode last, final ASTNode anchor, final Boolean before) { final PomModel model = PomManager.getModel(getProject()); final XmlAspect aspect = model.getModelAspect(XmlAspect.class); final TreeElement[] holder = new TreeElement[1]; try { model.runTransaction( new PomTransactionBase(this, aspect) { @Override public PomModelEvent runInner() { holder[0] = XmlDocumentImpl.super.addInternal(first, last, anchor, before); return XmlDocumentChangedImpl.createXmlDocumentChanged(model, XmlDocumentImpl.this); } }); } catch (IncorrectOperationException ignored) { } return holder[0]; } @Override public void deleteChildInternal(@NotNull final ASTNode child) { final PomModel model = PomManager.getModel(getProject()); final XmlAspect aspect = model.getModelAspect(XmlAspect.class); try { model.runTransaction( new PomTransactionBase(this, aspect) { @Override public PomModelEvent runInner() { XmlDocumentImpl.super.deleteChildInternal(child); return XmlDocumentChangedImpl.createXmlDocumentChanged(model, XmlDocumentImpl.this); } }); } catch (IncorrectOperationException ignored) { } } @Override public void replaceChildInternal( @NotNull final ASTNode child, @NotNull final TreeElement newElement) { final PomModel model = PomManager.getModel(getProject()); final XmlAspect aspect = model.getModelAspect(XmlAspect.class); try { model.runTransaction( new PomTransactionBase(this, aspect) { @Override public PomModelEvent runInner() { XmlDocumentImpl.super.replaceChildInternal(child, newElement); return XmlDocumentChangedImpl.createXmlDocumentChanged(model, XmlDocumentImpl.this); } }); } catch (IncorrectOperationException ignored) { } } }
public class IndentsPass extends TextEditorHighlightingPass implements DumbAware { private static final ConcurrentMap<IElementType, String> COMMENT_PREFIXES = ContainerUtil.newConcurrentMap(); private static final String NO_COMMENT_INFO_MARKER = "hopefully, noone uses this string as a comment prefix"; private static final Key<List<RangeHighlighter>> INDENT_HIGHLIGHTERS_IN_EDITOR_KEY = Key.create("INDENT_HIGHLIGHTERS_IN_EDITOR_KEY"); private static final Key<Long> LAST_TIME_INDENTS_BUILT = Key.create("LAST_TIME_INDENTS_BUILT"); private final EditorEx myEditor; private final PsiFile myFile; public static final Comparator<TextRange> RANGE_COMPARATOR = new Comparator<TextRange>() { @Override public int compare(TextRange o1, TextRange o2) { if (o1.getStartOffset() == o2.getStartOffset()) { return o1.getEndOffset() - o2.getEndOffset(); } return o1.getStartOffset() - o2.getStartOffset(); } }; private static final CustomHighlighterRenderer RENDERER = new CustomHighlighterRenderer() { @Override @SuppressWarnings({"AssignmentToForLoopParameter"}) public void paint( @NotNull Editor editor, @NotNull RangeHighlighter highlighter, @NotNull Graphics g) { int startOffset = highlighter.getStartOffset(); final Document doc = highlighter.getDocument(); if (startOffset >= doc.getTextLength()) return; final int endOffset = highlighter.getEndOffset(); final int endLine = doc.getLineNumber(endOffset); int off; int startLine = doc.getLineNumber(startOffset); IndentGuideDescriptor descriptor = editor.getIndentsModel().getDescriptor(startLine, endLine); final CharSequence chars = doc.getCharsSequence(); do { int start = doc.getLineStartOffset(startLine); int end = doc.getLineEndOffset(startLine); off = CharArrayUtil.shiftForward(chars, start, end, " \t"); startLine--; } while (startLine > 1 && off < doc.getTextLength() && chars.charAt(off) == '\n'); final VisualPosition startPosition = editor.offsetToVisualPosition(off); int indentColumn = startPosition.column; // It's considered that indent guide can cross not only white space but comments, javadocs // etc. Hence, there is a possible // case that the first indent guide line is, say, single-line comment where comment // symbols ('//') are located at the first // visual column. We need to calculate correct indent guide column then. int lineShift = 1; if (indentColumn <= 0 && descriptor != null) { indentColumn = descriptor.indentLevel; lineShift = 0; } if (indentColumn <= 0) return; final FoldingModel foldingModel = editor.getFoldingModel(); if (foldingModel.isOffsetCollapsed(off)) return; final FoldRegion headerRegion = foldingModel.getCollapsedRegionAtOffset(doc.getLineEndOffset(doc.getLineNumber(off))); final FoldRegion tailRegion = foldingModel.getCollapsedRegionAtOffset( doc.getLineStartOffset(doc.getLineNumber(endOffset))); if (tailRegion != null && tailRegion == headerRegion) return; final boolean selected; final IndentGuideDescriptor guide = editor.getIndentsModel().getCaretIndentGuide(); if (guide != null) { final CaretModel caretModel = editor.getCaretModel(); final int caretOffset = caretModel.getOffset(); selected = caretOffset >= off && caretOffset < endOffset && caretModel.getLogicalPosition().column == indentColumn; } else { selected = false; } Point start = editor.visualPositionToXY( new VisualPosition(startPosition.line + lineShift, indentColumn)); final VisualPosition endPosition = editor.offsetToVisualPosition(endOffset); Point end = editor.visualPositionToXY(new VisualPosition(endPosition.line, endPosition.column)); int maxY = end.y; if (endPosition.line == editor.offsetToVisualPosition(doc.getTextLength()).line) { maxY += editor.getLineHeight(); } Rectangle clip = g.getClipBounds(); if (clip != null) { if (clip.y >= maxY || clip.y + clip.height <= start.y) { return; } maxY = Math.min(maxY, clip.y + clip.height); } final EditorColorsScheme scheme = editor.getColorsScheme(); g.setColor( selected ? scheme.getColor(EditorColors.SELECTED_INDENT_GUIDE_COLOR) : scheme.getColor(EditorColors.INDENT_GUIDE_COLOR)); // There is a possible case that indent line intersects soft wrap-introduced text. // Example: // this is a long line <soft-wrap> // that| is soft-wrapped // | // | <- vertical indent // // Also it's possible that no additional intersections are added because of soft wrap: // this is a long line <soft-wrap> // | that is soft-wrapped // | // | <- vertical indent // We want to use the following approach then: // 1. Show only active indent if it crosses soft wrap-introduced text; // 2. Show indent as is if it doesn't intersect with soft wrap-introduced text; if (selected) { g.drawLine(start.x + 2, start.y, start.x + 2, maxY); } else { int y = start.y; int newY = start.y; SoftWrapModel softWrapModel = editor.getSoftWrapModel(); int lineHeight = editor.getLineHeight(); for (int i = Math.max(0, startLine + lineShift); i < endLine && newY < maxY; i++) { List<? extends SoftWrap> softWraps = softWrapModel.getSoftWrapsForLine(i); int logicalLineHeight = softWraps.size() * lineHeight; if (i > startLine + lineShift) { logicalLineHeight += lineHeight; // We assume that initial 'y' value points just below the target // line. } if (!softWraps.isEmpty() && softWraps.get(0).getIndentInColumns() < indentColumn) { if (y < newY || i > startLine + lineShift) { // There is a possible case that soft wrap is located on // indent start line. g.drawLine(start.x + 2, y, start.x + 2, newY + lineHeight); } newY += logicalLineHeight; y = newY; } else { newY += logicalLineHeight; } FoldRegion foldRegion = foldingModel.getCollapsedRegionAtOffset(doc.getLineEndOffset(i)); if (foldRegion != null && foldRegion.getEndOffset() < doc.getTextLength()) { i = doc.getLineNumber(foldRegion.getEndOffset()); } } if (y < maxY) { g.drawLine(start.x + 2, y, start.x + 2, maxY); } } } }; private volatile List<TextRange> myRanges; private volatile List<IndentGuideDescriptor> myDescriptors; public IndentsPass(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) { super(project, editor.getDocument(), false); myEditor = (EditorEx) editor; myFile = file; } @Override public void doCollectInformation(@NotNull ProgressIndicator progress) { assert myDocument != null; final Long stamp = myEditor.getUserData(LAST_TIME_INDENTS_BUILT); if (stamp != null && stamp.longValue() == nowStamp()) return; myDescriptors = buildDescriptors(); ArrayList<TextRange> ranges = new ArrayList<TextRange>(); for (IndentGuideDescriptor descriptor : myDescriptors) { ProgressManager.checkCanceled(); int endOffset = descriptor.endLine < myDocument.getLineCount() ? myDocument.getLineStartOffset(descriptor.endLine) : myDocument.getTextLength(); ranges.add(new TextRange(myDocument.getLineStartOffset(descriptor.startLine), endOffset)); } Collections.sort(ranges, RANGE_COMPARATOR); myRanges = ranges; } private long nowStamp() { if (!myEditor.getSettings().isIndentGuidesShown()) return -1; assert myDocument != null; return myDocument.getModificationStamp(); } @Override public void doApplyInformationToEditor() { final Long stamp = myEditor.getUserData(LAST_TIME_INDENTS_BUILT); if (stamp != null && stamp.longValue() == nowStamp()) return; List<RangeHighlighter> oldHighlighters = myEditor.getUserData(INDENT_HIGHLIGHTERS_IN_EDITOR_KEY); final List<RangeHighlighter> newHighlighters = new ArrayList<RangeHighlighter>(); final MarkupModel mm = myEditor.getMarkupModel(); int curRange = 0; if (oldHighlighters != null) { int curHighlight = 0; while (curRange < myRanges.size() && curHighlight < oldHighlighters.size()) { TextRange range = myRanges.get(curRange); RangeHighlighter highlighter = oldHighlighters.get(curHighlight); int cmp = compare(range, highlighter); if (cmp < 0) { newHighlighters.add(createHighlighter(mm, range)); curRange++; } else if (cmp > 0) { highlighter.dispose(); curHighlight++; } else { newHighlighters.add(highlighter); curHighlight++; curRange++; } } for (; curHighlight < oldHighlighters.size(); curHighlight++) { RangeHighlighter highlighter = oldHighlighters.get(curHighlight); highlighter.dispose(); } } final int startRangeIndex = curRange; assert myDocument != null; DocumentUtil.executeInBulk( myDocument, myRanges.size() > 10000, new Runnable() { @Override public void run() { for (int i = startRangeIndex; i < myRanges.size(); i++) { newHighlighters.add(createHighlighter(mm, myRanges.get(i))); } } }); myEditor.putUserData(INDENT_HIGHLIGHTERS_IN_EDITOR_KEY, newHighlighters); myEditor.putUserData(LAST_TIME_INDENTS_BUILT, nowStamp()); myEditor.getIndentsModel().assumeIndents(myDescriptors); } private List<IndentGuideDescriptor> buildDescriptors() { if (!myEditor.getSettings().isIndentGuidesShown()) return Collections.emptyList(); IndentsCalculator calculator = new IndentsCalculator(); calculator.calculate(); int[] lineIndents = calculator.lineIndents; TIntIntHashMap effectiveCommentColumns = calculator.indentAfterUncomment; List<IndentGuideDescriptor> descriptors = new ArrayList<IndentGuideDescriptor>(); IntStack lines = new IntStack(); IntStack indents = new IntStack(); lines.push(0); indents.push(0); assert myDocument != null; final CharSequence chars = myDocument.getCharsSequence(); for (int line = 1; line < lineIndents.length; line++) { ProgressManager.checkCanceled(); int curIndent = lineIndents[line]; while (!indents.empty() && curIndent <= indents.peek()) { ProgressManager.checkCanceled(); final int level = indents.pop(); int startLine = lines.pop(); if (level > 0) { boolean addDescriptor = effectiveCommentColumns.contains(startLine); // Indent started at comment if (!addDescriptor) { for (int i = startLine; i < line; i++) { if (level != lineIndents[i] && level != effectiveCommentColumns.get(i)) { addDescriptor = true; break; } } } if (addDescriptor) { descriptors.add(createDescriptor(level, startLine, line, chars)); } } } int prevLine = line - 1; int prevIndent = lineIndents[prevLine]; if (curIndent - prevIndent > 1) { lines.push(prevLine); indents.push(prevIndent); } } while (!indents.empty()) { ProgressManager.checkCanceled(); final int level = indents.pop(); int startLine = lines.pop(); if (level > 0) { descriptors.add(createDescriptor(level, startLine, myDocument.getLineCount(), chars)); } } return descriptors; } private IndentGuideDescriptor createDescriptor( int level, int startLine, int endLine, CharSequence chars) { while (startLine > 0 && isBlankLine(startLine, chars)) startLine--; return new IndentGuideDescriptor(level, startLine, endLine); } private boolean isBlankLine(int line, CharSequence chars) { Document document = myDocument; if (document == null) { return true; } int startOffset = document.getLineStartOffset(line); int endOffset = document.getLineEndOffset(line); return CharArrayUtil.shiftForward(chars, startOffset, endOffset, " \t") >= myDocument.getLineEndOffset(line); } /** * We want to treat comments specially in a way to skip comment prefix on line indent calculation. * * <p>Example: * * <pre> * if (true) { * int i1; * // int i2; * int i3; * } * </pre> * * We want to use 'int i2;' start offset as the third line indent (though it has non-white space * comment prefix (//) at the first column. * * <p>This method tries to parse comment prefix for the language implied by the given comment * type. It uses {@link #NO_COMMENT_INFO_MARKER} as an indicator that that information is * unavailable * * @param commentType target comment type * @return prefix of the comment denoted by the given type if any; {@link #NO_COMMENT_INFO_MARKER} * otherwise */ @NotNull private static String getCommentPrefix(@NotNull IElementType commentType) { Commenter c = LanguageCommenters.INSTANCE.forLanguage(commentType.getLanguage()); if (!(c instanceof CodeDocumentationAwareCommenter)) { COMMENT_PREFIXES.put(commentType, NO_COMMENT_INFO_MARKER); return NO_COMMENT_INFO_MARKER; } CodeDocumentationAwareCommenter commenter = (CodeDocumentationAwareCommenter) c; IElementType lineCommentType = commenter.getLineCommentTokenType(); String lineCommentPrefix = commenter.getLineCommentPrefix(); if (lineCommentType != null) { COMMENT_PREFIXES.put( lineCommentType, lineCommentPrefix == null ? NO_COMMENT_INFO_MARKER : lineCommentPrefix); } IElementType blockCommentType = commenter.getBlockCommentTokenType(); String blockCommentPrefix = commenter.getBlockCommentPrefix(); if (blockCommentType != null) { COMMENT_PREFIXES.put( blockCommentType, blockCommentPrefix == null ? NO_COMMENT_INFO_MARKER : blockCommentPrefix); } IElementType docCommentType = commenter.getDocumentationCommentTokenType(); String docCommentPrefix = commenter.getDocumentationCommentPrefix(); if (docCommentType != null) { COMMENT_PREFIXES.put( docCommentType, docCommentPrefix == null ? NO_COMMENT_INFO_MARKER : docCommentPrefix); } COMMENT_PREFIXES.putIfAbsent(commentType, NO_COMMENT_INFO_MARKER); return COMMENT_PREFIXES.get(commentType); } @NotNull private static RangeHighlighter createHighlighter(MarkupModel mm, TextRange range) { final RangeHighlighter highlighter = mm.addRangeHighlighter( range.getStartOffset(), range.getEndOffset(), 0, null, HighlighterTargetArea.EXACT_RANGE); highlighter.setCustomRenderer(RENDERER); return highlighter; } private static int compare(@NotNull TextRange r, @NotNull RangeHighlighter h) { int answer = r.getStartOffset() - h.getStartOffset(); return answer != 0 ? answer : r.getEndOffset() - h.getEndOffset(); } private class IndentsCalculator { @NotNull public final Map<Language, TokenSet> myComments = ContainerUtilRt.newHashMap(); /** * We need to treat specially commented lines. Consider a situation like below: * * <pre> * void test() { * if (true) { * int i; * // int j; * } * } * </pre> * * We don't want to show indent guide after 'int i;' line because un-commented line below ('int * j;') would have the same indent level. That's why we remember 'indents after un-comment' at * this collection. */ @NotNull public final TIntIntHashMap /* line -> indent column after un-comment */ indentAfterUncomment = new TIntIntHashMap(); @NotNull public final int[] lineIndents; @NotNull public final CharSequence myChars; IndentsCalculator() { assert myDocument != null; lineIndents = new int[myDocument.getLineCount()]; myChars = myDocument.getCharsSequence(); } /** Calculates line indents for the {@link #myDocument target document}. */ void calculate() { final FileType fileType = myFile.getFileType(); int prevLineIndent = -1; for (int line = 0; line < lineIndents.length; line++) { ProgressManager.checkCanceled(); int lineStart = myDocument.getLineStartOffset(line); int lineEnd = myDocument.getLineEndOffset(line); final int nonWhitespaceOffset = CharArrayUtil.shiftForward(myChars, lineStart, lineEnd, " \t"); if (nonWhitespaceOffset == lineEnd) { lineIndents[line] = -1; // Blank line marker } else { final int column = ((EditorImpl) myEditor).calcColumnNumber(nonWhitespaceOffset, line, true, myChars); if (prevLineIndent > 0 && prevLineIndent > column) { lineIndents[line] = calcIndent(line, nonWhitespaceOffset, lineEnd, column); } else { lineIndents[line] = column; } prevLineIndent = lineIndents[line]; } } int topIndent = 0; for (int line = 0; line < lineIndents.length; line++) { ProgressManager.checkCanceled(); if (lineIndents[line] >= 0) { topIndent = lineIndents[line]; } else { int startLine = line; while (line < lineIndents.length && lineIndents[line] < 0) { //noinspection AssignmentToForLoopParameter line++; } int bottomIndent = line < lineIndents.length ? lineIndents[line] : topIndent; int indent = Math.min(topIndent, bottomIndent); if (bottomIndent < topIndent) { int lineStart = myDocument.getLineStartOffset(line); int lineEnd = myDocument.getLineEndOffset(line); int nonWhitespaceOffset = CharArrayUtil.shiftForward(myChars, lineStart, lineEnd, " \t"); HighlighterIterator iterator = myEditor.getHighlighter().createIterator(nonWhitespaceOffset); if (BraceMatchingUtil.isRBraceToken(iterator, myChars, fileType)) { indent = topIndent; } } for (int blankLine = startLine; blankLine < line; blankLine++) { assert lineIndents[blankLine] == -1; lineIndents[blankLine] = Math.min(topIndent, indent); } //noinspection AssignmentToForLoopParameter line--; // will be incremented back at the end of the loop; } } } /** * Tries to calculate given line's indent column assuming that there might be a comment at the * given indent offset (see {@link #getCommentPrefix(IElementType)}). * * @param line target line * @param indentOffset start indent offset to use for the given line * @param lineEndOffset given line's end offset * @param fallbackColumn column to return if it's not possible to apply comment-specific indent * calculation rules * @return given line's indent column to use */ private int calcIndent(int line, int indentOffset, int lineEndOffset, int fallbackColumn) { final HighlighterIterator it = myEditor.getHighlighter().createIterator(indentOffset); IElementType tokenType = it.getTokenType(); Language language = tokenType.getLanguage(); TokenSet comments = myComments.get(language); if (comments == null) { ParserDefinition definition = LanguageParserDefinitions.INSTANCE.forLanguage(language); if (definition != null) { comments = definition.getCommentTokens(); } if (comments == null) { return fallbackColumn; } else { myComments.put(language, comments); } } if (comments.contains(tokenType) && indentOffset == it.getStart()) { String prefix = COMMENT_PREFIXES.get(tokenType); if (prefix == null) { prefix = getCommentPrefix(tokenType); } if (!NO_COMMENT_INFO_MARKER.equals(prefix)) { final int indentInsideCommentOffset = CharArrayUtil.shiftForward( myChars, indentOffset + prefix.length(), lineEndOffset, " \t"); if (indentInsideCommentOffset < lineEndOffset) { int indent = myEditor.calcColumnNumber(indentInsideCommentOffset, line); indentAfterUncomment.put(line, indent - prefix.length()); return indent; } } } return fallbackColumn; } } }
public class DumbServiceImpl extends DumbService implements Disposable { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.project.DumbServiceImpl"); private volatile boolean myDumb = false; private final DumbModeListener myPublisher; private final Queue<DumbModeTask> myUpdatesQueue = new Queue<DumbModeTask>(5); /** * Per-task progress indicators. Modified from EDT only. The task is removed from this map after * it's finished or when the project is disposed. */ private final Map<DumbModeTask, ProgressIndicatorEx> myProgresses = ContainerUtil.newConcurrentMap(); private final Queue<Runnable> myRunWhenSmartQueue = new Queue<Runnable>(5); private final Project myProject; public DumbServiceImpl(Project project) { myProject = project; myPublisher = project.getMessageBus().syncPublisher(DUMB_MODE); } @SuppressWarnings({"MethodOverridesStaticMethodOfSuperclass"}) public static DumbServiceImpl getInstance(@NotNull Project project) { return (DumbServiceImpl) DumbService.getInstance(project); } @Override public void queueTask(@NotNull final DumbModeTask task) { scheduleCacheUpdate(task, true); } @Override public void cancelTask(@NotNull DumbModeTask task) { if (ApplicationManager.getApplication().isInternal()) LOG.info("cancel " + task); ProgressIndicatorEx indicator = myProgresses.get(task); if (indicator != null) { indicator.cancel(); } } @Override public void dispose() { ApplicationManager.getApplication().assertIsDispatchThread(); myUpdatesQueue.clear(); myRunWhenSmartQueue.clear(); for (DumbModeTask task : new ArrayList<DumbModeTask>(myProgresses.keySet())) { cancelTask(task); Disposer.dispose(task); } } @Override public Project getProject() { return myProject; } @Override public boolean isDumb() { return myDumb; } @TestOnly public void setDumb(boolean dumb) { if (dumb) { myDumb = true; myPublisher.enteredDumbMode(); } else { updateFinished(); } } @Override public void runWhenSmart(@NotNull Runnable runnable) { if (!isDumb()) { runnable.run(); } else { synchronized (myRunWhenSmartQueue) { myRunWhenSmartQueue.addLast(runnable); } } } @SuppressWarnings("deprecation") public void queueCacheUpdate(@NotNull Collection<CacheUpdater> updaters) { scheduleCacheUpdate( new CacheUpdateRunner(myProject, new ArrayList<CacheUpdater>(updaters)), false); } @SuppressWarnings("deprecation") public void queueCacheUpdateInDumbMode(@NotNull Collection<CacheUpdater> updaters) { scheduleCacheUpdate( new CacheUpdateRunner(myProject, new ArrayList<CacheUpdater>(updaters)), true); } private void scheduleCacheUpdate(@NotNull final DumbModeTask task, boolean forceDumbMode) { if (ApplicationManager.getApplication().isInternal()) LOG.info("schedule " + task); final Application application = ApplicationManager.getApplication(); if (application.isUnitTestMode() || application.isHeadlessEnvironment() || !forceDumbMode && !myDumb && application.isReadAccessAllowed()) { final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); if (indicator != null) { indicator.pushState(); } try { HeavyProcessLatch.INSTANCE.processStarted(); task.performInDumbMode(indicator != null ? indicator : new EmptyProgressIndicator()); } finally { HeavyProcessLatch.INSTANCE.processFinished(); if (indicator != null) { indicator.popState(); } Disposer.dispose(task); } return; } UIUtil.invokeLaterIfNeeded( new DumbAwareRunnable() { @Override public void run() { if (myProject.isDisposed()) { return; } final ProgressIndicatorBase indicator = new ProgressIndicatorBase(); myProgresses.put(task, indicator); Disposer.register( task, new Disposable() { @Override public void dispose() { application.assertIsDispatchThread(); myProgresses.remove(task); } }); // ok to test and set the flag like this, because the change is always done from // dispatch thread if (!myDumb) { // always change dumb status inside write action. // This will ensure all active read actions are completed before the app goes dumb boolean startSuccess = application.runWriteAction( new Computable<Boolean>() { @Override public Boolean compute() { myDumb = true; try { myPublisher.enteredDumbMode(); } catch (Throwable e) { LOG.error(e); } try { startBackgroundProcess(task, indicator); } catch (Throwable e) { LOG.error("Failed to start background index update task", e); return false; } return true; } }); if (!startSuccess) { updateFinished(); } } else { myUpdatesQueue.addLast(task); } } }); } private void updateFinished() { myDumb = false; if (myProject.isDisposed()) return; if (ApplicationManager.getApplication().isInternal()) LOG.info("updateFinished"); try { myPublisher.exitDumbMode(); FileEditorManagerEx.getInstanceEx(myProject).refreshIcons(); } finally { // It may happen that one of the pending runWhenSmart actions triggers new dumb mode; // in this case we should quit processing pending actions and postpone them until the newly // started dumb mode finishes. while (!myDumb) { final Runnable runnable; synchronized (myRunWhenSmartQueue) { if (myRunWhenSmartQueue.isEmpty()) { break; } runnable = myRunWhenSmartQueue.pullFirst(); } try { runnable.run(); } catch (Throwable e) { LOG.error(e); } } } } @Override public void showDumbModeNotification(@NotNull final String message) { UIUtil.invokeLaterIfNeeded( new Runnable() { @Override public void run() { final IdeFrame ideFrame = WindowManager.getInstance().getIdeFrame(myProject); if (ideFrame != null) { StatusBarEx statusBar = (StatusBarEx) ideFrame.getStatusBar(); statusBar.notifyProgressByBalloon(MessageType.WARNING, message, null, null); } } }); } @Override public void waitForSmartMode() { final Application application = ApplicationManager.getApplication(); if (!application.isUnitTestMode()) { assert !application.isDispatchThread(); assert !application.isReadAccessAllowed(); } if (!isDumb()) { return; } final Semaphore semaphore = new Semaphore(); semaphore.down(); runWhenSmart( new Runnable() { @Override public void run() { semaphore.up(); } }); semaphore.waitFor(); } @Override public JComponent wrapGently( @NotNull JComponent dumbUnawareContent, @NotNull Disposable parentDisposable) { final DumbUnawareHider wrapper = new DumbUnawareHider(dumbUnawareContent); wrapper.setContentVisible(!isDumb()); getProject() .getMessageBus() .connect(parentDisposable) .subscribe( DUMB_MODE, new DumbModeListener() { @Override public void enteredDumbMode() { wrapper.setContentVisible(false); } @Override public void exitDumbMode() { wrapper.setContentVisible(true); } }); return wrapper; } public void smartInvokeLater(@NotNull final Runnable runnable) { ApplicationManager.getApplication() .invokeLater( new Runnable() { @Override public void run() { runWhenSmart(runnable); } }, myProject.getDisposed()); } public void smartInvokeLater( @NotNull final Runnable runnable, @NotNull ModalityState modalityState) { ApplicationManager.getApplication() .invokeLater( new Runnable() { @Override public void run() { runWhenSmart(runnable); } }, modalityState, myProject.getDisposed()); } private void startBackgroundProcess( @NotNull final DumbModeTask task, @NotNull final ProgressIndicatorEx indicator) { ProgressManager.getInstance() .run( new Task.Backgroundable(myProject, IdeBundle.message("progress.indexing"), false) { @Override public void run(@NotNull final ProgressIndicator visibleIndicator) { if (ApplicationManager.getApplication().isInternal()) LOG.info("Running dumb mode task: " + task); final ShutDownTracker shutdownTracker = ShutDownTracker.getInstance(); final Thread self = Thread.currentThread(); try { HeavyProcessLatch.INSTANCE.processStarted(); shutdownTracker.registerStopperThread(self); indicator.checkCanceled(); if (visibleIndicator instanceof ProgressIndicatorEx) { indicator.addStateDelegate((ProgressIndicatorEx) visibleIndicator); ((ProgressIndicatorEx) visibleIndicator) .addStateDelegate(new AppIconProgress()); } indicator.setIndeterminate(true); indicator.setText(IdeBundle.message("progress.indexing.scanning")); task.performInDumbMode(indicator); } catch (ProcessCanceledException ignored) { } catch (Throwable unexpected) { LOG.error(unexpected); } finally { shutdownTracker.unregisterStopperThread(self); HeavyProcessLatch.INSTANCE.processFinished(); taskFinished(task); } } }); } private void taskFinished(@NotNull final DumbModeTask prevTask) { UIUtil.invokeAndWaitIfNeeded( new Runnable() { @Override public void run() { if (myProject.isDisposed()) return; Disposer.dispose(prevTask); while (true) { if (myUpdatesQueue.isEmpty()) { updateFinished(); return; } DumbModeTask queuedTask = myUpdatesQueue.pullFirst(); ProgressIndicatorEx indicator = myProgresses.get(queuedTask); if (indicator.isCanceled()) { Disposer.dispose(queuedTask); continue; } startBackgroundProcess(queuedTask, indicator); return; } } }); } private class AppIconProgress extends ProgressIndicatorBase { private double lastFraction; @Override public void setFraction(final double fraction) { if (fraction - lastFraction < 0.01d) return; lastFraction = fraction; UIUtil.invokeLaterIfNeeded( new Runnable() { @Override public void run() { AppIcon.getInstance() .setProgress( myProject, "indexUpdate", AppIconScheme.Progress.INDEXING, fraction, true); } }); } @Override public void finish(@NotNull TaskInfo task) { if (lastFraction != 0) { // we should call setProgress at least once before UIUtil.invokeLaterIfNeeded( new Runnable() { @Override public void run() { AppIcon appIcon = AppIcon.getInstance(); if (appIcon.hideProgress(myProject, "indexUpdate")) { appIcon.requestAttention(myProject, false); appIcon.setOkBadge(myProject, true); } } }); } } } }
public class PsiElementFactoryImpl extends PsiJavaParserFacadeImpl implements PsiElementFactory { private final NotNullLazyValue<PsiClass> myArrayClass = new AtomicNotNullLazyValue<PsiClass>() { @NotNull @Override protected PsiClass compute() { return createArrayClass( "public class __Array__{\n public final int length;\n public Object clone() {}\n}", LanguageLevel.JDK_1_3); } }; private final NotNullLazyValue<PsiClass> myArrayClass15 = new AtomicNotNullLazyValue<PsiClass>() { @NotNull @Override protected PsiClass compute() { return createArrayClass( "public class __Array__<T> {\n public final int length;\n public T[] clone() {}\n}", LanguageLevel.JDK_1_5); } }; private final ConcurrentMap<GlobalSearchScope, PsiClassType> myCachedObjectType = ContainerUtil.newConcurrentMap(); public PsiElementFactoryImpl(final PsiManagerEx manager) { super(manager); manager.registerRunnableToRunOnChange( new Runnable() { @Override public void run() { myCachedObjectType.clear(); } }); } @NotNull @Override public PsiClass getArrayClass(@NotNull LanguageLevel languageLevel) { return (languageLevel.isAtLeast(LanguageLevel.JDK_1_5) ? myArrayClass15 : myArrayClass) .getValue(); } private PsiClass createArrayClass(String text, LanguageLevel level) { PsiClass psiClass = ((PsiExtensibleClass) createClassFromText(text, null)).getOwnInnerClasses().get(0); ensureNonWritable(psiClass); PsiFile file = psiClass.getContainingFile(); ((PsiJavaFileBaseImpl) file).clearCaches(); PsiUtil.FILE_LANGUAGE_LEVEL_KEY.set(file, level); return psiClass; } private static void ensureNonWritable(PsiClass arrayClass) { try { arrayClass.getContainingFile().getViewProvider().getVirtualFile().setWritable(false); } catch (IOException ignored) { } } @NotNull @Override public PsiClassType getArrayClassType( @NotNull final PsiType componentType, @NotNull final LanguageLevel languageLevel) { final PsiClass arrayClass = getArrayClass(languageLevel); final PsiTypeParameter[] typeParameters = arrayClass.getTypeParameters(); PsiSubstitutor substitutor = PsiSubstitutor.EMPTY; if (typeParameters.length == 1) { substitutor = substitutor.put(typeParameters[0], componentType); } return createType(arrayClass, substitutor); } @NotNull @Override public PsiClassType createType(@NotNull PsiClass resolve, @NotNull PsiSubstitutor substitutor) { return new PsiImmediateClassType(resolve, substitutor); } @NotNull @Override public PsiClassType createType( @NotNull PsiClass resolve, @NotNull PsiSubstitutor substitutor, @Nullable LanguageLevel languageLevel) { return new PsiImmediateClassType(resolve, substitutor, languageLevel); } @NotNull @Override public PsiClassType createType( @NotNull PsiClass resolve, @NotNull PsiSubstitutor substitutor, @Nullable LanguageLevel languageLevel, @NotNull PsiAnnotation[] annotations) { return new PsiImmediateClassType(resolve, substitutor, languageLevel, annotations); } @NotNull @Override public PsiClass createClass(@NotNull final String name) throws IncorrectOperationException { return createClassInner("class", name); } @NotNull @Override public PsiClass createInterface(@NotNull final String name) throws IncorrectOperationException { return createClassInner("interface", name); } @NotNull @Override public PsiClass createEnum(@NotNull final String name) throws IncorrectOperationException { return createClassInner("enum", name); } @NotNull @Override public PsiClass createAnnotationType(@NotNull @NonNls String name) throws IncorrectOperationException { return createClassInner("@interface", name); } private PsiClass createClassInner(@NonNls final String type, @NonNls String name) { PsiUtil.checkIsIdentifier(myManager, name); final PsiJavaFile aFile = createDummyJavaFile("public " + type + " " + name + " { }"); final PsiClass[] classes = aFile.getClasses(); if (classes.length != 1) { throw new IncorrectOperationException("Incorrect " + type + " name \"" + name + "\"."); } return classes[0]; } @NotNull @Override public PsiTypeElement createTypeElement(@NotNull final PsiType psiType) { final LightTypeElement element = new LightTypeElement(myManager, psiType); CodeEditUtil.setNodeGenerated(element.getNode(), true); return element; } @NotNull @Override public PsiJavaCodeReferenceElement createReferenceElementByType( @NotNull final PsiClassType type) { if (type instanceof PsiClassReferenceType) { return ((PsiClassReferenceType) type).getReference(); } return new LightClassTypeReference(myManager, type); } @NotNull @Override public PsiTypeParameterList createTypeParameterList() { final PsiTypeParameterList parameterList = createMethodFromText("void foo()", null).getTypeParameterList(); assert parameterList != null; return parameterList; } @NotNull @Override public PsiTypeParameter createTypeParameter(String name, PsiClassType[] superTypes) { @NonNls StringBuilder builder = new StringBuilder(); builder.append("public <").append(name); if (superTypes.length > 1 || superTypes.length == 1 && !superTypes[0].equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) { builder.append(" extends "); for (PsiClassType type : superTypes) { if (type.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) continue; builder.append(type.getCanonicalText(true)).append('&'); } builder.delete(builder.length() - 1, builder.length()); } builder.append("> void foo(){}"); try { return createMethodFromText(builder.toString(), null).getTypeParameters()[0]; } catch (RuntimeException e) { throw new IncorrectOperationException("type parameter text: " + builder.toString()); } } @NotNull @Override public PsiField createField(@NotNull final String name, @NotNull final PsiType type) throws IncorrectOperationException { PsiUtil.checkIsIdentifier(myManager, name); if (PsiType.NULL.equals(type)) { throw new IncorrectOperationException("Cannot create field with type \"null\"."); } @NonNls final String text = "class _Dummy_ { private " + type.getCanonicalText(true) + " " + name + "; }"; final PsiJavaFile aFile = createDummyJavaFile(text); final PsiClass[] classes = aFile.getClasses(); if (classes.length < 1) { throw new IncorrectOperationException("Class was not created " + text); } final PsiClass psiClass = classes[0]; final PsiField[] fields = psiClass.getFields(); if (fields.length < 1) { throw new IncorrectOperationException("Field was not created " + text); } PsiField field = fields[0]; field = (PsiField) JavaCodeStyleManager.getInstance(myManager.getProject()).shortenClassReferences(field); return (PsiField) CodeStyleManager.getInstance(myManager.getProject()).reformat(field); } @NotNull @Override public PsiMethod createMethod(@NotNull final String name, final PsiType returnType) throws IncorrectOperationException { PsiUtil.checkIsIdentifier(myManager, name); if (PsiType.NULL.equals(returnType)) { throw new IncorrectOperationException("Cannot create method with type \"null\"."); } final String canonicalText = returnType.getCanonicalText(true); final PsiJavaFile aFile = createDummyJavaFile("class _Dummy_ { public " + canonicalText + " " + name + "() {} }"); final PsiClass[] classes = aFile.getClasses(); if (classes.length < 1) { throw new IncorrectOperationException( "Class was not created. Method name: " + name + "; return type: " + canonicalText); } final PsiMethod[] methods = classes[0].getMethods(); if (methods.length < 1) { throw new IncorrectOperationException( "Method was not created. Method name: " + name + "; return type: " + canonicalText); } PsiMethod method = methods[0]; method = (PsiMethod) JavaCodeStyleManager.getInstance(myManager.getProject()).shortenClassReferences(method); return (PsiMethod) CodeStyleManager.getInstance(myManager.getProject()).reformat(method); } @NotNull @Override public PsiMethod createMethod( @NotNull @NonNls String name, PsiType returnType, PsiElement context) throws IncorrectOperationException { return createMethodFromText( "public " + returnType.getCanonicalText(true) + " " + name + "() {}", context); } @NotNull @Override public PsiMethod createConstructor() { return createConstructor("_Dummy_"); } @NotNull @Override public PsiMethod createConstructor(@NotNull @NonNls final String name) { final PsiJavaFile aFile = createDummyJavaFile("class " + name + " { public " + name + "() {} }"); final PsiMethod method = aFile.getClasses()[0].getMethods()[0]; return (PsiMethod) CodeStyleManager.getInstance(myManager.getProject()).reformat(method); } @Override public PsiMethod createConstructor(@NotNull @NonNls String name, PsiElement context) { return createMethodFromText(name + "() {}", context); } @NotNull @Override public PsiClassInitializer createClassInitializer() throws IncorrectOperationException { final PsiJavaFile aFile = createDummyJavaFile("class _Dummy_ { {} }"); final PsiClassInitializer classInitializer = aFile.getClasses()[0].getInitializers()[0]; return (PsiClassInitializer) CodeStyleManager.getInstance(myManager.getProject()).reformat(classInitializer); } @NotNull @Override public PsiParameter createParameter(@NotNull final String name, @NotNull final PsiType type) throws IncorrectOperationException { PsiUtil.checkIsIdentifier(myManager, name); if (PsiType.NULL.equals(type)) { throw new IncorrectOperationException("Cannot create parameter with type \"null\"."); } final String text = type.getCanonicalText(true) + " " + name; PsiParameter parameter = createParameterFromText(text, null); final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(myManager.getProject()); PsiUtil.setModifierProperty( parameter, PsiModifier.FINAL, JavaCodeStyleSettingsFacade.getInstance(myManager.getProject()) .isGenerateFinalParameters()); GeneratedMarkerVisitor.markGenerated(parameter); parameter = (PsiParameter) JavaCodeStyleManager.getInstance(myManager.getProject()) .shortenClassReferences(parameter); return (PsiParameter) codeStyleManager.reformat(parameter); } @Override public PsiParameter createParameter( @NotNull @NonNls String name, PsiType type, PsiElement context) throws IncorrectOperationException { final PsiMethod psiMethod = createMethodFromText( "void f(" + type.getCanonicalText(true) + " " + name + ") {}", context); final PsiParameter[] parameters = psiMethod.getParameterList().getParameters(); return parameters[0]; } @NotNull @Override public PsiCodeBlock createCodeBlock() { final PsiCodeBlock block = createCodeBlockFromText("{}", null); return (PsiCodeBlock) CodeStyleManager.getInstance(myManager.getProject()).reformat(block); } @NotNull @Override public PsiClassType createType(@NotNull final PsiClass aClass) { return new PsiImmediateClassType( aClass, aClass instanceof PsiTypeParameter ? PsiSubstitutor.EMPTY : createRawSubstitutor(aClass)); } @NotNull @Override public PsiClassType createType(@NotNull final PsiJavaCodeReferenceElement classReference) { return new PsiClassReferenceType(classReference, null); } @NotNull @Override public PsiClassType createType(@NotNull final PsiClass aClass, final PsiType parameter) { final PsiTypeParameter[] typeParameters = aClass.getTypeParameters(); assert typeParameters.length == 1 : aClass; return createType(aClass, PsiSubstitutor.EMPTY.put(typeParameters[0], parameter)); } @NotNull @Override public PsiClassType createType(@NotNull final PsiClass aClass, final PsiType... parameters) { return createType(aClass, PsiSubstitutor.EMPTY.putAll(aClass, parameters)); } @NotNull @Override public PsiSubstitutor createRawSubstitutor(@NotNull final PsiTypeParameterListOwner owner) { Map<PsiTypeParameter, PsiType> substitutorMap = null; for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable(owner)) { if (substitutorMap == null) substitutorMap = new HashMap<PsiTypeParameter, PsiType>(); substitutorMap.put(parameter, null); } return PsiSubstitutorImpl.createSubstitutor(substitutorMap); } @NotNull @Override public PsiSubstitutor createRawSubstitutor( @NotNull final PsiSubstitutor baseSubstitutor, @NotNull final PsiTypeParameter[] typeParameters) { Map<PsiTypeParameter, PsiType> substitutorMap = null; for (PsiTypeParameter parameter : typeParameters) { if (substitutorMap == null) substitutorMap = new HashMap<PsiTypeParameter, PsiType>(); substitutorMap.put(parameter, null); } return PsiSubstitutorImpl.createSubstitutor(substitutorMap).putAll(baseSubstitutor); } @NotNull @Override public PsiElement createDummyHolder( @NotNull final String text, @NotNull final IElementType type, @Nullable final PsiElement context) { final DummyHolder result = DummyHolderFactory.createHolder(myManager, context); final FileElement holder = result.getTreeElement(); final Language language = type.getLanguage(); final ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(language); assert parserDefinition != null : "No parser definition for language " + language; final Project project = myManager.getProject(); final Lexer lexer = parserDefinition.createLexer(project); final PsiBuilder builder = PsiBuilderFactory.getInstance().createBuilder(project, holder, lexer, language, text); final ASTNode node = parserDefinition.createParser(project).parse(type, builder); holder.rawAddChildren((TreeElement) node); final PsiElement psi = node.getPsi(); assert psi != null : text; return psi; } @NotNull @Override public PsiSubstitutor createSubstitutor(@NotNull final Map<PsiTypeParameter, PsiType> map) { return PsiSubstitutorImpl.createSubstitutor(map); } @Nullable @Override public PsiPrimitiveType createPrimitiveType(@NotNull final String text) { return PsiJavaParserFacadeImpl.getPrimitiveType(text); } @NotNull @Override public PsiClassType createTypeByFQClassName(@NotNull final String qName) { return createTypeByFQClassName(qName, GlobalSearchScope.allScope(myManager.getProject())); } @NotNull @Override public PsiClassType createTypeByFQClassName( @NotNull final String qName, @NotNull final GlobalSearchScope resolveScope) { if (CommonClassNames.JAVA_LANG_OBJECT.equals(qName)) { PsiClassType cachedObjectType = myCachedObjectType.get(resolveScope); if (cachedObjectType != null) { return cachedObjectType; } PsiClass aClass = JavaPsiFacade.getInstance(myManager.getProject()) .findClass(CommonClassNames.JAVA_LANG_OBJECT, resolveScope); if (aClass != null) { cachedObjectType = new PsiImmediateClassType(aClass, PsiSubstitutor.EMPTY); cachedObjectType = ConcurrencyUtil.cacheOrGet(myCachedObjectType, resolveScope, cachedObjectType); return cachedObjectType; } } return new PsiClassReferenceType( createReferenceElementByFQClassName(qName, resolveScope), null); } @NotNull @Override public PsiJavaCodeReferenceElement createClassReferenceElement(@NotNull final PsiClass aClass) { final String text; if (aClass instanceof PsiAnonymousClass) { text = ((PsiAnonymousClass) aClass).getBaseClassType().getPresentableText(); } else { text = aClass.getName(); } if (text == null) { throw new IncorrectOperationException("Invalid class: " + aClass); } return new LightClassReference(myManager, text, aClass); } @NotNull @Override public PsiJavaCodeReferenceElement createReferenceElementByFQClassName( @NotNull final String qName, @NotNull final GlobalSearchScope resolveScope) { final String shortName = PsiNameHelper.getShortClassName(qName); return new LightClassReference(myManager, shortName, qName, resolveScope); } @NotNull @Override public PsiJavaCodeReferenceElement createFQClassNameReferenceElement( @NotNull final String qName, @NotNull final GlobalSearchScope resolveScope) { return new LightClassReference(myManager, qName, qName, resolveScope); } @NotNull @Override public PsiJavaCodeReferenceElement createPackageReferenceElement( @NotNull final PsiPackage aPackage) throws IncorrectOperationException { if (aPackage.getQualifiedName().isEmpty()) { throw new IncorrectOperationException("Cannot create reference to default package."); } return new LightPackageReference(myManager, aPackage); } @NotNull @Override public PsiPackageStatement createPackageStatement(@NotNull final String name) throws IncorrectOperationException { final PsiJavaFile aFile = createDummyJavaFile("package " + name + ";"); final PsiPackageStatement stmt = aFile.getPackageStatement(); if (stmt == null) { throw new IncorrectOperationException("Incorrect package name: " + name); } return stmt; } @NotNull @Override public PsiImportStaticStatement createImportStaticStatement( @NotNull final PsiClass aClass, @NotNull final String memberName) throws IncorrectOperationException { if (aClass instanceof PsiAnonymousClass) { throw new IncorrectOperationException("Cannot create import statement for anonymous class."); } else if (aClass.getParent() instanceof PsiDeclarationStatement) { throw new IncorrectOperationException("Cannot create import statement for local class."); } final PsiJavaFile aFile = createDummyJavaFile("import static " + aClass.getQualifiedName() + "." + memberName + ";"); final PsiImportStatementBase statement = extractImport(aFile, true); return (PsiImportStaticStatement) CodeStyleManager.getInstance(myManager.getProject()).reformat(statement); } @NotNull @Override public PsiParameterList createParameterList( @NotNull final String[] names, @NotNull final PsiType[] types) throws IncorrectOperationException { @NonNls StringBuilder builder = new StringBuilder(); builder.append("void method("); for (int i = 0; i < names.length; i++) { if (i > 0) builder.append(", "); builder.append(types[i].getCanonicalText(true)).append(' ').append(names[i]); } builder.append(");"); return createMethodFromText(builder.toString(), null).getParameterList(); } @NotNull @Override public PsiReferenceList createReferenceList( @NotNull final PsiJavaCodeReferenceElement[] references) throws IncorrectOperationException { @NonNls final StringBuilder builder = new StringBuilder(); builder.append("void method()"); if (references.length > 0) { builder.append(" throws "); for (int i = 0; i < references.length; i++) { if (i > 0) builder.append(", "); builder.append(references[i].getCanonicalText()); } } builder.append(';'); return createMethodFromText(builder.toString(), null).getThrowsList(); } @NotNull @Override public PsiJavaCodeReferenceElement createPackageReferenceElement( @NotNull final String packageName) throws IncorrectOperationException { if (packageName.isEmpty()) { throw new IncorrectOperationException("Cannot create reference to default package."); } return new LightPackageReference(myManager, packageName); } @NotNull @Override public PsiReferenceExpression createReferenceExpression(@NotNull final PsiClass aClass) throws IncorrectOperationException { final String text; if (aClass instanceof PsiAnonymousClass) { text = ((PsiAnonymousClass) aClass).getBaseClassType().getPresentableText(); } else { text = aClass.getName(); } return new LightClassReferenceExpression(myManager, text, aClass); } @NotNull @Override public PsiReferenceExpression createReferenceExpression(@NotNull final PsiPackage aPackage) throws IncorrectOperationException { if (aPackage.getQualifiedName().isEmpty()) { throw new IncorrectOperationException("Cannot create reference to default package."); } return new LightPackageReferenceExpression(myManager, aPackage); } @NotNull @Override public PsiIdentifier createIdentifier(@NotNull final String text) throws IncorrectOperationException { PsiUtil.checkIsIdentifier(myManager, text); return new LightIdentifier(myManager, text); } @NotNull @Override public PsiKeyword createKeyword(@NotNull final String text) throws IncorrectOperationException { if (!PsiNameHelper.getInstance(myManager.getProject()).isKeyword(text)) { throw new IncorrectOperationException("\"" + text + "\" is not a keyword."); } return new LightKeyword(myManager, text); } @NotNull @Override public PsiKeyword createKeyword(@NotNull @NonNls String keyword, PsiElement context) throws IncorrectOperationException { LanguageLevel level = PsiUtil.getLanguageLevel(context); if (!JavaLexer.isKeyword(keyword, level) && !JavaLexer.isSoftKeyword(keyword, level)) { throw new IncorrectOperationException("\"" + keyword + "\" is not a keyword."); } return new LightKeyword(myManager, keyword); } @NotNull @Override public PsiImportStatement createImportStatement(@NotNull final PsiClass aClass) throws IncorrectOperationException { if (aClass instanceof PsiAnonymousClass) { throw new IncorrectOperationException("Cannot create import statement for anonymous class."); } else if (aClass.getParent() instanceof PsiDeclarationStatement) { throw new IncorrectOperationException("Cannot create import statement for local class."); } final PsiJavaFile aFile = createDummyJavaFile("import " + aClass.getQualifiedName() + ";"); final PsiImportStatementBase statement = extractImport(aFile, false); return (PsiImportStatement) CodeStyleManager.getInstance(myManager.getProject()).reformat(statement); } @NotNull @Override public PsiImportStatement createImportStatementOnDemand(@NotNull final String packageName) throws IncorrectOperationException { if (packageName.isEmpty()) { throw new IncorrectOperationException("Cannot create import statement for default package."); } if (!PsiNameHelper.getInstance(myManager.getProject()).isQualifiedName(packageName)) { throw new IncorrectOperationException("Incorrect package name: \"" + packageName + "\"."); } final PsiJavaFile aFile = createDummyJavaFile("import " + packageName + ".*;"); final PsiImportStatementBase statement = extractImport(aFile, false); return (PsiImportStatement) CodeStyleManager.getInstance(myManager.getProject()).reformat(statement); } @NotNull @Override public PsiDeclarationStatement createVariableDeclarationStatement( @NonNls @NotNull String name, @NotNull PsiType type, @Nullable PsiExpression initializer) throws IncorrectOperationException { return createVariableDeclarationStatement(name, type, initializer, null); } @NotNull @Override public PsiDeclarationStatement createVariableDeclarationStatement( @NonNls @NotNull String name, @NotNull PsiType type, @Nullable PsiExpression initializer, @Nullable PsiElement context) throws IncorrectOperationException { if (!isIdentifier(name)) { throw new IncorrectOperationException("\"" + name + "\" is not an identifier."); } if (PsiType.NULL.equals(type)) { throw new IncorrectOperationException("Cannot create variable with type \"null\"."); } String text = "X " + name + (initializer != null ? " = x" : "") + ";"; PsiDeclarationStatement statement = (PsiDeclarationStatement) createStatementFromText(text, context); PsiVariable variable = (PsiVariable) statement.getDeclaredElements()[0]; replace(variable.getTypeElement(), createTypeElement(type), text); boolean generateFinalLocals = JavaCodeStyleSettingsFacade.getInstance(myManager.getProject()).isGenerateFinalLocals(); PsiUtil.setModifierProperty(variable, PsiModifier.FINAL, generateFinalLocals); if (initializer != null) { replace(variable.getInitializer(), initializer, text); } GeneratedMarkerVisitor.markGenerated(statement); return statement; } private static void replace( @Nullable PsiElement original, @NotNull PsiElement replacement, @NotNull String message) { assert original != null : message; original.replace(replacement); } @NotNull @Override public PsiDocTag createParamTag( @NotNull final String parameterName, @NonNls final String description) throws IncorrectOperationException { @NonNls final StringBuilder builder = new StringBuilder(); builder.append(" * @param "); builder.append(parameterName); builder.append(" "); final String[] strings = description.split("\\n"); for (int i = 0; i < strings.length; i++) { if (i > 0) builder.append("\n * "); builder.append(strings[i]); } return createDocTagFromText(builder.toString()); } @NotNull @Override public PsiAnnotation createAnnotationFromText( @NotNull final String annotationText, @Nullable final PsiElement context) throws IncorrectOperationException { final PsiAnnotation psiAnnotation = super.createAnnotationFromText(annotationText, context); GeneratedMarkerVisitor.markGenerated(psiAnnotation); return psiAnnotation; } @NotNull @Override public PsiCodeBlock createCodeBlockFromText( @NotNull final String text, @Nullable final PsiElement context) throws IncorrectOperationException { final PsiCodeBlock psiCodeBlock = super.createCodeBlockFromText(text, context); GeneratedMarkerVisitor.markGenerated(psiCodeBlock); return psiCodeBlock; } @NotNull @Override public PsiEnumConstant createEnumConstantFromText( @NotNull final String text, @Nullable final PsiElement context) throws IncorrectOperationException { final PsiEnumConstant enumConstant = super.createEnumConstantFromText(text, context); GeneratedMarkerVisitor.markGenerated(enumConstant); return enumConstant; } @NotNull @Override public PsiExpression createExpressionFromText( @NotNull final String text, @Nullable final PsiElement context) throws IncorrectOperationException { final PsiExpression expression = super.createExpressionFromText(text, context); GeneratedMarkerVisitor.markGenerated(expression); return expression; } @NotNull @Override public PsiField createFieldFromText( @NotNull final String text, @Nullable final PsiElement context) throws IncorrectOperationException { final PsiField psiField = super.createFieldFromText(text, context); GeneratedMarkerVisitor.markGenerated(psiField); return psiField; } @NotNull @Override public PsiParameter createParameterFromText( @NotNull final String text, @Nullable final PsiElement context) throws IncorrectOperationException { final PsiParameter parameter = super.createParameterFromText(text, context); GeneratedMarkerVisitor.markGenerated(parameter); return parameter; } @NotNull @Override public PsiStatement createStatementFromText( @NotNull final String text, @Nullable final PsiElement context) throws IncorrectOperationException { final PsiStatement statement = super.createStatementFromText(text, context); GeneratedMarkerVisitor.markGenerated(statement); return statement; } @NotNull @Override public PsiType createTypeFromText(@NotNull final String text, @Nullable final PsiElement context) throws IncorrectOperationException { return createTypeInner(text, context, true); } @NotNull @Override public PsiTypeParameter createTypeParameterFromText( @NotNull final String text, final PsiElement context) throws IncorrectOperationException { final PsiTypeParameter typeParameter = super.createTypeParameterFromText(text, context); GeneratedMarkerVisitor.markGenerated(typeParameter); return typeParameter; } @NotNull @Override public PsiMethod createMethodFromText( @NotNull final String text, final PsiElement context, final LanguageLevel level) throws IncorrectOperationException { final PsiMethod method = super.createMethodFromText(text, context, level); GeneratedMarkerVisitor.markGenerated(method); return method; } private static PsiImportStatementBase extractImport( final PsiJavaFile aFile, final boolean isStatic) { final PsiImportList importList = aFile.getImportList(); assert importList != null : aFile; final PsiImportStatementBase[] statements = isStatic ? importList.getImportStaticStatements() : importList.getImportStatements(); assert statements.length == 1 : aFile.getText(); return statements[0]; } private static final JavaParserUtil.ParserWrapper CATCH_SECTION = new JavaParserUtil.ParserWrapper() { @Override public void parse(final PsiBuilder builder) { JavaParser.INSTANCE.getStatementParser().parseCatchBlock(builder); } }; @NotNull @Override public PsiCatchSection createCatchSection( @NotNull final PsiType exceptionType, @NotNull final String exceptionName, @Nullable final PsiElement context) throws IncorrectOperationException { if (!(exceptionType instanceof PsiClassType || exceptionType instanceof PsiDisjunctionType)) { throw new IncorrectOperationException("Unexpected type:" + exceptionType); } @NonNls final String text = "catch (" + exceptionType.getCanonicalText(true) + " " + exceptionName + ") {}"; final DummyHolder holder = DummyHolderFactory.createHolder( myManager, new JavaDummyElement(text, CATCH_SECTION, level(context)), context); final PsiElement element = SourceTreeToPsiMap.treeElementToPsi(holder.getTreeElement().getFirstChildNode()); if (!(element instanceof PsiCatchSection)) { throw new IncorrectOperationException( "Incorrect catch section '" + text + "'. Parsed element: " + element); } final Project project = myManager.getProject(); final JavaPsiImplementationHelper helper = JavaPsiImplementationHelper.getInstance(project); helper.setupCatchBlock(exceptionName, exceptionType, context, (PsiCatchSection) element); final CodeStyleManager styleManager = CodeStyleManager.getInstance(project); final PsiCatchSection catchSection = (PsiCatchSection) styleManager.reformat(element); GeneratedMarkerVisitor.markGenerated(catchSection); return catchSection; } @Override public boolean isValidClassName(@NotNull String name) { return isIdentifier(name); } @Override public boolean isValidMethodName(@NotNull String name) { return isIdentifier(name); } @Override public boolean isValidParameterName(@NotNull String name) { return isIdentifier(name); } @Override public boolean isValidFieldName(@NotNull String name) { return isIdentifier(name); } @Override public boolean isValidLocalVariableName(@NotNull String name) { return isIdentifier(name); } private boolean isIdentifier(@NotNull String name) { return PsiNameHelper.getInstance(myManager.getProject()).isIdentifier(name); } }
/** User: anna Date: 24-Feb-2006 */ public class SeverityRegistrar implements JDOMExternalizable, Comparator<HighlightSeverity> { @NonNls private static final String INFO_TAG = "info"; @NonNls private static final String COLOR_ATTRIBUTE = "color"; private final Map<String, SeverityBasedTextAttributes> myMap = ContainerUtil.newConcurrentMap(); private final Map<String, Color> myRendererColors = ContainerUtil.newConcurrentMap(); public static final Topic<Runnable> SEVERITIES_CHANGED_TOPIC = Topic.create("SEVERITIES_CHANGED_TOPIC", Runnable.class, Topic.BroadcastDirection.TO_PARENT); @NotNull private final MessageBus myMessageBus; private volatile OrderMap myOrderMap; private JDOMExternalizableStringList myReadOrder; private static final Map<String, HighlightInfoType> STANDARD_SEVERITIES = ContainerUtil.newConcurrentMap(); public SeverityRegistrar(@NotNull MessageBus messageBus) { myMessageBus = messageBus; } static { registerStandard(HighlightInfoType.ERROR, HighlightSeverity.ERROR); registerStandard(HighlightInfoType.WARNING, HighlightSeverity.WARNING); registerStandard(HighlightInfoType.INFO, HighlightSeverity.INFO); registerStandard(HighlightInfoType.WEAK_WARNING, HighlightSeverity.WEAK_WARNING); registerStandard( HighlightInfoType.GENERIC_WARNINGS_OR_ERRORS_FROM_SERVER, HighlightSeverity.GENERIC_SERVER_ERROR_OR_WARNING); } public static void registerStandard( @NotNull HighlightInfoType highlightInfoType, @NotNull HighlightSeverity highlightSeverity) { STANDARD_SEVERITIES.put(highlightSeverity.getName(), highlightInfoType); } @NotNull public static SeverityRegistrar getSeverityRegistrar(@Nullable Project project) { return project == null ? InspectionProfileManager.getInstance().getSeverityRegistrar() : InspectionProjectProfileManager.getInstance(project).getSeverityRegistrar(); } public void registerSeverity(@NotNull SeverityBasedTextAttributes info, Color renderColor) { final HighlightSeverity severity = info.getType().getSeverity(null); myMap.put(severity.getName(), info); if (renderColor != null) { myRendererColors.put(severity.getName(), renderColor); } myOrderMap = null; HighlightDisplayLevel.registerSeverity( severity, getHighlightInfoTypeBySeverity(severity).getAttributesKey()); severitiesChanged(); } private void severitiesChanged() { myMessageBus.syncPublisher(SEVERITIES_CHANGED_TOPIC).run(); } public SeverityBasedTextAttributes unregisterSeverity(@NotNull HighlightSeverity severity) { return myMap.remove(severity.getName()); } @NotNull public HighlightInfoType.HighlightInfoTypeImpl getHighlightInfoTypeBySeverity( @NotNull HighlightSeverity severity) { HighlightInfoType infoType = STANDARD_SEVERITIES.get(severity.getName()); if (infoType != null) { return (HighlightInfoType.HighlightInfoTypeImpl) infoType; } if (severity == HighlightSeverity.INFORMATION) { return (HighlightInfoType.HighlightInfoTypeImpl) HighlightInfoType.INFORMATION; } final SeverityBasedTextAttributes type = getAttributesBySeverity(severity); return (HighlightInfoType.HighlightInfoTypeImpl) (type == null ? HighlightInfoType.WARNING : type.getType()); } private SeverityBasedTextAttributes getAttributesBySeverity(@NotNull HighlightSeverity severity) { return myMap.get(severity.getName()); } @Nullable public TextAttributes getTextAttributesBySeverity(@NotNull HighlightSeverity severity) { final SeverityBasedTextAttributes infoType = getAttributesBySeverity(severity); if (infoType != null) { return infoType.getAttributes(); } return null; } @Override public void readExternal(Element element) throws InvalidDataException { myMap.clear(); myRendererColors.clear(); final List children = element.getChildren(INFO_TAG); for (Object child : children) { final Element infoElement = (Element) child; final SeverityBasedTextAttributes highlightInfo = new SeverityBasedTextAttributes(infoElement); Color color = null; final String colorStr = infoElement.getAttributeValue(COLOR_ATTRIBUTE); if (colorStr != null) { color = new Color(Integer.parseInt(colorStr, 16)); } registerSeverity(highlightInfo, color); } myReadOrder = new JDOMExternalizableStringList(); myReadOrder.readExternal(element); List<HighlightSeverity> read = new ArrayList<HighlightSeverity>(myReadOrder.size()); final List<HighlightSeverity> knownSeverities = getDefaultOrder(); for (String name : myReadOrder) { HighlightSeverity severity = getSeverity(name); if (severity == null || !knownSeverities.contains(severity)) continue; read.add(severity); } OrderMap orderMap = fromList(read); if (orderMap.isEmpty()) { orderMap = fromList(knownSeverities); } else { // enforce include all known List<HighlightSeverity> list = getOrderAsList(orderMap); for (int i = 0; i < knownSeverities.size(); i++) { HighlightSeverity stdSeverity = knownSeverities.get(i); if (!list.contains(stdSeverity)) { for (int oIdx = 0; oIdx < list.size(); oIdx++) { HighlightSeverity orderSeverity = list.get(oIdx); HighlightInfoType type = STANDARD_SEVERITIES.get(orderSeverity.getName()); if (type != null && knownSeverities.indexOf(type.getSeverity(null)) > i) { list.add(oIdx, stdSeverity); myReadOrder = null; break; } } } } orderMap = fromList(list); } myOrderMap = orderMap; severitiesChanged(); } @Override public void writeExternal(Element element) throws WriteExternalException { List<HighlightSeverity> list = getOrderAsList(getOrderMap()); for (HighlightSeverity severity : list) { Element info = new Element(INFO_TAG); String severityName = severity.getName(); final SeverityBasedTextAttributes infoType = getAttributesBySeverity(severity); if (infoType != null) { infoType.writeExternal(info); final Color color = myRendererColors.get(severityName); if (color != null) { info.setAttribute(COLOR_ATTRIBUTE, Integer.toString(color.getRGB() & 0xFFFFFF, 16)); } element.addContent(info); } } if (myReadOrder != null && !myReadOrder.isEmpty()) { myReadOrder.writeExternal(element); } else if (!getDefaultOrder().equals(list)) { final JDOMExternalizableStringList ext = new JDOMExternalizableStringList(Collections.nCopies(getOrderMap().size(), "")); getOrderMap() .forEachEntry( new TObjectIntProcedure<HighlightSeverity>() { @Override public boolean execute(HighlightSeverity orderSeverity, int oIdx) { ext.set(oIdx, orderSeverity.getName()); return true; } }); ext.writeExternal(element); } } @NotNull private static List<HighlightSeverity> getOrderAsList(@NotNull final OrderMap orderMap) { List<HighlightSeverity> list = new ArrayList<HighlightSeverity>(); for (Object o : orderMap.keys()) { list.add((HighlightSeverity) o); } Collections.sort( list, new Comparator<HighlightSeverity>() { @Override public int compare(HighlightSeverity o1, HighlightSeverity o2) { return SeverityRegistrar.compare(o1, o2, orderMap); } }); return list; } public int getSeveritiesCount() { return createCurrentSeverityNames().size(); } public HighlightSeverity getSeverityByIndex(final int i) { final HighlightSeverity[] found = new HighlightSeverity[1]; getOrderMap() .forEachEntry( new TObjectIntProcedure<HighlightSeverity>() { @Override public boolean execute(HighlightSeverity severity, int order) { if (order == i) { found[0] = severity; return false; } return true; } }); return found[0]; } public int getSeverityMaxIndex() { int[] values = getOrderMap().getValues(); int max = values[0]; for (int i = 1; i < values.length; ++i) if (values[i] > max) max = values[i]; return max; } @Nullable public HighlightSeverity getSeverity(@NotNull String name) { final HighlightInfoType type = STANDARD_SEVERITIES.get(name); if (type != null) return type.getSeverity(null); final SeverityBasedTextAttributes attributes = myMap.get(name); if (attributes != null) return attributes.getSeverity(); return null; } @NotNull private List<String> createCurrentSeverityNames() { List<String> list = new ArrayList<String>(); list.addAll(STANDARD_SEVERITIES.keySet()); list.addAll(myMap.keySet()); ContainerUtil.sort(list); return list; } public Icon getRendererIconByIndex(int i) { final HighlightSeverity severity = getSeverityByIndex(i); HighlightDisplayLevel level = HighlightDisplayLevel.find(severity); if (level != null) { return level.getIcon(); } return HighlightDisplayLevel.createIconByMask(myRendererColors.get(severity.getName())); } public boolean isSeverityValid(@NotNull String severityName) { return createCurrentSeverityNames().contains(severityName); } @Override public int compare(final HighlightSeverity s1, final HighlightSeverity s2) { return compare(s1, s2, getOrderMap()); } private static int compare(HighlightSeverity s1, HighlightSeverity s2, OrderMap orderMap) { int o1 = orderMap.getOrder(s1, -1); int o2 = orderMap.getOrder(s2, -1); return o1 - o2; } @NotNull private OrderMap getOrderMap() { OrderMap orderMap; OrderMap defaultOrder = null; while ((orderMap = myOrderMap) == null) { if (defaultOrder == null) { defaultOrder = fromList(getDefaultOrder()); } boolean replaced = ORDER_MAP_UPDATER.compareAndSet(this, null, defaultOrder); if (replaced) { orderMap = defaultOrder; break; } } return orderMap; } private static final AtomicFieldUpdater<SeverityRegistrar, OrderMap> ORDER_MAP_UPDATER = AtomicFieldUpdater.forFieldOfType(SeverityRegistrar.class, OrderMap.class); @NotNull private static OrderMap fromList(@NotNull List<HighlightSeverity> orderList) { TObjectIntHashMap<HighlightSeverity> map = new TObjectIntHashMap<HighlightSeverity>(); for (int i = 0; i < orderList.size(); i++) { HighlightSeverity severity = orderList.get(i); map.put(severity, i); } return new OrderMap(map); } @NotNull private List<HighlightSeverity> getDefaultOrder() { Collection<SeverityBasedTextAttributes> values = myMap.values(); List<HighlightSeverity> order = new ArrayList<HighlightSeverity>(STANDARD_SEVERITIES.size() + values.size()); for (HighlightInfoType type : STANDARD_SEVERITIES.values()) { order.add(type.getSeverity(null)); } for (SeverityBasedTextAttributes attributes : values) { order.add(attributes.getSeverity()); } ContainerUtil.sort(order); return order; } public void setOrder(@NotNull List<HighlightSeverity> orderList) { myOrderMap = fromList(orderList); myReadOrder = null; severitiesChanged(); } public int getSeverityIdx(@NotNull HighlightSeverity severity) { return getOrderMap().getOrder(severity, -1); } public boolean isDefaultSeverity(@NotNull HighlightSeverity severity) { return STANDARD_SEVERITIES.containsKey(severity.myName); } public static boolean isGotoBySeverityEnabled(@NotNull HighlightSeverity minSeverity) { for (SeveritiesProvider provider : Extensions.getExtensions(SeveritiesProvider.EP_NAME)) { if (provider.isGotoBySeverityEnabled(minSeverity)) return true; } return minSeverity != HighlightSeverity.INFORMATION; } private static class OrderMap extends TObjectIntHashMap<HighlightSeverity> { private OrderMap(@NotNull TObjectIntHashMap<HighlightSeverity> map) { super(map.size()); map.forEachEntry( new TObjectIntProcedure<HighlightSeverity>() { @Override public boolean execute(HighlightSeverity key, int value) { OrderMap.super.put(key, value); return true; } }); trimToSize(); } private int getOrder(@NotNull HighlightSeverity severity, int defaultOrder) { int index = index(severity); return index < 0 ? defaultOrder : _values[index]; } @Override public void clear() { throw new IncorrectOperationException("readonly"); } @Override protected void removeAt(int index) { throw new IncorrectOperationException("readonly"); } @Override public void transformValues(TIntFunction function) { throw new IncorrectOperationException("readonly"); } @Override public boolean adjustValue(HighlightSeverity key, int amount) { throw new IncorrectOperationException("readonly"); } @Override public int put(HighlightSeverity key, int value) { throw new IncorrectOperationException("readonly"); } @Override public int remove(HighlightSeverity key) { throw new IncorrectOperationException("readonly"); } } public static class SeverityBasedTextAttributes { private final TextAttributes myAttributes; private final HighlightInfoType.HighlightInfoTypeImpl myType; // read external public SeverityBasedTextAttributes(@NotNull Element element) throws InvalidDataException { this(new TextAttributes(element), new HighlightInfoType.HighlightInfoTypeImpl(element)); } public SeverityBasedTextAttributes( @NotNull TextAttributes attributes, @NotNull HighlightInfoType.HighlightInfoTypeImpl type) { myAttributes = attributes; myType = type; } @NotNull public TextAttributes getAttributes() { return myAttributes; } @NotNull public HighlightInfoType.HighlightInfoTypeImpl getType() { return myType; } private void writeExternal(@NotNull Element element) throws WriteExternalException { myAttributes.writeExternal(element); myType.writeExternal(element); } @NotNull public HighlightSeverity getSeverity() { return myType.getSeverity(null); } @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final SeverityBasedTextAttributes that = (SeverityBasedTextAttributes) o; if (!myAttributes.equals(that.myAttributes)) return false; if (!myType.equals(that.myType)) return false; return true; } @Override public int hashCode() { int result = myAttributes.hashCode(); result = 31 * result + myType.hashCode(); return result; } } @NotNull Collection<SeverityBasedTextAttributes> allRegisteredAttributes() { return new ArrayList<SeverityBasedTextAttributes>(myMap.values()); } @NotNull Collection<HighlightInfoType> standardSeverities() { return STANDARD_SEVERITIES.values(); } }
public class VcsLogDataHolder implements Disposable, VcsLogDataProvider { private static final Logger LOG = Logger.getInstance(VcsLogDataHolder.class); @NotNull private final Project myProject; @NotNull private final Map<VirtualFile, VcsLogProvider> myLogProviders; @NotNull private final BackgroundTaskQueue myDataLoaderQueue; @NotNull private final MiniDetailsGetter myMiniDetailsGetter; @NotNull private final CommitDetailsGetter myDetailsGetter; @NotNull private final VcsLogSettings mySettings; /** * Current user name, as specified in the VCS settings. It can be configured differently for * different roots => store in a map. */ private final Map<VirtualFile, VcsUser> myCurrentUser = ContainerUtil.newHashMap(); private final Consumer<DataPack> myDataPackUpdateHandler; /** * Cached details of the latest commits. We store them separately from the cache of {@link * DataGetter}, to make sure that they are always available, which is important because these * details will be constantly visible to the user, thus it would be annoying to re-load them from * VCS if the cache overflows. */ @NotNull private final Map<Integer, VcsCommitMetadata> myTopCommitsDetailsCache = ContainerUtil.newConcurrentMap(); private final VcsUserRegistryImpl myUserRegistry; private final VcsLogHashMapImpl myHashMap; private final ContainingBranchesGetter myContainingBranchesGetter; @NotNull private final VcsLogRefresher myRefresher; private final VcsLogFiltererImpl myFilterer; public VcsLogDataHolder( @NotNull Project project, @NotNull Disposable parentDisposable, @NotNull Map<VirtualFile, VcsLogProvider> logProviders, @NotNull VcsLogSettings settings, @NotNull VcsLogUiProperties uiProperties, @NotNull Consumer<VisiblePack> visiblePackConsumer) { Disposer.register(parentDisposable, this); myProject = project; myLogProviders = logProviders; myDataLoaderQueue = new BackgroundTaskQueue(project, "Loading history..."); mySettings = settings; myUserRegistry = (VcsUserRegistryImpl) ServiceManager.getService(project, VcsUserRegistry.class); try { myHashMap = new VcsLogHashMapImpl(myProject, logProviders); } catch (IOException e) { throw new RuntimeException( e); // TODO: show a message to the user & fallback to using in-memory Hashes } myMiniDetailsGetter = new MiniDetailsGetter(myHashMap, logProviders, myTopCommitsDetailsCache, this); myDetailsGetter = new CommitDetailsGetter(myHashMap, logProviders, this); myContainingBranchesGetter = new ContainingBranchesGetter(this, this); myFilterer = new VcsLogFiltererImpl( myProject, myLogProviders, myHashMap, myTopCommitsDetailsCache, myDetailsGetter, PermanentGraph.SortType.values()[uiProperties.getBekSortType()], visiblePackConsumer); myDataPackUpdateHandler = new Consumer<DataPack>() { @Override public void consume(DataPack dataPack) { myFilterer.onRefresh(dataPack); } }; myRefresher = new VcsLogRefresherImpl( myProject, myHashMap, myLogProviders, myUserRegistry, myTopCommitsDetailsCache, myDataPackUpdateHandler, new Consumer<Exception>() { @Override public void consume(Exception e) { if (!(e instanceof ProcessCanceledException)) { LOG.error(e); } } }, mySettings.getRecentCommitsCount()); } @NotNull public VcsLogFilterer getFilterer() { return myFilterer; } @Override @NotNull public Hash getHash(int commitIndex) { return myHashMap.getHash(commitIndex); } @Override public int getCommitIndex(@NotNull Hash hash) { return myHashMap.getCommitIndex(hash); } @NotNull public VcsLogHashMapImpl getHashMap() { return myHashMap; } public void initialize() { final StopWatch initSw = StopWatch.start("initialize"); myDataLoaderQueue.clear(); runInBackground( new ThrowableConsumer<ProgressIndicator, VcsException>() { @Override public void consume(ProgressIndicator indicator) throws VcsException { resetState(); readCurrentUser(); DataPack dataPack = myRefresher.readFirstBlock(); myDataPackUpdateHandler.consume(dataPack); initSw.report(); } }, "Loading History..."); } private void readCurrentUser() { StopWatch sw = StopWatch.start("readCurrentUser"); for (Map.Entry<VirtualFile, VcsLogProvider> entry : myLogProviders.entrySet()) { VirtualFile root = entry.getKey(); try { VcsUser me = entry.getValue().getCurrentUser(root); if (me != null) { myCurrentUser.put(root, me); } else { LOG.info("Username not configured for root " + root); } } catch (VcsException e) { LOG.warn("Couldn't read the username from root " + root, e); } } sw.report(); } private void resetState() { myTopCommitsDetailsCache.clear(); } @NotNull public Set<VcsUser> getAllUsers() { return myUserRegistry.getUsers(); } @NotNull public Map<VirtualFile, VcsUser> getCurrentUser() { return myCurrentUser; } public boolean isMultiRoot() { return myLogProviders.size() > 1; } @NotNull public Project getProject() { return myProject; } @NotNull public Collection<VirtualFile> getRoots() { return myLogProviders.keySet(); } @NotNull public Collection<VcsLogProvider> getLogProviders() { return myLogProviders.values(); } @NotNull public VcsLogSettings getSettings() { return mySettings; } public ContainingBranchesGetter getContainingBranchesGetter() { return myContainingBranchesGetter; } private void runInBackground( final ThrowableConsumer<ProgressIndicator, VcsException> task, final String title) { myDataLoaderQueue.run( new Task.Backgroundable(myProject, title, false) { @Override public void run(@NotNull ProgressIndicator indicator) { indicator.setIndeterminate(true); try { task.consume(indicator); } catch (VcsException e) { throw new RuntimeException(e); // TODO } } }); } /** * Makes the log perform complete refresh for all roots. It fairly retrieves the data from the VCS * and rebuilds the whole log. */ public void refreshCompletely() { initialize(); } /** * Makes the log perform refresh for the given root. This refresh can be optimized, i. e. it can * query VCS just for the part of the log. */ public void refresh(@NotNull Collection<VirtualFile> roots) { myRefresher.refresh(roots); } @Nullable public VcsCommitMetadata getTopCommitDetails(@NotNull Integer commitId) { return myTopCommitsDetailsCache.get(commitId); } public CommitDetailsGetter getCommitDetailsGetter() { return myDetailsGetter; } @NotNull public MiniDetailsGetter getMiniDetailsGetter() { return myMiniDetailsGetter; } @Override public void dispose() { myDataLoaderQueue.clear(); resetState(); } @NotNull public VcsLogProvider getLogProvider(@NotNull VirtualFile root) { return myLogProviders.get(root); } @NotNull public VcsUserRegistryImpl getUserRegistry() { return myUserRegistry; } }
/** @author max */ public class InspectionResultsView extends JPanel implements Disposable, OccurenceNavigator, DataProvider { public static final DataKey<InspectionResultsView> DATA_KEY = DataKey.create("inspectionView"); private final Project myProject; private final InspectionTree myTree; private final Browser myBrowser; private final ConcurrentMap<HighlightDisplayLevel, ConcurrentMap<String, InspectionGroupNode>> myGroups = ContainerUtil.newConcurrentMap(); private final OccurenceNavigator myOccurenceNavigator; private volatile InspectionProfile myInspectionProfile; private final AnalysisScope myScope; @NonNls private static final String HELP_ID = "reference.toolWindows.inspections"; private final ConcurrentMap<HighlightDisplayLevel, InspectionSeverityGroupNode> mySeverityGroupNodes = ContainerUtil.newConcurrentMap(); private final Splitter mySplitter; @NotNull private final GlobalInspectionContextImpl myGlobalInspectionContext; private boolean myRerun; private volatile boolean myDisposed; @NotNull private final InspectionRVContentProvider myProvider; private AnAction myIncludeAction; private AnAction myExcludeAction; public InspectionResultsView( @NotNull final Project project, final InspectionProfile inspectionProfile, @NotNull AnalysisScope scope, @NotNull GlobalInspectionContextImpl globalInspectionContext, @NotNull InspectionRVContentProvider provider) { setLayout(new BorderLayout()); myProject = project; myInspectionProfile = inspectionProfile; myScope = scope; myGlobalInspectionContext = globalInspectionContext; myProvider = provider; myTree = new InspectionTree(project, globalInspectionContext); initTreeListeners(); myOccurenceNavigator = initOccurenceNavigator(); myBrowser = new Browser(this); mySplitter = new OnePixelSplitter(false, AnalysisUIOptions.getInstance(myProject).SPLITTER_PROPORTION); mySplitter.setFirstComponent( ScrollPaneFactory.createScrollPane(myTree, SideBorder.LEFT | SideBorder.RIGHT)); mySplitter.setSecondComponent(myBrowser); mySplitter.addPropertyChangeListener( new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if (Splitter.PROP_PROPORTION.equals(evt.getPropertyName())) { myGlobalInspectionContext.setSplitterProportion( ((Float) evt.getNewValue()).floatValue()); } } }); add(mySplitter, BorderLayout.CENTER); myBrowser.addClickListener( new Browser.ClickListener() { @Override public void referenceClicked(final Browser.ClickEvent e) { if (e.getEventType() == Browser.ClickEvent.REF_ELEMENT) { final RefElement refElement = e.getClickedElement(); final OpenFileDescriptor descriptor = getOpenFileDescriptor(refElement); if (descriptor != null) { FileEditorManager.getInstance(project).openTextEditor(descriptor, false); } } else if (e.getEventType() == Browser.ClickEvent.FILE_OFFSET) { final VirtualFile file = e.getFile(); final OpenFileDescriptor descriptor = new OpenFileDescriptor(project, file, e.getStartOffset()); final Editor editor = FileEditorManager.getInstance(project).openTextEditor(descriptor, true); if (editor != null) { final TextAttributes selectionAttributes = EditorColorsManager.getInstance() .getGlobalScheme() .getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES); HighlightManager.getInstance(project) .addRangeHighlight( editor, e.getStartOffset(), e.getEndOffset(), selectionAttributes, true, null); } } } }); createActionsToolbar(); TreeUtil.selectFirstNode(myTree); } private void initTreeListeners() { myTree .getSelectionModel() .addTreeSelectionListener( new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent e) { syncBrowser(); if (isAutoScrollMode()) { OpenSourceUtil.openSourcesFrom( DataManager.getInstance().getDataContext(InspectionResultsView.this), false); } } }); EditSourceOnDoubleClickHandler.install(myTree); myTree.addKeyListener( new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) { OpenSourceUtil.openSourcesFrom( DataManager.getInstance().getDataContext(InspectionResultsView.this), false); } } }); myTree.addMouseListener( new PopupHandler() { @Override public void invokePopup(Component comp, int x, int y) { popupInvoked(comp, x, y); } }); SmartExpander.installOn(myTree); } private OccurenceNavigatorSupport initOccurenceNavigator() { return new OccurenceNavigatorSupport(myTree) { @Override @Nullable protected Navigatable createDescriptorForNode(DefaultMutableTreeNode node) { if (node instanceof InspectionTreeNode && ((InspectionTreeNode) node).isResolved()) { return null; } if (node instanceof RefElementNode) { final RefElementNode refNode = (RefElementNode) node; if (refNode.hasDescriptorsUnder()) return null; final RefEntity element = refNode.getElement(); if (element == null || !element.isValid()) return null; final CommonProblemDescriptor problem = refNode.getProblem(); if (problem != null) { return navigate(problem); } if (element instanceof RefElement) { return getOpenFileDescriptor((RefElement) element); } } else if (node instanceof ProblemDescriptionNode) { if (!((ProblemDescriptionNode) node).isValid()) return null; return navigate(((ProblemDescriptionNode) node).getDescriptor()); } return null; } @Nullable private Navigatable navigate(final CommonProblemDescriptor descriptor) { return getSelectedNavigatable(descriptor); } @Override public String getNextOccurenceActionName() { return InspectionsBundle.message("inspection.action.go.next"); } @Override public String getPreviousOccurenceActionName() { return InspectionsBundle.message("inspection.actiongo.prev"); } }; } private void createActionsToolbar() { final JComponent leftActionsToolbar = createLeftActionsToolbar(); final JComponent rightActionsToolbar = createRightActionsToolbar(); JPanel westPanel = new JPanel(new BorderLayout()); westPanel.add(leftActionsToolbar, BorderLayout.WEST); westPanel.add(rightActionsToolbar, BorderLayout.EAST); add(westPanel, BorderLayout.WEST); } @SuppressWarnings({"NonStaticInitializer"}) private JComponent createRightActionsToolbar() { myIncludeAction = new AnAction(InspectionsBundle.message("inspections.result.view.include.action.text")) { { registerCustomShortcutSet(CommonShortcuts.INSERT, myTree); } @Override public void actionPerformed(AnActionEvent e) { final TreePath[] paths = myTree.getSelectionPaths(); if (paths != null) { for (TreePath path : paths) { ((InspectionTreeNode) path.getLastPathComponent()).amnesty(); } } updateView(false); } @Override public void update(final AnActionEvent e) { final TreePath[] paths = myTree.getSelectionPaths(); e.getPresentation() .setEnabled( paths != null && paths.length > 0 && !myGlobalInspectionContext.getUIOptions().FILTER_RESOLVED_ITEMS); } }; myExcludeAction = new AnAction(InspectionsBundle.message("inspections.result.view.exclude.action.text")) { { registerCustomShortcutSet(CommonShortcuts.getDelete(), myTree); } @Override public void actionPerformed(final AnActionEvent e) { final TreePath[] paths = myTree.getSelectionPaths(); if (paths != null) { for (TreePath path : paths) { ((InspectionTreeNode) path.getLastPathComponent()).ignoreElement(); } } updateView(false); } @Override public void update(final AnActionEvent e) { final TreePath[] path = myTree.getSelectionPaths(); e.getPresentation().setEnabled(path != null && path.length > 0); } }; DefaultActionGroup specialGroup = new DefaultActionGroup(); specialGroup.add(myGlobalInspectionContext.getUIOptions().createGroupBySeverityAction(this)); specialGroup.add(myGlobalInspectionContext.getUIOptions().createGroupByDirectoryAction(this)); specialGroup.add( myGlobalInspectionContext.getUIOptions().createFilterResolvedItemsAction(this)); specialGroup.add( myGlobalInspectionContext.getUIOptions().createShowOutdatedProblemsAction(this)); specialGroup.add(myGlobalInspectionContext.getUIOptions().createShowDiffOnlyAction(this)); specialGroup.add(new EditSettingsAction()); specialGroup.add(new InvokeQuickFixAction(this)); specialGroup.add(new InspectionsOptionsToolbarAction(this)); return createToolbar(specialGroup); } private JComponent createLeftActionsToolbar() { final CommonActionsManager actionsManager = CommonActionsManager.getInstance(); DefaultActionGroup group = new DefaultActionGroup(); group.add(new RerunAction(this)); group.add(new CloseAction()); final TreeExpander treeExpander = new TreeExpander() { @Override public void expandAll() { TreeUtil.expandAll(myTree); } @Override public boolean canExpand() { return true; } @Override public void collapseAll() { TreeUtil.collapseAll(myTree, 0); } @Override public boolean canCollapse() { return true; } }; group.add(actionsManager.createExpandAllAction(treeExpander, myTree)); group.add(actionsManager.createCollapseAllAction(treeExpander, myTree)); group.add(actionsManager.createPrevOccurenceAction(getOccurenceNavigator())); group.add(actionsManager.createNextOccurenceAction(getOccurenceNavigator())); group.add(myGlobalInspectionContext.createToggleAutoscrollAction()); group.add(new ExportHTMLAction(this)); group.add(new ContextHelpAction(HELP_ID)); return createToolbar(group); } private static JComponent createToolbar(final DefaultActionGroup specialGroup) { return ActionManager.getInstance() .createActionToolbar(ActionPlaces.CODE_INSPECTION, specialGroup, false) .getComponent(); } @Override public void dispose() { mySplitter.dispose(); myBrowser.dispose(); myInspectionProfile = null; myDisposed = true; } private boolean isAutoScrollMode() { String activeToolWindowId = ToolWindowManager.getInstance(myProject).getActiveToolWindowId(); return myGlobalInspectionContext.getUIOptions().AUTOSCROLL_TO_SOURCE && (activeToolWindowId == null || activeToolWindowId.equals(ToolWindowId.INSPECTION)); } @Nullable private static OpenFileDescriptor getOpenFileDescriptor(final RefElement refElement) { final VirtualFile[] file = new VirtualFile[1]; final int[] offset = new int[1]; ApplicationManager.getApplication() .runReadAction( new Runnable() { @Override public void run() { PsiElement psiElement = refElement.getElement(); if (psiElement != null) { final PsiFile containingFile = psiElement.getContainingFile(); if (containingFile != null) { file[0] = containingFile.getVirtualFile(); offset[0] = psiElement.getTextOffset(); } } else { file[0] = null; } } }); if (file[0] != null && file[0].isValid()) { return new OpenFileDescriptor(refElement.getRefManager().getProject(), file[0], offset[0]); } return null; } private void syncBrowser() { if (myTree.getSelectionModel().getSelectionCount() != 1) { myBrowser.showEmpty(); } else { TreePath pathSelected = myTree.getSelectionModel().getLeadSelectionPath(); if (pathSelected != null) { final InspectionTreeNode node = (InspectionTreeNode) pathSelected.getLastPathComponent(); if (node instanceof RefElementNode) { final RefElementNode refElementNode = (RefElementNode) node; final CommonProblemDescriptor problem = refElementNode.getProblem(); final RefEntity refSelected = refElementNode.getElement(); if (problem != null) { showInBrowser(refSelected, problem); } else { showInBrowser(refSelected); } } else if (node instanceof ProblemDescriptionNode) { final ProblemDescriptionNode problemNode = (ProblemDescriptionNode) node; showInBrowser(problemNode.getElement(), problemNode.getDescriptor()); } else if (node instanceof InspectionNode) { showInBrowser(((InspectionNode) node).getToolWrapper()); } else { myBrowser.showEmpty(); } } } } private void showInBrowser(final RefEntity refEntity) { Cursor currentCursor = getCursor(); setCursor(new Cursor(Cursor.WAIT_CURSOR)); myBrowser.showPageFor(refEntity); setCursor(currentCursor); } private void showInBrowser(@NotNull InspectionToolWrapper toolWrapper) { Cursor currentCursor = getCursor(); setCursor(new Cursor(Cursor.WAIT_CURSOR)); myBrowser.showDescription(toolWrapper); setCursor(currentCursor); } private void showInBrowser(final RefEntity refEntity, CommonProblemDescriptor descriptor) { Cursor currentCursor = getCursor(); setCursor(new Cursor(Cursor.WAIT_CURSOR)); myBrowser.showPageFor(refEntity, descriptor); setCursor(currentCursor); } @NotNull public InspectionNode addTool( @NotNull final InspectionToolWrapper toolWrapper, HighlightDisplayLevel errorLevel, boolean groupedBySeverity) { String groupName = toolWrapper.getGroupDisplayName().isEmpty() ? InspectionProfileEntry.GENERAL_GROUP_NAME : toolWrapper.getGroupDisplayName(); InspectionTreeNode parentNode = getToolParentNode(groupName, errorLevel, groupedBySeverity); InspectionNode toolNode = new InspectionNode(toolWrapper); boolean showStructure = myGlobalInspectionContext.getUIOptions().SHOW_STRUCTURE; myProvider.appendToolNodeContent( myGlobalInspectionContext, toolNode, parentNode, showStructure); InspectionToolPresentation presentation = myGlobalInspectionContext.getPresentation(toolWrapper); toolNode = presentation.createToolNode( myGlobalInspectionContext, toolNode, myProvider, parentNode, showStructure); ((DefaultInspectionToolPresentation) presentation).setToolNode(toolNode); registerActionShortcuts(presentation); return toolNode; } private void registerActionShortcuts(@NotNull InspectionToolPresentation presentation) { final QuickFixAction[] fixes = presentation.getQuickFixes(RefEntity.EMPTY_ELEMENTS_ARRAY); if (fixes != null) { for (QuickFixAction fix : fixes) { fix.registerCustomShortcutSet(fix.getShortcutSet(), this); } } } private void clearTree() { myTree.removeAllNodes(); mySeverityGroupNodes.clear(); } @Nullable public String getCurrentProfileName() { return myInspectionProfile == null ? null : myInspectionProfile.getDisplayName(); } public InspectionProfile getCurrentProfile() { return myInspectionProfile; } public boolean update() { return updateView(true); } public boolean updateView(boolean strict) { if (!strict && !myGlobalInspectionContext.getUIOptions().FILTER_RESOLVED_ITEMS) { myTree.repaint(); return false; } clearTree(); boolean resultsFound = buildTree(); myTree.restoreExpansionAndSelection(); return resultsFound; } private boolean buildTree() { InspectionProfile profile = myInspectionProfile; boolean isGroupedBySeverity = myGlobalInspectionContext.getUIOptions().GROUP_BY_SEVERITY; myGroups.clear(); final Map<String, Tools> tools = myGlobalInspectionContext.getTools(); boolean resultsFound = false; for (Tools currentTools : tools.values()) { InspectionToolWrapper defaultToolWrapper = currentTools.getDefaultState().getTool(); final HighlightDisplayKey key = HighlightDisplayKey.find(defaultToolWrapper.getShortName()); for (ScopeToolState state : myProvider.getTools(currentTools)) { InspectionToolWrapper toolWrapper = state.getTool(); if (myProvider.checkReportedProblems(myGlobalInspectionContext, toolWrapper)) { addTool( toolWrapper, ((InspectionProfileImpl) profile) .getErrorLevel(key, state.getScope(myProject), myProject), isGroupedBySeverity); resultsFound = true; } } } return resultsFound; } @NotNull private InspectionTreeNode getToolParentNode( @NotNull String groupName, HighlightDisplayLevel errorLevel, boolean groupedBySeverity) { if (groupName.isEmpty()) { return getRelativeRootNode(groupedBySeverity, errorLevel); } ConcurrentMap<String, InspectionGroupNode> map = myGroups.get(errorLevel); if (map == null) { map = ConcurrencyUtil.cacheOrGet( myGroups, errorLevel, ContainerUtil.<String, InspectionGroupNode>newConcurrentMap()); } InspectionGroupNode group; if (groupedBySeverity) { group = map.get(groupName); } else { group = null; for (Map<String, InspectionGroupNode> groupMap : myGroups.values()) { if ((group = groupMap.get(groupName)) != null) break; } } if (group == null) { group = ConcurrencyUtil.cacheOrGet(map, groupName, new InspectionGroupNode(groupName)); addChildNodeInEDT(getRelativeRootNode(groupedBySeverity, errorLevel), group); } return group; } @NotNull private InspectionTreeNode getRelativeRootNode( boolean isGroupedBySeverity, HighlightDisplayLevel level) { if (isGroupedBySeverity) { InspectionSeverityGroupNode severityGroupNode = mySeverityGroupNodes.get(level); if (severityGroupNode == null) { InspectionSeverityGroupNode newNode = new InspectionSeverityGroupNode(myProject, level); severityGroupNode = ConcurrencyUtil.cacheOrGet(mySeverityGroupNodes, level, newNode); if (severityGroupNode == newNode) { InspectionTreeNode root = myTree.getRoot(); addChildNodeInEDT(root, severityGroupNode); } } return severityGroupNode; } return myTree.getRoot(); } private void addChildNodeInEDT( @NotNull final DefaultMutableTreeNode root, @NotNull final MutableTreeNode severityGroupNode) { UIUtil.invokeLaterIfNeeded( new Runnable() { @Override public void run() { if (!myDisposed) { root.add(severityGroupNode); } } }); } private OccurenceNavigator getOccurenceNavigator() { return myOccurenceNavigator; } @Override public boolean hasNextOccurence() { return myOccurenceNavigator != null && myOccurenceNavigator.hasNextOccurence(); } @Override public boolean hasPreviousOccurence() { return myOccurenceNavigator != null && myOccurenceNavigator.hasPreviousOccurence(); } @Override public OccurenceInfo goNextOccurence() { return myOccurenceNavigator != null ? myOccurenceNavigator.goNextOccurence() : null; } @Override public OccurenceInfo goPreviousOccurence() { return myOccurenceNavigator != null ? myOccurenceNavigator.goPreviousOccurence() : null; } @Override public String getNextOccurenceActionName() { return myOccurenceNavigator != null ? myOccurenceNavigator.getNextOccurenceActionName() : ""; } @Override public String getPreviousOccurenceActionName() { return myOccurenceNavigator != null ? myOccurenceNavigator.getPreviousOccurenceActionName() : ""; } @NotNull public Project getProject() { return myProject; } @Override public Object getData(String dataId) { if (PlatformDataKeys.HELP_ID.is(dataId)) return HELP_ID; if (DATA_KEY.is(dataId)) return this; if (myTree == null) return null; TreePath[] paths = myTree.getSelectionPaths(); if (paths == null || paths.length == 0) return null; if (paths.length > 1) { if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) { return collectPsiElements(); } return null; } TreePath path = paths[0]; InspectionTreeNode selectedNode = (InspectionTreeNode) path.getLastPathComponent(); if (selectedNode instanceof RefElementNode) { final RefElementNode refElementNode = (RefElementNode) selectedNode; RefEntity refElement = refElementNode.getElement(); if (refElement == null) return null; final RefEntity item = refElement.getRefManager().getRefinedElement(refElement); if (!item.isValid()) return null; PsiElement psiElement = item instanceof RefElement ? ((RefElement) item).getElement() : null; if (psiElement == null) return null; final CommonProblemDescriptor problem = refElementNode.getProblem(); if (problem != null) { if (problem instanceof ProblemDescriptor) { psiElement = ((ProblemDescriptor) problem).getPsiElement(); if (psiElement == null) return null; } else { return null; } } if (CommonDataKeys.NAVIGATABLE.is(dataId)) { return getSelectedNavigatable(problem, psiElement); } else if (CommonDataKeys.PSI_ELEMENT.is(dataId)) { return psiElement.isValid() ? psiElement : null; } } else if (selectedNode instanceof ProblemDescriptionNode && CommonDataKeys.NAVIGATABLE.is(dataId)) { return getSelectedNavigatable(((ProblemDescriptionNode) selectedNode).getDescriptor()); } return null; } @Nullable private Navigatable getSelectedNavigatable(final CommonProblemDescriptor descriptor) { return getSelectedNavigatable( descriptor, descriptor instanceof ProblemDescriptor ? ((ProblemDescriptor) descriptor).getPsiElement() : null); } @Nullable private Navigatable getSelectedNavigatable( final CommonProblemDescriptor descriptor, final PsiElement psiElement) { if (descriptor instanceof ProblemDescriptorBase) { Navigatable navigatable = ((ProblemDescriptorBase) descriptor).getNavigatable(); if (navigatable != null) { return navigatable; } } if (psiElement == null || !psiElement.isValid()) return null; PsiFile containingFile = psiElement.getContainingFile(); VirtualFile virtualFile = containingFile == null ? null : containingFile.getVirtualFile(); if (virtualFile != null) { int startOffset = psiElement.getTextOffset(); if (descriptor instanceof ProblemDescriptorBase) { final TextRange textRange = ((ProblemDescriptorBase) descriptor).getTextRangeForNavigation(); if (textRange != null) { if (virtualFile instanceof VirtualFileWindow) { virtualFile = ((VirtualFileWindow) virtualFile).getDelegate(); } startOffset = textRange.getStartOffset(); } } return new OpenFileDescriptor(myProject, virtualFile, startOffset); } return null; } private PsiElement[] collectPsiElements() { RefEntity[] refElements = myTree.getSelectedElements(); List<PsiElement> psiElements = new ArrayList<PsiElement>(); for (RefEntity refElement : refElements) { PsiElement psiElement = refElement instanceof RefElement ? ((RefElement) refElement).getElement() : null; if (psiElement != null && psiElement.isValid()) { psiElements.add(psiElement); } } return PsiUtilCore.toPsiElementArray(psiElements); } private void popupInvoked(Component component, int x, int y) { final TreePath path = myTree.getLeadSelectionPath(); if (path == null) return; final DefaultActionGroup actions = new DefaultActionGroup(); final ActionManager actionManager = ActionManager.getInstance(); actions.add(actionManager.getAction(IdeActions.ACTION_EDIT_SOURCE)); actions.add(actionManager.getAction(IdeActions.ACTION_FIND_USAGES)); actions.add(myIncludeAction); actions.add(myExcludeAction); actions.addSeparator(); final InspectionToolWrapper toolWrapper = myTree.getSelectedToolWrapper(); if (toolWrapper != null) { final QuickFixAction[] quickFixes = myProvider.getQuickFixes(toolWrapper, myTree); if (quickFixes != null) { for (QuickFixAction quickFixe : quickFixes) { actions.add(quickFixe); } } final HighlightDisplayKey key = HighlightDisplayKey.find(toolWrapper.getShortName()); if (key == null) return; // e.g. DummyEntryPointsTool // options actions.addSeparator(); actions.add(new EditSettingsAction()); final List<AnAction> options = new InspectionsOptionsToolbarAction(this).createActions(); for (AnAction action : options) { actions.add(action); } } actions.addSeparator(); actions.add(actionManager.getAction(IdeActions.GROUP_VERSION_CONTROLS)); final ActionPopupMenu menu = actionManager.createActionPopupMenu(ActionPlaces.CODE_INSPECTION, actions); menu.getComponent().show(component, x, y); } @NotNull public InspectionTree getTree() { return myTree; } @NotNull public GlobalInspectionContextImpl getGlobalInspectionContext() { return myGlobalInspectionContext; } @NotNull public InspectionRVContentProvider getProvider() { return myProvider; } public boolean isSingleToolInSelection() { return myTree != null && myTree.getSelectedToolWrapper() != null; } public boolean isRerun() { boolean rerun = myRerun; myRerun = false; return rerun; } private InspectionProfile guessProfileToSelect( final InspectionProjectProfileManager profileManager) { final Set<InspectionProfile> profiles = new HashSet<InspectionProfile>(); final RefEntity[] selectedElements = myTree.getSelectedElements(); for (RefEntity selectedElement : selectedElements) { if (selectedElement instanceof RefElement) { final RefElement refElement = (RefElement) selectedElement; final PsiElement element = refElement.getElement(); if (element != null) { profiles.add(profileManager.getInspectionProfile()); } } } if (profiles.isEmpty()) { return (InspectionProfile) profileManager.getProjectProfileImpl(); } return profiles.iterator().next(); } public boolean isProfileDefined() { return myInspectionProfile != null && myInspectionProfile.isEditable(); } public static void showPopup(AnActionEvent e, JBPopup popup) { final InputEvent event = e.getInputEvent(); if (event instanceof MouseEvent) { popup.showUnderneathOf(event.getComponent()); } else { popup.showInBestPositionFor(e.getDataContext()); } } public AnalysisScope getScope() { return myScope; } private class CloseAction extends AnAction implements DumbAware { private CloseAction() { super(CommonBundle.message("action.close"), null, AllIcons.Actions.Cancel); } @Override public void actionPerformed(AnActionEvent e) { myGlobalInspectionContext.close(true); } } private class EditSettingsAction extends AnAction { private EditSettingsAction() { super( InspectionsBundle.message("inspection.action.edit.settings"), InspectionsBundle.message("inspection.action.edit.settings"), AllIcons.General.Settings); } @Override public void actionPerformed(AnActionEvent e) { final InspectionProjectProfileManager profileManager = InspectionProjectProfileManager.getInstance(myProject); final InspectionToolWrapper toolWrapper = myTree.getSelectedToolWrapper(); InspectionProfile inspectionProfile = myInspectionProfile; final boolean profileIsDefined = isProfileDefined(); if (!profileIsDefined) { inspectionProfile = guessProfileToSelect(profileManager); } if (toolWrapper != null) { final HighlightDisplayKey key = HighlightDisplayKey.find( toolWrapper.getShortName()); // do not search for dead code entry point tool if (key != null) { if (new EditInspectionToolsSettingsAction(key) .editToolSettings( myProject, (InspectionProfileImpl) inspectionProfile, profileIsDefined) && profileIsDefined) { updateCurrentProfile(); } return; } } if (EditInspectionToolsSettingsAction.editToolSettings( myProject, inspectionProfile, profileIsDefined, null) && profileIsDefined) { updateCurrentProfile(); } } } public void updateCurrentProfile() { final String name = myInspectionProfile.getName(); myInspectionProfile = (InspectionProfile) myInspectionProfile.getProfileManager().getProfile(name); } private class RerunAction extends AnAction { public RerunAction(JComponent comp) { super( InspectionsBundle.message("inspection.action.rerun"), InspectionsBundle.message("inspection.action.rerun"), AllIcons.Actions.Rerun); registerCustomShortcutSet(CommonShortcuts.getRerun(), comp); } @Override public void update(AnActionEvent e) { e.getPresentation().setEnabled(myScope.isValid()); } @Override public void actionPerformed(AnActionEvent e) { rerun(); } private void rerun() { myRerun = true; if (myScope.isValid()) { AnalysisUIOptions.getInstance(myProject).save(myGlobalInspectionContext.getUIOptions()); myGlobalInspectionContext.doInspections(myScope); } } } }
public class RefManagerImpl extends RefManager { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.reference.RefManager"); private long myLastUsedMask = 256 * 256 * 256 * 8; @NotNull private final Project myProject; private AnalysisScope myScope; private RefProject myRefProject; private final Map<PsiAnchor, RefElement> myRefTable = new THashMap<>(); // guarded by myRefTable private List<RefElement> mySortedRefs; // guarded by myRefTable private final ConcurrentMap<Module, RefModule> myModules = ContainerUtil.newConcurrentMap(); private final ProjectIterator myProjectIterator = new ProjectIterator(); private volatile boolean myDeclarationsFound; private final PsiManager myPsiManager; private volatile boolean myIsInProcess; private volatile boolean myOfflineView; private final List<RefGraphAnnotator> myGraphAnnotators = new ArrayList<>(); private GlobalInspectionContext myContext; private final Map<Key, RefManagerExtension> myExtensions = new THashMap<>(); private final Map<Language, RefManagerExtension> myLanguageExtensions = new HashMap<>(); private final StringInterner myNameInterner = new StringInterner(); public RefManagerImpl( @NotNull Project project, @Nullable AnalysisScope scope, @NotNull GlobalInspectionContext context) { myProject = project; myScope = scope; myContext = context; myPsiManager = PsiManager.getInstance(project); myRefProject = new RefProjectImpl(this); for (InspectionExtensionsFactory factory : Extensions.getExtensions(InspectionExtensionsFactory.EP_NAME)) { final RefManagerExtension extension = factory.createRefManagerExtension(this); if (extension != null) { myExtensions.put(extension.getID(), extension); myLanguageExtensions.put(extension.getLanguage(), extension); } } if (scope != null) { for (Module module : ModuleManager.getInstance(getProject()).getModules()) { getRefModule(module); } } } public String internName(@NotNull String name) { synchronized (myNameInterner) { return myNameInterner.intern(name); } } @NotNull public GlobalInspectionContext getContext() { return myContext; } @Override public void iterate(@NotNull RefVisitor visitor) { for (RefElement refElement : getSortedElements()) { refElement.accept(visitor); } if (myModules != null) { for (RefModule refModule : myModules.values()) { refModule.accept(visitor); } } for (RefManagerExtension extension : myExtensions.values()) { extension.iterate(visitor); } } public void cleanup() { myScope = null; myRefProject = null; synchronized (myRefTable) { myRefTable.clear(); mySortedRefs = null; } myModules.clear(); myContext = null; myGraphAnnotators.clear(); for (RefManagerExtension extension : myExtensions.values()) { extension.cleanup(); } } @Nullable @Override public AnalysisScope getScope() { return myScope; } public void fireNodeInitialized(RefElement refElement) { for (RefGraphAnnotator annotator : myGraphAnnotators) { annotator.onInitialize(refElement); } } public void fireNodeMarkedReferenced( RefElement refWhat, RefElement refFrom, boolean referencedFromClassInitializer, final boolean forReading, final boolean forWriting) { for (RefGraphAnnotator annotator : myGraphAnnotators) { annotator.onMarkReferenced( refWhat, refFrom, referencedFromClassInitializer, forReading, forWriting); } } public void fireNodeMarkedReferenced( PsiElement what, PsiElement from, boolean referencedFromClassInitializer) { for (RefGraphAnnotator annotator : myGraphAnnotators) { annotator.onMarkReferenced(what, from, referencedFromClassInitializer); } } public void fireBuildReferences(RefElement refElement) { for (RefGraphAnnotator annotator : myGraphAnnotators) { annotator.onReferencesBuild(refElement); } } public void registerGraphAnnotator(@NotNull RefGraphAnnotator annotator) { myGraphAnnotators.add(annotator); if (annotator instanceof RefGraphAnnotatorEx) { ((RefGraphAnnotatorEx) annotator).initialize(this); } } @Override public long getLastUsedMask() { myLastUsedMask *= 2; return myLastUsedMask; } @Override public <T> T getExtension(@NotNull final Key<T> key) { return (T) myExtensions.get(key); } @Override @Nullable public String getType(final RefEntity ref) { for (RefManagerExtension extension : myExtensions.values()) { final String type = extension.getType(ref); if (type != null) return type; } if (ref instanceof RefFile) { return SmartRefElementPointer.FILE; } if (ref instanceof RefModule) { return SmartRefElementPointer.MODULE; } if (ref instanceof RefProject) { return SmartRefElementPointer.PROJECT; } if (ref instanceof RefDirectory) { return SmartRefElementPointer.DIR; } return null; } @NotNull @Override public RefEntity getRefinedElement(@NotNull RefEntity ref) { for (RefManagerExtension extension : myExtensions.values()) { ref = extension.getRefinedElement(ref); } return ref; } @Override public Element export( @NotNull RefEntity refEntity, @NotNull final Element element, final int actualLine) { refEntity = getRefinedElement(refEntity); Element problem = new Element("problem"); if (refEntity instanceof RefElement) { final RefElement refElement = (RefElement) refEntity; final SmartPsiElementPointer pointer = refElement.getPointer(); PsiFile psiFile = pointer.getContainingFile(); if (psiFile == null) return null; Element fileElement = new Element("file"); Element lineElement = new Element("line"); final VirtualFile virtualFile = psiFile.getVirtualFile(); LOG.assertTrue(virtualFile != null); fileElement.addContent(virtualFile.getUrl()); if (actualLine == -1) { final Document document = PsiDocumentManager.getInstance(pointer.getProject()).getDocument(psiFile); LOG.assertTrue(document != null); final Segment range = pointer.getRange(); lineElement.addContent( String.valueOf( range != null ? document.getLineNumber(range.getStartOffset()) + 1 : -1)); } else { lineElement.addContent(String.valueOf(actualLine)); } problem.addContent(fileElement); problem.addContent(lineElement); appendModule(problem, refElement.getModule()); } else if (refEntity instanceof RefModule) { final RefModule refModule = (RefModule) refEntity; final VirtualFile moduleFile = refModule.getModule().getModuleFile(); final Element fileElement = new Element("file"); fileElement.addContent(moduleFile != null ? moduleFile.getUrl() : refEntity.getName()); problem.addContent(fileElement); appendModule(problem, refModule); } for (RefManagerExtension extension : myExtensions.values()) { extension.export(refEntity, problem); } new SmartRefElementPointerImpl(refEntity, true).writeExternal(problem); element.addContent(problem); return problem; } @Override @Nullable public String getGroupName(final RefElement entity) { for (RefManagerExtension extension : myExtensions.values()) { final String groupName = extension.getGroupName(entity); if (groupName != null) return groupName; } final LinkedList<String> containingDirs = new LinkedList<>(); RefEntity parent = entity.getOwner(); while (parent != null && !(parent instanceof RefDirectory)) { parent = parent.getOwner(); } while (parent instanceof RefDirectory) { containingDirs.addFirst(parent.getName()); parent = parent.getOwner(); } return containingDirs.isEmpty() ? null : StringUtil.join(containingDirs, File.separator); } private static void appendModule(final Element problem, final RefModule refModule) { if (refModule != null) { Element moduleElement = new Element("module"); moduleElement.addContent(refModule.getName()); problem.addContent(moduleElement); } } public void findAllDeclarations() { if (!myDeclarationsFound) { long before = System.currentTimeMillis(); final AnalysisScope scope = getScope(); if (scope != null) { scope.accept(myProjectIterator); } myDeclarationsFound = true; LOG.info( "Total duration of processing project usages:" + (System.currentTimeMillis() - before)); } } public boolean isDeclarationsFound() { return myDeclarationsFound; } public void inspectionReadActionStarted() { myIsInProcess = true; } public void inspectionReadActionFinished() { myIsInProcess = false; if (myScope != null) myScope.invalidate(); synchronized (myRefTable) { mySortedRefs = null; } } public void startOfflineView() { myOfflineView = true; } public boolean isOfflineView() { return myOfflineView; } public boolean isInProcess() { return myIsInProcess; } @NotNull @Override public Project getProject() { return myProject; } @NotNull @Override public RefProject getRefProject() { return myRefProject; } @NotNull public List<RefElement> getSortedElements() { List<RefElement> answer; synchronized (myRefTable) { if (mySortedRefs != null) return mySortedRefs; answer = new ArrayList<>(myRefTable.values()); } ReadAction.run( () -> ContainerUtil.quickSort( answer, (o1, o2) -> { VirtualFile v1 = ((RefElementImpl) o1).getVirtualFile(); VirtualFile v2 = ((RefElementImpl) o2).getVirtualFile(); return (v1 != null ? v1.hashCode() : 0) - (v2 != null ? v2.hashCode() : 0); })); synchronized (myRefTable) { return mySortedRefs = Collections.unmodifiableList(answer); } } @NotNull @Override public PsiManager getPsiManager() { return myPsiManager; } void removeReference(@NotNull RefElement refElem) { final PsiElement element = refElem.getElement(); final RefManagerExtension extension = element != null ? getExtension(element.getLanguage()) : null; if (extension != null) { extension.removeReference(refElem); } synchronized (myRefTable) { mySortedRefs = null; if (element != null && myRefTable.remove(createAnchor(element)) != null) return; // PsiElement may have been invalidated and new one returned by getElement() is different so // we need to do this stuff. for (Map.Entry<PsiAnchor, RefElement> entry : myRefTable.entrySet()) { RefElement value = entry.getValue(); PsiAnchor anchor = entry.getKey(); if (value == refElem) { myRefTable.remove(anchor); break; } } } } @NotNull private static PsiAnchor createAnchor(@NotNull final PsiElement element) { return ApplicationManager.getApplication() .runReadAction( new Computable<PsiAnchor>() { @Override public PsiAnchor compute() { return PsiAnchor.create(element); } }); } public void initializeAnnotators() { ExtensionPoint<RefGraphAnnotator> point = Extensions.getRootArea().getExtensionPoint(ToolExtensionPoints.INSPECTIONS_GRAPH_ANNOTATOR); final RefGraphAnnotator[] graphAnnotators = point.getExtensions(); for (RefGraphAnnotator annotator : graphAnnotators) { registerGraphAnnotator(annotator); } } private class ProjectIterator extends PsiElementVisitor { @Override public void visitElement(PsiElement element) { final RefManagerExtension extension = getExtension(element.getLanguage()); if (extension != null) { extension.visitElement(element); } for (PsiElement aChildren : element.getChildren()) { aChildren.accept(this); } } @Override public void visitFile(PsiFile file) { final VirtualFile virtualFile = file.getVirtualFile(); if (virtualFile != null) { String relative = ProjectUtilCore.displayUrlRelativeToProject( virtualFile, virtualFile.getPresentableUrl(), myProject, true, false); myContext.incrementJobDoneAmount(myContext.getStdJobDescriptors().BUILD_GRAPH, relative); } final FileViewProvider viewProvider = file.getViewProvider(); final Set<Language> relevantLanguages = viewProvider.getLanguages(); for (Language language : relevantLanguages) { visitElement(viewProvider.getPsi(language)); } myPsiManager.dropResolveCaches(); InjectedLanguageManager.getInstance(myProject).dropFileCaches(file); } } @Override @Nullable public RefElement getReference(final PsiElement elem) { return getReference(elem, false); } @Nullable public RefElement getReference(final PsiElement elem, final boolean ignoreScope) { if (ApplicationManager.getApplication() .runReadAction( new Computable<Boolean>() { @Override public Boolean compute() { return elem == null || !elem.isValid() || elem instanceof LightElement || !(elem instanceof PsiDirectory) && !belongsToScope(elem, ignoreScope); } })) { return null; } return getFromRefTableOrCache( elem, () -> ApplicationManager.getApplication() .runReadAction( new Computable<RefElementImpl>() { @Override @Nullable public RefElementImpl compute() { final RefManagerExtension extension = getExtension(elem.getLanguage()); if (extension != null) { final RefElement refElement = extension.createRefElement(elem); if (refElement != null) return (RefElementImpl) refElement; } if (elem instanceof PsiFile) { return new RefFileImpl((PsiFile) elem, RefManagerImpl.this); } if (elem instanceof PsiDirectory) { return new RefDirectoryImpl((PsiDirectory) elem, RefManagerImpl.this); } return null; } }), element -> { element.initialize(); for (RefManagerExtension each : myExtensions.values()) { each.onEntityInitialized(element, elem); } fireNodeInitialized(element); }); } private RefManagerExtension getExtension(final Language language) { return myLanguageExtensions.get(language); } @Nullable @Override public RefEntity getReference(final String type, final String fqName) { for (RefManagerExtension extension : myExtensions.values()) { final RefEntity refEntity = extension.getReference(type, fqName); if (refEntity != null) return refEntity; } if (SmartRefElementPointer.FILE.equals(type)) { return RefFileImpl.fileFromExternalName(this, fqName); } if (SmartRefElementPointer.MODULE.equals(type)) { return RefModuleImpl.moduleFromName(this, fqName); } if (SmartRefElementPointer.PROJECT.equals(type)) { return getRefProject(); } if (SmartRefElementPointer.DIR.equals(type)) { String url = VfsUtilCore.pathToUrl(PathMacroManager.getInstance(getProject()).expandPath(fqName)); VirtualFile vFile = VirtualFileManager.getInstance().findFileByUrl(url); if (vFile != null) { final PsiDirectory dir = PsiManager.getInstance(getProject()).findDirectory(vFile); return getReference(dir); } } return null; } @Nullable protected <T extends RefElement> T getFromRefTableOrCache( final PsiElement element, @NotNull NullableFactory<T> factory) { return getFromRefTableOrCache(element, factory, null); } @Nullable private <T extends RefElement> T getFromRefTableOrCache( final PsiElement element, @NotNull NullableFactory<T> factory, @Nullable Consumer<T> whenCached) { PsiAnchor psiAnchor = createAnchor(element); T result; synchronized (myRefTable) { //noinspection unchecked result = (T) myRefTable.get(psiAnchor); if (result != null) return result; if (!isValidPointForReference()) { // LOG.assertTrue(true, "References may become invalid after process is finished"); return null; } result = factory.create(); if (result == null) return null; myRefTable.put(psiAnchor, result); mySortedRefs = null; if (whenCached != null) { whenCached.consume(result); } } return result; } @Override public RefModule getRefModule(Module module) { if (module == null) { return null; } RefModule refModule = myModules.get(module); if (refModule == null) { refModule = ConcurrencyUtil.cacheOrGet(myModules, module, new RefModuleImpl(module, this)); } return refModule; } @Override public boolean belongsToScope(final PsiElement psiElement) { return belongsToScope(psiElement, false); } private boolean belongsToScope(final PsiElement psiElement, final boolean ignoreScope) { if (psiElement == null || !psiElement.isValid()) return false; if (psiElement instanceof PsiCompiledElement) return false; final PsiFile containingFile = ApplicationManager.getApplication() .runReadAction( new Computable<PsiFile>() { @Override public PsiFile compute() { return psiElement.getContainingFile(); } }); if (containingFile == null) { return false; } for (RefManagerExtension extension : myExtensions.values()) { if (!extension.belongsToScope(psiElement)) return false; } final Boolean inProject = ApplicationManager.getApplication() .runReadAction( new Computable<Boolean>() { @Override public Boolean compute() { return psiElement.getManager().isInProject(psiElement); } }); return inProject.booleanValue() && (ignoreScope || getScope() == null || getScope().contains(psiElement)); } @Override public String getQualifiedName(RefEntity refEntity) { if (refEntity == null || refEntity instanceof RefElementImpl && !refEntity.isValid()) { return InspectionsBundle.message("inspection.reference.invalid"); } return refEntity.getQualifiedName(); } @Override public void removeRefElement( @NotNull RefElement refElement, @NotNull List<RefElement> deletedRefs) { List<RefEntity> children = refElement.getChildren(); if (children != null) { RefElement[] refElements = children.toArray(new RefElement[children.size()]); for (RefElement refChild : refElements) { removeRefElement(refChild, deletedRefs); } } ((RefManagerImpl) refElement.getRefManager()).removeReference(refElement); ((RefElementImpl) refElement).referenceRemoved(); if (!deletedRefs.contains(refElement)) deletedRefs.add(refElement); } protected boolean isValidPointForReference() { return myIsInProcess || myOfflineView || ApplicationManager.getApplication().isUnitTestMode(); } }
public abstract class PsiDocumentManagerBase extends PsiDocumentManager implements DocumentListener { static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.PsiDocumentManagerImpl"); private static final Key<Document> HARD_REF_TO_DOCUMENT = Key.create("HARD_REFERENCE_TO_DOCUMENT"); private static final Key<PsiFile> HARD_REF_TO_PSI = Key.create("HARD_REFERENCE_TO_PSI"); private static final Key<List<Runnable>> ACTION_AFTER_COMMIT = Key.create("ACTION_AFTER_COMMIT"); protected final Project myProject; private final PsiManager myPsiManager; private final DocumentCommitProcessor myDocumentCommitProcessor; protected final Set<Document> myUncommittedDocuments = ContainerUtil.newConcurrentSet(); private final Map<Document, Pair<CharSequence, Long>> myLastCommittedTexts = ContainerUtil.newConcurrentMap(); protected boolean myStopTrackingDocuments; protected boolean myPerformBackgroundCommit = true; private volatile boolean myIsCommitInProgress; private final PsiToDocumentSynchronizer mySynchronizer; private final List<Listener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); protected PsiDocumentManagerBase( @NotNull final Project project, @NotNull PsiManager psiManager, @NotNull MessageBus bus, @NonNls @NotNull final DocumentCommitProcessor documentCommitProcessor) { myProject = project; myPsiManager = psiManager; myDocumentCommitProcessor = documentCommitProcessor; mySynchronizer = new PsiToDocumentSynchronizer(this, bus); myPsiManager.addPsiTreeChangeListener(mySynchronizer); bus.connect() .subscribe( PsiDocumentTransactionListener.TOPIC, new PsiDocumentTransactionListener() { @Override public void transactionStarted(@NotNull Document document, @NotNull PsiFile file) { myUncommittedDocuments.remove(document); } @Override public void transactionCompleted(@NotNull Document document, @NotNull PsiFile file) {} }); } @Override @Nullable public PsiFile getPsiFile(@NotNull Document document) { final PsiFile userData = document.getUserData(HARD_REF_TO_PSI); if (userData != null) return userData; PsiFile psiFile = getCachedPsiFile(document); if (psiFile != null) return psiFile; final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); if (virtualFile == null || !virtualFile.isValid()) return null; psiFile = getPsiFile(virtualFile); if (psiFile == null) return null; fireFileCreated(document, psiFile); return psiFile; } public static void cachePsi(@NotNull Document document, @Nullable PsiFile file) { document.putUserData(HARD_REF_TO_PSI, file); } @Override public PsiFile getCachedPsiFile(@NotNull Document document) { final PsiFile userData = document.getUserData(HARD_REF_TO_PSI); if (userData != null) return userData; final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); if (virtualFile == null || !virtualFile.isValid()) return null; return getCachedPsiFile(virtualFile); } @Nullable FileViewProvider getCachedViewProvider(@NotNull Document document) { final VirtualFile virtualFile = getVirtualFile(document); if (virtualFile == null) return null; return getCachedViewProvider(virtualFile); } private FileViewProvider getCachedViewProvider(@NotNull VirtualFile virtualFile) { return ((PsiManagerEx) myPsiManager).getFileManager().findCachedViewProvider(virtualFile); } private static VirtualFile getVirtualFile(@NotNull Document document) { final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); if (virtualFile == null || !virtualFile.isValid()) return null; return virtualFile; } @Nullable PsiFile getCachedPsiFile(@NotNull VirtualFile virtualFile) { return ((PsiManagerEx) myPsiManager).getFileManager().getCachedPsiFile(virtualFile); } @Nullable private PsiFile getPsiFile(@NotNull VirtualFile virtualFile) { return ((PsiManagerEx) myPsiManager).getFileManager().findFile(virtualFile); } @Nullable @Override public Document getDocument(@NotNull PsiFile file) { if (file instanceof PsiBinaryFile) return null; Document document = getCachedDocument(file); if (document != null) { if (!file.getViewProvider().isPhysical() && document.getUserData(HARD_REF_TO_PSI) == null) { PsiUtilCore.ensureValid(file); cachePsi(document, file); } return document; } FileViewProvider viewProvider = file.getViewProvider(); if (!viewProvider.isEventSystemEnabled()) return null; document = FileDocumentManager.getInstance().getDocument(viewProvider.getVirtualFile()); if (document != null) { if (document.getTextLength() != file.getTextLength()) { String message = "Document/PSI mismatch: " + file + " (" + file.getClass() + "); physical=" + viewProvider.isPhysical(); if (document.getTextLength() + file.getTextLength() < 8096) { message += "\n=== document ===\n" + document.getText() + "\n=== PSI ===\n" + file.getText(); } throw new AssertionError(message); } if (!viewProvider.isPhysical()) { PsiUtilCore.ensureValid(file); cachePsi(document, file); file.putUserData(HARD_REF_TO_DOCUMENT, document); } } return document; } @Override public Document getCachedDocument(@NotNull PsiFile file) { if (!file.isPhysical()) return null; VirtualFile vFile = file.getViewProvider().getVirtualFile(); return FileDocumentManager.getInstance().getCachedDocument(vFile); } @Override public void commitAllDocuments() { ApplicationManager.getApplication().assertIsDispatchThread(); if (myUncommittedDocuments.isEmpty()) return; final Document[] documents = getUncommittedDocuments(); for (Document document : documents) { commitDocument(document); } LOG.assertTrue(!hasUncommitedDocuments(), myUncommittedDocuments); } @Override public void performForCommittedDocument( @NotNull final Document doc, @NotNull final Runnable action) { final Document document = doc instanceof DocumentWindow ? ((DocumentWindow) doc).getDelegate() : doc; if (isCommitted(document)) { action.run(); } else { addRunOnCommit(document, action); } } private final Map<Object, Runnable> actionsWhenAllDocumentsAreCommitted = new LinkedHashMap<Object, Runnable>(); // accessed from EDT only private static final Object PERFORM_ALWAYS_KEY = new Object() { @Override @NonNls public String toString() { return "PERFORM_ALWAYS"; } }; /** * Cancel previously registered action and schedules (new) action to be executed when all * documents are committed. * * @param key the (unique) id of the action. * @param action The action to be executed after automatic commit. This action will overwrite any * action which was registered under this key earlier. The action will be executed in EDT. * @return true if action has been run immediately, or false if action was scheduled for execution * later. */ public boolean cancelAndRunWhenAllCommitted( @NonNls @NotNull Object key, @NotNull final Runnable action) { ApplicationManager.getApplication().assertIsDispatchThread(); if (myProject.isDisposed()) { action.run(); return true; } if (myUncommittedDocuments.isEmpty()) { action.run(); if (!hasUncommitedDocuments()) { assert actionsWhenAllDocumentsAreCommitted.isEmpty() : actionsWhenAllDocumentsAreCommitted; } return true; } actionsWhenAllDocumentsAreCommitted.put(key, action); return false; } public static void addRunOnCommit(@NotNull Document document, @NotNull Runnable action) { synchronized (ACTION_AFTER_COMMIT) { List<Runnable> list = document.getUserData(ACTION_AFTER_COMMIT); if (list == null) { document.putUserData(ACTION_AFTER_COMMIT, list = new SmartList<Runnable>()); } list.add(action); } } @Override public void commitDocument(@NotNull final Document doc) { final Document document = doc instanceof DocumentWindow ? ((DocumentWindow) doc).getDelegate() : doc; if (!isCommitted(document)) { doCommit(document); } } // public for Upsource public boolean finishCommit( @NotNull final Document document, @NotNull final List<Processor<Document>> finishProcessors, final boolean synchronously, @NotNull final Object reason) { assert !myProject.isDisposed() : "Already disposed"; final boolean[] ok = {true}; ApplicationManager.getApplication() .runWriteAction( new CommitToPsiFileAction(document, myProject) { @Override public void run() { ok[0] = finishCommitInWriteAction(document, finishProcessors, synchronously); } }); if (ok[0]) { // otherwise changes maybe not synced to the document yet, and injectors will crash if (!mySynchronizer.isDocumentAffectedByTransactions(document)) { InjectedLanguageManager.getInstance(myProject).startRunInjectors(document, synchronously); } // run after commit actions outside write action runAfterCommitActions(document); if (DebugUtil.DO_EXPENSIVE_CHECKS && !ApplicationInfoImpl.isInPerformanceTest()) { checkAllElementsValid(document, reason); } } return ok[0]; } protected boolean finishCommitInWriteAction( @NotNull final Document document, @NotNull final List<Processor<Document>> finishProcessors, final boolean synchronously) { if (myProject.isDisposed()) return false; assert !(document instanceof DocumentWindow); myIsCommitInProgress = true; boolean success = true; try { final FileViewProvider viewProvider = getCachedViewProvider(document); if (viewProvider != null) { for (Processor<Document> finishRunnable : finishProcessors) { success = finishRunnable.process(document); if (synchronously) { assert success : finishRunnable + " in " + finishProcessors; } if (!success) { break; } } if (success) { myLastCommittedTexts.remove(document); viewProvider.contentsSynchronized(); } } else { handleCommitWithoutPsi(document); } } finally { myDocumentCommitProcessor.log( "in PDI.finishDoc: ", null, synchronously, success, myUncommittedDocuments); if (success) { myUncommittedDocuments.remove(document); myDocumentCommitProcessor.log( "in PDI.finishDoc: removed doc", null, synchronously, success, myUncommittedDocuments); } myIsCommitInProgress = false; myDocumentCommitProcessor.log( "in PDI.finishDoc: exit", null, synchronously, success, myUncommittedDocuments); } return success; } private void checkAllElementsValid(@NotNull Document document, @NotNull final Object reason) { final PsiFile psiFile = getCachedPsiFile(document); if (psiFile != null) { psiFile.accept( new PsiRecursiveElementWalkingVisitor() { @Override public void visitElement(PsiElement element) { if (!element.isValid()) { throw new AssertionError( "Commit to '" + psiFile.getVirtualFile() + "' has led to invalid element: " + element + "; Reason: '" + reason + "'"); } } }); } } private void doCommit(@NotNull final Document document) { assert !myIsCommitInProgress : "Do not call commitDocument() from inside PSI change listener"; ApplicationManager.getApplication() .runWriteAction( new Runnable() { @Override public void run() { // otherwise there are many clients calling commitAllDocs() on PSI childrenChanged() if (getSynchronizer().isDocumentAffectedByTransactions(document)) return; myIsCommitInProgress = true; try { myDocumentCommitProcessor.commitSynchronously(document, myProject); } finally { myIsCommitInProgress = false; } assert !isInUncommittedSet(document) : "Document :" + document; } }); } @Override public <T> T commitAndRunReadAction(@NotNull final Computable<T> computation) { final Ref<T> ref = Ref.create(null); commitAndRunReadAction( new Runnable() { @Override public void run() { ref.set(computation.compute()); } }); return ref.get(); } @Override public void reparseFiles(@NotNull Collection<VirtualFile> files, boolean includeOpenFiles) { FileContentUtilCore.reparseFiles(files); } @Override public void commitAndRunReadAction(@NotNull final Runnable runnable) { final Application application = ApplicationManager.getApplication(); if (SwingUtilities.isEventDispatchThread()) { commitAllDocuments(); runnable.run(); } else { if (ApplicationManager.getApplication().isReadAccessAllowed()) { LOG.error( "Don't call commitAndRunReadAction inside ReadAction, it will cause a deadlock otherwise. " + Thread.currentThread()); } final Semaphore s1 = new Semaphore(); final Semaphore s2 = new Semaphore(); final boolean[] committed = {false}; application.runReadAction( new Runnable() { @Override public void run() { if (myUncommittedDocuments.isEmpty()) { runnable.run(); committed[0] = true; } else { s1.down(); s2.down(); final Runnable commitRunnable = new Runnable() { @Override public void run() { commitAllDocuments(); s1.up(); s2.waitFor(); } }; final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); if (progressIndicator == null) { ApplicationManager.getApplication().invokeLater(commitRunnable); } else { ApplicationManager.getApplication() .invokeLater(commitRunnable, progressIndicator.getModalityState()); } } } }); if (!committed[0]) { s1.waitFor(); application.runReadAction( new Runnable() { @Override public void run() { s2.up(); runnable.run(); } }); } } } /** * Schedules action to be executed when all documents are committed. * * @return true if action has been run immediately, or false if action was scheduled for execution * later. */ @Override public boolean performWhenAllCommitted(@NotNull final Runnable action) { ApplicationManager.getApplication().assertIsDispatchThread(); assert !myProject.isDisposed() : "Already disposed: " + myProject; if (myUncommittedDocuments.isEmpty()) { action.run(); return true; } CompositeRunnable actions = (CompositeRunnable) actionsWhenAllDocumentsAreCommitted.get(PERFORM_ALWAYS_KEY); if (actions == null) { actions = new CompositeRunnable(); actionsWhenAllDocumentsAreCommitted.put(PERFORM_ALWAYS_KEY, actions); } actions.add(action); myDocumentCommitProcessor.log( "PDI: added performWhenAllCommitted", null, false, action, myUncommittedDocuments); return false; } private static class CompositeRunnable extends ArrayList<Runnable> implements Runnable { @Override public void run() { for (Runnable runnable : this) { runnable.run(); } } } private void runAfterCommitActions(@NotNull Document document) { ApplicationManager.getApplication().assertIsDispatchThread(); List<Runnable> list; synchronized (ACTION_AFTER_COMMIT) { list = document.getUserData(ACTION_AFTER_COMMIT); if (list != null) { list = new ArrayList<Runnable>(list); document.putUserData(ACTION_AFTER_COMMIT, null); } } if (list != null) { for (final Runnable runnable : list) { runnable.run(); } } if (!hasUncommitedDocuments() && !actionsWhenAllDocumentsAreCommitted.isEmpty()) { List<Object> keys = new ArrayList<Object>(actionsWhenAllDocumentsAreCommitted.keySet()); for (Object key : keys) { try { Runnable action = actionsWhenAllDocumentsAreCommitted.remove(key); myDocumentCommitProcessor.log( "Running after commit runnable: ", null, false, key, action); action.run(); } catch (Throwable e) { LOG.error(e); } } } } @Override public void addListener(@NotNull Listener listener) { myListeners.add(listener); } @Override public void removeListener(@NotNull Listener listener) { myListeners.remove(listener); } @Override public boolean isDocumentBlockedByPsi(@NotNull Document doc) { return false; } @Override public void doPostponedOperationsAndUnblockDocument(@NotNull Document doc) {} void fireDocumentCreated(@NotNull Document document, PsiFile file) { for (Listener listener : myListeners) { listener.documentCreated(document, file); } } private void fireFileCreated(@NotNull Document document, @NotNull PsiFile file) { for (Listener listener : myListeners) { listener.fileCreated(file, document); } } @Override @NotNull public CharSequence getLastCommittedText(@NotNull Document document) { Pair<CharSequence, Long> pair = myLastCommittedTexts.get(document); return pair != null ? pair.first : document.getImmutableCharSequence(); } @Override public long getLastCommittedStamp(@NotNull Document document) { Pair<CharSequence, Long> pair = myLastCommittedTexts.get(document); return pair != null ? pair.second : document.getModificationStamp(); } @Override @NotNull public Document[] getUncommittedDocuments() { ApplicationManager.getApplication().assertIsDispatchThread(); Document[] documents = myUncommittedDocuments.toArray(new Document[myUncommittedDocuments.size()]); return ArrayUtil.stripTrailingNulls(documents); } boolean isInUncommittedSet(@NotNull Document document) { if (document instanceof DocumentWindow) return isInUncommittedSet(((DocumentWindow) document).getDelegate()); return myUncommittedDocuments.contains(document); } @Override public boolean isUncommited(@NotNull Document document) { return !isCommitted(document); } @Override public boolean isCommitted(@NotNull Document document) { if (document instanceof DocumentWindow) return isCommitted(((DocumentWindow) document).getDelegate()); if (getSynchronizer().isInSynchronization(document)) return true; return !((DocumentEx) document).isInEventsHandling() && !isInUncommittedSet(document); } @Override public boolean hasUncommitedDocuments() { return !myIsCommitInProgress && !myUncommittedDocuments.isEmpty(); } @Override public void beforeDocumentChange(@NotNull DocumentEvent event) { if (myStopTrackingDocuments) return; final Document document = event.getDocument(); if (!(document instanceof DocumentWindow) && !myLastCommittedTexts.containsKey(document)) { myLastCommittedTexts.put( document, Pair.create(document.getImmutableCharSequence(), document.getModificationStamp())); } VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); boolean isRelevant = virtualFile != null && isRelevant(virtualFile); final FileViewProvider viewProvider = getCachedViewProvider(document); boolean inMyProject = viewProvider != null && viewProvider.getManager() == myPsiManager; if (!isRelevant || !inMyProject) { return; } final List<PsiFile> files = viewProvider.getAllFiles(); PsiFile psiCause = null; for (PsiFile file : files) { if (file == null) { throw new AssertionError( "View provider " + viewProvider + " (" + viewProvider.getClass() + ") returned null in its files array: " + files + " for file " + viewProvider.getVirtualFile()); } if (mySynchronizer.isInsideAtomicChange(file)) { psiCause = file; } } if (psiCause == null) { beforeDocumentChangeOnUnlockedDocument(viewProvider); } ((SingleRootFileViewProvider) viewProvider).beforeDocumentChanged(psiCause); } protected void beforeDocumentChangeOnUnlockedDocument( @NotNull final FileViewProvider viewProvider) {} @Override public void documentChanged(DocumentEvent event) { if (myStopTrackingDocuments) return; final Document document = event.getDocument(); VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); boolean isRelevant = virtualFile != null && isRelevant(virtualFile); final FileViewProvider viewProvider = getCachedViewProvider(document); if (viewProvider == null) { handleCommitWithoutPsi(document); return; } boolean inMyProject = viewProvider.getManager() == myPsiManager; if (!isRelevant || !inMyProject) { myLastCommittedTexts.remove(document); return; } ApplicationManager.getApplication().assertWriteAccessAllowed(); final List<PsiFile> files = viewProvider.getAllFiles(); boolean commitNecessary = true; for (PsiFile file : files) { if (mySynchronizer.isInsideAtomicChange(file)) { commitNecessary = false; continue; } assert file instanceof PsiFileImpl || "mock.file".equals(file.getName()) && ApplicationManager.getApplication().isUnitTestMode() : event + "; file=" + file + "; allFiles=" + files + "; viewProvider=" + viewProvider; } boolean forceCommit = ApplicationManager.getApplication().hasWriteAction(ExternalChangeAction.class) && (SystemProperties.getBooleanProperty("idea.force.commit.on.external.change", false) || ApplicationManager.getApplication().isHeadlessEnvironment() && !ApplicationManager.getApplication().isUnitTestMode()); // Consider that it's worth to perform complete re-parse instead of merge if the whole document // text is replaced and // current document lines number is roughly above 5000. This makes sense in situations when // external change is performed // for the huge file (that causes the whole document to be reloaded and 'merge' way takes a // while to complete). if (event.isWholeTextReplaced() && document.getTextLength() > 100000) { document.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, Boolean.TRUE); } if (commitNecessary) { assert !(document instanceof DocumentWindow); myUncommittedDocuments.add(document); myDocumentCommitProcessor.log( "added uncommitted doc", null, false, myProject, document, ((DocumentEx) document).isInBulkUpdate()); if (forceCommit) { commitDocument(document); } else if (!((DocumentEx) document).isInBulkUpdate() && myPerformBackgroundCommit) { myDocumentCommitProcessor.commitAsynchronously(myProject, document, event); } } else { myLastCommittedTexts.remove(document); } } void handleCommitWithoutPsi(@NotNull Document document) { final Pair<CharSequence, Long> prevPair = myLastCommittedTexts.remove(document); if (prevPair == null) { return; } if (!myProject.isInitialized() || myProject.isDisposed()) { return; } myUncommittedDocuments.remove(document); VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); if (virtualFile == null || !FileIndexFacade.getInstance(myProject).isInContent(virtualFile)) { return; } final PsiFile psiFile = getPsiFile(document); if (psiFile == null) { return; } // we can end up outside write action here if the document has forUseInNonAWTThread=true ApplicationManager.getApplication() .runWriteAction( new ExternalChangeAction() { @Override public void run() { psiFile.getViewProvider().beforeContentsSynchronized(); synchronized (PsiLock.LOCK) { final int oldLength = prevPair.first.length(); PsiManagerImpl manager = (PsiManagerImpl) psiFile.getManager(); BlockSupportImpl.sendBeforeChildrenChangeEvent(manager, psiFile, true); BlockSupportImpl.sendBeforeChildrenChangeEvent(manager, psiFile, false); if (psiFile instanceof PsiFileImpl) { ((PsiFileImpl) psiFile).onContentReload(); } BlockSupportImpl.sendAfterChildrenChangedEvent( manager, psiFile, oldLength, false); BlockSupportImpl.sendAfterChildrenChangedEvent(manager, psiFile, oldLength, true); } psiFile.getViewProvider().contentsSynchronized(); } }); } private boolean isRelevant(@NotNull VirtualFile virtualFile) { return !virtualFile.getFileType().isBinary() && !myProject.isDisposed(); } public static boolean checkConsistency(@NotNull PsiFile psiFile, @NotNull Document document) { // todo hack if (psiFile.getVirtualFile() == null) return true; CharSequence editorText = document.getCharsSequence(); int documentLength = document.getTextLength(); if (psiFile.textMatches(editorText)) { LOG.assertTrue(psiFile.getTextLength() == documentLength); return true; } char[] fileText = psiFile.textToCharArray(); @SuppressWarnings("NonConstantStringShouldBeStringBuffer") @NonNls String error = "File '" + psiFile.getName() + "' text mismatch after reparse. " + "File length=" + fileText.length + "; Doc length=" + documentLength + "\n"; int i = 0; for (; i < documentLength; i++) { if (i >= fileText.length) { error += "editorText.length > psiText.length i=" + i + "\n"; break; } if (i >= editorText.length()) { error += "editorText.length > psiText.length i=" + i + "\n"; break; } if (editorText.charAt(i) != fileText[i]) { error += "first unequal char i=" + i + "\n"; break; } } // error += "*********************************************" + "\n"; // if (i <= 500){ // error += "Equal part:" + editorText.subSequence(0, i) + "\n"; // } // else{ // error += "Equal part start:\n" + editorText.subSequence(0, 200) + "\n"; // error += "................................................" + "\n"; // error += "................................................" + "\n"; // error += "................................................" + "\n"; // error += "Equal part end:\n" + editorText.subSequence(i - 200, i) + "\n"; // } error += "*********************************************" + "\n"; error += "Editor Text tail:(" + (documentLength - i) + ")\n"; // + editorText.subSequence(i, Math.min(i + 300, documentLength)) + "\n"; error += "*********************************************" + "\n"; error += "Psi Text tail:(" + (fileText.length - i) + ")\n"; error += "*********************************************" + "\n"; if (document instanceof DocumentWindow) { error += "doc: '" + document.getText() + "'\n"; error += "psi: '" + psiFile.getText() + "'\n"; error += "ast: '" + psiFile.getNode().getText() + "'\n"; error += psiFile.getLanguage() + "\n"; PsiElement context = InjectedLanguageManager.getInstance(psiFile.getProject()).getInjectionHost(psiFile); if (context != null) { error += "context: " + context + "; text: '" + context.getText() + "'\n"; error += "context file: " + context.getContainingFile() + "\n"; } error += "document window ranges: " + Arrays.asList(((DocumentWindow) document).getHostRanges()) + "\n"; } LOG.error(error); // document.replaceString(0, documentLength, psiFile.getText()); return false; } @TestOnly public void clearUncommittedDocuments() { myLastCommittedTexts.clear(); myUncommittedDocuments.clear(); mySynchronizer.cleanupForNextTest(); } @TestOnly public void disableBackgroundCommit(@NotNull Disposable parentDisposable) { assert myPerformBackgroundCommit; myPerformBackgroundCommit = false; Disposer.register( parentDisposable, new Disposable() { @Override public void dispose() { myPerformBackgroundCommit = true; } }); } @NotNull public PsiToDocumentSynchronizer getSynchronizer() { return mySynchronizer; } }
public final class IconLoader { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.IconLoader"); public static boolean STRICT = false; private static boolean USE_DARK_ICONS = UIUtil.isUnderDarcula(); private static float SCALE = JBUI.scale(1f); private static ImageFilter IMAGE_FILTER; @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") private static final ConcurrentMap<URL, CachedImageIcon> ourIconsCache = ContainerUtil.newConcurrentMap(100, 0.9f, 2); /** This cache contains mapping between icons and disabled icons. */ private static final Map<Icon, Icon> ourIcon2DisabledIcon = new WeakHashMap<Icon, Icon>(200); @NonNls private static final Map<String, String> ourDeprecatedIconsReplacements = new HashMap<String, String>(); static { ourDeprecatedIconsReplacements.put( "/general/toolWindowDebugger.png", "AllIcons.Toolwindows.ToolWindowDebugger"); ourDeprecatedIconsReplacements.put( "/general/toolWindowChanges.png", "AllIcons.Toolwindows.ToolWindowChanges"); ourDeprecatedIconsReplacements.put( "/actions/showSettings.png", "AllIcons.General.ProjectSettings"); ourDeprecatedIconsReplacements.put("/general/ideOptions.png", "AllIcons.General.Settings"); ourDeprecatedIconsReplacements.put( "/general/applicationSettings.png", "AllIcons.General.Settings"); ourDeprecatedIconsReplacements.put("/toolbarDecorator/add.png", "AllIcons.General.Add"); ourDeprecatedIconsReplacements.put("/vcs/customizeView.png", "AllIcons.General.Settings"); ourDeprecatedIconsReplacements.put("/vcs/refresh.png", "AllIcons.Actions.Refresh"); ourDeprecatedIconsReplacements.put("/actions/sync.png", "AllIcons.Actions.Refresh"); ourDeprecatedIconsReplacements.put("/actions/refreshUsages.png", "AllIcons.Actions.Rerun"); ourDeprecatedIconsReplacements.put("/compiler/error.png", "AllIcons.General.Error"); ourDeprecatedIconsReplacements.put( "/compiler/hideWarnings.png", "AllIcons.General.HideWarnings"); ourDeprecatedIconsReplacements.put("/compiler/information.png", "AllIcons.General.Information"); ourDeprecatedIconsReplacements.put("/compiler/warning.png", "AllIcons.General.Warning"); ourDeprecatedIconsReplacements.put("/ide/errorSign.png", "AllIcons.General.Error"); ourDeprecatedIconsReplacements.put("/ant/filter.png", "AllIcons.General.Filter"); ourDeprecatedIconsReplacements.put("/inspector/useFilter.png", "AllIcons.General.Filter"); ourDeprecatedIconsReplacements.put("/actions/showSource.png", "AllIcons.Actions.Preview"); ourDeprecatedIconsReplacements.put( "/actions/consoleHistory.png", "AllIcons.General.MessageHistory"); ourDeprecatedIconsReplacements.put( "/vcs/messageHistory.png", "AllIcons.General.MessageHistory"); } private static final ImageIcon EMPTY_ICON = new ImageIcon(UIUtil.createImage(1, 1, BufferedImage.TYPE_3BYTE_BGR)) { @NonNls public String toString() { return "Empty icon " + super.toString(); } }; private static boolean ourIsActivated = false; private IconLoader() {} @Deprecated public static Icon getIcon(@NotNull final Image image) { return new JBImageIcon(image); } public static void setUseDarkIcons(boolean useDarkIcons) { USE_DARK_ICONS = useDarkIcons; clearCache(); } public static void setScale(float scale) { if (scale != SCALE) { SCALE = scale; clearCache(); } } public static void setFilter(ImageFilter filter) { if (!Registry.is("color.blindness.icon.filter")) { filter = null; } if (IMAGE_FILTER != filter) { IMAGE_FILTER = filter; clearCache(); } } private static void clearCache() { ourIconsCache.clear(); ourIcon2DisabledIcon.clear(); } // TODO[kb] support iconsets // public static Icon getIcon(@NotNull final String path, @NotNull final String darkVariantPath) { // return new InvariantIcon(getIcon(path), getIcon(darkVariantPath)); // } @NotNull public static Icon getIcon(@NonNls @NotNull final String path) { Class callerClass = ReflectionUtil.getGrandCallerClass(); assert callerClass != null : path; return getIcon(path, callerClass); } @Nullable private static Icon getReflectiveIcon(@NotNull String path, ClassLoader classLoader) { try { @NonNls String pckg = path.startsWith("AllIcons.") ? "com.intellij.icons." : "icons."; Class cur = Class.forName( pckg + path.substring(0, path.lastIndexOf('.')).replace('.', '$'), true, classLoader); Field field = cur.getField(path.substring(path.lastIndexOf('.') + 1)); return (Icon) field.get(null); } catch (Exception e) { return null; } } @Nullable /** * Might return null if icon was not found. Use only if you expected null return value, otherwise * see {@link IconLoader#getIcon(java.lang.String)} */ public static Icon findIcon(@NonNls @NotNull String path) { Class callerClass = ReflectionUtil.getGrandCallerClass(); if (callerClass == null) return null; return findIcon(path, callerClass); } @NotNull public static Icon getIcon(@NotNull String path, @NotNull final Class aClass) { final Icon icon = findIcon(path, aClass); if (icon == null) { LOG.error("Icon cannot be found in '" + path + "', aClass='" + aClass + "'"); } return icon; } public static void activate() { ourIsActivated = true; } private static boolean isLoaderDisabled() { return !ourIsActivated; } /** * Might return null if icon was not found. Use only if you expected null return value, otherwise * see {@link IconLoader#getIcon(java.lang.String, java.lang.Class)} */ @Nullable public static Icon findIcon(@NotNull final String path, @NotNull final Class aClass) { return findIcon(path, aClass, false); } @Nullable public static Icon findIcon( @NotNull String path, @NotNull final Class aClass, boolean computeNow) { path = undeprecate(path); if (isReflectivePath(path)) return getReflectiveIcon(path, aClass.getClassLoader()); URL myURL = aClass.getResource(path); if (myURL == null) { if (STRICT) throw new RuntimeException("Can't find icon in '" + path + "' near " + aClass); return null; } return findIcon(myURL); } @NotNull private static String undeprecate(@NotNull String path) { String replacement = ourDeprecatedIconsReplacements.get(path); return replacement == null ? path : replacement; } private static boolean isReflectivePath(@NotNull String path) { List<String> paths = StringUtil.split(path, "."); return paths.size() > 1 && paths.get(0).endsWith("Icons"); } @Nullable public static Icon findIcon(URL url) { return findIcon(url, true); } @Nullable public static Icon findIcon(URL url, boolean useCache) { if (url == null) { return null; } CachedImageIcon icon = ourIconsCache.get(url); if (icon == null) { icon = new CachedImageIcon(url); if (useCache) { icon = ConcurrencyUtil.cacheOrGet(ourIconsCache, url, icon); } } return icon; } @Nullable public static Icon findIcon(@NotNull String path, @NotNull ClassLoader classLoader) { path = undeprecate(path); if (isReflectivePath(path)) return getReflectiveIcon(path, classLoader); if (!StringUtil.startsWithChar(path, '/')) return null; final URL url = classLoader.getResource(path.substring(1)); return findIcon(url); } @Nullable private static Icon checkIcon(final Image image, @NotNull URL url) { if (image == null || image.getHeight(LabelHolder.ourFakeComponent) < 1) { // image wasn't loaded or broken return null; } final Icon icon = getIcon(image); if (icon != null && !isGoodSize(icon)) { LOG.error("Invalid icon: " + url); // # 22481 return EMPTY_ICON; } return icon; } public static boolean isGoodSize(@NotNull final Icon icon) { return icon.getIconWidth() > 0 && icon.getIconHeight() > 0; } /** * Gets (creates if necessary) disabled icon based on the passed one. * * @return <code>ImageIcon</code> constructed from disabled image of passed icon. */ @Nullable public static Icon getDisabledIcon(Icon icon) { if (icon instanceof LazyIcon) icon = ((LazyIcon) icon).getOrComputeIcon(); if (icon == null) return null; Icon disabledIcon = ourIcon2DisabledIcon.get(icon); if (disabledIcon == null) { if (!isGoodSize(icon)) { LOG.error(icon); // # 22481 return EMPTY_ICON; } final int scale = UIUtil.isRetina() ? 2 : 1; @SuppressWarnings("UndesirableClassUsage") BufferedImage image = new BufferedImage( scale * icon.getIconWidth(), scale * icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB); final Graphics2D graphics = image.createGraphics(); graphics.setColor(UIUtil.TRANSPARENT_COLOR); graphics.fillRect(0, 0, icon.getIconWidth(), icon.getIconHeight()); graphics.scale(scale, scale); icon.paintIcon(LabelHolder.ourFakeComponent, graphics, 0, 0); graphics.dispose(); Image img = ImageUtil.filter(image, UIUtil.getGrayFilter()); if (UIUtil.isRetina()) img = RetinaImage.createFrom(img, 2, ImageLoader.ourComponent); disabledIcon = new JBImageIcon(img); ourIcon2DisabledIcon.put(icon, disabledIcon); } return disabledIcon; } public static Icon getTransparentIcon(@NotNull final Icon icon) { return getTransparentIcon(icon, 0.5f); } public static Icon getTransparentIcon(@NotNull final Icon icon, final float alpha) { return new Icon() { @Override public int getIconHeight() { return icon.getIconHeight(); } @Override public int getIconWidth() { return icon.getIconWidth(); } @Override public void paintIcon(final Component c, final Graphics g, final int x, final int y) { final Graphics2D g2 = (Graphics2D) g; final Composite saveComposite = g2.getComposite(); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha)); icon.paintIcon(c, g2, x, y); g2.setComposite(saveComposite); } }; } private static final class CachedImageIcon implements ScalableIcon { private Object myRealIcon; @NotNull private final URL myUrl; private boolean dark; private float scale; private ImageFilter filter; private HashMap<Float, Icon> scaledIcons; public CachedImageIcon(@NotNull URL url) { myUrl = url; dark = USE_DARK_ICONS; scale = SCALE; filter = IMAGE_FILTER; } @NotNull private synchronized Icon getRealIcon() { if (isLoaderDisabled() && (myRealIcon == null || dark != USE_DARK_ICONS || scale != SCALE || filter != IMAGE_FILTER)) return EMPTY_ICON; if (dark != USE_DARK_ICONS || scale != SCALE || filter != IMAGE_FILTER) { myRealIcon = null; dark = USE_DARK_ICONS; scale = SCALE; filter = IMAGE_FILTER; } Object realIcon = myRealIcon; if (realIcon instanceof Icon) return (Icon) realIcon; Icon icon; if (realIcon instanceof Reference) { icon = ((Reference<Icon>) realIcon).get(); if (icon != null) return icon; } Image image = ImageLoader.loadFromUrl(myUrl, true, filter); icon = checkIcon(image, myUrl); if (icon != null) { if (icon.getIconWidth() < 50 && icon.getIconHeight() < 50) { realIcon = icon; } else { realIcon = new SoftReference<Icon>(icon); } myRealIcon = realIcon; } return icon == null ? EMPTY_ICON : icon; } @Override public void paintIcon(Component c, Graphics g, int x, int y) { getRealIcon().paintIcon(c, g, x, y); } @Override public int getIconWidth() { return getRealIcon().getIconWidth(); } @Override public int getIconHeight() { return getRealIcon().getIconHeight(); } @Override public String toString() { return myUrl.toString(); } @Override public Icon scale(float scaleFactor) { if (scaleFactor == 1f) { return this; } if (scaledIcons == null) { scaledIcons = new HashMap<Float, Icon>(1); } Icon result = scaledIcons.get(scaleFactor); if (result != null) { return result; } final Image image = ImageLoader.loadFromUrl(myUrl, UIUtil.isUnderDarcula(), scaleFactor >= 1.5f, filter); if (image != null) { int width = (int) (getIconWidth() * scaleFactor); int height = (int) (getIconHeight() * scaleFactor); final BufferedImage resizedImage = Scalr.resize( ImageUtil.toBufferedImage(image), Scalr.Method.ULTRA_QUALITY, width, height); result = getIcon(resizedImage); scaledIcons.put(scaleFactor, result); return result; } return this; } } public abstract static class LazyIcon implements Icon { private boolean myWasComputed; private Icon myIcon; private boolean isDarkVariant = USE_DARK_ICONS; private float scale = SCALE; private ImageFilter filter = IMAGE_FILTER; @Override public void paintIcon(Component c, Graphics g, int x, int y) { final Icon icon = getOrComputeIcon(); if (icon != null) { icon.paintIcon(c, g, x, y); } } @Override public int getIconWidth() { final Icon icon = getOrComputeIcon(); return icon != null ? icon.getIconWidth() : 0; } @Override public int getIconHeight() { final Icon icon = getOrComputeIcon(); return icon != null ? icon.getIconHeight() : 0; } protected final synchronized Icon getOrComputeIcon() { if (!myWasComputed || isDarkVariant != USE_DARK_ICONS || scale != SCALE || filter != IMAGE_FILTER) { isDarkVariant = USE_DARK_ICONS; scale = SCALE; filter = IMAGE_FILTER; myWasComputed = true; myIcon = compute(); } return myIcon; } public final void load() { getIconWidth(); } protected abstract Icon compute(); } private static class LabelHolder { /** * To get disabled icon with paint it into the image. Some icons require not null component to * paint. */ private static final JComponent ourFakeComponent = new JLabel(); } }
public abstract class MultiplePsiFilesPerDocumentFileViewProvider extends SingleRootFileViewProvider { private final ConcurrentMap<Language, PsiFileImpl> myRoots = ContainerUtil.newConcurrentMap(1, 0.75f, 1); private MultiplePsiFilesPerDocumentFileViewProvider myOriginal = null; public MultiplePsiFilesPerDocumentFileViewProvider( PsiManager manager, VirtualFile virtualFile, boolean eventSystemEnabled) { super(manager, virtualFile, eventSystemEnabled, Language.ANY); } @Override @NotNull public abstract Language getBaseLanguage(); @Override @NotNull public List<PsiFile> getAllFiles() { final List<PsiFile> roots = new ArrayList<PsiFile>(); for (Language language : getLanguages()) { PsiFile psi = getPsi(language); if (psi != null) roots.add(psi); } final PsiFile base = getPsi(getBaseLanguage()); if (!roots.isEmpty() && roots.get(0) != base) { roots.remove(base); roots.add(0, base); } return roots; } protected void removeFile(final Language language) { PsiFileImpl file = myRoots.remove(language); if (file != null) { file.markInvalidated(); } } @Override protected PsiFile getPsiInner(@NotNull final Language target) { PsiFileImpl file = myRoots.get(target); if (file == null) { if (isPhysical()) { VirtualFile virtualFile = getVirtualFile(); if (isIgnored()) return null; VirtualFile parent = virtualFile.getParent(); if (parent != null) { getManager().findDirectory(parent); } } if (target != getBaseLanguage() && !getLanguages().contains(target)) { return null; } file = (PsiFileImpl) createFile(target); if (file == null) return null; if (myOriginal != null) { final PsiFile originalFile = myOriginal.getPsi(target); if (originalFile != null) { file.setOriginalFile(originalFile); } } file = ConcurrencyUtil.cacheOrGet(myRoots, target, file); } return file; } @Override public PsiFile getCachedPsi(@NotNull Language target) { return myRoots.get(target); } @NotNull @Override public FileElement[] getKnownTreeRoots() { List<FileElement> files = new ArrayList<FileElement>(myRoots.size()); for (PsiFile file : myRoots.values()) { final FileElement treeElement = ((PsiFileImpl) file).getTreeElement(); if (treeElement != null) { files.add(treeElement); } } return files.toArray(new FileElement[files.size()]); } @TestOnly public void checkAllTreesEqual() { Collection<PsiFileImpl> roots = myRoots.values(); PsiDocumentManager documentManager = PsiDocumentManager.getInstance(getManager().getProject()); documentManager.commitAllDocuments(); for (PsiFile root : roots) { Document document = documentManager.getDocument(root); PsiDocumentManagerBase.checkConsistency(root, document); assert root.getText().equals(document.getText()); } } @NotNull @Override public final MultiplePsiFilesPerDocumentFileViewProvider createCopy( @NotNull final VirtualFile fileCopy) { final MultiplePsiFilesPerDocumentFileViewProvider copy = cloneInner(fileCopy); copy.myOriginal = myOriginal == null ? this : myOriginal; return copy; } protected abstract MultiplePsiFilesPerDocumentFileViewProvider cloneInner(VirtualFile fileCopy); @Override @Nullable public PsiElement findElementAt(int offset, @NotNull Class<? extends Language> lang) { final PsiFile mainRoot = getPsi(getBaseLanguage()); PsiElement ret = null; for (final Language language : getLanguages()) { if (!ReflectionUtil.isAssignable(lang, language.getClass())) continue; if (lang.equals(Language.class) && !getLanguages().contains(language)) continue; final PsiFile psiRoot = getPsi(language); final PsiElement psiElement = findElementAt(psiRoot, offset); if (psiElement == null || psiElement instanceof OuterLanguageElement) continue; if (ret == null || psiRoot != mainRoot) { ret = psiElement; } } return ret; } @Override @Nullable public PsiElement findElementAt(int offset) { return findElementAt(offset, Language.class); } @Override @Nullable public PsiReference findReferenceAt(int offset) { TextRange minRange = new TextRange(0, getContents().length()); PsiReference ret = null; for (final Language language : getLanguages()) { final PsiElement psiRoot = getPsi(language); final PsiReference reference = SharedPsiElementImplUtil.findReferenceAt(psiRoot, offset, language); if (reference == null) continue; final TextRange textRange = reference .getRangeInElement() .shiftRight(reference.getElement().getTextRange().getStartOffset()); if (minRange.contains(textRange) && !textRange.contains(minRange)) { minRange = textRange; ret = reference; } } return ret; } @Override public void contentsSynchronized() { super.contentsSynchronized(); Set<Language> languages = getLanguages(); for (Iterator<Map.Entry<Language, PsiFileImpl>> iterator = myRoots.entrySet().iterator(); iterator.hasNext(); ) { Map.Entry<Language, PsiFileImpl> entry = iterator.next(); if (!languages.contains(entry.getKey())) { PsiFileImpl file = entry.getValue(); iterator.remove(); file.markInvalidated(); } } } @Override public void markInvalidated() { for (PsiFileImpl file : myRoots.values()) { file.markInvalidated(); } super.markInvalidated(); } }
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 } }
private static class PackageCache { final ConcurrentMap<Pair<String, GlobalSearchScope>, PsiJavaPackage> packageInScopeCache = ContainerUtil.newConcurrentMap(); final ConcurrentMap<String, Boolean> hasPackageInAllScopeCache = ContainerUtil.newConcurrentMap(); }
public class RefCountHolder { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.RefCountHolder"); private final PsiFile myFile; private final BidirectionalMap<PsiReference, PsiElement> myLocalRefsMap = new BidirectionalMap<PsiReference, PsiElement>(); private final Map<PsiAnchor, Boolean> myDclsUsedMap = ContainerUtil.newConcurrentMap(); private final Map<PsiReference, PsiImportStatementBase> myImportStatements = ContainerUtil.newConcurrentMap(); private final AtomicReference<ProgressIndicator> myState = new AtomicReference<ProgressIndicator>(VIRGIN); private static final ProgressIndicator VIRGIN = new DaemonProgressIndicator(); // just created or cleared private static final ProgressIndicator READY = new DaemonProgressIndicator(); private volatile ProgressIndicator analyzedUnder; private static class HolderReference extends SoftReference<RefCountHolder> { // Map holding hard references to RefCountHolder for each highlighting pass (identified by its // progress indicator) // there can be multiple passes running simultaneously (one actual and several passes just // canceled and winding down but still alive) // so there is a chance they overlap the usage of RCH // As soon as everybody finished using RCH, map become empty and the RefCountHolder is eligible // for gc private final Map<ProgressIndicator, RefCountHolder> map = new ConcurrentHashMap<ProgressIndicator, RefCountHolder>(); public HolderReference(@NotNull RefCountHolder holder) { super(holder); } private void acquire(@NotNull ProgressIndicator indicator) { RefCountHolder holder = get(); assert holder != null : "no way"; map.put(indicator, holder); holder = get(); assert holder != null : "can't be!"; } private RefCountHolder release(@NotNull ProgressIndicator indicator) { return map.remove(indicator); } } private static final Key<HolderReference> REF_COUNT_HOLDER_IN_FILE_KEY = Key.create("REF_COUNT_HOLDER_IN_FILE_KEY"); private static RefCountHolder getInstance( @NotNull PsiFile file, @NotNull ProgressIndicator indicator, boolean acquire) { HolderReference ref = file.getUserData(REF_COUNT_HOLDER_IN_FILE_KEY); RefCountHolder holder = com.intellij.reference.SoftReference.dereference(ref); if (holder == null && acquire) { holder = new RefCountHolder(file); HolderReference newRef = new HolderReference(holder); while (true) { boolean replaced = ((UserDataHolderEx) file).replace(REF_COUNT_HOLDER_IN_FILE_KEY, ref, newRef); if (replaced) { ref = newRef; break; } ref = file.getUserData(REF_COUNT_HOLDER_IN_FILE_KEY); RefCountHolder newHolder = com.intellij.reference.SoftReference.dereference(ref); if (newHolder != null) { holder = newHolder; break; } } } if (ref != null) { if (acquire) { ref.acquire(indicator); } else { ref.release(indicator); } } return holder; } @NotNull public static RefCountHolder startUsing( @NotNull PsiFile file, @NotNull ProgressIndicator indicator) { return getInstance(file, indicator, true); } @Nullable("might be gced") public static RefCountHolder endUsing( @NotNull PsiFile file, @NotNull ProgressIndicator indicator) { return getInstance(file, indicator, false); } private RefCountHolder(@NotNull PsiFile file) { myFile = file; log("c: created: ", myState.get(), " for ", file); } private void clear() { synchronized (myLocalRefsMap) { myLocalRefsMap.clear(); } myImportStatements.clear(); myDclsUsedMap.clear(); } public void registerLocallyReferenced(@NotNull PsiNamedElement result) { myDclsUsedMap.put(PsiAnchor.create(result), Boolean.TRUE); } public void registerReference( @NotNull PsiJavaReference ref, @NotNull JavaResolveResult resolveResult) { PsiElement refElement = resolveResult.getElement(); PsiFile psiFile = refElement == null ? null : refElement.getContainingFile(); if (psiFile != null) psiFile = (PsiFile) psiFile .getNavigationElement(); // look at navigation elements because all references // resolve into Cls elements when highlighting library // source if (refElement != null && psiFile != null && myFile.getViewProvider().equals(psiFile.getViewProvider())) { registerLocalRef(ref, refElement.getNavigationElement()); } PsiElement resolveScope = resolveResult.getCurrentFileResolveScope(); if (resolveScope instanceof PsiImportStatementBase) { registerImportStatement(ref, (PsiImportStatementBase) resolveScope); } } private void registerImportStatement( @NotNull PsiReference ref, @NotNull PsiImportStatementBase importStatement) { myImportStatements.put(ref, importStatement); } public boolean isRedundant(@NotNull PsiImportStatementBase importStatement) { return !myImportStatements.containsValue(importStatement); } private void registerLocalRef(@NotNull PsiReference ref, PsiElement refElement) { if (refElement instanceof PsiMethod && PsiTreeUtil.isAncestor(refElement, ref.getElement(), true)) return; // filter self-recursive calls if (refElement instanceof PsiClass && PsiTreeUtil.isAncestor(refElement, ref.getElement(), true)) return; // filter inner use of itself synchronized (myLocalRefsMap) { myLocalRefsMap.put(ref, refElement); } } private void removeInvalidRefs() { synchronized (myLocalRefsMap) { for (Iterator<PsiReference> iterator = myLocalRefsMap.keySet().iterator(); iterator.hasNext(); ) { PsiReference ref = iterator.next(); if (!ref.getElement().isValid()) { PsiElement value = myLocalRefsMap.get(ref); iterator.remove(); List<PsiReference> array = myLocalRefsMap.getKeysByValue(value); LOG.assertTrue(array != null); array.remove(ref); } } } for (Iterator<PsiReference> iterator = myImportStatements.keySet().iterator(); iterator.hasNext(); ) { PsiReference ref = iterator.next(); if (!ref.getElement().isValid()) { iterator.remove(); } } removeInvalidFrom(myDclsUsedMap.keySet()); } private static void removeInvalidFrom(@NotNull Collection<? extends PsiAnchor> collection) { for (Iterator<? extends PsiAnchor> it = collection.iterator(); it.hasNext(); ) { PsiAnchor element = it.next(); if (element.retrieve() == null) it.remove(); } } public boolean isReferenced(@NotNull PsiNamedElement element) { List<PsiReference> array; synchronized (myLocalRefsMap) { array = myLocalRefsMap.getKeysByValue(element); } if (array != null && !array.isEmpty() && !isParameterUsedRecursively(element, array)) return true; Boolean usedStatus = myDclsUsedMap.get(PsiAnchor.create(element)); return usedStatus == Boolean.TRUE; } public boolean isReferencedByMethodReference( @NotNull PsiMethod method, @NotNull LanguageLevel languageLevel) { if (!languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) return false; List<PsiReference> array; synchronized (myLocalRefsMap) { array = myLocalRefsMap.getKeysByValue(method); } if (array != null && !array.isEmpty()) { for (PsiReference reference : array) { final PsiElement element = reference.getElement(); if (element != null && element instanceof PsiMethodReferenceExpression) { return true; } } } return false; } private static boolean isParameterUsedRecursively( @NotNull PsiElement element, @NotNull List<PsiReference> array) { if (!(element instanceof PsiParameter)) return false; PsiParameter parameter = (PsiParameter) element; PsiElement scope = parameter.getDeclarationScope(); if (!(scope instanceof PsiMethod)) return false; PsiMethod method = (PsiMethod) scope; int paramIndex = ArrayUtilRt.find(method.getParameterList().getParameters(), parameter); for (PsiReference reference : array) { if (!(reference instanceof PsiElement)) return false; PsiElement argument = (PsiElement) reference; PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression) new PsiMatcherImpl(argument) .dot(PsiMatchers.hasClass(PsiReferenceExpression.class)) .parent(PsiMatchers.hasClass(PsiExpressionList.class)) .parent(PsiMatchers.hasClass(PsiMethodCallExpression.class)) .getElement(); if (methodCallExpression == null) return false; PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression(); if (method != methodExpression.resolve()) return false; PsiExpressionList argumentList = methodCallExpression.getArgumentList(); PsiExpression[] arguments = argumentList.getExpressions(); int argumentIndex = ArrayUtilRt.find(arguments, argument); if (paramIndex != argumentIndex) return false; } return true; } public boolean isReferencedForRead(@NotNull PsiVariable variable) { List<PsiReference> array; synchronized (myLocalRefsMap) { array = myLocalRefsMap.getKeysByValue(variable); } if (array == null) return false; for (PsiReference ref : array) { PsiElement refElement = ref.getElement(); if (!(refElement instanceof PsiExpression)) { // possible with incomplete code return true; } if (PsiUtil.isAccessedForReading((PsiExpression) refElement)) { if (refElement.getParent() instanceof PsiExpression && refElement.getParent().getParent() instanceof PsiExpressionStatement && PsiUtil.isAccessedForWriting((PsiExpression) refElement)) { continue; // "var++;" } return true; } } return false; } public boolean isReferencedForWrite(@NotNull PsiVariable variable) { List<PsiReference> array; synchronized (myLocalRefsMap) { array = myLocalRefsMap.getKeysByValue(variable); } if (array == null) return false; for (PsiReference ref : array) { final PsiElement refElement = ref.getElement(); if (!(refElement instanceof PsiExpression)) { // possible with incomplete code return true; } if (PsiUtil.isAccessedForWriting((PsiExpression) refElement)) { return true; } } return false; } public boolean analyze( @NotNull PsiFile file, TextRange dirtyScope, @NotNull Runnable analyze, @NotNull ProgressIndicator indicator) { ProgressIndicator old = myState.get(); if (old != VIRGIN && old != READY) return false; if (!myState.compareAndSet(old, indicator)) { log("a: failed to change ", old, "->", indicator); return false; } log("a: changed ", old, "->", indicator); analyzedUnder = null; boolean completed = false; try { if (dirtyScope != null) { if (dirtyScope.equals(file.getTextRange())) { clear(); } else { removeInvalidRefs(); } } analyze.run(); analyzedUnder = indicator; completed = true; } finally { ProgressIndicator resultState = completed ? READY : VIRGIN; boolean set = myState.compareAndSet(indicator, resultState); assert set : myState.get(); log("a: changed after analyze", indicator, "->", resultState); } return true; } private static void log(@NonNls Object... s) { // System.err.println("RFC: "+ Arrays.asList(s)); } public boolean retrieveUnusedReferencesInfo( @NotNull ProgressIndicator indicator, @NotNull Runnable analyze) { ProgressIndicator old = myState.get(); if (!myState.compareAndSet(READY, indicator)) { log("r: failed to change ", old, "->", indicator); return false; } log("r: changed ", old, "->", indicator); try { if (analyzedUnder != indicator) { return false; } analyze.run(); } finally { boolean set = myState.compareAndSet(indicator, READY); assert set : myState.get(); log("r: changed back ", indicator, "->", READY); } return true; } }
public class GitUserRegistry implements Disposable, VcsListener { private static final Logger LOG = Logger.getInstance(GitUserRegistry.class); @NotNull private final Project myProject; @NotNull private final ProjectLevelVcsManager myVcsManager; @NotNull private final VcsLogObjectsFactory myFactory; @NotNull private final Map<VirtualFile, VcsUser> myUserMap = ContainerUtil.newConcurrentMap(); public GitUserRegistry( @NotNull Project project, @NotNull ProjectLevelVcsManager vcsManager, @NotNull VcsLogObjectsFactory factory) { myProject = project; myVcsManager = vcsManager; myFactory = factory; } public void activate() { myProject .getMessageBus() .connect() .subscribe(ProjectLevelVcsManager.VCS_CONFIGURATION_CHANGED, this); directoryMappingChanged(); } @Nullable public VcsUser getUser(@NotNull VirtualFile root) { return myUserMap.get(root); } @Nullable public VcsUser getOrReadUser(@NotNull VirtualFile root) { VcsUser user = myUserMap.get(root); if (user == null) { try { user = readCurrentUser(myProject, root); if (user != null) { myUserMap.put(root, user); } } catch (VcsException e) { LOG.warn("Could not retrieve user name in " + root, e); } } return user; } @Nullable private VcsUser readCurrentUser(@NotNull Project project, @NotNull VirtualFile root) throws VcsException { String userName = GitConfigUtil.getValue(project, root, GitConfigUtil.USER_NAME); String userEmail = StringUtil.notNullize(GitConfigUtil.getValue(project, root, GitConfigUtil.USER_EMAIL)); return userName == null ? null : myFactory.createUser(userName, userEmail); } @Override public void dispose() { myUserMap.clear(); } @Override public void directoryMappingChanged() { GitVcs vcs = GitVcs.getInstance(myProject); if (vcs == null) { return; } final VirtualFile[] roots = myVcsManager.getRootsUnderVcs(vcs); final Collection<VirtualFile> rootsToCheck = ContainerUtil.filter( roots, new Condition<VirtualFile>() { @Override public boolean value(VirtualFile root) { return getUser(root) == null; } }); if (!rootsToCheck.isEmpty()) { ApplicationManager.getApplication() .executeOnPooledThread( new Runnable() { public void run() { for (VirtualFile root : rootsToCheck) { getOrReadUser(root); } } }); } } }