| /*=============================================================================# |
| # Copyright (c) 2008, 2021 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.ltk.ui.sourceediting; |
| |
| import static org.eclipse.statet.ecommons.ui.actions.UIActions.VIEW_GROUP_ID; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import org.eclipse.core.commands.IHandler2; |
| import org.eclipse.jface.action.Action; |
| import org.eclipse.jface.action.GroupMarker; |
| import org.eclipse.jface.action.IMenuListener; |
| import org.eclipse.jface.action.IMenuManager; |
| import org.eclipse.jface.action.MenuManager; |
| import org.eclipse.jface.action.Separator; |
| import org.eclipse.jface.text.Document; |
| import org.eclipse.jface.text.DocumentEvent; |
| import org.eclipse.jface.text.IDocumentListener; |
| import org.eclipse.jface.text.source.SourceViewer; |
| import org.eclipse.jface.viewers.ISelectionChangedListener; |
| import org.eclipse.jface.viewers.SelectionChangedEvent; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.StyledText; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Menu; |
| import org.eclipse.ui.services.IServiceLocator; |
| import org.eclipse.ui.texteditor.ITextEditorActionConstants; |
| import org.eclipse.ui.texteditor.IUpdate; |
| |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| |
| import org.eclipse.statet.ecommons.commands.core.BasicHandlerCollection; |
| import org.eclipse.statet.ecommons.commands.core.HandlerCollection; |
| import org.eclipse.statet.ecommons.preferences.ui.SettingsUpdater; |
| import org.eclipse.statet.ecommons.text.ui.TextViewerAction; |
| import org.eclipse.statet.ecommons.ui.actions.ControlServicesUtil; |
| import org.eclipse.statet.ecommons.ui.components.WidgetToolsButton; |
| import org.eclipse.statet.ecommons.ui.util.LayoutUtils; |
| import org.eclipse.statet.ecommons.ui.util.UIAccess; |
| import org.eclipse.statet.ecommons.ui.workbench.ContextHandlers; |
| import org.eclipse.statet.ecommons.ui.workbench.texteditor.ActionHandler; |
| |
| import org.eclipse.statet.ltk.ui.sourceediting.swt.EnhStyledText; |
| import org.eclipse.statet.ltk.ui.sourceediting.util.SourceViewerEditorPreferenceUpdater; |
| |
| |
| /** |
| * Text snippet editor (no Eclipse editor) supporting {@link SourceEditorViewerConfigurator}. |
| */ |
| @NonNullByDefault |
| public class SnippetEditor extends Object { |
| |
| public static final int DEFAULT_SINGLE_LINE_STYLE= SWT.BORDER | SWT.SINGLE | SWT.LEFT_TO_RIGHT; |
| public static final int DEFAULT_MULTI_LINE_STYLE= SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.MULTI | SWT.LEFT_TO_RIGHT; |
| |
| |
| private class Updater implements ISelectionChangedListener, IDocumentListener, Runnable { |
| |
| private boolean actionUpdateScheduled= false; |
| |
| |
| @Override |
| public void selectionChanged(final SelectionChangedEvent event) { |
| schedule(); |
| } |
| |
| @Override |
| public void documentAboutToBeChanged(final DocumentEvent event) { |
| } |
| @Override |
| public void documentChanged(final DocumentEvent event) { |
| schedule(); |
| } |
| |
| |
| public void schedule() { |
| if (this.actionUpdateScheduled) { |
| return; |
| } |
| Display.getCurrent().asyncExec(this); |
| this.actionUpdateScheduled= true; |
| } |
| |
| @Override |
| public void run() { |
| this.actionUpdateScheduled= false; |
| if (UIAccess.isOkToUse(SnippetEditor.this.sourceViewer)) { |
| SnippetEditor.this.handlers.update(null); |
| for (final Action action : SnippetEditor.this.globalActions.values()) { |
| if (action instanceof IUpdate) { |
| ((IUpdate) action).update(); |
| } |
| } |
| } |
| } |
| } |
| |
| |
| private Composite composite; |
| |
| private final Document document; |
| private SourceViewer sourceViewer; |
| |
| private final boolean withToolButton; |
| private WidgetToolsButton toolButton; |
| |
| private final SourceEditorViewerConfigurator configurator; |
| private Map<String, Action> globalActions; |
| private Updater updater; |
| |
| private final @Nullable IServiceLocator serviceLocator; |
| private final HandlerCollection handlers; |
| |
| |
| /** |
| * Creates snippet editor with initial content. |
| */ |
| public SnippetEditor(final SourceEditorViewerConfigurator configurator, |
| final @Nullable String initialContent, |
| final @Nullable IServiceLocator serviceParent, final boolean withToolButton) { |
| this.configurator= configurator; |
| this.document= (initialContent != null) ? new Document(initialContent) : new Document(); |
| this.configurator.getDocumentSetupParticipant().setup(this.document); |
| |
| this.serviceLocator= serviceParent; |
| this.handlers= (serviceParent != null) ? |
| new ContextHandlers(serviceParent) : new BasicHandlerCollection(); |
| this.withToolButton= withToolButton; |
| } |
| |
| /** |
| * Creates snippet editor with initial content. |
| */ |
| public SnippetEditor(final SourceEditorViewerConfigurator configurator, final String initialContent, |
| final IServiceLocator serviceParent) { |
| this(configurator, initialContent, serviceParent, false); |
| } |
| |
| /** |
| * Creates snippet editor with empty document. |
| */ |
| public SnippetEditor(final SourceEditorViewerConfigurator configurator) { |
| this(configurator, null, null, false); |
| } |
| |
| |
| protected final @Nullable IServiceLocator getServiceLocator() { |
| return this.serviceLocator; |
| } |
| |
| public void create(final Composite parent, final int style) { |
| if (this.withToolButton) { |
| this.composite= new Composite(parent, SWT.NONE) { |
| @Override |
| public boolean setFocus() { |
| return SnippetEditor.this.sourceViewer.getTextWidget().setFocus(); |
| } |
| }; |
| this.composite.setLayout(LayoutUtils.newSashGrid(2)); |
| |
| createSourceViewer(this.composite, style); |
| this.sourceViewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); |
| |
| final WidgetToolsButton button= new WidgetToolsButton(this.sourceViewer.getTextWidget()) { |
| @Override |
| protected void fillMenu(final Menu menu) { |
| SnippetEditor.this.fillToolMenu(menu); |
| } |
| }; |
| button.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); |
| this.toolButton= button; |
| } |
| else { |
| createSourceViewer(parent, style); |
| this.composite= this.sourceViewer.getTextWidget(); |
| } |
| |
| initActions(); |
| this.sourceViewer.activatePlugins(); |
| this.updater= new Updater(); |
| this.sourceViewer.addSelectionChangedListener(this.updater); |
| this.sourceViewer.getDocument().addDocumentListener(this.updater); |
| |
| this.composite.addDisposeListener(this::dispose); |
| } |
| |
| private void createSourceViewer(final Composite composite, final int style) { |
| this.sourceViewer= new SourceViewer(composite, null, style) { |
| |
| @Override |
| protected StyledText createTextWidget(final Composite parent, final int styles) { |
| return EnhStyledText.forSourceEditor(parent, styles); |
| } |
| |
| }; |
| this.sourceViewer.setEditable(true); |
| |
| this.sourceViewer.setDocument(this.document); |
| |
| final ViewerSourceEditorAdapter adapter= new ViewerSourceEditorAdapter(this.sourceViewer, this.configurator); |
| this.configurator.setTarget(adapter); |
| new SourceViewerEditorPreferenceUpdater(this.sourceViewer, |
| this.configurator.getSourceViewerConfiguration().getPreferences() ); |
| new SettingsUpdater(this.configurator, this.sourceViewer.getControl()); |
| } |
| |
| protected void dispose(final DisposeEvent e) { |
| this.handlers.dispose(); |
| } |
| |
| |
| public void registerCommandHandler(final String commandId, final IHandler2 handler) { |
| if (this.handlers instanceof ContextHandlers) { |
| ((ContextHandlers)this.handlers).addActivate(commandId, handler); |
| } |
| else { |
| this.handlers.add(commandId, handler); |
| } |
| } |
| |
| public HandlerCollection getCommandHandlers() { |
| return this.handlers; |
| } |
| |
| public void addAction(final Action action) { |
| this.globalActions.put(action.getId(), action); |
| final String commandId= action.getActionDefinitionId(); |
| if (this.handlers instanceof ContextHandlers && commandId != null) { |
| ((ContextHandlers)this.handlers).addActivate(commandId, new ActionHandler(action)); |
| } |
| } |
| |
| public @Nullable Action getAction(final String id) { |
| return this.globalActions.get(id); |
| } |
| |
| protected void initActions() { |
| this.globalActions= new HashMap<>(10); |
| |
| if (this.handlers instanceof ContextHandlers) { |
| final var contextHandlers= (ContextHandlers)this.handlers; |
| final var serviceUtil= new ControlServicesUtil(this.serviceLocator, |
| getClass().getName() + '#'+hashCode(), getSourceViewer().getControl() ); |
| serviceUtil.addControl(getSourceViewer().getControl()); |
| if (this.toolButton != null) { |
| serviceUtil.addControl(this.toolButton.getButton()); |
| } |
| contextHandlers.setDefaultActivationExpression(serviceUtil.getExpression()); |
| contextHandlers.setDeactivateOnDisposal(serviceUtil.getRequireDeactivation()); |
| } |
| |
| // default actions |
| addAction(TextViewerAction.createUndoAction(this.sourceViewer)); |
| addAction(TextViewerAction.createRedoAction(this.sourceViewer)); |
| addAction(TextViewerAction.createCutAction(this.sourceViewer)); |
| addAction(TextViewerAction.createCopyAction(this.sourceViewer)); |
| addAction(TextViewerAction.createPasteAction(this.sourceViewer)); |
| addAction(TextViewerAction.createSelectAllAction(this.sourceViewer)); |
| |
| // create context menu |
| final MenuManager manager= new MenuManager(null, null); |
| manager.setRemoveAllWhenShown(true); |
| manager.addMenuListener(new IMenuListener() { |
| @Override |
| public void menuAboutToShow(final IMenuManager mgr) { |
| fillContextMenu(mgr); |
| } |
| }); |
| |
| final StyledText text= this.sourceViewer.getTextWidget(); |
| final Menu menu= manager.createContextMenu(text); |
| text.setMenu(menu); |
| } |
| |
| protected void fillContextMenu(final IMenuManager menu) { |
| menu.add(new GroupMarker(ITextEditorActionConstants.GROUP_UNDO)); |
| menu.appendToGroup(ITextEditorActionConstants.GROUP_UNDO, getAction(ITextEditorActionConstants.UNDO)); |
| menu.appendToGroup(ITextEditorActionConstants.GROUP_UNDO, getAction(ITextEditorActionConstants.REDO)); |
| |
| menu.add(new Separator(ITextEditorActionConstants.GROUP_EDIT)); |
| menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, getAction(ITextEditorActionConstants.CUT)); |
| menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, getAction(ITextEditorActionConstants.COPY)); |
| menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, getAction(ITextEditorActionConstants.PASTE)); |
| menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, getAction(ITextEditorActionConstants.SELECT_ALL)); |
| |
| menu.add(new Separator(ITextEditorActionConstants.GROUP_ASSIST)); |
| final Action action= this.globalActions.get("ContentAssistProposal"); //$NON-NLS-1$ |
| if (action != null && action.getText() != null) { |
| menu.appendToGroup(ITextEditorActionConstants.GROUP_ASSIST, action); |
| } |
| |
| menu.add(new Separator(VIEW_GROUP_ID)); |
| } |
| |
| protected void fillToolMenu(final Menu menu) { |
| } |
| |
| |
| public Document getDocument() { |
| return this.document; |
| } |
| |
| public SourceViewer getSourceViewer() { |
| return this.sourceViewer; |
| } |
| |
| public Composite getControl() { |
| return this.composite; |
| } |
| |
| public StyledText getTextControl() { |
| return this.sourceViewer.getTextWidget(); |
| } |
| |
| public void reset() { |
| this.sourceViewer.resetPlugins(); |
| this.updater.run(); |
| } |
| |
| } |