@NotNull public static DiffLog mergeTrees( @NotNull final PsiFileImpl fileImpl, @NotNull final ASTNode oldRoot, @NotNull final ASTNode newRoot, @NotNull ProgressIndicator indicator) { if (newRoot instanceof FileElement) { ((FileElement) newRoot).setCharTable(fileImpl.getTreeElement().getCharTable()); } try { newRoot.putUserData(TREE_TO_BE_REPARSED, oldRoot); if (isReplaceWholeNode(fileImpl, newRoot)) { DiffLog treeChangeEvent = replaceElementWithEvents((CompositeElement) oldRoot, (CompositeElement) newRoot); fileImpl.putUserData(TREE_DEPTH_LIMIT_EXCEEDED, Boolean.TRUE); return treeChangeEvent; } newRoot .getFirstChildNode(); // maybe reparsed in PsiBuilderImpl and have thrown exception here } catch (ReparsedSuccessfullyException e) { // reparsed in PsiBuilderImpl return e.getDiffLog(); } finally { newRoot.putUserData(TREE_TO_BE_REPARSED, null); } final ASTShallowComparator comparator = new ASTShallowComparator(indicator); final ASTStructure treeStructure = createInterruptibleASTStructure(newRoot, indicator); DiffLog diffLog = new DiffLog(); diffTrees(oldRoot, diffLog, comparator, treeStructure, indicator); return diffLog; }
@NotNull private static DiffLog makeFullParse( @NotNull PsiFileImpl fileImpl, @NotNull CharSequence newFileText, @NotNull ProgressIndicator indicator, @NotNull CharSequence lastCommittedText) { if (fileImpl instanceof PsiCodeFragment) { FileElement parent = fileImpl.getTreeElement(); final FileElement holderElement = new DummyHolder(fileImpl.getManager(), null).getTreeElement(); holderElement.rawAddChildren( fileImpl.createContentLeafElement( holderElement.getCharTable().intern(newFileText, 0, newFileText.length()))); DiffLog diffLog = new DiffLog(); diffLog.appendReplaceFileElement(parent, (FileElement) holderElement.getFirstChildNode()); return diffLog; } else { FileViewProvider viewProvider = fileImpl.getViewProvider(); viewProvider.getLanguages(); FileType fileType = viewProvider.getVirtualFile().getFileType(); String fileName = fileImpl.getName(); final LightVirtualFile lightFile = new LightVirtualFile( fileName, fileType, newFileText, viewProvider.getVirtualFile().getCharset(), fileImpl.getViewProvider().getModificationStamp()); lightFile.setOriginalFile(viewProvider.getVirtualFile()); FileViewProvider copy = viewProvider.createCopy(lightFile); if (copy.isEventSystemEnabled()) { throw new AssertionError( "Copied view provider must be non-physical for reparse to deliver correct events: " + viewProvider); } copy.getLanguages(); SingleRootFileViewProvider.doNotCheckFileSizeLimit( lightFile); // optimization: do not convert file contents to bytes to determine if we // should codeinsight it PsiFileImpl newFile = getFileCopy(fileImpl, copy); newFile.setOriginalFile(fileImpl); final FileElement newFileElement = (FileElement) newFile.getNode(); final FileElement oldFileElement = (FileElement) fileImpl.getNode(); if (!lastCommittedText.toString().equals(oldFileElement.getText())) { throw new IncorrectOperationException(); } DiffLog diffLog = mergeTrees(fileImpl, oldFileElement, newFileElement, indicator, lastCommittedText); ((PsiManagerEx) fileImpl.getManager()).getFileManager().setViewProvider(lightFile, null); return diffLog; } }
@Override @NotNull public ASTNode getNode() { ASTNode node = myNode; if (node == null) { ApplicationManager.getApplication().assertReadAccessAllowed(); PsiFileImpl file = (PsiFileImpl) getContainingFile(); synchronized (file.getStubLock()) { node = myNode; if (node == null) { NonCancelableSection criticalSection = ProgressIndicatorProvider.startNonCancelableSectionIfSupported(); try { if (!file.isValid()) throw new PsiInvalidElementAccessException(this); FileElement treeElement = file.getTreeElement(); StubTree stubTree = file.getStubTree(); if (treeElement != null) { return notBoundInExistingAst(file, treeElement, stubTree); } final FileElement fileElement = file.loadTreeElement(); node = myNode; if (node == null) { @NonNls String message = "Failed to bind stub to AST for element " + getClass() + " in " + (file.getVirtualFile() == null ? "<unknown file>" : file.getVirtualFile().getPath()) + "\nFile stub tree:\n" + (stubTree != null ? StringUtil.trimLog( ((PsiFileStubImpl) stubTree.getRoot()).printTree(), 1024) : " is null") + "\nLoaded file AST:\n" + StringUtil.trimLog(DebugUtil.treeToString(fileElement, true), 1024); throw new IllegalArgumentException(message); } } finally { criticalSection.done(); } } } } return node; }
@Override @NotNull public DiffLog reparseRange( @NotNull final PsiFile file, @NotNull TextRange changedPsiRange, @NotNull final CharSequence newFileText, @NotNull final ProgressIndicator indicator) { final PsiFileImpl fileImpl = (PsiFileImpl) file; final Couple<ASTNode> reparseableRoots = findReparseableRoots(fileImpl, changedPsiRange, newFileText); return reparseableRoots != null ? mergeTrees(fileImpl, reparseableRoots.first, reparseableRoots.second, indicator) : makeFullParse( fileImpl.getTreeElement(), newFileText, newFileText.length(), fileImpl, indicator); }
/** * This method searches ast node that could be reparsed incrementally and returns pair of target * reparseable node and new replacement node. Returns null if there is no any chance to make * incremental parsing. */ @Nullable public Couple<ASTNode> findReparseableRoots( @NotNull PsiFileImpl file, @NotNull TextRange changedPsiRange, @NotNull CharSequence newFileText) { Project project = file.getProject(); final FileElement fileElement = file.getTreeElement(); final CharTable charTable = fileElement.getCharTable(); int lengthShift = newFileText.length() - fileElement.getTextLength(); if (fileElement.getElementType() instanceof ITemplateDataElementType || isTooDeep(file)) { // unable to perform incremental reparse for template data in JSP, or in exceptionally deep // trees return null; } final ASTNode leafAtStart = fileElement.findLeafElementAt(Math.max(0, changedPsiRange.getStartOffset() - 1)); final ASTNode leafAtEnd = fileElement.findLeafElementAt( Math.min(changedPsiRange.getEndOffset(), fileElement.getTextLength() - 1)); ASTNode node = leafAtStart != null && leafAtEnd != null ? TreeUtil.findCommonParent(leafAtStart, leafAtEnd) : fileElement; Language baseLanguage = file.getViewProvider().getBaseLanguage(); while (node != null && !(node instanceof FileElement)) { IElementType elementType = node.getElementType(); if (elementType instanceof IReparseableElementType) { final TextRange textRange = node.getTextRange(); final IReparseableElementType reparseable = (IReparseableElementType) elementType; if (baseLanguage.isKindOf(reparseable.getLanguage()) && textRange.getLength() + lengthShift > 0) { final int start = textRange.getStartOffset(); final int end = start + textRange.getLength() + lengthShift; if (end > newFileText.length()) { reportInconsistentLength(file, newFileText, node, start, end); break; } CharSequence newTextStr = newFileText.subSequence(start, end); if (reparseable.isParsable(node.getTreeParent(), newTextStr, baseLanguage, project)) { ASTNode chameleon = reparseable.createNode(newTextStr); if (chameleon != null) { DummyHolder holder = DummyHolderFactory.createHolder( file.getManager(), null, node.getPsi(), charTable); holder.getTreeElement().rawAddChildren((TreeElement) chameleon); if (holder.getTextLength() != newTextStr.length()) { String details = ApplicationManager.getApplication().isInternal() ? "text=" + newTextStr + "; treeText=" + holder.getText() + ";" : ""; LOG.error("Inconsistent reparse: " + details + " type=" + elementType); } return Couple.of(node, chameleon); } } } } node = node.getTreeParent(); } return null; }