@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);
    }
  }