@Override
  public void saveAllDocuments() {
    ApplicationManager.getApplication().assertIsDispatchThread();

    myMultiCaster.beforeAllDocumentsSaving();
    if (myUnsavedDocuments.isEmpty()) return;

    final Map<Document, IOException> failedToSave = new HashMap<Document, IOException>();
    final Set<Document> vetoed = new HashSet<Document>();
    while (true) {
      int count = 0;

      for (Document document : myUnsavedDocuments) {
        if (failedToSave.containsKey(document)) continue;
        if (vetoed.contains(document)) continue;
        try {
          doSaveDocument(document);
        } catch (IOException e) {
          //noinspection ThrowableResultOfMethodCallIgnored
          failedToSave.put(document, e);
        } catch (SaveVetoException e) {
          vetoed.add(document);
        }
        count++;
      }

      if (count == 0) break;
    }

    if (!failedToSave.isEmpty()) {
      handleErrorsOnSave(failedToSave);
    }
  }
 @TestOnly
 public void dropAllUnsavedDocuments() {
   if (!ApplicationManager.getApplication().isUnitTestMode()) {
     throw new RuntimeException("This method is only for test mode!");
   }
   ApplicationManager.getApplication().assertWriteAccessAllowed();
   if (!myUnsavedDocuments.isEmpty()) {
     myUnsavedDocuments.clear();
     fireUnsavedDocumentsDropped();
   }
 }
  @Override
  public void run() {
    while (!myDisposed) {
      boolean isEmpty;
      synchronized (filesToResolve) {
        isEmpty = filesToResolve.isEmpty();
      }
      if (enableVetoes.get() > 0
          || isEmpty
          || !resolveProcess.isDone()
          || HeavyProcessLatch.INSTANCE.isRunning()
          || PsiDocumentManager.getInstance(myProject).hasUncommitedDocuments()) {
        try {
          waitForQueue();
        } catch (InterruptedException e) {
          break;
        }
        continue;
      }
      final Set<VirtualFile> files = pollFilesToResolve();
      if (files.isEmpty()) continue;

      upToDate = false;

      myApplication.invokeLater(
          () -> {
            if (!resolveProcess.isDone()) return;
            log("Started to resolve " + files.size() + " files");

            Task.Backgroundable backgroundable =
                new Task.Backgroundable(myProject, "Resolving files...", false) {
                  @Override
                  public void run(@NotNull final ProgressIndicator indicator) {
                    if (!myApplication.isDisposed()) {
                      processBatch(indicator, files);
                    }
                  }
                };
            ProgressIndicator indicator;
            if (files.size() > 1) {
              // show progress
              indicator = new BackgroundableProcessIndicator(backgroundable);
            } else {
              indicator = new MyProgress();
            }
            resolveProcess =
                ((ProgressManagerImpl) ProgressManager.getInstance())
                    .runProcessWithProgressAsynchronously(backgroundable, indicator, null);
          },
          myProject.getDisposed());

      flushLog();
    }
  }
 @Override
 public boolean canCloseProject(Project project) {
   if (!myUnsavedDocuments.isEmpty()) {
     myOnClose = true;
     try {
       saveAllDocuments();
     } finally {
       myOnClose = false;
     }
   }
   return myUnsavedDocuments.isEmpty();
 }
 // return true if continue to process sub-directories of the {@code file}, false if the file is
 // already processed
 private boolean doCountAndMarkUnresolved(
     @NotNull VirtualFile file, @NotNull Set<VirtualFile> result) {
   if (file.isDirectory()) {
     fileIsResolved.set(getAbsId(file));
     return result.add(file);
   }
   if (toResolve(file, myProject)) {
     result.add(file);
     fileIsResolved.clear(getAbsId(file));
   }
   return true;
 }
  private void doSaveDocument(@NotNull final Document document)
      throws IOException, SaveVetoException {
    VirtualFile file = getFile(document);

    if (file == null
        || file instanceof LightVirtualFile
        || file.isValid() && !isFileModified(file)) {
      removeFromUnsaved(document);
      return;
    }

    if (file.isValid() && needsRefresh(file)) {
      file.refresh(false, false);
      if (!myUnsavedDocuments.contains(document)) return;
    }

    for (FileDocumentSynchronizationVetoer vetoer :
        Extensions.getExtensions(FileDocumentSynchronizationVetoer.EP_NAME)) {
      if (!vetoer.maySaveDocument(document)) {
        throw new SaveVetoException();
      }
    }

    final AccessToken token =
        ApplicationManager.getApplication().acquireWriteActionLock(getClass());
    try {
      doSaveDocumentInWriteAction(document, file);
    } finally {
      token.finish();
    }
  }
 @NotNull
 private Set<VirtualFile> pollFilesToResolve() {
   Set<VirtualFile> set;
   synchronized (filesToResolve) {
     int queuedSize = filesToResolve.size();
     set = new LinkedHashSet<VirtualFile>(queuedSize);
     // someone might have cleared this bit to mark file as processed
     for (VirtualFile file : filesToResolve) {
       if (fileIsInQueue.clear(getAbsId(file))) {
         set.add(file);
       }
     }
     filesToResolve.clear();
   }
   return countAndMarkUnresolved(set, false);
 }
 private void resolveReference(
     @NotNull PsiReference reference, @NotNull Set<PsiElement> resolved) {
   PsiElement element = reference.resolve();
   if (element != null) {
     resolved.add(element);
   }
   refCount.incrementAndGet();
 }
  @Override
  @NotNull
  public Document[] getUnsavedDocuments() {
    if (myUnsavedDocuments.isEmpty()) {
      return Document.EMPTY_ARRAY;
    }

    List<Document> list = new ArrayList<Document>(myUnsavedDocuments);
    return list.toArray(new Document[list.size()]);
  }
  @Override
  public void saveDocument(@NotNull final Document document) {
    ApplicationManager.getApplication().assertIsDispatchThread();
    if (!myUnsavedDocuments.contains(document)) return;

    try {
      doSaveDocument(document);
    } catch (IOException e) {
      handleErrorsOnSave(Collections.singletonMap(document, e));
    } catch (SaveVetoException ignored) {
    }
  }
  private void doSaveDocumentInWriteAction(@NotNull Document document, @NotNull VirtualFile file)
      throws IOException {
    if (!file.isValid()) {
      removeFromUnsaved(document);
      return;
    }

    if (!file.equals(getFile(document))) {
      registerDocument(document, file);
    }

    if (!isSaveNeeded(document, file)) {
      if (document instanceof DocumentEx) {
        ((DocumentEx) document).setModificationStamp(file.getModificationStamp());
      }
      removeFromUnsaved(document);
      updateModifiedProperty(file);
      return;
    }

    myMultiCaster.beforeDocumentSaving(document);

    LOG.assertTrue(file.isValid());

    String text = document.getText();
    String lineSeparator = getLineSeparator(document, file);
    if (!lineSeparator.equals("\n")) {
      text = StringUtil.convertLineSeparators(text, lineSeparator);
    }

    Project project = ProjectLocator.getInstance().guessProjectForFile(file);
    LoadTextUtil.write(project, file, this, text, document.getModificationStamp());

    myUnsavedDocuments.remove(document);
    LOG.assertTrue(!myUnsavedDocuments.contains(document));
    myTrailingSpacesStripper.clearLineModificationFlags(document);
  }
  @Override
  public void reloadFromDisk(@NotNull final Document document) {
    ApplicationManager.getApplication().assertIsDispatchThread();

    final VirtualFile file = getFile(document);
    assert file != null;

    if (!fireBeforeFileContentReload(file, document)) {
      return;
    }

    final Project project = ProjectLocator.getInstance().guessProjectForFile(file);
    CommandProcessor.getInstance()
        .executeCommand(
            project,
            new Runnable() {
              @Override
              public void run() {
                ApplicationManager.getApplication()
                    .runWriteAction(
                        new ExternalChangeAction.ExternalDocumentChange(document, project) {
                          @Override
                          public void run() {
                            boolean wasWritable = document.isWritable();
                            DocumentEx documentEx = (DocumentEx) document;
                            documentEx.setReadOnly(false);
                            LoadTextUtil.setCharsetWasDetectedFromBytes(file, null);
                            documentEx.replaceText(
                                LoadTextUtil.loadText(file), file.getModificationStamp());
                            documentEx.setReadOnly(!wasWritable);
                          }
                        });
              }
            },
            UIBundle.message("file.cache.conflict.action"),
            null,
            UndoConfirmationPolicy.REQUEST_CONFIRMATION);

    myUnsavedDocuments.remove(document);

    myMultiCaster.fileContentReloaded(file, document);
  }
 @Override
 public boolean isDocumentUnsaved(@NotNull Document document) {
   return myUnsavedDocuments.contains(document);
 }
 private void removeFromUnsaved(@NotNull Document document) {
   myUnsavedDocuments.remove(document);
   fireUnsavedDocumentsDropped();
   LOG.assertTrue(!myUnsavedDocuments.contains(document));
 }
  private void processBatch(
      @NotNull final ProgressIndicator indicator, @NotNull Set<VirtualFile> files) {
    assert !myApplication.isDispatchThread();
    final int resolvedInPreviousBatch = this.resolvedInPreviousBatch;
    final int totalSize = files.size() + resolvedInPreviousBatch;
    final ConcurrentIntObjectMap<int[]> fileToForwardIds =
        ContainerUtil.createConcurrentIntObjectMap();
    final Set<VirtualFile> toProcess = Collections.synchronizedSet(files);
    indicator.setIndeterminate(false);
    ProgressIndicatorUtils.forceWriteActionPriority(indicator, (Disposable) indicator);
    long start = System.currentTimeMillis();
    Processor<VirtualFile> processor =
        file -> {
          double fraction = 1 - toProcess.size() * 1.0 / totalSize;
          indicator.setFraction(fraction);
          try {
            if (!file.isDirectory() && toResolve(file, myProject)) {
              int fileId = getAbsId(file);
              int i = totalSize - toProcess.size();
              indicator.setText(i + "/" + totalSize + ": Resolving " + file.getPresentableUrl());
              int[] forwardIds = processFile(file, fileId, indicator);
              if (forwardIds == null) {
                // queueUpdate(file);
                return false;
              }
              fileToForwardIds.put(fileId, forwardIds);
            }
            toProcess.remove(file);
            return true;
          } catch (RuntimeException e) {
            indicator.checkCanceled();
          }
          return true;
        };
    boolean success = true;
    try {
      success = processFilesConcurrently(files, indicator, processor);
    } finally {
      this.resolvedInPreviousBatch = toProcess.isEmpty() ? 0 : totalSize - toProcess.size();
      queue(toProcess, "re-added after fail. success=" + success);
      storeIds(fileToForwardIds);

      long end = System.currentTimeMillis();
      log(
          "Resolved batch of "
              + (totalSize - toProcess.size())
              + " from "
              + totalSize
              + " files in "
              + ((end - start) / 1000)
              + "sec. (Gap: "
              + storage.gap
              + ")");
      synchronized (filesToResolve) {
        upToDate = filesToResolve.isEmpty();
        log("upToDate = " + upToDate);
        if (upToDate) {
          for (Listener listener : myListeners) {
            listener.allFilesResolved();
          }
        }
      }
    }
  }