@Nullable public static PsiReference findReferenceAt( PsiElement thisElement, int offset, @Nullable Language lang) { if (thisElement == null) return null; PsiElement element = lang != null ? thisElement.getContainingFile().getViewProvider().findElementAt(offset, lang) : thisElement.findElementAt(offset); if (element == null || element instanceof OuterLanguageElement) return null; offset = thisElement.getTextRange().getStartOffset() + offset - element.getTextRange().getStartOffset(); List<PsiReference> referencesList = new ArrayList<PsiReference>(); while (element != null) { addReferences(offset, element, referencesList); offset = element.getStartOffsetInParent() + offset; if (element instanceof PsiFile) break; element = element.getParent(); } if (referencesList.isEmpty()) return null; if (referencesList.size() == 1) return referencesList.get(0); return new PsiMultiReference( referencesList.toArray(new PsiReference[referencesList.size()]), referencesList.get(referencesList.size() - 1).getElement()); }
public void replace( int psiStart, int length, @NotNull String replace, @Nullable PsiElement replacement) { // calculating fragment // minimize replace int start = 0; int end = start + length; final CharSequence chars = myPsiText.subSequence(psiStart, psiStart + length); if (StringUtil.equals(chars, replace)) return; int newStartInReplace = 0; final int replaceLength = replace.length(); while (newStartInReplace < replaceLength && start < end && replace.charAt(newStartInReplace) == chars.charAt(start)) { start++; newStartInReplace++; } int newEndInReplace = replaceLength; while (start < end && newStartInReplace < newEndInReplace && replace.charAt(newEndInReplace - 1) == chars.charAt(end - 1)) { newEndInReplace--; end--; } // increase the changed range to start and end on PSI token boundaries // this will help to survive smart pointers with the same boundaries if (replacement != null && (newStartInReplace > 0 || newEndInReplace < replaceLength)) { PsiElement startLeaf = replacement.findElementAt(newStartInReplace); PsiElement endLeaf = replacement.findElementAt(newEndInReplace - 1); if (startLeaf != null && endLeaf != null) { int leafStart = startLeaf.getTextRange().getStartOffset() - replacement.getTextRange().getStartOffset(); int leafEnd = endLeaf.getTextRange().getEndOffset() - replacement.getTextRange().getStartOffset(); start += leafStart - newStartInReplace; end += leafEnd - newEndInReplace; newStartInReplace = leafStart; newEndInReplace = leafEnd; } } // optimization: when delete fragment from the middle of the text, prefer split at the line // boundaries if (newStartInReplace == newEndInReplace && start > 0 && start < end && StringUtil.indexOf(chars, '\n', start, end) != -1) { // try to align to the line boundaries while (start > 0 && newStartInReplace > 0 && chars.charAt(start - 1) == chars.charAt(end - 1) && chars.charAt(end - 1) != '\n') { start--; end--; newStartInReplace--; newEndInReplace--; } } start += psiStart; end += psiStart; // [mike] dirty hack for xml: // make sure that deletion of <t> in: <tag><t/><tag> doesn't remove t/>< // which is perfectly valid but invalidates range markers final CharSequence charsSequence = myPsiText; while (start < charsSequence.length() && end < charsSequence.length() && start > 0 && charsSequence.subSequence(start, end).toString().endsWith("><") && charsSequence.charAt(start - 1) == '<') { start--; newStartInReplace--; end--; newEndInReplace--; } updateFragments(start, end, replace.substring(newStartInReplace, newEndInReplace)); }
@Override public void getLanguagesToInject( @NotNull MultiHostRegistrar registrar, @NotNull PsiElement context) { // check that we have angular directives indexed before injecting final Project project = context.getProject(); if (!AngularIndexUtil.hasAngularJS(project)) return; final PsiElement parent = context.getParent(); if (context instanceof XmlAttributeValueImpl && parent instanceof XmlAttribute) { final String value = context.getText(); final int start = value.startsWith("'") || value.startsWith("\"") ? 1 : 0; final int end = value.endsWith("'") || value.endsWith("\"") ? 1 : 0; final int length = value.length(); if (AngularAttributesRegistry.isAngularExpressionAttribute((XmlAttribute) parent) && length > 1) { registrar .startInjecting(AngularJSLanguage.INSTANCE) .addPlace( null, null, (PsiLanguageInjectionHost) context, new TextRange(start, length - end)) .doneInjecting(); return; } if (AngularAttributesRegistry.isJSONAttribute((XmlAttribute) parent) && length > 1) { registrar .startInjecting(JsonLanguage.INSTANCE) .addPlace( null, null, (PsiLanguageInjectionHost) context, new TextRange(start, length - end)) .doneInjecting(); return; } if (AngularAttributesRegistry.isEventAttribute( ((XmlAttribute) parent).getName(), ((XmlAttribute) parent).getProject()) && length > 1) { registrar .startInjecting(JavascriptLanguage.INSTANCE) .addPlace( null, null, (PsiLanguageInjectionHost) context, new TextRange(start, length - end)) .doneInjecting(); return; } } if (context instanceof XmlTextImpl || context instanceof XmlAttributeValueImpl) { final String start = AngularJSBracesUtil.getInjectionStart(project); final String end = AngularJSBracesUtil.getInjectionEnd(project); if (AngularJSBracesUtil.hasConflicts(start, end, context)) return; final String text = context.getText(); int startIndex; int endIndex = -1; do { startIndex = text.indexOf(start, endIndex); int afterStart = startIndex + start.length(); endIndex = startIndex >= 0 ? text.indexOf(end, afterStart) : -1; endIndex = endIndex > 0 ? endIndex : ElementManipulators.getValueTextRange(context).getEndOffset(); final PsiElement injectionCandidate = startIndex >= 0 ? context.findElementAt(startIndex) : null; if (injectionCandidate != null && injectionCandidate.getNode().getElementType() != XmlTokenType.XML_COMMENT_CHARACTERS && !(injectionCandidate instanceof OuterLanguageElement)) { if (afterStart > endIndex) { LOG.error( "Braces: " + start + "," + end + "\n" + "Text: \"" + text + "\"" + "\n" + "Interval: (" + afterStart + "," + endIndex + ")" + "\n" + "File: " + context.getContainingFile().getName() + ", language:" + context.getContainingFile().getLanguage()); } registrar .startInjecting(AngularJSLanguage.INSTANCE) .addPlace( null, null, (PsiLanguageInjectionHost) context, new TextRange(afterStart, endIndex)) .doneInjecting(); } } while (startIndex >= 0); } }