/**
  * intersection may spread over several injected fragments
  *
  * @param rangeToEdit range in encoded(raw) PSI
  * @return list of ranges in encoded (raw) PSI
  */
 @Override
 @SuppressWarnings({"ConstantConditions", "unchecked"})
 @NotNull
 public List<TextRange> intersectWithAllEditableFragments(
     @NotNull PsiFile injectedPsi, @NotNull TextRange rangeToEdit) {
   Place shreds = InjectedLanguageUtil.getShreds(injectedPsi);
   if (shreds == null) return Collections.emptyList();
   Object result = null; // optimization: TextRange or ArrayList
   int count = 0;
   int offset = 0;
   for (PsiLanguageInjectionHost.Shred shred : shreds) {
     TextRange encodedRange =
         TextRange.from(
             offset + shred.getPrefix().length(), shred.getRangeInsideHost().getLength());
     TextRange intersection = encodedRange.intersection(rangeToEdit);
     if (intersection != null) {
       count++;
       if (count == 1) {
         result = intersection;
       } else if (count == 2) {
         TextRange range = (TextRange) result;
         if (range.isEmpty()) {
           result = intersection;
           count = 1;
         } else if (intersection.isEmpty()) {
           count = 1;
         } else {
           List<TextRange> list = new ArrayList<TextRange>();
           list.add(range);
           list.add(intersection);
           result = list;
         }
       } else if (intersection.isEmpty()) {
         count--;
       } else {
         ((List<TextRange>) result).add(intersection);
       }
     }
     offset +=
         shred.getPrefix().length()
             + shred.getRangeInsideHost().getLength()
             + shred.getSuffix().length();
   }
   return count == 0
       ? Collections.<TextRange>emptyList()
       : count == 1 ? Collections.singletonList((TextRange) result) : (List<TextRange>) result;
 }
  private boolean addInjectedPsiHighlights(
      @NotNull PsiFile injectedPsi,
      TextAttributes injectedAttributes,
      @NotNull Collection<HighlightInfo> outInfos,
      @NotNull ProgressIndicator progress,
      @NotNull InjectedLanguageManager injectedLanguageManager) {
    DocumentWindow documentWindow =
        (DocumentWindow) PsiDocumentManager.getInstance(myProject).getCachedDocument(injectedPsi);
    if (documentWindow == null) return true;
    Place places = InjectedLanguageUtil.getShreds(injectedPsi);
    for (PsiLanguageInjectionHost.Shred place : places) {
      PsiLanguageInjectionHost host = place.getHost();
      if (host == null) continue;
      TextRange textRange =
          place.getRangeInsideHost().shiftRight(host.getTextRange().getStartOffset());
      if (textRange.isEmpty()) continue;
      String desc = injectedPsi.getLanguage().getDisplayName() + ": " + injectedPsi.getText();
      HighlightInfo.Builder builder =
          HighlightInfo.newHighlightInfo(HighlightInfoType.INJECTED_LANGUAGE_BACKGROUND)
              .range(textRange);
      if (injectedAttributes != null && InjectedLanguageUtil.isHighlightInjectionBackground(host)) {
        builder.textAttributes(injectedAttributes);
      }
      builder.unescapedToolTip(desc);
      HighlightInfo info = builder.createUnconditionally();
      info.setFromInjection(true);
      outInfos.add(info);
    }

    HighlightInfoHolder holder = createInfoHolder(injectedPsi);
    runHighlightVisitorsForInjected(injectedPsi, holder, progress);
    for (int i = 0; i < holder.size(); i++) {
      HighlightInfo info = holder.get(i);
      final int startOffset = documentWindow.injectedToHost(info.startOffset);
      final TextRange fixedTextRange = getFixedTextRange(documentWindow, startOffset);
      addPatchedInfos(
          info, injectedPsi, documentWindow, injectedLanguageManager, fixedTextRange, outInfos);
    }
    int injectedStart = holder.size();
    highlightInjectedSyntax(injectedPsi, holder);
    for (int i = injectedStart; i < holder.size(); i++) {
      HighlightInfo info = holder.get(i);
      final int startOffset = info.startOffset;
      final TextRange fixedTextRange = getFixedTextRange(documentWindow, startOffset);
      if (fixedTextRange == null) {
        info.setFromInjection(true);
        outInfos.add(info);
      } else {
        HighlightInfo patched =
            new HighlightInfo(
                info.forcedTextAttributes,
                info.forcedTextAttributesKey,
                info.type,
                fixedTextRange.getStartOffset(),
                fixedTextRange.getEndOffset(),
                info.getDescription(),
                info.getToolTip(),
                info.type.getSeverity(null),
                info.isAfterEndOfLine(),
                null,
                false,
                0,
                info.getProblemGroup(),
                info.getGutterIconRenderer());
        patched.setFromInjection(true);
        outInfos.add(patched);
      }
    }

    if (!isDumbMode()) {
      List<HighlightInfo> todos = new ArrayList<HighlightInfo>();
      highlightTodos(
          injectedPsi,
          injectedPsi.getText(),
          0,
          injectedPsi.getTextLength(),
          progress,
          myPriorityRange,
          todos,
          todos);
      for (HighlightInfo info : todos) {
        addPatchedInfos(info, injectedPsi, documentWindow, injectedLanguageManager, null, outInfos);
      }
    }
    advanceProgress(1);
    return true;
  }
  QuickEditHandler(
      Project project,
      @NotNull PsiFile injectedFile,
      final PsiFile origFile,
      Editor editor,
      QuickEditAction action) {
    myProject = project;
    myEditor = editor;
    myAction = action;
    myOrigDocument = editor.getDocument();
    Place shreds = InjectedLanguageUtil.getShreds(injectedFile);
    FileType fileType = injectedFile.getFileType();
    Language language = injectedFile.getLanguage();
    PsiLanguageInjectionHost.Shred firstShred = ContainerUtil.getFirstItem(shreds);

    PsiFileFactory factory = PsiFileFactory.getInstance(project);
    String text = InjectedLanguageManager.getInstance(project).getUnescapedText(injectedFile);
    String newFileName =
        StringUtil.notNullize(language.getDisplayName(), "Injected")
            + " Fragment "
            + "("
            + origFile.getName()
            + ":"
            + firstShred.getHost().getTextRange().getStartOffset()
            + ")"
            + "."
            + fileType.getDefaultExtension();

    // preserve \r\n as it is done in MultiHostRegistrarImpl
    myNewFile = factory.createFileFromText(newFileName, language, text, true, false);
    myNewVirtualFile = ObjectUtils.assertNotNull((LightVirtualFile) myNewFile.getVirtualFile());
    myNewVirtualFile.setOriginalFile(origFile.getVirtualFile());

    assert myNewFile != null : "PSI file is null";
    assert myNewFile.getTextLength() == myNewVirtualFile.getContent().length()
        : "PSI / Virtual file text mismatch";

    myNewVirtualFile.setOriginalFile(origFile.getVirtualFile());
    // suppress possible errors as in injected mode
    myNewFile.putUserData(
        InjectedLanguageUtil.FRANKENSTEIN_INJECTION,
        injectedFile.getUserData(InjectedLanguageUtil.FRANKENSTEIN_INJECTION));
    myNewFile.putUserData(FileContextUtil.INJECTED_IN_ELEMENT, shreds.getHostPointer());
    myNewDocument = PsiDocumentManager.getInstance(project).getDocument(myNewFile);
    assert myNewDocument != null;
    EditorActionManager.getInstance()
        .setReadonlyFragmentModificationHandler(myNewDocument, new MyQuietHandler());
    myOrigCreationStamp =
        myOrigDocument.getModificationStamp(); // store creation stamp for UNDO tracking
    myOrigDocument.addDocumentListener(this, this);
    myNewDocument.addDocumentListener(this, this);
    EditorFactory editorFactory = ObjectUtils.assertNotNull(EditorFactory.getInstance());
    // not FileEditorManager listener because of RegExp checker and alike
    editorFactory.addEditorFactoryListener(
        new EditorFactoryAdapter() {
          int useCount;

          @Override
          public void editorCreated(@NotNull EditorFactoryEvent event) {
            if (event.getEditor().getDocument() != myNewDocument) return;
            useCount++;
          }

          @Override
          public void editorReleased(@NotNull EditorFactoryEvent event) {
            if (event.getEditor().getDocument() != myNewDocument) return;
            if (--useCount > 0) return;
            if (Boolean.TRUE.equals(
                myNewVirtualFile.getUserData(FileEditorManagerImpl.CLOSING_TO_REOPEN))) return;

            Disposer.dispose(QuickEditHandler.this);
          }
        },
        this);

    if ("JAVA".equals(firstShred.getHost().getLanguage().getID())) {
      PsiLanguageInjectionHost.Shred lastShred = ContainerUtil.getLastItem(shreds);
      myAltFullRange =
          myOrigDocument.createRangeMarker(
              firstShred.getHostRangeMarker().getStartOffset(),
              lastShred.getHostRangeMarker().getEndOffset());
      myAltFullRange.setGreedyToLeft(true);
      myAltFullRange.setGreedyToRight(true);

      initGuardedBlocks(shreds);
      myInjectedFile = null;
    } else {
      initMarkers(shreds);
      myAltFullRange = null;
      myInjectedFile = injectedFile;
    }
  }