/*
* Copyright 2003-2016 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 jetbrains.mps.typesystem.checking;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.project.IndexNotReadyException;
import jetbrains.mps.checkers.ErrorReportUtil;
import jetbrains.mps.errors.IErrorReporter;
import jetbrains.mps.errors.MessageStatus;
import jetbrains.mps.errors.QuickFixProvider;
import jetbrains.mps.errors.QuickFix_Runtime;
import jetbrains.mps.nodeEditor.EditorComponent;
import jetbrains.mps.nodeEditor.EditorMessage;
import jetbrains.mps.nodeEditor.HighlighterMessage;
import jetbrains.mps.nodeEditor.checking.BaseEditorChecker;
import jetbrains.mps.nodeEditor.checking.UpdateResult;
import jetbrains.mps.openapi.editor.EditorContext;
import jetbrains.mps.openapi.editor.cells.EditorCell;
import jetbrains.mps.openapi.editor.message.EditorMessageOwner;
import jetbrains.mps.smodel.event.SModelEvent;
import jetbrains.mps.typesystem.inference.TypeCheckingContext;
import jetbrains.mps.typesystem.inference.TypeContextManager;
import jetbrains.mps.util.Cancellable;
import jetbrains.mps.util.NameUtil;
import jetbrains.mps.util.Pair;
import jetbrains.mps.util.WeakSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.model.SNode;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* User: fyodor
* Date: 4/30/13
*/
public abstract class AbstractTypesystemEditorChecker extends BaseEditorChecker implements EditorMessageOwner {
public static boolean IMMEDIATE_QFIX_DISABLED = false;
private WeakSet<QuickFix_Runtime> myOnceExecutedQuickFixes = new WeakSet<QuickFix_Runtime>();
private boolean myHasEvents = false;
@NotNull
protected abstract UpdateResult doCreateMessages(TypeCheckingContext context, boolean wasCheckedOnce, EditorContext editorContext,
SNode rootNode, Cancellable cancellable, boolean applyQuickFixes);
@Override
public void processEvents(List<SModelEvent> events) {
myHasEvents |= !events.isEmpty();
}
@Override
public boolean needsUpdate(EditorComponent editorComponent) {
return myHasEvents;
}
@Override
public void doneUpdating() {
myHasEvents = false;
}
@NotNull
@Override
public UpdateResult update(final EditorComponent editorComponent, final boolean incremental, final boolean applyQuickFixes,
final Cancellable cancellable) {
try {
return TypeContextManager.getInstance().runTypeCheckingComputation(editorComponent.getTypecheckingContextOwner(), editorComponent.getEditedNode(),
context -> doCreateMessages(context, incremental, editorComponent.getEditorContext(), editorComponent.getEditedNode(), cancellable, applyQuickFixes));
} catch (IndexNotReadyException e) {
if (editorComponent.getNodeForTypechecking() != null) {
TypeContextManager.getInstance().acquireTypecheckingContext(editorComponent.getNodeForTypechecking(), editorComponent);
TypeContextManager.getInstance().releaseTypecheckingContext(editorComponent);
}
throw e;
}
}
protected Collection<EditorMessage> collectMessagesForNodesWithErrors(TypeCheckingContext context, final EditorContext editorContext,
boolean typesystemErrors, boolean applyQuickFixes) {
Set<EditorMessage> messages = new HashSet<EditorMessage>();
for (Pair<SNode, List<IErrorReporter>> errorNode : context.getNodesWithErrors(typesystemErrors)) {
if (!ErrorReportUtil.shouldReportError(errorNode.o1)) {
// although we might need to check IErrorReporter.getSNode(), I assume pair's first element always match that of IErrorReporter
continue;
}
List<IErrorReporter> errors = new ArrayList<IErrorReporter>(errorNode.o2);
Collections.sort(errors, new Comparator<IErrorReporter>() {
@Override
public int compare(IErrorReporter o1, IErrorReporter o2) {
return o2.getMessageStatus().compareTo(o1.getMessageStatus());
}
});
boolean instantIntentionApplied = false;
for (IErrorReporter errorReporter : errors) {
MessageStatus status = errorReporter.getMessageStatus();
String errorString = errorReporter.reportError();
HighlighterMessage message = HighlightUtil.createHighlighterMessage(
errorNode.o1,
NameUtil.capitalize(status.getPresentation()) + ": " + errorString,
errorReporter,
AbstractTypesystemEditorChecker.this
);
List<QuickFixProvider> intentionProviders = message.getIntentionProviders();
final SNode quickFixNode = errorNode.o1;
if (applyQuickFixes && !instantIntentionApplied) {
if (intentionProviders.size() == 1 &&
intentionProviders.get(0) != null &&
intentionProviders.get(0).isExecutedImmediately() &&
!AbstractTypesystemEditorChecker.IMMEDIATE_QFIX_DISABLED) {
QuickFixProvider intentionProvider = intentionProviders.get(0);
instantIntentionApplied = applyInstantIntention(editorContext, quickFixNode, intentionProvider);
if (instantIntentionApplied) {
// skip the message
continue;
}
}
}
messages.add(message);
}
}
return messages;
}
private boolean applyInstantIntention(final EditorContext editorContext, final SNode quickFixNode,
QuickFixProvider intentionProvider) {
final QuickFix_Runtime intention = intentionProvider.getQuickFix();
if (intention != null) {
if (!myOnceExecutedQuickFixes.contains(intention)) {
myOnceExecutedQuickFixes.add(intention);
// XXX why Application.invokeLater, not ThreadUtils or ModelAccess (likely, shall use SNodeReference for quickFixNode, not SNode, and resolve inside)
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
EditorCell selectedCell = editorContext.getSelectedCell();
if (selectedCell == null) {
return;
}
int caretX = selectedCell.getCaretX();
int caretY = selectedCell.getBaseline();
editorContext.getRepository().getModelAccess().executeUndoTransparentCommand(new Runnable() {
@Override
public void run() {
intention.execute(quickFixNode);
}
});
editorContext.flushEvents();
if (editorContext.getSelectionManager().getSelection() == null) {
EditorCell rootCell = editorContext.getEditorComponent().getRootCell();
EditorCell leaf = rootCell.findLeaf(caretX, caretY);
if (leaf != null) {
editorContext.getEditorComponent().changeSelection(leaf);
leaf.setCaretX(caretX);
}
}
}
}, ModalityState.NON_MODAL);
}
return true;
}
return false;
}
}