/* * 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.nodeEditor; import com.intellij.ide.CopyProvider; import com.intellij.ide.CutProvider; import com.intellij.ide.DataManager; import com.intellij.ide.PasteProvider; import com.intellij.ide.SelectInContext; import com.intellij.openapi.actionSystem.ActionManager; import com.intellij.openapi.actionSystem.ActionPlaces; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.DataProvider; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.actionSystem.KeyboardShortcut; import com.intellij.openapi.actionSystem.PlatformDataKeys; import com.intellij.openapi.actionSystem.Separator; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.colors.EditorColors; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.impl.LeftHandScrollbarLayout; import com.intellij.openapi.keymap.KeymapManager; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.wm.IdeFrame; import com.intellij.openapi.wm.IdeGlassPane; import com.intellij.openapi.wm.WindowManager; import com.intellij.openapi.wm.ex.StatusBarEx; import com.intellij.ui.ScrollPaneFactory; import com.intellij.ui.components.JBScrollBar; import com.intellij.ui.components.JBScrollPane; import com.intellij.util.ui.ButtonlessScrollBarUI; import com.intellij.util.ui.UIUtil; import jetbrains.mps.RuntimeFlags; import jetbrains.mps.classloading.ClassLoaderManager; import jetbrains.mps.classloading.MPSClassesListener; import jetbrains.mps.classloading.MPSClassesListenerAdapter; import jetbrains.mps.editor.runtime.cells.ReadOnlyUtil; import jetbrains.mps.editor.runtime.commands.EditorCommand; import jetbrains.mps.editor.runtime.commands.EditorCommandAdapter; import jetbrains.mps.editor.runtime.impl.cellActions.CellAction_CommentOrUncommentCurrentSelectedNode; import jetbrains.mps.editor.runtime.style.StyleAttributes; import jetbrains.mps.errors.IErrorReporter; import jetbrains.mps.ide.MPSCoreComponents; import jetbrains.mps.ide.ThreadUtils; import jetbrains.mps.ide.actions.MPSActions; import jetbrains.mps.ide.actions.MPSCommonDataKeys; import jetbrains.mps.ide.editor.MPSEditorDataKeys; import jetbrains.mps.ide.project.ProjectHelper; import jetbrains.mps.ide.projectView.ProjectViewSelectInProvider; import jetbrains.mps.ide.tooltips.MPSToolTipManager; import jetbrains.mps.ide.tooltips.TooltipComponent; import jetbrains.mps.lang.smodel.generator.smodelAdapter.AttributeOperations; import jetbrains.mps.logging.Logger; import jetbrains.mps.module.ReloadableModuleBase; import jetbrains.mps.nodeEditor.NodeEditorActions.CompleteSmart; import jetbrains.mps.nodeEditor.NodeEditorActions.ShowMessage; import jetbrains.mps.nodeEditor.actions.ActionHandlerImpl; import jetbrains.mps.nodeEditor.actions.CursorPositionTracker; import jetbrains.mps.nodeEditor.assist.DefaultContextAssistantManager; import jetbrains.mps.nodeEditor.assist.DisabledContextAssistantManager; import jetbrains.mps.nodeEditor.cellActions.CellAction_CopyNode; import jetbrains.mps.nodeEditor.cellActions.CellAction_CutNode; import jetbrains.mps.nodeEditor.cellActions.CellAction_PasteNode; import jetbrains.mps.nodeEditor.cellActions.CellAction_PasteNodeRelative; import jetbrains.mps.nodeEditor.cellActions.CellAction_SideTransform; import jetbrains.mps.nodeEditor.cellActions.SideTransformSubstituteInfo.Side; import jetbrains.mps.nodeEditor.cellMenu.NodeSubstituteChooser; import jetbrains.mps.nodeEditor.cellMenu.NodeSubstitutePatternEditor; import jetbrains.mps.nodeEditor.cells.APICellAdapter; import jetbrains.mps.nodeEditor.cells.CellFinderUtil; import jetbrains.mps.nodeEditor.cells.CellFinderUtil.Finder; import jetbrains.mps.nodeEditor.cells.EditorCell; import jetbrains.mps.nodeEditor.cells.EditorCell_Basic; import jetbrains.mps.nodeEditor.cells.EditorCell_Collection; import jetbrains.mps.nodeEditor.cells.EditorCell_Constant; import jetbrains.mps.nodeEditor.cells.EditorCell_Label; import jetbrains.mps.nodeEditor.cells.EditorCell_Property; import jetbrains.mps.nodeEditor.commands.CommandContextImpl; import jetbrains.mps.nodeEditor.commands.CommandContextWrapper; import jetbrains.mps.nodeEditor.configuration.EditorConfiguration; import jetbrains.mps.nodeEditor.configuration.EditorConfigurationBuilder; import jetbrains.mps.nodeEditor.folding.CallAction_ToggleCellFolding; import jetbrains.mps.nodeEditor.folding.CellAction_FoldCell; import jetbrains.mps.nodeEditor.folding.CellAction_UnfoldCell; import jetbrains.mps.nodeEditor.folding.CollapseAllCellAction; import jetbrains.mps.nodeEditor.folding.CollapseRecursivelyCellAction; import jetbrains.mps.nodeEditor.highlighter.EditorComponentCreateListener; import jetbrains.mps.nodeEditor.highlighter.EditorHighlighter; import jetbrains.mps.nodeEditor.keymaps.AWTKeymapHandler; import jetbrains.mps.nodeEditor.keymaps.KeymapHandler; import jetbrains.mps.nodeEditor.leftHighlighter.LeftEditorHighlighter; import jetbrains.mps.nodeEditor.selection.SelectionInternal; import jetbrains.mps.nodeEditor.selection.SelectionManagerImpl; import jetbrains.mps.nodeEditor.sidetransform.EditorCell_STHint; import jetbrains.mps.nodeEditor.ui.InputMethodListenerImpl; import jetbrains.mps.nodeEditor.ui.InputMethodRequestsImpl; import jetbrains.mps.nodeEditor.updater.UpdaterImpl; import jetbrains.mps.nodefs.MPSNodeVirtualFile; import jetbrains.mps.openapi.editor.ActionHandler; import jetbrains.mps.openapi.editor.assist.ContextAssistant; import jetbrains.mps.openapi.editor.assist.ContextAssistantManager; import jetbrains.mps.openapi.editor.cells.CellAction; import jetbrains.mps.openapi.editor.cells.CellActionType; import jetbrains.mps.openapi.editor.cells.CellMessagesUtil; import jetbrains.mps.openapi.editor.cells.CellTraversalUtil; import jetbrains.mps.openapi.editor.cells.EditorCellContext; import jetbrains.mps.openapi.editor.cells.KeyMapAction; import jetbrains.mps.openapi.editor.cells.SubstituteAction; import jetbrains.mps.openapi.editor.cells.SubstituteInfo; import jetbrains.mps.openapi.editor.commands.CommandContext; import jetbrains.mps.openapi.editor.message.EditorMessageOwner; import jetbrains.mps.openapi.editor.message.SimpleEditorMessage; import jetbrains.mps.openapi.editor.selection.Selection; import jetbrains.mps.openapi.editor.selection.SelectionListener; import jetbrains.mps.openapi.editor.selection.SelectionManager; import jetbrains.mps.openapi.editor.selection.SingularSelection; import jetbrains.mps.openapi.editor.style.StyleRegistry; import jetbrains.mps.openapi.editor.update.Updater; import jetbrains.mps.openapi.navigation.EditorNavigator; import jetbrains.mps.project.MPSProject; import jetbrains.mps.smodel.ModelAccess; import jetbrains.mps.smodel.ModelAccessHelper; import jetbrains.mps.typesystem.inference.DefaultTypecheckingContextOwner; import jetbrains.mps.typesystem.inference.ITypeContextOwner; import jetbrains.mps.typesystem.inference.NonReusableTypecheckingContextOwner; import jetbrains.mps.typesystem.inference.TypeCheckingContext; import jetbrains.mps.typesystem.inference.TypeContextManager; import jetbrains.mps.typesystem.inference.util.ConcurrentSubtypingCache; import jetbrains.mps.typesystem.inference.util.SubtypingCache; import jetbrains.mps.util.Computable; import jetbrains.mps.util.ComputeRunnable; import jetbrains.mps.util.Pair; import jetbrains.mps.util.annotation.ToRemove; import jetbrains.mps.workbench.ActionPlace; import jetbrains.mps.workbench.action.ActionUtils; import jetbrains.mps.workbench.action.BaseAction; import org.apache.log4j.LogManager; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SNodeReference; import org.jetbrains.mps.openapi.model.SNodeUtil; import org.jetbrains.mps.openapi.module.SRepository; import org.jetbrains.mps.util.Condition; import javax.swing.AbstractAction; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JViewport; import javax.swing.KeyStroke; import javax.swing.Scrollable; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.plaf.ScrollBarUI; import javax.swing.plaf.basic.BasicScrollBarUI; import javax.swing.text.DefaultEditorKit; import java.awt.Adjustable; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; import java.awt.FocusTraversalPolicy; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridLayout; import java.awt.Insets; import java.awt.KeyboardFocusManager; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Toolkit; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.im.InputMethodRequests; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.Stack; import java.util.TreeSet; public abstract class EditorComponent extends JComponent implements Scrollable, DataProvider, ITypeContextOwner, TooltipComponent, jetbrains.mps.openapi.editor.EditorComponent { private static final Logger LOG = Logger.wrap(LogManager.getLogger(EditorComponent.class)); public static final String EDITOR_POPUP_MENU_ACTIONS = MPSActions.EDITOR_POPUP_GROUP; private static final int SCROLL_GAP = 15; private final ClassLoaderManager myClassLoaderManager; private String myDefaultPopupGroupId = MPSActions.EDITOR_POPUP_GROUP; private InputMethodRequests myInputMethodRequests; public static void turnOnAliasingIfPossible(Graphics2D g) { if (EditorSettings.getInstance().isUseAntialiasing()) { Toolkit tk = Toolkit.getDefaultToolkit(); //noinspection HardCodedStringLiteral Map map = (Map) tk.getDesktopProperty("awt.font.desktophints"); if (map != null) { g.addRenderingHints(map); } else { g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); } g.setRenderingHint(RenderingHints.KEY_TEXT_LCD_CONTRAST, UIUtil.getLcdContrastValue()); } } private final Object myAdditionalPaintersLock = new Object(); private Map<jetbrains.mps.openapi.editor.cells.EditorCell, Boolean> myCollapseStates = new HashMap<>(); private Set<EditorCell> myBracesEnabledCells = new HashSet<>(); private CellTracker myCellTracker = new CellTracker(); private boolean myDisposed = false; // additional debugging field private StackTraceElement[] myModelDisposedStackTrace = null; private Throwable myDisposedTrace = null; private Set<AdditionalPainter> myAdditionalPainters = new TreeSet<>((o1, o2) -> { if (o1.isAbove(o2, EditorComponent.this)) { return 1; } if (o2.isAbove(o1, EditorComponent.this)) { return -1; } return o1.equals(o2) ? 0 : Integer.signum(System.identityHashCode(o1) - System.identityHashCode(o2)); }); private Map<Object, AdditionalPainter> myItemsToAdditionalPainters = new HashMap<>(); private final List<LeftMarginMouseListener> myLeftMarginPressListeners = new ArrayList<>(0); private EditorSettingsListener mySettingsListener = new EditorSettingsListener() { @Override public void settingsChanged() { getModelAccess().runReadInEDT(() -> { if (isDisposed()) { return; } rebuildEditorContent(); }); } }; private MPSClassesListener myClassesListener = new MPSClassesListenerAdapter() { @Override public void afterClassesLoaded(Set<? extends ReloadableModuleBase> modules) { getModelAccess().runReadInEDT(() -> { if (isDisposed() || isModuleDisposed() || isProjectDisposed() || isNodeDisposed()) { return; } rebuildEditorContent(); myNodeSubstituteChooser.clearContent(); }); } }; private boolean myReadOnly = false; private String myLastWrittenStatus = ""; @NotNull private final SRepository myRepository; //TODO: make @NotNull after separating UI-less logic into AbstractEditorComponent class private JScrollPane myScrollPane; //TODO: make @NotNull after separating UI-less logic into AbstractEditorComponent class private MyScrollBar myVerticalScrollBar; //TODO: make @NotNull after separating UI-less logic into AbstractEditorComponent class private JComponent myContainer; protected EditorCell myRootCell; private int myShiftX = 15; private int myShiftY = 10; private SelectionManagerImpl mySelectionManager = new SelectionManagerImpl(this); @NotNull private final CommandContextImpl myCommandContext; private final UpdaterImpl myUpdater; private Stack<KeyboardHandler> myKbdHandlersStack; private MouseListener myMouseEventHandler; private HashMap<CellActionType, CellAction> myActionMap; private NodeSubstituteChooser myNodeSubstituteChooser; private NodeInformationDialog myNodeInformationDialog; private List<EditorDisposeListener> myDisposeListeners = new ArrayList<>(); private final NodeHighlightManager myHighlightManager; private MessagesGutter myMessagesGutter; private LeftEditorHighlighter myLeftHighlighter; @Nullable protected SNode myNode; private boolean myNoVirtualFile; @Nullable protected SNodeReference myNodePointer; @NotNull private EditorContext myEditorContext; @NotNull private final EditorConfiguration myEditorConfiguration; private final EditorMessageOwner myOwner = new EditorMessageOwner() { }; private IntentionsSupport myIntentionsSupport; @SuppressWarnings({"UnusedDeclaration"}) private AutoValidator myAutoValidator; private SearchPanel mySearchPanel = null; private JPanel myUpperPanel = null; private Map<String, JComponent> myUpperComponents = new HashMap<>(); @SuppressWarnings({"UnusedDeclaration"}) private ReferenceUnderliner myReferenceUnderliner = new ReferenceUnderliner(); private BracesHighlighter myBracesHighlighter = new BracesHighlighter(this); private boolean myPopupMenuEnabled = true; private boolean myIsInFiguresHierarchy = false; private KeymapHandler<KeyEvent> myKeymapHandler = new AWTKeymapHandler(); private ActionHandler myActionHandler = new ActionHandlerImpl(this); @NotNull private final EditorHighlighter myHighlighter = new EditorHighlighter(this); @NotNull private final EditorComponentFocusTracker myFocusTracker = new EditorComponentFocusTracker(this); public EditorComponent(@NotNull SRepository repository) { this(repository, EditorConfigurationBuilder.buildDefault()); } /** * @deprecated since MPS 3.4 use {@link #EditorComponent(SRepository, EditorConfiguration)} */ @Deprecated public EditorComponent(@NotNull SRepository repository, boolean showErrorsGutter, boolean rightToLeft) { this(repository, new EditorConfigurationBuilder().showErrorsGutter(showErrorsGutter).rightToLeft(rightToLeft).build()); } /** * @deprecated since MPS 3.4 use {@link #EditorComponent(SRepository, EditorConfiguration)} */ @Deprecated protected EditorComponent(@NotNull SRepository repository, boolean showErrorsGutter, boolean rightToLeft, boolean createUI) { this(repository, new EditorConfigurationBuilder().showErrorsGutter(showErrorsGutter).rightToLeft(rightToLeft).withUI(createUI).build()); } protected EditorComponent(@NotNull SRepository repository, @NotNull EditorConfiguration configuration) { myRepository = repository; myEditorConfiguration = configuration; myCommandContext = createCommandContext(); myUpdater = createUpdater(myCommandContext); myHighlightManager = new NodeHighlightManager(this); if (ApplicationManager.getApplication() != null && ApplicationManager.getApplication().getComponent(MPSCoreComponents.class) != null) { myClassLoaderManager = ApplicationManager.getApplication().getComponent(MPSCoreComponents.class).getClassLoaderManager(); } else { myClassLoaderManager = ClassLoaderManager.getInstance(); } setLayout(new EditorComponentLayoutManager(this)); setEditorContext(null, repository); myRootCell = new EditorCell_Constant(getEditorContext(), null, ""); myRootCell.setSelectable(false); setBackground(StyleRegistry.getInstance().getEditorBackground()); setFocusCycleRoot(true); setFocusTraversalPolicy(new FocusTraversalPolicy() { @Override public Component getComponentAfter(Container aContainer, Component aComponent) { if (myIsInFiguresHierarchy) { executeComponentAction(CellActionType.NEXT); } return myIsInFiguresHierarchy ? aContainer : null; } @Override public Component getComponentBefore(Container aContainer, Component aComponent) { if (myIsInFiguresHierarchy) { executeComponentAction(CellActionType.PREV); } return myIsInFiguresHierarchy ? aContainer : null; } @Override public Component getFirstComponent(Container aContainer) { return myIsInFiguresHierarchy ? aContainer : null; } @Override public Component getLastComponent(Container aContainer) { return myIsInFiguresHierarchy ? aContainer : null; } @Override public Component getDefaultComponent(Container aContainer) { return myIsInFiguresHierarchy ? aContainer : null; } }); setFocusTraversalKeysEnabled(false); setDoubleBuffered(true); myNodeSubstituteChooser = new NodeSubstituteChooser(this); // --- keyboard handling --- myKbdHandlersStack = new Stack<>(); myKbdHandlersStack.push(new EditorComponentKeyboardHandler(myKeymapHandler)); // --- init action map -- myActionMap = new HashMap<>(); // -- navigation myActionMap.put(CellActionType.LEFT, new NodeEditorActions.MoveLeft()); myActionMap.put(CellActionType.RIGHT, new NodeEditorActions.MoveRight()); CursorPositionTracker cursorPositionTracker = new CursorPositionTracker(getEditorContext()); myActionMap.put(CellActionType.UP, new NodeEditorActions.MoveUp(cursorPositionTracker)); myActionMap.put(CellActionType.DOWN, new NodeEditorActions.MoveDown(cursorPositionTracker)); myActionMap.put(CellActionType.NEXT, new NodeEditorActions.MoveNext()); myActionMap.put(CellActionType.PREV, new NodeEditorActions.MovePrev()); myActionMap.put(CellActionType.LOCAL_HOME, new NodeEditorActions.MoveLocal(true)); myActionMap.put(CellActionType.LOCAL_END, new NodeEditorActions.MoveLocal(false)); myActionMap.put(CellActionType.ROOT_HOME, new NodeEditorActions.MoveToRoot(true)); myActionMap.put(CellActionType.ROOT_END, new NodeEditorActions.MoveToRoot(false)); myActionMap.put(CellActionType.HOME, new NodeEditorActions.MoveHome()); myActionMap.put(CellActionType.END, new NodeEditorActions.MoveEnd()); myActionMap.put(CellActionType.PAGE_DOWN, new NodeEditorActions.MovePageUp()); myActionMap.put(CellActionType.PAGE_UP, new NodeEditorActions.MovePageDown()); myActionMap.put(CellActionType.SELECT_UP, new NodeEditorActions.SelectUp()); myActionMap.put(CellActionType.SELECT_DOWN, new NodeEditorActions.SelectDown()); myActionMap.put(CellActionType.SELECT_RIGHT, new NodeEditorActions.SideSelect(CellSide.RIGHT)); myActionMap.put(CellActionType.SELECT_LEFT, new NodeEditorActions.SideSelect(CellSide.LEFT)); myActionMap.put(CellActionType.SELECT_NEXT, new NodeEditorActions.EnlargeSelection(true)); myActionMap.put(CellActionType.SELECT_PREVIOUS, new NodeEditorActions.EnlargeSelection(false)); myActionMap.put(CellActionType.COPY, new CellAction_CopyNode()); myActionMap.put(CellActionType.CUT, new CellAction_CutNode()); myActionMap.put(CellActionType.PASTE, new CellAction_PasteNode()); myActionMap.put(CellActionType.PASTE_BEFORE, new CellAction_PasteNodeRelative(true)); myActionMap.put(CellActionType.PASTE_AFTER, new CellAction_PasteNodeRelative(false)); myActionMap.put(CellActionType.FOLD, new CellAction_FoldCell()); myActionMap.put(CellActionType.UNFOLD, new CellAction_UnfoldCell()); myActionMap.put(CellActionType.FOLD_ALL, new CollapseAllCellAction(true)); myActionMap.put(CellActionType.UNFOLD_ALL, new CollapseAllCellAction(false)); myActionMap.put(CellActionType.FOLD_RECURSIVELY, new CollapseRecursivelyCellAction(true)); myActionMap.put(CellActionType.UNFOLD_RECURSIVELY, new CollapseRecursivelyCellAction(false)); myActionMap.put(CellActionType.TOGGLE_FOLDING, new CallAction_ToggleCellFolding()); myActionMap.put(CellActionType.RIGHT_TRANSFORM, new CellAction_SideTransform(Side.RIGHT)); myActionMap.put(CellActionType.LEFT_TRANSFORM, new CellAction_SideTransform(Side.LEFT)); myActionMap.put(CellActionType.COMPLETE, new NodeEditorActions.Complete()); myActionMap.put(CellActionType.COMPLETE_SMART, new CompleteSmart()); myActionMap.put(CellActionType.SHOW_MESSAGE, new ShowMessage()); myActionMap.put(CellActionType.COMMENT, new CellAction_CommentOrUncommentCurrentSelectedNode()); registerKeyboardAction(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { goToNextErrorCell(false); } }, KeyStroke.getKeyStroke("F2"), WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); registerKeyboardAction(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { goToNextErrorCell(true); } }, KeyStroke.getKeyStroke("shift F2"), WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); registerKeyboardAction(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { goToNextHighlightedCell(false); } }, KeyStroke.getKeyStroke("F3"), WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); registerKeyboardAction(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { goToNextHighlightedCell(true); } }, KeyStroke.getKeyStroke("shift F3"), WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); registerKeyboardAction(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { final jetbrains.mps.openapi.editor.cells.EditorCell cell = getSelectedCell(); if (cell == null) { return; } getModelAccess().runReadAction(() -> showPopupMenu(cell.getX(), cell.getY())); } }, KeyStroke.getKeyStroke("CONTEXT_MENU"), WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); addMouseListener(new MouseAdapter() { @Override public void mousePressed(final MouseEvent e) { if (areMouseEventsBlocked()) { return; } if (e.isPopupTrigger()) { processPopupMenu(e); } else { processMousePressed(e); } } @Override public void mouseClicked(MouseEvent e) { if (areMouseEventsBlocked()) { return; } jetbrains.mps.openapi.editor.cells.EditorCell selectedCell = getSelectedCell(); boolean inSelectedCell = selectedCell != null && myRootCell.findLeaf(e.getX(), e.getY()) == selectedCell; if (inSelectedCell) { Selection selection = getSelectionManager().getSelection(); if (selection.canExecuteAction(CellActionType.CLICK)) { selection.executeAction(CellActionType.CLICK); } else if (e.getClickCount() == 2 && selectedCell instanceof EditorCell_Label) { ((EditorCell_Label) selectedCell).selectWordOrAll(); repaintExternalComponent(); } } } @Override public void mouseReleased(MouseEvent e) { if (areMouseEventsBlocked()) { return; } if (e.isPopupTrigger()) { processPopupMenu(e); } super.mouseReleased(e); } }); addKeyListener(new KeyAdapter() { @Override public void keyPressed(final KeyEvent e) { processKeyPressed(e); } @Override public void keyTyped(KeyEvent e) { processKeyTyped(e); } @Override public void keyReleased(final KeyEvent e) { processKeyReleased(e); } }); addFocusListener(new FocusListener() { @Override public void focusGained(FocusEvent e) { if (isDisposed()) { return; } setDefaultSelection(); activateCaretBlinker(); } @Override public void focusLost(FocusEvent e) { closeSubstituteChooser(e.getOppositeComponent()); commitAllCellValues(); deActivateCaretBlinker(); } }); myAutoValidator = new AutoValidator(this); attachListeners(); enablePasteFromHistory(); if (configuration.withUI) { createUI(configuration); } } // TODO: // - extract all UI-free common logic into a super-class (AbstractEditorComponent) // - let HeadlessEditorComponent extend AbstractEditorComponent // - make this method again a part of constructor for this class private void createUI(EditorConfiguration editorConfiguration) { myVerticalScrollBar = new MyScrollBar(Adjustable.VERTICAL); myScrollPane = ScrollPaneFactory.createScrollPane(); if (editorConfiguration.rightToLeft) { myScrollPane.setLayout(new LeftHandScrollbarLayout()); } myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); myScrollPane.setVerticalScrollBar(myVerticalScrollBar); myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); myScrollPane.setViewportView(this); myScrollPane.getViewport().addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { if (!getNodeSubstituteChooser().isVisible()) { return; } Point point = getNodeSubstituteChooser().calcPatternEditorLocation(); Rectangle viewRect = getViewport().getViewRect(); if (isInsideEditor(point, viewRect)) { getNodeSubstituteChooser().moveToContextCell(); } else { deactivateSubstituteChooser(); } } private boolean isInsideEditor(Point point, Rectangle viewRect) { return isShowing() && point != null && point.getX() >= 0 && point.getX() <= getLocationOnScreen().getX() + viewRect.getX() + viewRect.getWidth() && point.getY() >= 0 && point.getY() <= getLocationOnScreen().getY() + viewRect.getY() + viewRect.getHeight() + myScrollPane.getHorizontalScrollBar().getHeight(); } }); myContainer = new JPanel() { @Override public void addNotify() { super.addNotify(); myIsInFiguresHierarchy = true; } @Override public void removeNotify() { myIsInFiguresHierarchy = false; super.removeNotify(); } }; myContainer.setMinimumSize(new Dimension(0, 0)); myContainer.setLayout(new BorderLayout()); myContainer.add(myScrollPane, BorderLayout.CENTER); myMessagesGutter = new MessagesGutter(this, editorConfiguration.rightToLeft); if (editorConfiguration.showErrorsGutter) { getVerticalScrollBar().setPersistentUI(myMessagesGutter); } else { getVerticalScrollBar().setPersistentUI(new ButtonlessScrollBarUI() { @Override public boolean alwaysShowTrack() { return true; } }); } myLeftHighlighter = new LeftEditorHighlighter(this, editorConfiguration.rightToLeft); myLeftHighlighter.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { for (LeftMarginMouseListener listener : new ArrayList<>(myLeftMarginPressListeners)) { listener.mousePressed(e, EditorComponent.this); } } @Override public void mouseReleased(MouseEvent e) { for (LeftMarginMouseListener listener : new ArrayList<>(myLeftMarginPressListeners)) { listener.mouseReleased(e, EditorComponent.this); } } @Override public void mouseClicked(MouseEvent e) { for (LeftMarginMouseListener listener : new ArrayList<>(myLeftMarginPressListeners)) { listener.mouseClicked(e, EditorComponent.this); } } }); myScrollPane.setRowHeaderView(myLeftHighlighter); myIntentionsSupport = new IntentionsSupport(this); if (MPSToolTipManager.getInstance() != null) { MPSToolTipManager.getInstance().registerComponent(this); } getSelectionManager().addSelectionListener(new SelectionListener() { @Override public void selectionChanged(jetbrains.mps.openapi.editor.EditorComponent editorComponent, Selection oldSelection, Selection newSelection) { if (oldSelection == newSelection) { return; } deactivateSubstituteChooser(); updateStatusBarMessage(); if (oldSelection != null) { for (jetbrains.mps.openapi.editor.cells.EditorCell editorCell : oldSelection.getSelectedCells()) { repaint(editorCell); } } if (newSelection != null) { for (jetbrains.mps.openapi.editor.cells.EditorCell editorCell : newSelection.getSelectedCells()) { repaint(editorCell); } } myLeftHighlighter.repaint(); } }); } boolean hasUI() { return myEditorConfiguration.withUI; } private void enablePasteFromHistory() { // Registering DefaultEditorKit.pasteAction in the action map enables 'Paste from History' getActionMap().put(DefaultEditorKit.pasteAction, new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { performPaste(); } }); } protected UpdaterImpl createUpdater(CommandContextImpl commandContext) { return new UpdaterImpl(this, commandContext); } protected CommandContextImpl createCommandContext() { return new CommandContextImpl(this); } protected void attachListeners() { EditorSettings.getInstance().addEditorSettingsListener(mySettingsListener); myClassLoaderManager.addClassesHandler(myClassesListener); } protected void notifyCreation() { jetbrains.mps.project.Project project = ProjectHelper.getProject(myRepository); if (project == null) { return; } Project ideaProject = ProjectHelper.toIdeaProject(project); if (ideaProject == null) { return; } EditorComponentCreateListener listener = ideaProject.getMessageBus().syncPublisher(EditorComponentCreateListener.EDITOR_COMPONENT_CREATION); listener.editorComponentCreated(EditorComponent.this); } protected void notifyDisposal() { jetbrains.mps.project.Project project = ProjectHelper.getProject(myRepository); if (project == null) { return; } if (project.isDisposed()) { LOG.error("Trying to notify disposal of EditorComponent related to disposed project. This may cause memory leaks."); return; } Project ideaProject = ProjectHelper.toIdeaProject(project); if (ideaProject == null) { return; } EditorComponentCreateListener listener = ideaProject.getMessageBus().syncPublisher(EditorComponentCreateListener.EDITOR_COMPONENT_CREATION); listener.editorComponentDisposed(this); } public boolean onEscape() { return false; } /** * From now on only NodeEditorComponent has virtual file. * This method will be removed in the next release. * * @param noVirtualFile * @deprecated since MPS 3.4 */ @Deprecated public void setNoVirtualFile(boolean noVirtualFile) { } public int getShiftX() { return myShiftX; } public JViewport getViewport() { assert hasUI(); return myScrollPane.getViewport(); } Point getViewPosition() { return hasUI() ? getViewport().getViewPosition() : new Point(0, 0); } void setViewPosition(Point point) { if (!hasUI()) { return; } getViewport().setViewPosition(point); } @NotNull public MyScrollBar getVerticalScrollBar() { assert hasUI(); return myVerticalScrollBar; } @Override public SNode getSelectedNode() { jetbrains.mps.openapi.editor.cells.EditorCell selectedCell = getSelectedCell(); if (selectedCell == null) { return null; } return selectedCell.getSNode(); } @Override public List<SNode> getSelectedNodes() { Selection selection = mySelectionManager.getSelection(); return selection != null ? selection.getSelectedNodes() : Collections.emptyList(); } public String[] getEditorHintsForNode(SNode node) { jetbrains.mps.openapi.editor.cells.EditorCell nodeCell = findNodeCell(node); if (nodeCell != null) { EditorCellContext cellContext = nodeCell.getCellContext(); if (cellContext == null) { return null; } final Collection<String> nodeContextHints = cellContext.getHints(); return nodeContextHints.toArray(new String[nodeContextHints.size()]); } return null; } public EditorMessageOwner getHighlightMessagesOwner() { return myOwner; } private void goToNextErrorCell(boolean backwards) { if (getSelectedCell() == null) { return; } new CellNavigator(this) { @Override boolean isSuitableCell(jetbrains.mps.openapi.editor.cells.EditorCell cell) { return CellMessagesUtil.hasErrorMessages(cell); } }.goToNextCell(backwards); } private void goToNextHighlightedCell(boolean backwards) { if (getSelectedCell() == null) { return; } new CellNavigator(this) { @Override boolean isSuitableCell(jetbrains.mps.openapi.editor.cells.EditorCell cell) { for (SimpleEditorMessage m : getHighlightManager().getMessagesFor(cell.getSNode())) { if (m.getOwner() == getHighlightMessagesOwner()) { return true; } } return false; } }.goToNextCell(backwards); } @Override public SNode getEditedNode() { return myNode; } /** * From now on only NodeEditorComponent has virtual file. * This method will be removed in the next release. * * @deprecated since MPS 3.4 */ @Deprecated @Nullable public MPSNodeVirtualFile getVirtualFile() { return null; } @Override public void touch() { } @Override public SNodeReference getEditedNodePointer() { return myNodePointer; } @Override public String getMPSTooltipText(final MouseEvent event) { return ModelAccess.instance().tryRead(new Computable<String>() { @Override public String compute() { if (isDisposed()) { return null; } jetbrains.mps.openapi.editor.cells.EditorCell cell = myRootCell.findLeaf(event.getX(), event.getY()); if (cell == null) { return null; } return getMessagesTextFor(cell); } }); } @Override public Point getToolTipLocation(final MouseEvent event) { return ModelAccess.instance().tryRead(new Computable<Point>() { @Override public Point compute() { if (isDisposed()) { return null; } jetbrains.mps.openapi.editor.cells.EditorCell cell = myRootCell.findLeaf(event.getX(), event.getY()); if (cell == null) { return null; } if (getMessagesTextFor(cell) != null) { return new Point(event.getX(), event.getY()); } else { return null; } } }); } public void updateStatusBarMessage() { if (!isFocusOwner()) { return; } getModelAccess().runReadInEDT(new Runnable() { @Override public void run() { if (!isFocusOwner() || getCurrentProject() == null || isProjectDisposed()) { return; } jetbrains.mps.openapi.editor.cells.EditorCell selection = getSelectedCell(); String info = ""; if (selection != null) { HighlighterMessage message = getHighlighterMessageFor(selection); if (message != null) { info = message.getMessage(); } } jetbrains.mps.project.Project project = getCurrentProject(); IdeFrame ideFrame = WindowManager.getInstance().getIdeFrame(ProjectHelper.toIdeaProject(project)); StatusBarEx statusBar = (StatusBarEx) ideFrame.getStatusBar(); //current info is significant or the editor removes its own message if (!info.equals("") || myLastWrittenStatus.equals(statusBar.getInfo())) { statusBar.setInfo(info); if (!info.equals("")) { myLastWrittenStatus = info; } } } }); } @Override public TypeCheckingContext createTypecheckingContext(SNode sNode, TypeContextManager typeContextManager) { return (new DefaultTypecheckingContextOwner()).createTypecheckingContext(sNode, typeContextManager); } @Override public boolean reuseTypecheckingContext() { return true; } @Override public SubtypingCache createSubtypingCache() { return new ConcurrentSubtypingCache(); } public String getMessagesTextFor(jetbrains.mps.openapi.editor.cells.EditorCell cell) { List<HighlighterMessage> messages = getHighlighterMessagesFor(cell); if (messages.isEmpty()) { return null; } StringBuilder result = new StringBuilder(); for (ListIterator<HighlighterMessage> it = messages.listIterator(messages.size()); it.hasPrevious(); ) { SimpleEditorMessage message = it.previous(); if (result.length() != 0) { result.append("\n"); } result.append(message.getMessage()); } return result.toString(); } @NotNull private List<HighlighterMessage> getHighlighterMessagesFor(jetbrains.mps.openapi.editor.cells.EditorCell cell) { jetbrains.mps.openapi.editor.cells.EditorCell parent = cell; while (parent != null) { if (cell.getBottom() < parent.getBottom() && parent.getSNode() != cell.getSNode()) { return Collections.emptyList(); } List<HighlighterMessage> messages = CellMessagesUtil.getMessages(parent, HighlighterMessage.class); if (!messages.isEmpty()) { return messages; } parent = parent.getParent(); } return Collections.emptyList(); } private HighlighterMessage getHighlighterMessageFor(jetbrains.mps.openapi.editor.cells.EditorCell cell) { List<HighlighterMessage> messages = getHighlighterMessagesFor(cell); ListIterator<HighlighterMessage> it = messages.listIterator(messages.size()); return it.hasPrevious() ? it.previous() : null; } @Nullable public IErrorReporter getErrorReporterFor(jetbrains.mps.openapi.editor.cells.EditorCell cell) { HighlighterMessage message = getHighlighterMessageFor(cell); if (message == null) { return null; } return message.getErrorReporter(); } public void showMessageTooltip() { jetbrains.mps.openapi.editor.cells.EditorCell cell = getSelectedCell(); if (cell == null) { return; } String text = getMessagesTextFor(cell); Point point = new Point(cell.getX(), cell.getY() + cell.getHeight()); if (MPSToolTipManager.getInstance() != null) { MPSToolTipManager.getInstance().showToolTip(text, this, point); } } public void hideMessageToolTip() { if (MPSToolTipManager.getInstance() != null) { MPSToolTipManager.getInstance().hideToolTip(); } } protected boolean notifiesCreation() { return false; } public void editNode(final SNode node) { if (isDisposed()) { return; } clearModelDisposedTrace(); getModelAccess().runReadAction(new Runnable() { @Override public void run() { if (node != null) { assert node.getModel() != null : "Can't edit a node that is not registered in a model"; assert SNodeUtil.isAccessible(node, myRepository) : "editNode() accepts nodes from its own repository only (model = " + node.getModel() + ", repository = " + node.getModel().getRepository() + ")"; } if (myNode != null && notifiesCreation()) { notifyDisposal(); } final boolean needNewTypecheckingContext = updateContainingRoot(node); if (needNewTypecheckingContext) { releaseTypeCheckingContext(); } myNode = node; if (myNode != null) { myNodePointer = myNode.getReference(); SModel model = node.getModel(); assert model != null : "Can't edit a node that is not registered in a model"; setEditorContext(model, myRepository); myReadOnly = model.isReadOnly(); } else { myNodePointer = null; setEditorContext(null, myRepository); myReadOnly = true; } myCommandContext.updateContextNode(); if (needNewTypecheckingContext) { acquireTypeCheckingContext(); } rebuildEditorContent(); if (myNode != null && notifiesCreation()) { notifyCreation(); } } }); } public void addAdditionalPainter(AdditionalPainter additionalPainter) { synchronized (myAdditionalPaintersLock) { if (!myAdditionalPainters.contains(additionalPainter)) { myAdditionalPainters.add(additionalPainter); myItemsToAdditionalPainters.put(additionalPainter.getItem(), additionalPainter); additionalPainter.afterAdding(this); } } } public void removeAdditionalPainter(AdditionalPainter additionalPainter) { synchronized (myAdditionalPaintersLock) { if (myAdditionalPainters.contains(additionalPainter)) { additionalPainter.beforeRemoval(this); myAdditionalPainters.remove(additionalPainter); myItemsToAdditionalPainters.remove(additionalPainter.getItem()); } } } public void removeAdditionalPainterByItem(Object item) { synchronized (myAdditionalPaintersLock) { AdditionalPainter additionalPainter = myItemsToAdditionalPainters.get(item); if (additionalPainter != null) { additionalPainter.beforeRemoval(this); myItemsToAdditionalPainters.remove(item); myAdditionalPainters.remove(additionalPainter); } } } public Color getAdditionalCellFontColor(@NotNull EditorCell_Label cell) { synchronized (myAdditionalPaintersLock) { for (AdditionalPainter additionalPainter : myAdditionalPainters) { Rectangle coverageArea = additionalPainter.getCoverageArea(this); if (coverageArea != null) { if (coverageArea.contains(cell.getX(), cell.getY(), cell.getWidth(), cell.getHeight())) { Color color = additionalPainter.getCellsFontColor(cell); if (color != null) { return color; } } } } return null; } } public AdditionalPainter getAdditionalPainterByItem(Object item) { synchronized (myAdditionalPaintersLock) { return myItemsToAdditionalPainters.get(item); } } public List<AdditionalPainter> getAdditionalPainters() { List<AdditionalPainter> result; synchronized (myAdditionalPaintersLock) { result = new ArrayList<>(myAdditionalPainters); } return result; } // TODO pool this method up to NodeEditorComponent @NotNull public MessagesGutter getMessagesGutter() { assert hasUI(); return myMessagesGutter; } @NotNull public LeftEditorHighlighter getLeftEditorHighlighter() { assert hasUI(); return myLeftHighlighter; } @NotNull public SearchPanel getSearchPanel() { assert hasUI(); if (mySearchPanel == null) { mySearchPanel = new SearchPanel(this); } return mySearchPanel; } public boolean isSearchPanelVisible() { return mySearchPanel != null && mySearchPanel.isVisible(); } public JPanel getUpperPanel() { assert hasUI(); if (myUpperPanel == null) { myUpperPanel = new JPanel(); myUpperPanel.setLayout(new GridLayout(0, 1)); myContainer.add(myUpperPanel, BorderLayout.NORTH); } return myUpperPanel; } public void addUpperComponent(JComponent component) { getUpperPanel().add(component); } public void addUpperComponent(JComponent component, String id) { getUpperPanel().add(component); myUpperComponents.put(id, component); } public void removeUpperComponent(JComponent component) { if (myUpperPanel == null) { return; } getUpperPanel().remove(component); for (String key : new HashSet<>(myUpperComponents.keySet())) { if (component == myUpperComponents) { myUpperComponents.remove(key); } } } public void removeUpperComponent(String id) { JComponent component = myUpperComponents.get(id); if (component != null) { removeUpperComponent(component); } } protected Set<SimpleEditorMessage> getMessages() { return new LinkedHashSet<>(myHighlightManager.getMessages()); } private EditorCell_WithComponent findCellForComponent(Component component, jetbrains.mps.openapi.editor.cells.EditorCell root) { if (root instanceof EditorCell_WithComponent && ((EditorCell_WithComponent) root).getComponent() == component) { return (EditorCell_WithComponent) root; } if (root instanceof EditorCell_Collection) { EditorCell_Collection collection = (EditorCell_Collection) root; for (jetbrains.mps.openapi.editor.cells.EditorCell cell : collection) { EditorCell_WithComponent result = findCellForComponent(component, cell); if (result != null) { return result; } } } return null; } private void processPopupMenu(final MouseEvent e) { getModelAccess().runReadAction(() -> showPopupMenu(e)); } private void showPopupMenu(MouseEvent e) { showPopupMenu(e.getX(), e.getY()); } private void showPopupMenu(int x, int y) { if (!myPopupMenuEnabled) { return; } DefaultActionGroup baseGroup = ActionUtils.getDefaultGroup(myDefaultPopupGroupId); if (baseGroup == null) { return; } baseGroup.setPopup(false); DefaultActionGroup group = ActionUtils.groupFromActions( baseGroup, new Separator(), getCellActionsGroup() ); JPopupMenu popupMenu = ActionManager.getInstance().createActionPopupMenu(ActionPlaces.EDITOR_POPUP, group).getComponent(); popupMenu.show(EditorComponent.this, x, y); } protected String getDefaultPopupGroupId() { return myDefaultPopupGroupId; } protected void setDefaultPopupGroupId(String id) { myDefaultPopupGroupId = id; } private DefaultActionGroup getCellActionsGroup() { DefaultActionGroup result = new DefaultActionGroup("Cell actions", true); result.setPopup(false); jetbrains.mps.openapi.editor.cells.EditorCell cell = getSelectedCell(); final EditorContext editorContext = createEditorContextForActions(); for (final KeyMapAction action : myKeymapHandler.getAllRegisteredActions(cell, editorContext)) { try { if (!(action.isShownInPopupMenu() && action.canExecute(editorContext))) { continue; } BaseAction mpsAction = new MyBaseAction(action, editorContext); mpsAction.addPlace(ActionPlace.EDITOR); result.add(mpsAction); } catch (Throwable t) { LOG.error(t); } } return result; } private EditorContext createEditorContextForActions() { return new EditorContext(this, getEditedNode() != null ? getEditedNode().getModel() : null, myRepository, myEditorConfiguration, new DisabledContextAssistantManager()); } @NotNull public JComponent getExternalComponent() { assert hasUI(); return myContainer; } public void repaintExternalComponent() { if (!hasUI()) { return; } getExternalComponent().repaint(); } public void validateExternalComponent() { if (!hasUI()) { return; } getExternalComponent().validate(); } @NotNull @Override public EditorContext getEditorContext() { // TODO: uncomment this assertion. Was commented out because this method is called indirectly from the dispose() method (failing tests). // assert !isDisposed(); return myEditorContext; } @NotNull protected EditorConfiguration getEditorConfiguration() { return myEditorConfiguration; } @NotNull protected SRepository getRepository() { return myRepository; } /** * Creating a cell representing empty editor content. Empty means editor has no node (getEditedNode() == null) * or currently editing node is not within a model (getEditedNode().getModel() == null) * * @return new EditorCell */ public jetbrains.mps.openapi.editor.cells.EditorCell createEmptyCell() { return new EditorCell_Constant(getEditorContext(), getEditedNode(), getEditedNode() == null ? "<no node>" : "<node is not inside a model>"); } public void setCollapseState(jetbrains.mps.openapi.editor.cells.EditorCell cell, Boolean collapsed) { if (collapsed == null) { resetCollapseState(cell); } else { myCollapseStates.put(cell, collapsed); } for (AdditionalPainter painter : getAdditionalPainters()) { painter.onUpdate(this); } } public void resetCollapseState(jetbrains.mps.openapi.editor.cells.EditorCell cell) { myCollapseStates.remove(cell); } public List<Pair<jetbrains.mps.openapi.editor.cells.EditorCell, Boolean>> getCollapseStates() { List<Pair<jetbrains.mps.openapi.editor.cells.EditorCell, Boolean>> result = new ArrayList<>(); for (Entry<jetbrains.mps.openapi.editor.cells.EditorCell, Boolean> collapseState : myCollapseStates.entrySet()) { result.add(new Pair<>(collapseState.getKey(), collapseState.getValue())); } return result; } public void setBracesEnabled(EditorCell cell, boolean enabled) { if (enabled) { myBracesEnabledCells.add(cell); } else { myBracesEnabledCells.remove(cell); } } public Set<EditorCell> getBracesEnabledCells() { return new HashSet<>(myBracesEnabledCells); } void clearBracesEnabledCells() { myBracesEnabledCells.clear(); } @Override public void dispose() { assertInEDT(); if (myDisposed) { throw new IllegalStateException(myDisposedTrace); } fireEditorWillBeDisposed(); myDisposed = true; myDisposedTrace = new Throwable("Editor was disposed by: "); if (!RuntimeFlags.isTestMode()) { hideMessageToolTip(); } releaseTypeCheckingContext(); myHighlightManager.dispose(); detachListeners(); // we expect this method to be executed at least inside model read // TODO: add assertion here myAutoValidator.dispose(); myUpdater.dispose(); if (hasUI()) { myLeftHighlighter.dispose(); myMessagesGutter.dispose(); } if (myNodeSubstituteChooser != null) { myNodeSubstituteChooser.dispose(); } if (myRootCell != null) { ((EditorCell_Basic) myRootCell).onRemove(); myRootCell = null; } mySelectionManager.dispose(); myLeftMarginPressListeners.clear(); myFocusTracker.dispose(); } protected void detachListeners() { EditorSettings.getInstance().removeEditorSettingsListener(mySettingsListener); myClassLoaderManager.removeClassesHandler(myClassesListener); } public boolean hasValidSelectedNode() { SNode selectedNode = getSelectedNode(); return selectedNode != null && SNodeUtil.isAccessible(selectedNode, myRepository); } @Override public boolean isDisposed() { return myDisposed; } public void assertModelNotDisposed() { boolean old = ModelAccess.instance().setReadEnabledFlag(true); try { assert myModelDisposedStackTrace == null : getModelDisposedMessage(); if (myNode == null) { return; } SModel model = myNode.getModel(); if (model == null) { return; } assert model.getRepository() != null : getNodeDisposedMessage(model); } finally { ModelAccess.instance().setReadEnabledFlag(old); } } private String getNodeDisposedMessage(SModel model) { StringBuilder sb = new StringBuilder("editor (" + this + ") is invalid"); if (myNode != null) { sb.append(", myNode is disposed"); StackTraceElement[] modelDisposedTrace = ((jetbrains.mps.smodel.SModelInternal) model).getDisposedStacktrace(); if (modelDisposedTrace != null) { for (StackTraceElement element : modelDisposedTrace) { sb.append("\nat "); sb.append(element); } } } else { sb.append(", myNode == null"); } sb.append("____________________________"); return sb.toString(); } private String getModelDisposedMessage() { StringBuilder sb = new StringBuilder("Model was disposed through:"); for (StackTraceElement element : myModelDisposedStackTrace) { sb.append("\nat "); sb.append(element); } sb.append("\n"); sb.append("EditorComponent.myDisposed == "); sb.append(isDisposed()); sb.append("\n"); return sb.toString(); } // This method should be called each time we set new node for and editor protected void clearModelDisposedTrace() { myModelDisposedStackTrace = null; } public void setModelDisposedTrace(StackTraceElement[] trace) { myModelDisposedStackTrace = trace; } /* Can be used to check if editor is in valid state or not. Editor can be in invalid state then corresponding model was reloaded, but current editor instance was not updated yet. */ public boolean isInvalid() { return isInvalidLightweight() || !SNodeUtil.isAccessible(getEditedNode(), myRepository); } /* Lightweight check for editor validity state. Similar to isInvalid, but can be called outside of read action. */ private boolean isInvalidLightweight() { return isDisposed() || getEditedNode() == null; } public void setRootCell(@NotNull jetbrains.mps.openapi.editor.cells.EditorCell rootCell) { if (getComponents().length > 0) { removeAll(); } ((EditorCell_Basic) myRootCell).onRemove(); myRootCell = (EditorCell) rootCell; ((EditorCell_Basic) myRootCell).onAdd(); for (EditorCell_WithComponent component : getCellTracker().getComponentCells()) { add(component.getComponent()); } for (AdditionalPainter painter : getAdditionalPainters()) { painter.onUpdate(this); } } @Override public EditorCell getRootCell() { return myRootCell; } @NotNull public NodeHighlightManager getHighlightManager() { return myHighlightManager; } public CellActionType getActionType(KeyEvent keyEvent, EditorContext editorContext) { if (keyPressed(keyEvent) && keyEvent.getKeyCode() == KeyEvent.VK_HOME && shiftDown(keyEvent)) { return CellActionType.SELECT_HOME; } if (keyPressed(keyEvent) && keyEvent.getKeyCode() == KeyEvent.VK_END && shiftDown(keyEvent)) { return CellActionType.SELECT_END; } if (keyPressed(keyEvent) && keyEvent.getKeyCode() == KeyEvent.VK_PAGE_DOWN && noKeysDown(keyEvent)) { return CellActionType.PAGE_DOWN; } if (keyPressed(keyEvent) && keyEvent.getKeyCode() == KeyEvent.VK_PAGE_UP && noKeysDown(keyEvent)) { return CellActionType.PAGE_UP; } if (keyPressed(keyEvent) && keyEvent.getKeyCode() == KeyEvent.VK_TAB && noKeysDown(keyEvent)) { return CellActionType.NEXT; } if (keyPressed(keyEvent) && keyEvent.getKeyCode() == KeyEvent.VK_TAB && shiftDown(keyEvent)) { return CellActionType.PREV; } if (keyEvent.getModifiers() == KeyEvent.CTRL_MASK && keyEvent.getKeyCode() == KeyEvent.VK_F1) { return CellActionType.SHOW_MESSAGE; } // --- if (keyTyped(keyEvent) && keyEvent.getKeyChar() == ' ' && noKeysDown(keyEvent)) { jetbrains.mps.openapi.editor.cells.EditorCell selectedCell = editorContext.getNodeEditorComponent().getSelectedCell(); if (!(selectedCell instanceof EditorCell_STHint)) { if (!(selectedCell instanceof EditorCell_Label)) { return CellActionType.RIGHT_TRANSFORM; } EditorCell_Label labelCell = (EditorCell_Label) selectedCell; // caret at the end of text ? String text = labelCell.getText(); int caretPosition = labelCell.getCaretPosition(); //System.out.println("text:" + text + " len:" + text.length() + "caret at:" + caretPosition); if (caretPosition == text.length()) { if (caretPosition == 0 && labelCell instanceof EditorCell_Constant) { //empty unbound constant cells should ignore the space key when empty return CellActionType.SELECT_END; } else { return CellActionType.RIGHT_TRANSFORM; } } if (caretPosition == 0) { return CellActionType.LEFT_TRANSFORM; } } } return null; } private boolean ctrlShiftDown(KeyEvent keyEvent) { return keyEvent.getModifiers() == (KeyEvent.CTRL_MASK + KeyEvent.SHIFT_MASK); } private boolean shiftDown(KeyEvent keyEvent) { return keyEvent.getModifiers() == KeyEvent.SHIFT_MASK; } private boolean noKeysDown(KeyEvent keyEvent) { return keyEvent.getModifiers() == 0; } private boolean keyTyped(KeyEvent keyEvent) { return keyEvent.getID() == KeyEvent.KEY_TYPED; } private boolean keyPressed(KeyEvent keyEvent) { return keyEvent.getID() == KeyEvent.KEY_PRESSED; } private boolean ctrlDown(KeyEvent keyEvent) { return keyEvent.getModifiers() == KeyEvent.CTRL_MASK; } boolean executeComponentAction(CellActionType type) { final CellAction action = getComponentAction(type); if (action != null && action.executeInCommand()) { getModelAccess().executeCommand(new EditorCommand(getCommandContext()) { @Override protected void doExecute() { action.execute(getEditorContext()); } }); return true; } return false; } @Override public CellAction getComponentAction(final CellActionType type) { return runRead(new Computable<CellAction>() { @Override public CellAction compute() { CellAction action = myActionMap.get(type); if (action != null && action.canExecute(getEditorContext())) { return action; } return null; } }); } public void relayout() { doRelayout(); revalidate(); repaintExternalComponent(); } private void doRelayout() { // TODO: check for myDisposed in all methods calling this one if (isDisposed()) { return; } myRootCell.setX(myShiftX); myRootCell.setY(myShiftY); myRootCell.relayout(); if (!hasUI()) { return; } myLeftHighlighter.relayout(true); if (mySearchPanel != null && mySearchPanel.isVisible()) { mySearchPanel.search(false); } } public void leftHighlightCell(EditorCell cell, Color c) { if (!hasUI()) { return; } myLeftHighlighter.highlight(cell, cell, c); } public void leftHighlightCells(EditorCell cell, EditorCell cell2, Color c) { if (!hasUI()) { return; } myLeftHighlighter.highlight(cell, cell2, c); } public void leftUnhighlightCell(EditorCell cell) { if (!hasUI()) { return; } myLeftHighlighter.unHighlight(cell); } @Override public void selectNode(final SNode node) { getModelAccess().runReadAction(() -> { EditorCell nodeCell = findNodeCell(node); if (nodeCell != null) { changeSelection(nodeCell); } }); } public void selectNode(final SNode node, final String cellId) { getModelAccess().runReadAction(() -> { EditorCell nodeCell = findCellWithId(node, cellId); if (nodeCell != null) { changeSelection(nodeCell); } }); } @Override @Nullable public EditorCell findNodeCell(final SNode node) { return findNodeCell(node, false); } @Override @Nullable public EditorCell findNodeCell(final SNode node, boolean findUnderFolded) { jetbrains.mps.openapi.editor.cells.EditorCell bigCell = myUpdater.getBigCell(node); if (bigCell != null && (bigCell.getRootParent() != getRootCell() || (!findUnderFolded && CellTraversalUtil.getFoldedParent(bigCell) != null))) { return null; } return (EditorCell) bigCell; } public EditorCell findNodeCellWithRole(SNode node, String role) { EditorCell rootCell = findNodeCell(node); if (rootCell == null) { return null; } return (EditorCell) findNodeCellWithRole(rootCell, role, node); } private jetbrains.mps.openapi.editor.cells.EditorCell findNodeCellWithRole(jetbrains.mps.openapi.editor.cells.EditorCell rootCell, String role, SNode node) { if (role == null) { return null; } if (role.equals(rootCell.getRole()) && node == rootCell.getSNode()) { return rootCell; } if (rootCell instanceof EditorCell_Collection) { EditorCell_Collection collection = (EditorCell_Collection) rootCell; for (jetbrains.mps.openapi.editor.cells.EditorCell child : collection) { jetbrains.mps.openapi.editor.cells.EditorCell result = findNodeCellWithRole(child, role, node); if (result != null) { return result; } } } return null; } @Override public EditorCell findCellWithId(final SNode node, final @NotNull String id) { final EditorCell bigCell = findNodeCell(node); if (bigCell == null) { return null; } final jetbrains.mps.openapi.editor.cells.EditorCell[] result = new jetbrains.mps.openapi.editor.cells.EditorCell[]{null}; myRepository.getModelAccess().runReadAction(() -> result[0] = findCellWithIdWithingBigCell(bigCell, id, node)); return (EditorCell) result[0]; } private jetbrains.mps.openapi.editor.cells.EditorCell findCellWithIdWithingBigCell(jetbrains.mps.openapi.editor.cells.EditorCell root, String id, SNode node) { if (id == null) { return null; } if (id.equals(root.getCellId()) && root.getSNode() == node) { return root; } if (root instanceof jetbrains.mps.openapi.editor.cells.EditorCell_Collection) { for (jetbrains.mps.openapi.editor.cells.EditorCell child : ((jetbrains.mps.openapi.editor.cells.EditorCell_Collection) root)) { SNode childNode = child.getSNode(); if (childNode == node || (childNode != null && AttributeOperations.isAttribute(childNode) && childNode.getParent() == node)) { jetbrains.mps.openapi.editor.cells.EditorCell result = findCellWithIdWithingBigCell(child, id, node); if (result != null) { return result; } } } } return null; } @Override public void rebuildEditorContent() { assertInEDT(); getUpdater().update(); relayout(); } protected void assertInEDT() { LOG.assertLog(ThreadUtils.isInEDT(), "You should do this in EDT"); } private void fireEditorWillBeDisposed() { for (EditorDisposeListener listener : new ArrayList<>(myDisposeListeners)) { listener.editorWillBeDisposed(this); } myDisposeListeners.clear(); } /** * Adds a listener to be called when this instance is disposed. It is not necessary to remove a listener when it has been called since the collection of * listeners is cleared during the disposal. * * @param listener the listener to add */ public void addDisposeListener(@NotNull EditorDisposeListener listener) { if (!myDisposeListeners.contains(listener)) { myDisposeListeners.add(listener); } } /** * Removes a listener so that it is no longer called when this instance is disposed. It is not necessary to remove a listener when it has been called since * the collection of listeners is cleared during the disposal. * * @param listener the listener to remove */ public void removeDisposeListener(@NotNull EditorDisposeListener listener) { myDisposeListeners.remove(listener); } public jetbrains.mps.openapi.editor.cells.EditorCell findCellWeak(int x, int y) { jetbrains.mps.openapi.editor.cells.EditorCell cell = myRootCell.findLeaf(x, y); if (cell == null) { cell = myRootCell.findNearestLeafOnLine(x, y, Condition.TRUE_CONDITION); } return cell; } private void processMousePressed(MouseEvent mouseEvent) { requestFocus(); processCoordSelection(mouseEvent); if (mouseEvent.getButton() == MouseEvent.BUTTON2) { goByCurrentReference(); } boolean ctrlDown = com.intellij.openapi.util.SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown(); if (ctrlDown) { if (mouseEvent.isAltDown()) { showCellError(); } else { goByCurrentReference(); } } } public void goByCurrentReference() { final DataContext dataContext = DataManager.getInstance().getDataContext(this); getModelAccess().executeCommand(new EditorCommand(getCommandContext()) { @Override protected void doExecute() { AnAction action = ActionManager.getInstance().getAction(MPSActions.EDITOR_GOTO_DECLARATION); if (action != null) { AnActionEvent event = ActionUtils.createEvent(ActionPlaces.EDITOR_POPUP, dataContext); ActionUtils.updateAndPerformAction(action, event); } } }); } @NotNull private org.jetbrains.mps.openapi.module.ModelAccess getModelAccess() { return myRepository.getModelAccess(); } public void showCellError() { final jetbrains.mps.openapi.editor.cells.EditorCell selectedCell = getSelectedCell(); if (selectedCell != null) { getModelAccess().runReadAction(new Runnable() { @Override public void run() { final HighlighterMessage message = getHighlighterMessageFor(selectedCell); if (message == null || message.getErrorReporter() == null) { return; } final IErrorReporter herror = message.getErrorReporter(); ThreadUtils.runInUIThreadNoWait(() -> { String s = message.getMessage(); final Window window = SwingUtilities.windowForComponent(EditorComponent.this); final MPSErrorDialog dialog = new MPSErrorDialog(window, s, message.getStatus().getPresentation(), false); if (herror.getRuleNode() != null) { final boolean hasAdditionalRuleIds = !herror.getAdditionalRulesIds().isEmpty(); final JButton button = new JButton(); class ToRuleAction extends AbstractAction { private final SNodeReference myRule; private final JDialog myToDispose; public ToRuleAction(String title, SNodeReference rule, JDialog toDispose) { super(title); myRule = rule; myToDispose = toDispose; } @Override public void actionPerformed(ActionEvent e) { new EditorNavigator(getCurrentProject()).shallSelect(true).open(myRule); myToDispose.dispose(); } } AbstractAction action = new ToRuleAction("Go To Rule", herror.getRuleNode(), dialog) { @Override public void actionPerformed(ActionEvent e) { if (hasAdditionalRuleIds) { JPopupMenu popupMenu = new JPopupMenu(); for (final SNodeReference id : herror.getAdditionalRulesIds()) { popupMenu.add(new ToRuleAction("Go To Rule " + id.getNodeId(), id, dialog)); } popupMenu.add(new ToRuleAction("Go To Immediate Rule", herror.getRuleNode(), dialog)); popupMenu.show(button, 0, button.getHeight()); } else { super.actionPerformed(e); } } }; button.setAction(action); dialog.addButton(button); } dialog.initializeUI(); dialog.setVisible(true); }); } }); } } public void addLeftMarginPressListener(LeftMarginMouseListener listener) { myLeftMarginPressListeners.add(listener); } public void removeLeftMarginPressListener(LeftMarginMouseListener listener) { myLeftMarginPressListeners.remove(listener); } public List<LeftMarginMouseListener> getLeftMarginPressListeners() { return Collections.unmodifiableList(myLeftMarginPressListeners); } public void clearLeftMarginListeners() { myLeftMarginPressListeners.clear(); } private void processCoordSelection(MouseEvent mouseEvent) { jetbrains.mps.openapi.editor.cells.EditorCell newSelectedCell = myRootCell.findLeaf(mouseEvent.getX(), mouseEvent.getY()); if (newSelectedCell != null && CellTraversalUtil.getFoldedParent(newSelectedCell) != null) { // mouse was pressed on a cell representing folded collection return; } if (newSelectedCell == null || !newSelectedCell.isSelectable()) { newSelectedCell = myRootCell.findNearestLeafOnLine(mouseEvent.getX(), mouseEvent.getY(), jetbrains.mps.openapi.editor.cells.CellConditions.SELECTABLE); } if (newSelectedCell != null && (mouseEvent.getButton() != MouseEvent.BUTTON3 || !isUnderSelection(getSelectionManager().getSelection(), newSelectedCell))) { mySelectionManager.setSelection(newSelectedCell); ((EditorCell) newSelectedCell).processMousePressed(mouseEvent); } } private boolean isUnderSelection(Selection selection, jetbrains.mps.openapi.editor.cells.EditorCell cell) { if (selection == null) { return false; } for (jetbrains.mps.openapi.editor.cells.EditorCell selectedCell : selection.getSelectedCells()) { if (CellTraversalUtil.isAncestorOrEquals(selectedCell, cell)) { return true; } } return false; } public void clearSelectionStack() { getSelectionManager().clearSelection(); } public void pushSelection(jetbrains.mps.openapi.editor.cells.EditorCell cell) { getSelectionManager().pushSelection(getSelectionManager().createSelection(cell)); } // public EditorCell popSelection() { // Selection selection = getSelectionManager().popSelection(); // return selection instanceof SingularSelection ? ((SingularSelection) selection).getEditorCell() : null; // } public jetbrains.mps.openapi.editor.cells.EditorCell peekSelection() { return getSelectedCell(); } public boolean selectionStackContains(EditorCell cell) { for (Selection nextSelection : getSelectionManager().getSelectionStackIterable()) { if (nextSelection instanceof SingularSelection) { if (((SingularSelection) nextSelection).getEditorCell().equals(cell)) { return true; } } } return false; } @Override public final void changeSelection(@NotNull jetbrains.mps.openapi.editor.cells.EditorCell newSelectedCell) { mySelectionManager.setSelection(newSelectedCell); showCellInViewPort(newSelectedCell); } // TODO: replace this method with selection listener private void showCellInViewPort(@NotNull jetbrains.mps.openapi.editor.cells.EditorCell newSelectedCell) { if (!hasUI()) { return; } if (getVisibleRect().isEmpty()) { final JViewport viewport = getViewport(); viewport.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { if (!getVisibleRect().isEmpty()) { viewport.removeChangeListener(this); if (getSelectedCell() != null) { scrollToCell(getSelectedCell()); } } } }); } else { scrollToCell(newSelectedCell); } } @Override public void scrollToNode(SNode node) { EditorCell cell = findNodeCell(node); if (cell != null) { scrollToCell(cell); } } public void ensureSelectionVisible() { if (getSelectedCell() == null) { return; } scrollToCell(getSelectedCell()); } @Override public void scrollToCell(@NotNull jetbrains.mps.openapi.editor.cells.EditorCell cell) { if (!hasUI() || getVisibleRect().isEmpty()) { return; } int viewportWidth = getViewport().getWidth(); int x0; int width; if (cell instanceof EditorCell_Label) { EditorCell_Label cellLabel = (EditorCell_Label) cell; int caretX = cellLabel.getCaretX(); int charWidth = cellLabel.getCharWidth(); width = 4 * charWidth; x0 = caretX - 2 * charWidth; } else if (getDeepestSelectedCell() instanceof EditorCell_Label && cell.getWidth() > viewportWidth) { EditorCell_Label cellLabel = (EditorCell_Label) getDeepestSelectedCell(); int caretX = cellLabel.getCaretX(); int charWidth = cellLabel.getCharWidth(); x0 = Math.max(cell.getX(), caretX + 2 * charWidth - viewportWidth); width = viewportWidth; } else { x0 = cell.getX(); width = cell.getWidth(); } Rectangle visibleRect = getVisibleRect(); Rectangle rectangle = new Rectangle(x0, visibleRect.y, width, visibleRect.height); if (!rectangle.isEmpty()) { boolean adjustHorizontally = !visibleRect.contains(rectangle); if (adjustHorizontally) { if (width <= viewportWidth) { int x1 = Math.max(0, x0 + width - viewportWidth); scrollToRectIfNotVisible( expandRectangleOneLine( new Rectangle(x1, cell.getY(), x0 - x1 + width, cell.getHeight() ) ) ); } else { scrollToRectIfNotVisible( expandRectangleOneLine( new Rectangle(x0 - SCROLL_GAP, cell.getY(), viewportWidth + SCROLL_GAP, cell.getHeight() ) ) ); } } else { scrollToRectIfNotVisible( expandRectangleOneLine( new Rectangle(x0, cell.getY(), width, cell.getHeight() ) ) ); } } } private void scrollToRectIfNotVisible(Rectangle rect) { if (getVisibleRect().contains(rect)) { return; } scrollRectToVisible(rect); } private Rectangle expandRectangleOneLine(Rectangle r) { Font defaultFont = EditorSettings.getInstance().getDefaultEditorFont(); FontMetrics fontMetrics = getFontMetrics(defaultFont); int height = fontMetrics.getHeight(); return new Rectangle(r.x, r.y - height, r.width, r.height + 2 * height); } @Override protected void paintComponent(Graphics gg) { EditorSettings setting = EditorSettings.getInstance(); Graphics2D g = (Graphics2D) gg; turnOnAliasingIfPossible(g); g.setColor(getBackground()); Rectangle bounds = g.getClipBounds(); g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height); jetbrains.mps.openapi.editor.cells.EditorCell deepestCell = getDeepestSelectedCell(); if (deepestCell instanceof EditorCell_Label && ((EditorCell) deepestCell).isInClipRegion(g)) { EditorCell_Label label = (EditorCell_Label) deepestCell; g.setColor(setting.getCaretRowColor()); g.fillRect(0, deepestCell.getY(), getWidth(), deepestCell.getHeight() - deepestCell.getTopInset() - deepestCell.getBottomInset()); g.setColor(EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.IDENTIFIER_UNDER_CARET_ATTRIBUTES).getBackgroundColor()); g.fillRect(deepestCell.getX() + label.getLeftInset(), deepestCell.getY(), deepestCell.getWidth() - label.getLeftInset() - label.getRightInset(), deepestCell.getHeight() - deepestCell.getTopInset() - deepestCell.getBottomInset()); } List<AdditionalPainter> additionalPainters = getAdditionalPainters(); for (AdditionalPainter additionalPainter : additionalPainters) { if (additionalPainter.paintsBackground()) { additionalPainter.paintBackground(g, this); } } if (isDisposed()) { return; } myRootCell.relayout(); if (myRootCell.isInClipRegion(g)) { g.setColor(EditorColorsManager.getInstance().getGlobalScheme().getColor(EditorColors.RIGHT_MARGIN_COLOR)); int boundPosition = myRootCell.getX() + setting.getVerticalBoundWidth(); g.drawLine(boundPosition, 0, boundPosition, getHeight()); myRootCell.paint(g); } for (AdditionalPainter additionalPainter : additionalPainters) { if (additionalPainter.paintsAbove()) { additionalPainter.paint(g, this); } } } Dimension getPreferredComponentSize() { return isDisposed() ? new Dimension(0, 0) : new Dimension(myRootCell.getWidth() + myShiftX + 10, myRootCell.getHeight() + myShiftY + 10); } @Override public Dimension getPreferredScrollableViewportSize() { return getPreferredSize(); } @Override public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { if (orientation == SwingConstants.VERTICAL) { return 20; } else { // if orientation == SwingConstants.HORIZONTAL return 20; } } @Override public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { return visibleRect.height; } @Override public boolean getScrollableTracksViewportWidth() { assert hasUI(); return myScrollPane.getViewport().getWidth() > getPreferredSize().width; } @Override public boolean getScrollableTracksViewportHeight() { assert hasUI(); return myScrollPane.getViewport().getHeight() > getPreferredSize().height; } @Override public jetbrains.mps.openapi.editor.cells.EditorCell getDeepestSelectedCell() { if (isDisposed()) { return null; } Selection deepestSelection = getSelectionManager().getDeepestSelection(); return deepestSelection instanceof SingularSelection ? ((SingularSelection) deepestSelection).getEditorCell() : null; } @Nullable public jetbrains.mps.openapi.editor.cells.EditorCell getSelectedCell() { if (isDisposed()) { return null; } Selection currentSelection = getSelectionManager().getSelection(); return currentSelection instanceof SingularSelection ? ((SingularSelection) currentSelection).getEditorCell() : null; } @NotNull public SelectionManager getSelectionManager() { return mySelectionManager; } @NotNull @Override public Updater getUpdater() { return myUpdater; } public KeyboardHandler peekKeyboardHandler() { return myKbdHandlersStack.peek(); } public KeyboardHandler popKeyboardHandler() { return myKbdHandlersStack.pop(); } public void pushKeyboardHandler(KeyboardHandler kbdHandler) { myKbdHandlersStack.push(kbdHandler); } public void setMouseEventHandler(MouseListener l) { assert myMouseEventHandler == null : "Mouse event handler was already specified"; addMouseListener(myMouseEventHandler = l); } public void removeMouseEventHandler() { assert myMouseEventHandler != null : "Mouse event handler was was not specified"; removeMouseListener(myMouseEventHandler); myMouseEventHandler = null; } private boolean areMouseEventsBlocked() { return myMouseEventHandler != null; } @NotNull public ITypeContextOwner getTypecheckingContextOwner() { return this; } protected void acquireTypeCheckingContext() { getModelAccess().runReadAction(() -> TypeContextManager.getInstance().acquireTypecheckingContext(getNodeForTypechecking(), EditorComponent.this)); } protected void releaseTypeCheckingContext() { getModelAccess().runReadAction(() -> TypeContextManager.getInstance().releaseTypecheckingContext(EditorComponent.this)); } /** * Returns false iff the containing root has been changed as a result of this method call. */ protected boolean updateContainingRoot(SNode node) { return myNode != node; } public SNode getNodeForTypechecking() { return getEditedNode(); } public void sendKeyEvent(KeyEvent keyEvent) { if (keyEvent.getID() == KeyEvent.KEY_PRESSED) { processKeyPressed(keyEvent); } else if (keyEvent.getID() == KeyEvent.KEY_RELEASED) { processKeyReleased(keyEvent); } } @Override public void update() { final jetbrains.mps.project.Project p = getCurrentProject(); final Highlighter highlighter = p == null ? null : p.getComponent(Highlighter.class); getModelAccess().runReadAction(() -> { //TODO: check if it's necessary to clear updater caches here?.. rebuildAfterReloadModel(); if (highlighter != null) { highlighter.resetCheckedStateInBackground(EditorComponent.this); } rebuildEditorContent(); }); } public void processKeyPressed(final KeyEvent keyEvent) { if (keyEvent.isConsumed() || isDisposed()) { return; } // hardcoded "update" action if (keyEvent.getKeyCode() == KeyEvent.VK_F5 && noKeysDown(keyEvent)) { //this lock should be obtained before the following read action to avoid deadlock update(); keyEvent.consume(); return; } if (keyEvent.getKeyCode() == KeyEvent.VK_F11 && noKeysDown(keyEvent)) { relayout(); keyEvent.consume(); return; } if (isKeyboardHandlerProcessingEnabled(keyEvent) && peekKeyboardHandler().processKeyPressed(getEditorContext(), keyEvent)) { keyEvent.consume(); } repaintExternalComponent(); } public void processKeyReleased(final KeyEvent keyEvent) { if (keyEvent.isConsumed()) { return; } if (isKeyboardHandlerProcessingEnabled(keyEvent) && peekKeyboardHandler().processKeyReleased(getEditorContext(), keyEvent)) { keyEvent.consume(); } repaintExternalComponent(); } public void processKeyTyped(final KeyEvent keyEvent) { if (keyEvent.isConsumed()) { return; } if (isKeyboardHandlerProcessingEnabled(keyEvent) && peekKeyboardHandler().processKeyTyped(getEditorContext(), keyEvent)) { keyEvent.consume(); } repaintExternalComponent(); } private boolean isKeyboardHandlerProcessingEnabled(KeyEvent keyEvent) { if (!ReadOnlyUtil.isSelectionReadOnlyInEditor(this)) { return true; } CellActionType actionType = getActionType(keyEvent, getEditorContext()); if (actionType != null) { switch (actionType) { case SELECT_LEFT: case SELECT_RIGHT: case SELECT_HOME: case SELECT_END: case PAGE_UP: case PAGE_DOWN: case NEXT: case PREV: return true; } } return false; } public CommandContext getCommandContext() { return myCommandContext; } private CommandContext getNoSelectionProcessingCommandContext() { // Hiding command start/stop notification. return new CommandContextWrapper(getCommandContext()) { @Override public void commandStarted() { } @Override public void commandFinished() { } }; } <T> T runRead(final Computable<T> c) { final ComputeRunnable<T> r = new ComputeRunnable<>(c); getModelAccess().runReadAction(r); return r.getResult(); } /** * @deprecated editor component does not always correspond to a project! */ @Nullable @Deprecated @ToRemove(version = 3.5) protected final jetbrains.mps.project.Project getCurrentProject() { // there's no need in MPSProject, there's just no key for generic MPS project in MPSCommonDataKeys. final MPSProject p = MPSCommonDataKeys.MPS_PROJECT.getData(DataManager.getInstance().getDataContext(this)); return p != null ? p : ProjectHelper.getProject(myRepository); } public boolean activateNodeSubstituteChooser(jetbrains.mps.openapi.editor.cells.EditorCell editorCell, boolean resetPattern) { return activateNodeSubstituteChooser(editorCell, resetPattern, false); } public boolean activateNodeSubstituteChooser(jetbrains.mps.openapi.editor.cells.EditorCell editorCell, boolean resetPattern, boolean isSmart) { if (myNodeSubstituteChooser.isVisible()) { return true; //todo: rebuild menu if smartness changed } // try to obtain substitute info SubstituteInfo substituteInfo = null; if (editorCell != null) { substituteInfo = editorCell.getSubstituteInfo(); } return activateNodeSubstituteChooser(editorCell, substituteInfo, resetPattern, isSmart); } public boolean activateNodeSubstituteChooser(jetbrains.mps.openapi.editor.cells.EditorCell editorCell, SubstituteInfo substituteInfo, boolean resetPattern) { return activateNodeSubstituteChooser(editorCell, substituteInfo, resetPattern, false); } public boolean activateNodeSubstituteChooser(jetbrains.mps.openapi.editor.cells.EditorCell editorCell, SubstituteInfo substituteInfo, boolean resetPattern, boolean isSmart) { if (editorCell == null || substituteInfo == null) { return false; } // do substitute... LOG.debug("substitute info : " + substituteInfo); NodeSubstitutePatternEditor patternEditor = ((EditorCell) editorCell).createSubstitutePatternEditor(); if (resetPattern) { patternEditor.toggleReplaceMode(); } final String pattern = patternEditor.getPattern(); // user changed text within this cell before pressing Ctrl+Space // or cell has no text at this moment boolean originalTextChanged = !patternEditor.getText().equals(substituteInfo.getOriginalText()); // caret is at the end of line boolean atTheEndOfLine = pattern.equals(patternEditor.getText()); // 1st - try to do substitution with current pattern (if cursor at the end of text) substituteInfo.invalidateActions(); if (originalTextChanged || atTheEndOfLine) { List<SubstituteAction> matchingActions = getMatchingActions(editorCell, substituteInfo, isSmart, pattern); if (matchingActions.size() == 1 && pattern.length() > 0) { // Just one applicable action in the completion menu final SubstituteAction theAction = matchingActions.get(0); Pair<Boolean, Boolean> canSubstitute = new ModelAccessHelper(getRepository()).runReadAction(() -> new Pair(theAction.canSubstitute(pattern), theAction.canSubstituteStrictly(pattern))); // Invoking this action immediately if originalText was changed or // the cursor is at the end of line and !theAction.canSubstituteStrictly(pattern) // [means, action will change underlying code] if (canSubstitute.o1 && (originalTextChanged || editorCell.isErrorState() || (atTheEndOfLine && !canSubstitute.o2))) { getRepository().getModelAccess().executeCommand(new EditorCommand(getEditorContext()) { @Override protected void doExecute() { theAction.substitute(getEditorContext(), pattern); } }); return true; } } } myNodeSubstituteChooser.setNodeSubstituteInfo(substituteInfo); myNodeSubstituteChooser.setPatternEditor(patternEditor); myNodeSubstituteChooser.setIsSmart(isSmart); myNodeSubstituteChooser.setContextCell(editorCell); myNodeSubstituteChooser.setVisible(true); return true; } private List<SubstituteAction> getMatchingActions(final jetbrains.mps.openapi.editor.cells.EditorCell editorCell, final SubstituteInfo substituteInfo, final boolean isSmart, final String pattern) { return runRead(new Computable<List<SubstituteAction>>() { @Override public List<SubstituteAction> compute() { final ITypeContextOwner contextOwner = isSmart ? new NonReusableTypecheckingContextOwner() : getTypecheckingContextOwner(); return TypeContextManager.getInstance().runTypeCheckingComputation(contextOwner, myNode, context -> isSmart ? substituteInfo.getSmartMatchingActions(pattern, false, editorCell) : substituteInfo.getMatchingActions(pattern, false) ); } }); } private void deactivateSubstituteChooser() { myNodeSubstituteChooser.setVisible(false); } public NodeSubstituteChooser getNodeSubstituteChooser() { return myNodeSubstituteChooser; } void setNodeInformationDialog(NodeInformationDialog dialog) { myNodeInformationDialog = dialog; } public boolean hasNodeInformationDialog() { return myNodeInformationDialog != null; } @Override public void paint(Graphics g) { super.paint(g); Selection selection = getSelectionManager().getSelection(); if (selection != null) { ((SelectionInternal) selection).paintSelection((Graphics2D) g); } } public Set<SNode> getNodesCellDependOn(jetbrains.mps.openapi.editor.cells.EditorCell cell) { return myUpdater.getRelatedNodes(cell); } public Set<SNodeReference> getCopyOfRefTargetsCellDependsOn(jetbrains.mps.openapi.editor.cells.EditorCell cell) { return myUpdater.getRelatedRefTargets(cell); } @Nullable public EditorCell getBigValidCellForNode(SNode node) { EditorCell result = findNodeCell(node); if (isValid(result)) { return result; } return null; } public boolean isValid(jetbrains.mps.openapi.editor.cells.EditorCell cell) { if (cell == null) { return false; } return ((EditorCell_Basic) cell).isInTree() && cell.getEditorComponent() == this; } public jetbrains.mps.openapi.editor.cells.EditorCell changeSelectionWRTFocusPolicy(@NotNull jetbrains.mps.openapi.editor.cells.EditorCell cell) { jetbrains.mps.openapi.editor.cells.EditorCell focusPolicyCell = FocusPolicyUtil.findFocusedCell(cell); jetbrains.mps.openapi.editor.cells.EditorCell toSelect; if (focusPolicyCell == null || (focusPolicyCell == cell && !FocusPolicyUtil.hasFocusPolicy(focusPolicyCell))) { toSelect = CellFinderUtil.findChildByManyFinders(cell, Finder.FIRST_ERROR, Finder.FIRST_EDITABLE, Finder.FIRST_SELECTABLE_LEAF); } else { toSelect = focusPolicyCell; } if (toSelect == null) { toSelect = cell; } changeSelection(toSelect); if (toSelect instanceof EditorCell_Label) { EditorCell_Label label = (EditorCell_Label) toSelect; jetbrains.mps.editor.runtime.style.CaretPosition defaultCaretPosition = label.getStyle().get(StyleAttributes.DEFAULT_CARET_POSITION); if (defaultCaretPosition != null) { if (defaultCaretPosition == jetbrains.mps.editor.runtime.style.CaretPosition.FIRST) { label.home(); } if (defaultCaretPosition == jetbrains.mps.editor.runtime.style.CaretPosition.LAST) { label.end(); } } else if (!toSelect.isErrorState()) { label.end(); } } return toSelect; } private void setEditorContext(@Nullable SModel model, @NotNull SRepository repository) { if (myEditorContext != null && myEditorContext.getModel() == model && myEditorContext.getRepository() == repository) { myEditorContext.reset(); return; } myEditorContext = createEditorContext(model, repository); } /** * This method is called from the constructor, so you cannot use local variables and any other * EditorComponent state here! * * @param model * @param repository */ @NotNull protected EditorContext createEditorContext(@Nullable SModel model, @NotNull SRepository repository) { return new EditorContext(this, model, repository, getEditorConfiguration(), createContextAssistantManager(repository)); } protected ContextAssistantManager createContextAssistantManager(SRepository repository) { return DefaultContextAssistantManager.newInstance(this, repository); } @Override public boolean isReadOnly() { return myReadOnly; } public void setPopupMenuEnabled(boolean popupMenuEnabled) { myPopupMenuEnabled = popupMenuEnabled; } @Override @Nullable public Object getData(@NonNls String dataId) { if (mySearchPanel != null && mySearchPanel.isVisible() && mySearchPanel.isTextFieldFocused()) { return null; } if (isDisposed()) { return null; } //MPSDK if (dataId.equals(MPSCommonDataKeys.NODE.getName())) { return getSelectedNode(); } if (dataId.equals(MPSCommonDataKeys.NODES.getName())) { return getSelectedNodes(); } if (dataId.equals(MPSEditorDataKeys.CONTEXT_MODEL.getName())) { return runRead(new Computable() { @Override public Object compute() { SNode node = getRootCell().getSNode(); if (node == null) { return null; } SModel model = node.getModel(); if (model == null) { return null; //removed model } return model; } }); } if (dataId.equals(MPSEditorDataKeys.CONTEXT_MODULE.getName())) { // todo: copy-paste return runRead(new Computable() { @Override public Object compute() { SNode node = getRootCell().getSNode(); if (node == null) { return null; } SModel model = node.getModel(); if (model == null) { return null; //removed model } return model.getModule(); } }); } if (dataId.equals(MPSEditorDataKeys.EDITOR_CONTEXT.getName())) { return createEditorContextForActions(); } if (dataId.equals(MPSEditorDataKeys.EDITOR_CELL.getName())) { return getSelectedCell(); } if (dataId.equals(MPSEditorDataKeys.EDITOR_COMPONENT.getName())) { return this; } if (dataId.equals(MPSCommonDataKeys.PLACE.getName())) { return ActionPlace.EDITOR; } //PDK if (dataId.equals(PlatformDataKeys.CUT_PROVIDER.getName())) { return new MyCutProvider(); } if (dataId.equals(PlatformDataKeys.COPY_PROVIDER.getName())) { return new MyCopyProvider(); } if (dataId.equals(PlatformDataKeys.PASTE_PROVIDER.getName()) && (isFocusOwner() || mySearchPanel == null || !mySearchPanel.isVisible())) { return new MyPasteProvider(); } if (dataId.equals(SelectInContext.DATA_KEY.getName())) { ProjectViewSelectInProvider selectInHelper = ApplicationManager.getApplication() == null ? null : ApplicationManager.getApplication().getComponent(ProjectViewSelectInProvider.class); if (selectInHelper == null) { return null; } return selectInHelper.getContext(getCurrentProject(), myNodePointer); } //not found return null; } private void commitAllCellValues() { final List<EditorCell_Property> cellsToCommit = getCellsToCommit(); if (cellsToCommit.isEmpty()) { return; } getModelAccess().executeCommand(new EditorCommandAdapter(() -> doCommitAll(cellsToCommit), getNoSelectionProcessingCommandContext())); } private void setDefaultSelection() { if (getSelectionManager().getSelection() != null) { return; } EditorCell rootCell = getRootCell(); if (rootCell instanceof EditorCell_Collection) { jetbrains.mps.openapi.editor.cells.EditorCell focusPolicyCell = FocusPolicyUtil.findFocusedCell(rootCell); jetbrains.mps.openapi.editor.cells.EditorCell toSelect; if (focusPolicyCell == null || (focusPolicyCell == rootCell && !FocusPolicyUtil.hasFocusPolicy(focusPolicyCell))) { toSelect = CellFinderUtil.findChildByManyFinders(rootCell, Finder.FIRST_EDITABLE, Finder.FIRST_SELECTABLE_LEAF); } else { toSelect = focusPolicyCell; } if (toSelect == null) { toSelect = rootCell; } changeSelection(toSelect); return; } if (rootCell != null && rootCell.isSelectable()) { changeSelection(rootCell); } } private void closeSubstituteChooser(Component newFocusOwner) { if (myNodeSubstituteChooser.getWindow() != null && (myNodeSubstituteChooser.getWindow().isAncestorOf(newFocusOwner) || myNodeSubstituteChooser.getWindow() == newFocusOwner)) { return; } deactivateSubstituteChooser(); } private void activateCaretBlinker() { myEditorConfiguration.caretManager.setActiveEditor(this); } private void deActivateCaretBlinker() { myEditorConfiguration.caretManager.unsetActiveEditor(this); } private List<EditorCell_Property> getCellsToCommit() { List<EditorCell_Property> cells = new ArrayList<>(); for (EditorCell_Property cell : getCellTracker().getTransactionalCells()) { if (cell.hasUncommittedValue()) { cells.add(cell); } } return cells; } private void doCommitAll(List<EditorCell_Property> cells) { for (EditorCell_Property cell : cells) { cell.commit(); } } /** * It's possible that associated module was already removed from MPSModuleRepository (for example - transient models * modules are currently removed from MPSModuleRepository before next code generation session). In this case currently * open editor should be closed as a result of another notification processing. We need to suppress editor update * process in this case because an editor is not in valid state right now. */ private boolean isModuleDisposed() { // TODO review return false; // myOperationContext != null && myOperationContext.getModule() == null; } private boolean isProjectDisposed() { final jetbrains.mps.project.Project p = getCurrentProject(); // XXX NOTE, we check the project is there, i.e. missing project is not treated as disposed. Is it right? return p != null && p.isDisposed(); } private boolean isNodeDisposed() { SNode node = getEditedNode(); return node != null && !SNodeUtil.isAccessible(node, myEditorContext.getRepository()); } public CellTracker getCellTracker() { return myCellTracker; } public BracesHighlighter getBracesHighlighter() { return myBracesHighlighter; } public void rebuildAfterReloadModel() { releaseTypeCheckingContext(); if (myNodePointer != null) { myNode = myNodePointer.resolve(getRepository()); myEditorContext = createEditorContext(myNode == null ? null : myNode.getModel(), myRepository); myUpdater.clearExplicitHints(); } myCommandContext.updateContextNode(); acquireTypeCheckingContext(); } private static class MyBaseAction extends BaseAction implements DumbAware { private final KeyMapAction myAction; private final EditorContext myEditorContext; public MyBaseAction(KeyMapAction action, EditorContext editorContext) { super("" + action.getDescriptionText()); myAction = action; myEditorContext = editorContext; String keyStrokeString = action.getKeyStroke(); if (keyStrokeString == null || keyStrokeString.length() == 0) { return; } KeyStroke keyStroke = KeyStroke.getKeyStroke(keyStrokeString); if (keyStroke == null) { LOG.error("Invalid keystroke (" + keyStrokeString + ") specified for the action: " + action.getClass().getName()); return; } KeyboardShortcut shortcut = new KeyboardShortcut(keyStroke, null); KeymapManager.getInstance().getKeymap(KeymapManager.DEFAULT_IDEA_KEYMAP).addShortcut(getActionId(), shortcut); setExecuteOutsideCommand(true); } @Override protected void doExecute(AnActionEvent e, Map<String, Object> _params) { myEditorContext.getRepository().getModelAccess().executeCommand(new EditorCommand(myEditorContext) { @Override protected void doExecute() { try { myAction.execute(myEditorContext); } catch (Throwable t) { LOG.error(t); } } }); } } public interface EditorDisposeListener { void editorWillBeDisposed(@NotNull EditorComponent component); } public void repaint(@NotNull jetbrains.mps.openapi.editor.cells.EditorCell cell) { // The +1 for height takes into account decorations such as selection or border, which may currently be drawn outside the cell. repaint(0, cell.getY(), getWidth(), cell.getHeight() + 1); } @Override public ActionHandler getActionHandler() { return myActionHandler; } /** * Return true if UI focus "within" this editor component. Means: owned by this component or any child-components * (in case of component cells displayed inside this editor). Context assistant is a special case: if it is focused, * the editor is considered inactive. * * @return true if the focus is inside this EditorComponent */ public boolean isActive() { if (isContextAssistantFocused()) { return false; } if (isFocusOwner()) { return true; } Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); return isAncestorOf(focusOwner); } private boolean isContextAssistantFocused() { ContextAssistant activeAssistant = myEditorContext.getContextAssistantManager().getActiveAssistant(); return activeAssistant != null && activeAssistant.hasFocus(); } @NotNull public EditorHighlighter getHighlighter() { return myHighlighter; } @NotNull public EditorComponentFocusTracker getFocusTracker() { return myFocusTracker; } private class ReferenceUnderliner { private EditorCell myLastReferenceCell; private ReferenceUnderliner() { addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == getKeyCode()) { setControlOver(); } } @Override public void keyReleased(KeyEvent e) { if (e.getKeyCode() == getKeyCode()) { clearControlOver(); } } private int getKeyCode() { return com.intellij.openapi.util.SystemInfo.isMac ? KeyEvent.VK_META : KeyEvent.VK_CONTROL; } }); addMouseMotionListener(new MouseMotionListener() { @Override public void mouseDragged(MouseEvent e) { } @Override public void mouseMoved(MouseEvent e) { if (!myEditorContext.getNodeEditorComponent().isFocusOwner()) { return; } if (isDisposed()) { myLastReferenceCell = null; return; } clearControlOver(); if (!(com.intellij.openapi.util.SystemInfo.isMac ? e.isMetaDown() : e.isControlDown())) { myLastReferenceCell = null; return; } final jetbrains.mps.openapi.editor.cells.EditorCell editorCell = myRootCell.findLeaf(e.getX(), e.getY()); if (editorCell == null) { myLastReferenceCell = null; return; } SNode snodeWRTReference = runRead(() -> isInvalid() ? null : APICellAdapter.getSNodeWRTReference(editorCell)); if (editorCell.getSNode() == snodeWRTReference) { myLastReferenceCell = null; return; } myLastReferenceCell = (EditorCell) editorCell; setControlOver(); } }); addFocusListener(new FocusListener() { @Override public void focusGained(FocusEvent e) { } @Override public void focusLost(FocusEvent e) { clearControlOver(); myLastReferenceCell = null; } }); } private void clearControlOver() { if (myLastReferenceCell != null) { myLastReferenceCell.getStyle().set(StyleAttributes.CONTROL_OVERED_REFERENCE, false); setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); repaintExternalComponent(); } } private void setControlOver() { if (myLastReferenceCell != null) { myLastReferenceCell.getStyle().set(StyleAttributes.CONTROL_OVERED_REFERENCE, true); setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); repaintExternalComponent(); } } } private class MyCutProvider implements CutProvider { @Override public void performCut(@NotNull final DataContext dataContext) { getModelAccess().executeCommandInEDT(new EditorCommand(getCommandContext()) { @Override protected void doExecute() { if (isInvalid() || !isCutEnabled(dataContext)) { return; } jetbrains.mps.openapi.editor.cells.EditorCell selectedCell = getSelectedCell(); if (selectedCell != null) { myActionHandler.executeAction(selectedCell, CellActionType.CUT); } else { getSelectionManager().getSelection().executeAction(CellActionType.CUT); } } }); } @Override public boolean isCutEnabled(@NotNull DataContext dataContext) { return !(isDisposed() || isInvalidLightweight() || getSelectionManager().getSelection() == null || ReadOnlyUtil.canDeleteNodes(EditorComponent.this, getSelectedNodes())); } @Override public boolean isCutVisible(@NotNull DataContext dataContext) { return true; } } private class MyCopyProvider implements CopyProvider { @Override public void performCopy(@NotNull DataContext dataContext) { getModelAccess().executeCommandInEDT(new EditorCommand(getCommandContext()) { @Override protected void doExecute() { if (isDisposed() || isInvalid()) { return; } jetbrains.mps.openapi.editor.cells.EditorCell selectedCell = getSelectedCell(); if (selectedCell != null) { myActionHandler.executeAction(selectedCell, CellActionType.COPY); } else { getSelectionManager().getSelection().executeAction(CellActionType.COPY); } } }); } @Override public boolean isCopyEnabled(@NotNull DataContext dataContext) { return !isDisposed() && !isInvalidLightweight() && getSelectionManager().getSelection() != null; } @Override public boolean isCopyVisible(@NotNull DataContext dataContext) { return true; } } private class MyPasteProvider implements PasteProvider { @Override public void performPaste(@NotNull final DataContext dataContext) { EditorComponent.this.performPaste(); } @Override public boolean isPastePossible(@NotNull DataContext dataContext) { return EditorComponent.this.isPastePossible(); } @Override public boolean isPasteEnabled(@NotNull DataContext dataContext) { return true; } } private void performPaste() { getModelAccess().executeCommandInEDT(new EditorCommand(getCommandContext()) { @Override protected void doExecute() { if (isInvalid() || !isPastePossible()) { return; } jetbrains.mps.openapi.editor.cells.EditorCell selectedCell = getSelectedCell(); if (selectedCell != null) { myActionHandler.executeAction(selectedCell, CellActionType.PASTE); } else { getSelectionManager().getSelection().executeAction(CellActionType.PASTE); } } }); } private boolean isPastePossible() { return !(isDisposed() || isInvalidLightweight() || ReadOnlyUtil.isSelectionReadOnlyInEditor(EditorComponent.this) || getSelectionManager().getSelection() == null); } @Override public InputMethodRequests getInputMethodRequests() { // Uncomment at the moment https://youtrack.jetbrains.com/issue/JRE-252 is fixed // if (ReadOnlyUtil.isSelectionReadOnlyInEditor(this)) { // return null; // } hideMessageToolTip(); if (myInputMethodRequests == null) { myInputMethodRequests = new InputMethodRequestsImpl(this); addInputMethodListener(new InputMethodListenerImpl(this)); } return myInputMethodRequests; } /** * This is a copy of com.intellij.openapi.editor.impl.EditorImpl.MyScrollBar classwith some additional code */ private static final Field decrButtonField; private static final Field incrButtonField; static { try { decrButtonField = BasicScrollBarUI.class.getDeclaredField("decrButton"); decrButtonField.setAccessible(true); incrButtonField = BasicScrollBarUI.class.getDeclaredField("incrButton"); incrButtonField.setAccessible(true); } catch (NoSuchFieldException e) { throw new IllegalStateException(e); } } class MyScrollBar extends JBScrollBar implements IdeGlassPane.TopComponent, TooltipComponent { @NonNls private static final String APPLE_LAF_AQUA_SCROLL_BAR_UI_CLASS = "apple.laf.AquaScrollBarUI"; private ScrollBarUI myPersistentUI; MyScrollBar(int orientation) { super(orientation); } void setPersistentUI(ScrollBarUI ui) { myPersistentUI = ui; setUI(ui); } @Override public boolean canBePreprocessed(MouseEvent e) { return JBScrollPane.canBePreprocessed(e, this); } @Override public void setUI(ScrollBarUI ui) { if (myPersistentUI == null) { myPersistentUI = ui; } super.setUI(myPersistentUI); } /** * This is helper method. It returns height of the top (decrease) scroll bar * button. Please note, that it's possible to return real height only if scroll bar * is instance of BasicScrollBarUI. Otherwise it returns fake (but good enough :) ) * value. */ int getDecScrollButtonHeight() { ScrollBarUI barUI = getUI(); Insets insets = getInsets(); if (barUI instanceof ButtonlessScrollBarUI) { return insets.top + ((ButtonlessScrollBarUI) barUI).getDecrementButtonHeight(); } else if (barUI instanceof BasicScrollBarUI) { try { JButton decrButtonValue = (JButton) decrButtonField.get(barUI); LOG.assertLog(decrButtonValue != null); return insets.top + decrButtonValue.getHeight(); } catch (Exception exc) { throw new IllegalStateException(exc); } } else { return insets.top + 15; } } /** * This is helper method. It returns height of the bottom (increase) scroll bar * button. Please note, that it's possible to return real height only if scroll bar * is instance of BasicScrollBarUI. Otherwise it returns fake (but good enough :) ) * value. */ int getIncScrollButtonHeight() { ScrollBarUI barUI = getUI(); Insets insets = getInsets(); if (barUI instanceof ButtonlessScrollBarUI) { return insets.top + ((ButtonlessScrollBarUI) barUI).getIncrementButtonHeight(); } else if (barUI instanceof BasicScrollBarUI) { try { JButton incrButtonValue = (JButton) incrButtonField.get(barUI); LOG.assertLog(incrButtonValue != null); return insets.bottom + incrButtonValue.getHeight(); } catch (Exception exc) { throw new IllegalStateException(exc.getMessage()); } } else if (APPLE_LAF_AQUA_SCROLL_BAR_UI_CLASS.equals(barUI.getClass().getName())) { return insets.bottom + 30; } else { return insets.bottom + 15; } } @Override public int getUnitIncrement(int direction) { assert hasUI(); JViewport vp = myScrollPane.getViewport(); Rectangle vr = vp.getViewRect(); return getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction); } @Override public int getBlockIncrement(int direction) { assert hasUI(); JViewport vp = myScrollPane.getViewport(); Rectangle vr = vp.getViewRect(); return getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction); } @Override public String getMPSTooltipText(MouseEvent mouseEvent) { if (getUI() instanceof TooltipComponent) { return ((TooltipComponent) getUI()).getMPSTooltipText(mouseEvent); } return null; } } }