/* * Copyright 2003-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 jetbrains.mps.nodeEditor; import jetbrains.mps.core.aspects.behaviour.SMethodTrimmedId; import jetbrains.mps.editor.runtime.SideTransformInfoUtil; import jetbrains.mps.editor.runtime.commands.EditorComputable; import jetbrains.mps.nodeEditor.cellActions.SideTransformSubstituteInfo; import jetbrains.mps.nodeEditor.cellActions.SideTransformSubstituteInfo.Side; import jetbrains.mps.nodeEditor.cellMenu.NullSubstituteInfo; import jetbrains.mps.nodeEditor.cells.CellFinderUtil; import jetbrains.mps.nodeEditor.cells.CellFinderUtil.Finder; import jetbrains.mps.nodeEditor.cells.EditorCell_Constant; import jetbrains.mps.nodeEditor.cells.EditorCell_Label; import jetbrains.mps.nodeEditor.sidetransform.EditorCell_STHint; import jetbrains.mps.openapi.editor.EditorContext; import jetbrains.mps.openapi.editor.cells.CellAction; import jetbrains.mps.openapi.editor.cells.CellActionType; import jetbrains.mps.openapi.editor.cells.CellInfo; import jetbrains.mps.openapi.editor.cells.CellTraversalUtil; import jetbrains.mps.openapi.editor.cells.EditorCell; import jetbrains.mps.openapi.editor.cells.SubstituteAction; import jetbrains.mps.openapi.editor.cells.SubstituteInfo; import jetbrains.mps.smodel.adapter.MetaAdapterByDeclaration; import jetbrains.mps.smodel.behaviour.BHReflection; import jetbrains.mps.typesystem.inference.ITypechecking.Computation; import jetbrains.mps.typesystem.inference.TypeCheckingContext; import jetbrains.mps.typesystem.inference.TypeContextManager; import org.apache.log4j.Logger; import org.jetbrains.mps.openapi.model.SNode; import java.util.List; public class IntelligentInputUtil { private static final Logger LOG = Logger.getLogger(IntelligentInputUtil.class); public static boolean processCell(final EditorCell_Label cell, final EditorContext editorContext, final String pattern, final CellSide side) { if (pattern == null || pattern.equals("")) { return false; } EditorComputable<Boolean> sideTransformCommand = new EditorComputable<Boolean>(editorContext) { @Override protected Boolean doCompute() { return TypeContextManager.getInstance().runTypeCheckingComputation( ((EditorComponent) editorContext.getEditorComponent()).getTypecheckingContextOwner(), editorContext.getEditorComponent().getEditedNode(), new Computation<Boolean>() { @Override public Boolean compute(TypeCheckingContext context) { if (cell instanceof EditorCell_STHint) { EditorCell_STHint rtHintCell = (EditorCell_STHint) cell; return processSTHintCell(rtHintCell, editorContext, pattern); } if (side == CellSide.LEFT) { String head = "" + pattern.charAt(0); String smallPattern = pattern.substring(1); return processCellAtStart(cell, editorContext, head, smallPattern); } else { String smallPattern = pattern.substring(0, pattern.length() - 1); String tail = pattern.substring(pattern.length() - 1, pattern.length()); return processCellAtEnd(cell, editorContext, smallPattern, tail); } } }); } }; editorContext.getRepository().getModelAccess().executeCommand(sideTransformCommand); return sideTransformCommand.getResult(); } private static boolean processSTHintCell(EditorCell_STHint cell, EditorContext editorContext, String pattern) { SubstituteInfo info = cell.getSubstituteInfo(); String smallPattern = pattern.substring(0, pattern.length() - 1); String tail = "" + pattern.charAt(pattern.length() - 1); EditorCell nextCell = CellTraversalUtil.getNextLeaf(cell); while (nextCell != null && !nextCell.isSelectable()) { nextCell = CellTraversalUtil.getNextLeaf(nextCell); } if (canCompleteSmallPatternImmediately(info, pattern, "") || canCompleteSmallPatternImmediately(info, trimLeft(pattern), "")) { if (!canCompleteSmallPatternImmediately(info, pattern, "")) { pattern = trimLeft(pattern); } info.getMatchingActions(pattern, true).get(0).substitute(editorContext, pattern); return true; } else if (pattern.length() > 0 && (canCompleteSmallPatternImmediately(info, smallPattern, tail) || canCompleteSmallPatternImmediately(info, trimLeft(smallPattern), tail))) { if (!canCompleteSmallPatternImmediately(info, smallPattern, tail)) { smallPattern = trimLeft(smallPattern); } List<SubstituteAction> matchingActions = info.getMatchingActions(smallPattern, true); SubstituteAction item = matchingActions.get(0); SNode newNode = item.substitute(editorContext, smallPattern); if (newNode == null) { newNode = editorContext.getSelectedNode(); } editorContext.flushEvents(); EditorCell cellForNewNode; cellForNewNode = editorContext.getEditorComponent().findNodeCell(newNode); if (cellForNewNode != null) { EditorCell_Label target = null; EditorCell errorCell = CellFinderUtil.findChildByManyFinders(cellForNewNode, true, Finder.FIRST_ERROR); if (errorCell instanceof EditorCell_Label) { target = (EditorCell_Label) errorCell; } if (target != null) { target.changeText(tail); target.end(); if (target.isErrorState()) { target.validate(true, false); } editorContext.flushEvents(); if (editorContext.getSelectedCell() instanceof EditorCell_Label) { EditorCell_Label label = (EditorCell_Label) editorContext.getSelectedCell(); label.end(); } } } return true; } else if (info.getMatchingActions(pattern, false).isEmpty() && info.getMatchingActions(trimLeft(pattern), false).isEmpty() && nextCell != null && nextCell.isErrorState() && nextCell instanceof EditorCell_Label && ((EditorCell_Label) nextCell).isEditable()) { SideTransformInfoUtil.removeTransformInfo(cell.getSNode()); EditorCell_Label label = (EditorCell_Label) nextCell; label.changeText(pattern); label.end(); editorContext.getEditorComponent().changeSelection(label); return true; } else if (isInOneStepAmbigousPosition(info, smallPattern + tail)) { activateNodeSubstituteChooser(editorContext, cell, info); } return false; } private static boolean processCellAtEnd(EditorCell_Label cell, final EditorContext editorContext, String smallPattern, final String tail) { SubstituteInfo substituteInfo = cell.getSubstituteInfo(); if (substituteInfo == null) { substituteInfo = new NullSubstituteInfo(); } EditorCell cellForNewNode; final SNode newNode; if (cell.isValidText(smallPattern) && !"".equals(smallPattern) && substituteInfo.hasExactlyNActions(smallPattern + tail, false, 0)) { newNode = cell.getSNode(); cellForNewNode = cell; return applyRigthTransform(editorContext, smallPattern, tail, cellForNewNode, newNode); } else if (canCompleteSmallPatternImmediately(substituteInfo, smallPattern, tail) || canCompleteSmallPatternImmediately(substituteInfo, trimLeft(smallPattern), tail)) { if (!canCompleteSmallPatternImmediately(substituteInfo, smallPattern, tail) && canCompleteSmallPatternImmediately(substituteInfo, trimLeft(smallPattern), tail)) { smallPattern = trimLeft(smallPattern); } List<SubstituteAction> matchingActions = substituteInfo.getMatchingActions(smallPattern, true); SubstituteAction item = matchingActions.get(0); item.substitute(editorContext, smallPattern); newNode = editorContext.getSelectedCell().getSNode(); if (newNode == null) { return true; } cellForNewNode = editorContext.getEditorComponent().findNodeCell(newNode); EditorCell_Label errorCell = CellFinderUtil.findFirstError(cellForNewNode, true); if (errorCell != null) { editorContext.flushEvents(); EditorCell cellForNewNode1 = editorContext.getEditorComponent().findNodeCell(newNode); EditorCell_Label errorCell1 = CellFinderUtil.findFirstError(cellForNewNode1, true); errorCell1.changeText(tail); errorCell1.setCaretPosition(tail.length()); return true; } return applyRigthTransform(editorContext, smallPattern, tail, cellForNewNode, newNode); } else if (canCompleteTheWholeStringImmediately(substituteInfo, smallPattern + tail) || canCompleteTheWholeStringImmediately(substituteInfo, trimLeft(smallPattern) + tail)) { if (!canCompleteTheWholeStringImmediately(substituteInfo, smallPattern + tail) && canCompleteTheWholeStringImmediately(substituteInfo, trimLeft(smallPattern) + tail)) { smallPattern = trimLeft(smallPattern); } List<SubstituteAction> matchingActions = substituteInfo.getMatchingActions(smallPattern + tail, true); SubstituteAction item = matchingActions.get(0); item.substitute(editorContext, smallPattern + tail); return true; } else { if (isInOneStepAmbigousPosition(substituteInfo, smallPattern + tail)) { if (tryToSubstituteFirstSutable(editorContext, smallPattern + tail, substituteInfo)) { return true; } activateNodeSubstituteChooser(editorContext, cell, substituteInfo); } else if (isInAmbigousPosition(substituteInfo, smallPattern, tail)) { if (tryToSubstituteFirstSutable(editorContext, smallPattern, substituteInfo)) { return true; } cell.setText(smallPattern); activateNodeSubstituteChooser(editorContext, cell, substituteInfo); } } return false; } private static boolean applyRigthTransform(EditorContext editorContext, String smallPattern, final String tail, final EditorCell cellForNewNode, SNode newNode) { EditorCell selectableChild = CellFinderUtil.findLastSelectableLeaf(cellForNewNode, true); CellAction rtAction = selectableChild != null ? editorContext.getEditorComponent().getActionHandler().getApplicableCellAction(selectableChild, CellActionType.RIGHT_TRANSFORM) : null; boolean hasSideActions = hasSideActions(cellForNewNode, Side.RIGHT, tail); if (rtAction == null || !hasSideActions) { final CellInfo cellInfo = cellForNewNode.getCellInfo(); return putTextInErrorChild(cellInfo, smallPattern + tail, editorContext); } if (cellForNewNode instanceof EditorCell_Label) { ((EditorCell_Label) cellForNewNode).changeText(smallPattern); } rtAction.execute(editorContext); EditorCell rtHintCell = prepareSTCell(editorContext, newNode, tail); if (rtHintCell != null) { final SubstituteInfo rtSubstituteInfo = rtHintCell.getSubstituteInfo(); assert rtSubstituteInfo != null; List<SubstituteAction> rtMatchingActions = rtSubstituteInfo.getMatchingActions(tail, true); if (!canCompleteSmallPatternImmediately(rtSubstituteInfo, tail, "")) { //don't execute non-unique action on RT hint cell editorContext.flushEvents(); EditorCell_Label foundCell = prepareRTCell(editorContext, newNode, tail); if (foundCell != null) { editorContext.getEditorComponent().changeSelection(foundCell); processCell(foundCell, editorContext, tail, CellSide.RIGHT); } return true; } SubstituteAction rtItem = rtMatchingActions.get(0); SNode yetNewNode = rtItem.substitute(editorContext, tail); editorContext.flushEvents(); if (yetNewNode != null) { EditorCell yetNewNodeCell = findNodeCell(editorContext, yetNewNode); if (yetNewNodeCell == null) { LOG.warn("Unable to find editor cell for the node returned as a result of right-transform: " + yetNewNode.toString() + "(" + yetNewNode.getConcept() + "). Seems like the node is invisible in editor. Node was created by RT: " + rtItem.toString()); return true; } EditorCell errorCell = CellFinderUtil.findFirstError(yetNewNodeCell, true); if (errorCell != null) { editorContext.selectWRTFocusPolicy(errorCell); } else { editorContext.selectWRTFocusPolicy(yetNewNodeCell); } } } else { editorContext.flushEvents(); EditorCell_Label rtCell = prepareRTCell(editorContext, newNode, tail); if (rtCell != null) { processCell(rtCell, editorContext, tail, CellSide.RIGHT); } } return true; } private static boolean tryToSubstituteFirstSutable(EditorContext editorContext, String text, SubstituteInfo substituteInfo) { SNode concept = substituteInfo.getMatchingActions(text, true).get(0).getOutputConcept(); if (concept == null) { return false; } boolean property = (Boolean) BHReflection.invoke(MetaAdapterByDeclaration.getConcept(concept), SMethodTrimmedId.create("substituteInAmbigousPosition", null, "1653mnvAgq$")); if (property) { SNode outputConcept = substituteInfo.getMatchingActions(text, true).get(0).getOutputConcept(); for (SubstituteAction action : substituteInfo.getMatchingActions(text, true)) { if (outputConcept != action.getOutputConcept()) { return false; } } SubstituteAction action = substituteInfo.getMatchingActions(text, true).get(0); action.substitute(editorContext, text); return true; } return false; } private static boolean processCellAtStart(EditorCell_Label cell, final EditorContext editorContext, String head, String smallPattern) { SubstituteInfo info = cell.getSubstituteInfo(); if (info == null) { info = new NullSubstituteInfo(); } EditorCell cellForNewNode; SNode newNode; if (cell.isValidText(smallPattern) && (!"".equals(smallPattern) || cell instanceof EditorCell_Constant) && info.hasExactlyNActions(head + smallPattern, false, 0)) { newNode = cell.getSNode(); cellForNewNode = cell; return applyLeftTransform(editorContext, head, smallPattern, cellForNewNode, newNode, true); } else if (canCompleteSmallPatternImmediatelyLeft(info, head, smallPattern) && !canCompleteTheWholeStringImmediately(info, head + smallPattern)) { final SubstituteAction substituteAction = info.getMatchingActions(smallPattern, true).get(0); newNode = substituteAction.substitute(editorContext, smallPattern); if (newNode == null) { newNode = editorContext.getSelectedNode(); } if (newNode == null) { return true; } cellForNewNode = findNodeCell(editorContext, newNode); return applyLeftTransform(editorContext, head, smallPattern, cellForNewNode, newNode, false); } else if (canCompleteTheWholeStringImmediately(info, head + smallPattern)) { List<SubstituteAction> matchingActions = info.getMatchingActions(head + smallPattern, true); SubstituteAction item = matchingActions.get(0); item.substitute(editorContext, head + smallPattern); return true; } return false; } private static boolean applyLeftTransform(EditorContext editorContext, final String head, String smallPattern, final EditorCell cellForNewNode, SNode newNode, boolean sourceCellRemains) { CellAction ltAction = editorContext.getEditorComponent().getActionHandler().getApplicableCellAction(CellFinderUtil.findFirstSelectableLeaf(cellForNewNode, true), CellActionType.LEFT_TRANSFORM); boolean hasSideActions = hasSideActions(cellForNewNode, Side.LEFT, head); if (ltAction == null || !hasSideActions) { CellInfo cellInfo = cellForNewNode.getCellInfo(); if (!sourceCellRemains) { return putTextInErrorChild(cellInfo, head + smallPattern, editorContext); } else { return false; } } if (sourceCellRemains) { ((EditorCell_Label) cellForNewNode).changeText(smallPattern); } ltAction.execute(editorContext); final EditorCell_Label ltCell = prepareSTCell(editorContext, newNode, head); if (ltCell instanceof EditorCell_STHint && canCompleteSmallPatternImmediately(ltCell.getSubstituteInfo(), head, "")) { ltCell.getSubstituteInfo().getMatchingActions(head, true).get(0).substitute(editorContext, head); } return true; } public static String trimLeft(String text) { for (int i = 0; i < text.length(); i++) { if (!Character.isWhitespace(text.charAt(i))) { return text.substring(i); } } return ""; } private static boolean canCompleteSmallPatternImmediatelyLeft(SubstituteInfo info, String head, String smallPattern) { return info.hasExactlyNActions(smallPattern, true, 1) && info.hasExactlyNActions(head + smallPattern, false, 0); } private static boolean canCompleteSmallPatternImmediately(SubstituteInfo info, String smallPattern, String tail) { if ("".equals(tail)) { return info.hasExactlyNActions(smallPattern, true, 1) && info.hasExactlyNActions(smallPattern, false, 1); } // * has special meaning in completion patterns but we often want to complete multiplication // operations return info.hasExactlyNActions(smallPattern, true, 1) && (tail.equals("*") || info.hasExactlyNActions(smallPattern + tail, false, 0)); } private static boolean canCompleteTheWholeStringImmediately(SubstituteInfo info, String pattern) { return info.hasExactlyNActions(pattern, true, 1) && info.hasExactlyNActions(pattern, false, 1); } private static boolean isInAmbigousPosition(SubstituteInfo info, String smallPattern, String tail) { return info.getMatchingActions(smallPattern, true).size() > 1 && info.getMatchingActions(smallPattern + tail, false).isEmpty(); } private static boolean isInOneStepAmbigousPosition(SubstituteInfo info, String smallPattern) { return info.getMatchingActions(smallPattern, true).size() > 1 && info.getMatchingActions(smallPattern, true).size() == info.getMatchingActions(smallPattern, false).size(); } private static EditorCell_Label prepareSTCell(EditorContext context, SNode transformingNode, String textToSet) { EditorCell_Label rtCell = EditorCell_STHint.getSTHintCell(transformingNode, context.getEditorComponent()); if (rtCell == null) { EditorCell selectedCell = context.getSelectedCell(); if (selectedCell instanceof EditorCell_Label && selectedCell.isErrorState()) { rtCell = (EditorCell_Label) selectedCell; } else { return null; } } rtCell.changeText(textToSet); rtCell.end(); return rtCell; } private static EditorCell_Label prepareRTCell(EditorContext context, SNode node, String textToSet) { EditorCell root = findNodeCell(context, node); if (root == null) { return null; } return prepareSTCell(context, node, textToSet); } private static boolean putTextInErrorChild(CellInfo cellInfo, String textToSet, EditorContext editorContext) { editorContext.flushEvents(); EditorComponent component = (EditorComponent) editorContext.getEditorComponent(); EditorCell cellToSelect = cellInfo.findCell(component); if (cellToSelect != null) { EditorCell_Label label = CellFinderUtil.findFirstError(cellToSelect, true); if (label != null && label != cellToSelect && label.isEditable() && !(label instanceof EditorCell_Constant)) { label.changeText(textToSet); label.end(); return true; } } return false; } private static boolean hasSideActions(EditorCell cell, Side side, String prefix) { SubstituteInfo info = new SideTransformSubstituteInfo(cell, side); return !info.hasExactlyNActions(prefix, false, 0); } private static void activateNodeSubstituteChooser(EditorContext editorContext, EditorCell cell, SubstituteInfo info) { ((EditorComponent) editorContext.getEditorComponent()).activateNodeSubstituteChooser(cell, info, false); } private static EditorCell findNodeCell(EditorContext editorContext, SNode newNode) { return editorContext.getEditorComponent().findNodeCell(newNode); } }