blob: c4f272b11fd047c25a4f69ce205eaf03d17c3485 [file] [log] [blame]
/*
* Copyright 2000-2011 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.formatting;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.ide.IdeBundle;
import com.intellij.openapi.command.undo.UndoManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.util.SequentialModalProgressTask;
import com.intellij.util.SequentialTask;
import com.intellij.util.containers.ConcurrentHashMap;
import com.intellij.util.containers.ConcurrentHashSet;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.concurrent.ConcurrentMap;
/**
* Formatting progressable task.
*
* @author Denis Zhdanov
* @since 2/10/11 3:00 PM
*/
public class FormattingProgressTask extends SequentialModalProgressTask implements FormattingProgressCallback {
/**
* Holds flag that indicates whether formatting was cancelled by end-user or not.
*/
public static final ThreadLocal<Boolean> FORMATTING_CANCELLED_FLAG = new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() {
return false;
}
};
/**
* Holds max allowed progress bar value (defined at ProgressWindow.MyDialog.initDialog()).
*/
private static final double MAX_PROGRESS_VALUE = 1;
private static final double TOTAL_WEIGHT;
static {
double weight = 0;
for (FormattingStateId state : FormattingStateId.values()) {
weight += state.getProgressWeight();
}
TOTAL_WEIGHT = weight;
}
private final ConcurrentMap<EventType, Collection<Runnable>> myCallbacks = new ConcurrentHashMap<EventType, Collection<Runnable>>();
private final WeakReference<VirtualFile> myFile;
private final WeakReference<Document> myDocument;
private final int myFileTextLength;
@NotNull
private FormattingStateId myLastState = FormattingStateId.WRAPPING_BLOCKS;
private long myDocumentModificationStampBefore = -1;
private int myBlocksToModifyNumber;
private int myModifiedBlocksNumber;
public FormattingProgressTask(@Nullable Project project, @NotNull PsiFile file, @NotNull Document document) {
super(project, getTitle(file));
myFile = new WeakReference<VirtualFile>(file.getVirtualFile());
myDocument = new WeakReference<Document>(document);
myFileTextLength = file.getTextLength();
addCallback(EventType.CANCEL, new MyCancelCallback());
}
@NotNull
private static String getTitle(@NotNull PsiFile file) {
VirtualFile virtualFile = file.getOriginalFile().getVirtualFile();
if (virtualFile == null) {
return CodeInsightBundle.message("reformat.progress.common.text");
}
else {
return CodeInsightBundle.message("reformat.progress.file.with.known.name.text", virtualFile.getName());
}
}
@Override
protected void prepare(@NotNull final SequentialTask task) {
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
@Override
public void run() {
Document document = myDocument.get();
if (document != null) {
myDocumentModificationStampBefore = document.getModificationStamp();
}
task.prepare();
}
});
}
@Override
public boolean addCallback(@NotNull EventType eventType, @NotNull Runnable callback) {
return getCallbacks(eventType).add(callback);
}
@Override
public void onSuccess() {
super.onSuccess();
for (Runnable callback : getCallbacks(EventType.SUCCESS)) {
callback.run();
}
}
@Override
public void onCancel() {
super.onCancel();
for (Runnable callback : getCallbacks(EventType.CANCEL)) {
callback.run();
}
}
private Collection<Runnable> getCallbacks(@NotNull EventType eventType) {
Collection<Runnable> result = myCallbacks.get(eventType);
if (result == null) {
Collection<Runnable> candidate = myCallbacks.putIfAbsent(eventType, result = new ConcurrentHashSet<Runnable>());
if (candidate != null) {
result = candidate;
}
}
return result;
}
@Override
public void afterWrappingBlock(@NotNull LeafBlockWrapper wrapped) {
update(FormattingStateId.WRAPPING_BLOCKS, MAX_PROGRESS_VALUE * wrapped.getEndOffset() / myFileTextLength);
}
@Override
public void afterProcessingBlock(@NotNull LeafBlockWrapper block) {
update(FormattingStateId.PROCESSING_BLOCKS, MAX_PROGRESS_VALUE * block.getEndOffset() / myFileTextLength);
}
@Override
public void beforeApplyingFormatChanges(@NotNull Collection<LeafBlockWrapper> modifiedBlocks) {
myBlocksToModifyNumber = modifiedBlocks.size();
updateTextIfNecessary(FormattingStateId.APPLYING_CHANGES);
setCancelText(IdeBundle.message("action.stop"));
}
@Override
public void afterApplyingChange(@NotNull LeafBlockWrapper block) {
if (myModifiedBlocksNumber++ >= myBlocksToModifyNumber) {
return;
}
update(FormattingStateId.APPLYING_CHANGES, MAX_PROGRESS_VALUE * myModifiedBlocksNumber / myBlocksToModifyNumber);
}
/**
* Updates current progress state if necessary.
*
* @param state current state
* @param completionRate completion rate of the given state. Is assumed to belong to <code>[0; 1]</code> interval
*/
private void update(@NotNull FormattingStateId state, double completionRate) {
ProgressIndicator indicator = getIndicator();
if (indicator == null) {
return;
}
updateTextIfNecessary(state);
myLastState = state;
double newFraction = 0;
for (FormattingStateId prevState : state.getPreviousStates()) {
newFraction += MAX_PROGRESS_VALUE * prevState.getProgressWeight() / TOTAL_WEIGHT;
}
newFraction += completionRate * state.getProgressWeight() / TOTAL_WEIGHT;
// We don't bother about imprecise floating point arithmetic here because that is enough for progress representation.
double currentFraction = indicator.getFraction();
if (newFraction - currentFraction < MAX_PROGRESS_VALUE / 100) {
return;
}
indicator.setFraction(newFraction);
}
private void updateTextIfNecessary(@NotNull FormattingStateId currentState) {
ProgressIndicator indicator = getIndicator();
if (myLastState != currentState && indicator != null) {
indicator.setText(currentState.getDescription());
}
}
private class MyCancelCallback implements Runnable {
@Override
public void run() {
FORMATTING_CANCELLED_FLAG.set(true);
VirtualFile file = myFile.get();
Document document = myDocument.get();
if (file == null || document == null || myDocumentModificationStampBefore < 0) {
return;
}
FileEditor editor = FileEditorManager.getInstance(myProject).getSelectedEditor(file);
if (editor == null) {
return;
}
UndoManager manager = UndoManager.getInstance(myProject);
while (manager.isUndoAvailable(editor) && document.getModificationStamp() != myDocumentModificationStampBefore) {
manager.undo(editor);
}
}
}
}