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