/* * Copyright 2003-2015 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.editor.runtime.cells.AbstractCellAction; import jetbrains.mps.nodeEditor.actions.CursorPositionTracker; import jetbrains.mps.nodeEditor.cells.CellFinderUtil; import jetbrains.mps.nodeEditor.cells.EditorCell_Label; import jetbrains.mps.nodeEditor.cells.GeometryUtil; import jetbrains.mps.nodeEditor.selection.NodeRangeSelection; import jetbrains.mps.openapi.editor.EditorComponent; import jetbrains.mps.openapi.editor.EditorContext; import jetbrains.mps.openapi.editor.cells.CellConditions; import jetbrains.mps.openapi.editor.cells.CellTraversalUtil; import jetbrains.mps.openapi.editor.cells.EditorCell; import jetbrains.mps.openapi.editor.cells.EditorCell_Collection; import jetbrains.mps.openapi.editor.selection.Selection; import jetbrains.mps.openapi.editor.selection.SelectionManager; import jetbrains.mps.openapi.editor.selection.SingularSelection; import jetbrains.mps.openapi.editor.selection.SingularSelection.SideSelectDirection; import jetbrains.mps.smodel.SNodeLegacy; import jetbrains.mps.smodel.SNodeUtil; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.util.Condition; import java.awt.Rectangle; import java.util.List; @SuppressWarnings("WeakerAccess") public class NodeEditorActions { public abstract static class NavigationAction extends AbstractCellAction { public NavigationAction() { super(false); } } abstract static class HorizontalNavigationAction extends NavigationAction { public EditorCell getNextAlignedLeaf(EditorCell cell) { return CellTraversalUtil.getNextLeaf(cell, c -> c.isSelectable() && !GeometryUtil.isAbove(cell, c) && !GeometryUtil.isAbove(c, cell) && GeometryUtil.isLeftToRight(cell, c)); } public EditorCell getPrevAlignedLeaf(EditorCell cell) { return CellTraversalUtil.getPrevLeaf(cell, c -> c.isSelectable() && !GeometryUtil.isAbove(cell, c) && !GeometryUtil.isAbove(c, cell) && GeometryUtil.isLeftToRight(c, cell)); } } public static class MoveLocal extends HorizontalNavigationAction { private boolean myHome; public MoveLocal(boolean home) { myHome = home; } @Override public boolean canExecute(EditorContext context) { return findTarget(context.getEditorComponent().getSelectionManager()) != null; } @Override public void execute(EditorContext context) { SelectionManager selectionManager = context.getEditorComponent().getSelectionManager(); EditorCell cell = findTarget(selectionManager); selectionManager.setSelection(cell); if (cell == null) { return; } if (myHome) { cell.home(); } else { cell.end(); } } private EditorCell findTarget(SelectionManager selectionManager) { Selection selection = selectionManager.getSelection(); if (selection == null) { return null; } List<EditorCell> selectedCells = selection.getSelectedCells(); EditorCell cell = myHome ? selectedCells.get(0) : selectedCells.get(selectedCells.size() - 1); EditorCell leaf = myHome ? getPrevAlignedLeaf(cell) : getNextAlignedLeaf(cell); if (leaf != null) { return leaf; } return myHome ? CellTraversalUtil.getPrevLeaf(cell, CellConditions.SELECTABLE) : CellTraversalUtil.getNextLeaf(cell, CellConditions.SELECTABLE); } } public static class MoveLeft extends HorizontalNavigationAction { @Override public boolean canExecute(EditorContext context) { EditorCell selection = getDeepestSelectedCell(context); return selection != null && findTarget(selection) != null; } @Override public void execute(EditorContext context) { EditorCell selection = getDeepestSelectedCell(context); EditorCell target = findTarget(selection); context.getEditorComponent().changeSelection(target); if (target instanceof EditorCell_Label) { EditorCell_Label label = (EditorCell_Label) target; label.end(); } } private EditorCell getDeepestSelectedCell(EditorContext context) { Selection deepestSelection = context.getEditorComponent().getSelectionManager().getDeepestSelection(); if (deepestSelection instanceof SingularSelection) { return ((SingularSelection) deepestSelection).getEditorCell(); } else if (deepestSelection instanceof NodeRangeSelection) { return ((NodeRangeSelection) deepestSelection).getFirstCell(); } return null; } private EditorCell findTarget(EditorCell cell) { EditorCell toLeft = getPrevAlignedLeaf(cell); if (toLeft != null) { return toLeft; } return CellTraversalUtil.getPrevLeaf(cell, CellConditions.SELECTABLE); } } public static class MoveToRoot extends NavigationAction { private boolean myHome; public MoveToRoot(boolean home) { myHome = home; } @Override public boolean canExecute(EditorContext context) { return findTarget(context.getEditorComponent().getSelectionManager()) != null; } @Override public void execute(EditorContext context) { SelectionManager selectionManager = context.getEditorComponent().getSelectionManager(); selectionManager.setSelection(findTarget(selectionManager)); selectionManager.getSelection().ensureVisible(); } private EditorCell findTarget(SelectionManager selectionManager) { Selection selection = selectionManager.getSelection(); if (selection == null) { return null; } EditorCell cell = selection.getSelectedCells().get(0); while (cell.getParent() != null) { cell = cell.getParent(); } if (cell instanceof EditorCell_Collection) { //TODO remove this cast return myHome ? CellFinderUtil.findFirstSelectableLeaf(cell) : CellFinderUtil.findLastSelectableLeaf(cell); } return cell; } } public static class MoveHome extends HorizontalNavigationAction { @Override public boolean canExecute(EditorContext context) { return findTarget(context.getEditorComponent()) != null; } @Override public void execute(EditorContext context) { EditorComponent editorComponent = context.getEditorComponent(); EditorCell target = findTarget(editorComponent); if (target instanceof EditorCell_Label) { EditorCell_Label label = (EditorCell_Label) target; label.home(); } editorComponent.changeSelection(target); } private EditorCell findTarget(EditorComponent editorComponent) { Selection selection = editorComponent.getSelectionManager().getSelection(); if (selection == null) { return null; } List<EditorCell> selectedCells = selection.getSelectedCells(); EditorCell cell = selectedCells.get(0); while (getPrevAlignedLeaf(cell) != null) { cell = getPrevAlignedLeaf(cell); } if (cell != selectedCells.get(0)) { // another cell was found to the left from the current selection return cell; } else { // there is no cells to the left from selected one. // selecting first cell (important if currently selected cell is collection) return CellTraversalUtil.getFirstLeaf(cell); } } } public static class MoveEnd extends HorizontalNavigationAction { @Override public boolean canExecute(EditorContext context) { return findTarget(context.getEditorComponent()) != null; } @Override public void execute(EditorContext context) { EditorComponent editorComponent = context.getEditorComponent(); EditorCell target = findTarget(editorComponent); if (target instanceof EditorCell_Label) { EditorCell_Label label = (EditorCell_Label) target; label.end(); } editorComponent.changeSelection(target); } private EditorCell findTarget(EditorComponent editorComponent) { Selection selection = editorComponent.getSelectionManager().getSelection(); if (selection == null) { return null; } List<EditorCell> selectedCells = selection.getSelectedCells(); EditorCell cell = selectedCells.get(selectedCells.size() - 1); while (getNextAlignedLeaf(cell) != null) { cell = getNextAlignedLeaf(cell); } if (cell != selectedCells.get(0)) { // another cell was found to the right from the current selection return cell; } else { // there is no cells to the right from selected one. // selecting last cell (important if currently selected cell is collection) return CellTraversalUtil.getLastLeaf(cell); } } } public static class MoveRight extends HorizontalNavigationAction { @Override public boolean canExecute(EditorContext context) { EditorCell selection = getDeepestSelectedCell(context); return selection != null && findTarget(selection) != null; } @Override public void execute(EditorContext context) { EditorCell selection = getDeepestSelectedCell(context); EditorCell target = findTarget(selection); context.getEditorComponent().changeSelection(target); if (target instanceof EditorCell_Label) { EditorCell_Label label = (EditorCell_Label) target; label.home(); } } private EditorCell getDeepestSelectedCell(EditorContext context) { Selection deepestSelection = context.getEditorComponent().getSelectionManager().getDeepestSelection(); if (deepestSelection instanceof SingularSelection) { return ((SingularSelection) deepestSelection).getEditorCell(); } else if (deepestSelection instanceof NodeRangeSelection) { return ((NodeRangeSelection) deepestSelection).getLastCell(); } return null; } private EditorCell findTarget(EditorCell cell) { EditorCell toRight = getNextAlignedLeaf(cell); if (toRight != null) { return toRight; } return CellTraversalUtil.getNextLeaf(cell, CellConditions.SELECTABLE); } } public static class MoveUp extends NavigationAction { private final CursorPositionTracker myPositionTracker; public MoveUp(CursorPositionTracker positionTracker) { myPositionTracker = positionTracker; } @Override public boolean canExecute(EditorContext context) { EditorCell selectedCell = getDeepestSelectedCell(context); return selectedCell != null && selectedCell.getParent() != null && findTarget(selectedCell, selectedCell.getCaretX()) != null; } @Override public void execute(EditorContext context) { EditorCell selectedCell = getDeepestSelectedCell(context); int caretX = myPositionTracker.hasPosition() ? myPositionTracker.getPosition() : selectedCell.getCaretX(); EditorCell target = findTarget(selectedCell, caretX); target.setCaretX(caretX); context.getEditorComponent().changeSelection(target); myPositionTracker.savePosition(caretX); } private EditorCell getDeepestSelectedCell(EditorContext context) { Selection deepestSelection = context.getEditorComponent().getSelectionManager().getDeepestSelection(); if (deepestSelection instanceof SingularSelection) { return ((SingularSelection) deepestSelection).getEditorCell(); } else if (deepestSelection instanceof NodeRangeSelection) { return ((NodeRangeSelection) deepestSelection).getFirstCell(); } return null; } private EditorCell findTarget(EditorCell cell, int baseX) { EditorCell bestMatch = null; Condition<EditorCell> condition = c -> c.isSelectable() && GeometryUtil.isAbove(c, cell); for (EditorCell nextCandidate = CellTraversalUtil.getPrevLeaf(cell, condition); nextCandidate != null; nextCandidate = CellTraversalUtil.getPrevLeaf(nextCandidate, condition)) { if (bestMatch != null && GeometryUtil.isAbove(nextCandidate, bestMatch)) { break; } if (bestMatch == null || GeometryUtil.getHorizontalDistance(bestMatch, baseX) > GeometryUtil.getHorizontalDistance(nextCandidate, baseX)) { bestMatch = nextCandidate; } } return bestMatch; } } public static class MoveDown extends NavigationAction { private final CursorPositionTracker myPositionTracker; public MoveDown(CursorPositionTracker positionTracker) { myPositionTracker = positionTracker; } @Override public boolean canExecute(EditorContext context) { EditorCell selectedCell = getDeepestSelectedCell(context); return selectedCell != null && findTarget(selectedCell, selectedCell.getCaretX()) != null; } @Override public void execute(EditorContext context) { EditorCell selectedCell = getDeepestSelectedCell(context); int caretX = myPositionTracker.hasPosition() ? myPositionTracker.getPosition() : selectedCell.getCaretX(); EditorCell target = findTarget(selectedCell, caretX); target.setCaretX(caretX); context.getEditorComponent().changeSelection(target); myPositionTracker.savePosition(caretX); } private EditorCell getDeepestSelectedCell(EditorContext context) { Selection deepestSelection = context.getSelectionManager().getDeepestSelection(); if (deepestSelection instanceof SingularSelection) { return ((SingularSelection) deepestSelection).getEditorCell(); } else if (deepestSelection instanceof NodeRangeSelection) { return ((NodeRangeSelection) deepestSelection).getLastCell(); } return null; } private EditorCell findTarget(EditorCell cell, int caretX) { EditorCell bestMatch = null; Condition<EditorCell> condition = c -> c.isSelectable() && GeometryUtil.isAbove(cell, c); for (EditorCell nextCandidate = CellTraversalUtil.getNextLeaf(cell, condition); nextCandidate != null; nextCandidate = CellTraversalUtil.getNextLeaf(nextCandidate, condition)) { if (bestMatch != null && GeometryUtil.isAbove(bestMatch, nextCandidate)) { break; } if (bestMatch == null || GeometryUtil.getHorizontalDistance(bestMatch, caretX) > GeometryUtil.getHorizontalDistance(nextCandidate, caretX)) { bestMatch = nextCandidate; } } return bestMatch; } } public static class MoveNext extends NavigationAction { @Override public boolean canExecute(EditorContext context) { EditorCell selection = ((jetbrains.mps.nodeEditor.EditorComponent) context.getEditorComponent()).getSelectedCell(); return selection != null && CellTraversalUtil.getNextLeaf(selection, CellConditions.EDITABLE) != null; } @Override public void execute(EditorContext context) { EditorCell selection = ((jetbrains.mps.nodeEditor.EditorComponent) context.getEditorComponent()).getSelectedCell(); if (selection == null) { return; } context.getEditorComponent().changeSelection(CellTraversalUtil.getNextLeaf(selection, CellConditions.EDITABLE)); } } public static class MovePrev extends NavigationAction { @Override public boolean canExecute(EditorContext context) { EditorCell selection = ((jetbrains.mps.nodeEditor.EditorComponent) context.getEditorComponent()).getSelectedCell(); return selection != null && CellTraversalUtil.getPrevLeaf(selection, CellConditions.EDITABLE) != null; } @Override public void execute(EditorContext context) { EditorCell selection = ((jetbrains.mps.nodeEditor.EditorComponent) context.getEditorComponent()).getSelectedCell(); if (selection == null) { return; } context.getEditorComponent().changeSelection(CellTraversalUtil.getPrevLeaf(selection, CellConditions.EDITABLE)); } } private static void navigatePage(EditorContext context, boolean isDown) { jetbrains.mps.nodeEditor.EditorComponent editor = (jetbrains.mps.nodeEditor.EditorComponent) context.getEditorComponent(); EditorCell selection = editor.getSelectedCell(); if (selection == null) { return; } Rectangle rect = editor.getVisibleRect(); int height = (int) rect.getHeight(); height = isDown ? height : -height; int caretX = selection.getCaretX(); int y = selection.getY() + (selection.getHeight() / 2); int newY = y + height; EditorCell target = editor.findCellWeak(caretX, newY); if (target == null) { target = isDown ? CellFinderUtil.findLastSelectableLeaf(editor.myRootCell) : CellFinderUtil.findFirstSelectableLeaf(editor.myRootCell); editor.changeSelection(target); } else { target.setCaretX(caretX); editor.changeSelection(target); } } public static class MovePageUp extends NavigationAction { @Override public boolean canExecute(EditorContext context) { return true; } @Override public void execute(EditorContext context) { navigatePage(context, true); } } public static class MovePageDown extends NavigationAction { @Override public boolean canExecute(EditorContext context) { return true; } @Override public void execute(EditorContext context) { navigatePage(context, false); } } public static class SelectUp extends NavigationAction { @Override public boolean canExecute(EditorContext context) { return findTarget(context.getEditorComponent().getSelectionManager()) != null; } @Override public void execute(EditorContext context) { SelectionManager selectionManager = context.getEditorComponent().getSelectionManager(); EditorCell cell = findTarget(selectionManager); selectionManager.pushSelection(selectionManager.createSelection(cell)); if (cell instanceof EditorCell_Label) { ((EditorCell_Label) cell).selectWordOrAll(); } } private EditorCell findTarget(SelectionManager selectionManager) { Selection selection = selectionManager.getSelection(); if (selection == null) { return null; } EditorCell cell = selection.getSelectedCells().get(0); if (cell instanceof EditorCell_Label && !((EditorCell_Label) cell).isEverythingSelected()) { return cell; } if (cell.getParent() == null) { return null; } while (cell.getParent() != null && cell.getParent().isTransparentCollection()) { cell = cell.getParent(); } EditorCell_Collection parent = cell.getParent(); while (parent != null) { if (parent.isSelectable()) { while (parent.getParent() != null && parent.getParent().isTransparentCollection() && parent.getParent().isSelectable()) { parent = parent.getParent(); } return parent; } parent = parent.getParent(); } return null; } } public static class SelectDown extends NavigationAction { @Override public boolean canExecute(EditorContext context) { return context.getEditorComponent().getSelectionManager().getSelectionStackSize() > 1; } @Override public void execute(EditorContext context) { context.getEditorComponent().getSelectionManager().popSelection(); } } public static class SideSelect extends NavigationAction { private CellSide mySide; protected SideSelect(CellSide side) { mySide = side; } private EditorCell getNextLeaf(EditorCell current) { if (mySide == CellSide.LEFT) { return CellTraversalUtil.getPrevLeaf(current, jetbrains.mps.openapi.editor.cells.CellConditions.SELECTABLE); } else { return CellTraversalUtil.getNextLeaf(current, jetbrains.mps.openapi.editor.cells.CellConditions.SELECTABLE); } } @Override public boolean canExecute(EditorContext context) { SelectionManager selectionManager = context.getEditorComponent().getSelectionManager(); Selection selection = selectionManager.getSelection(); if (selection instanceof SingularSelection) { SingularSelection singularSelection = (SingularSelection) selection; if (!expandSelection(singularSelection) && selectionManager.getSelectionStackSize() > 1) { return true; } EditorCell selected = singularSelection.getEditorCell(); EditorCell nextLeaf = getNextLeaf(selected); return nextLeaf != null && getCommonSelectableAncestor(selected, nextLeaf) != null; } return false; } @Override public void execute(EditorContext context) { SelectionManager selectionManager = context.getEditorComponent().getSelectionManager(); SingularSelection selection = (SingularSelection) selectionManager.getSelection(); if (!expandSelection(selection) && selectionManager.getSelectionStackSize() > 1) { selectionManager.popSelection(); return; } EditorCell selected = selection.getEditorCell(); EditorCell nextLeaf = getNextLeaf(selected); EditorCell cellToSelect = getCommonSelectableAncestor(selected, nextLeaf); Selection newSelection = selectionManager.createSelection(cellToSelect); if (newSelection instanceof SingularSelection) { ((SingularSelection) newSelection).setSideSelectDirection(mySide == CellSide.LEFT ? SideSelectDirection.LEFT : SideSelectDirection.RIGHT); } selectionManager.pushSelection(newSelection); } private boolean expandSelection(SingularSelection selection) { switch (selection.getSideSelectDirection()) { case LEFT: return mySide == CellSide.LEFT; case RIGHT: return mySide == CellSide.RIGHT; } return true; } private EditorCell getCommonSelectableAncestor(EditorCell first, EditorCell... cells) { EditorCell_Collection result = first instanceof EditorCell_Collection ? (EditorCell_Collection) first : first.getParent(); while (result != null) { if (result.isSelectable()) { boolean common = true; for (EditorCell cell : cells) { if (!result.isAncestorOf(cell) && result != cell) { common = false; break; } } if (common) { return result; } } result = result.getParent(); } return null; } } public static class EnlargeSelection extends NavigationAction { private boolean myUp; public EnlargeSelection(boolean up) { myUp = up; } @Override public boolean canExecute(EditorContext context) { // TODO: this check should be reallocated into Selection.canExecuteAction() method once it created Selection selection = context.getEditorComponent().getSelectionManager().getSelection(); if (selection instanceof NodeRangeSelection) { return true; } if (selection instanceof SingularSelection) { EditorCell editorCell = ((SingularSelection) selection).getEditorCell(); return editorCell.getSNode().getParent() != null; } return false; } @Override public void execute(final EditorContext context) { EditorComponent editorComponent = context.getEditorComponent(); SelectionManager selectionManager = editorComponent.getSelectionManager(); Selection selection = selectionManager.getSelection(); if (selection instanceof SingularSelection) { EditorCell selectedCell = ((SingularSelection) selection).getEditorCell(); SNode selectedNode = selectedCell.getSNode(); SNode topMostNodeInSingularContainment = findTopMostNodeWithSingularContainment(selectedNode); if (topMostNodeInSingularContainment != selectedNode) { EditorCell nodeCell = editorComponent.findNodeCell(topMostNodeInSingularContainment); if (nodeCell != null) { ((jetbrains.mps.nodeEditor.EditorComponent) editorComponent).pushSelection(nodeCell); editorComponent.scrollToCell(nodeCell); } } else { Selection newSelection = selectionManager.createRangeSelection(selectedNode, selectedNode); if (newSelection instanceof NodeRangeSelection && selectedCell.isBig()) { newSelection = ((NodeRangeSelection) newSelection).enlargeSelection(myUp); } if (newSelection != null) { selectionManager.pushSelection(newSelection); newSelection.ensureVisible(); } } } else if (selection instanceof NodeRangeSelection) { Selection newSelection = ((NodeRangeSelection) selection).enlargeSelection(myUp); if (newSelection != null) { selectionManager.pushSelection(newSelection); newSelection.ensureVisible(); } } } private SNode findTopMostNodeWithSingularContainment(SNode childNode) { while (childNode.getParent() != null && SNodeUtil.getLinkDeclaration_IsSingular(new SNodeLegacy(childNode).getRoleLink())) { childNode = childNode.getParent(); } return childNode; } } public static class Complete extends AbstractCellAction { @Override public boolean canExecute(EditorContext context) { EditorCell selection = context.getSelectedCell(); return selection != null && selection.getSubstituteInfo() != null; } @Override public void execute(EditorContext context) { EditorCell selection = context.getSelectedCell(); ((jetbrains.mps.nodeEditor.EditorComponent) context.getEditorComponent()).activateNodeSubstituteChooser(selection, ((selection instanceof EditorCell_Label) && ((EditorCell_Label) selection).isEverythingSelected()), false); } } public static class CompleteSmart extends AbstractCellAction { @Override public boolean canExecute(EditorContext context) { EditorCell selection = context.getSelectedCell(); return selection != null && selection.getSubstituteInfo() != null; } @Override public void execute(EditorContext context) { EditorCell selection = context.getSelectedCell(); ((jetbrains.mps.nodeEditor.EditorComponent) context.getEditorComponent()).activateNodeSubstituteChooser(selection, ((selection instanceof EditorCell_Label) && ((EditorCell_Label) selection).isEverythingSelected()), true); } } public static class ShowMessage extends AbstractCellAction { @Override public void execute(EditorContext context) { ((jetbrains.mps.nodeEditor.EditorComponent) context.getEditorComponent()).showMessageTooltip(); } } }