blob: 8fed8ad80e51aa8840ba4f476b21582686c3ee6f [file] [log] [blame]
/*=============================================================================#
# 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.text.ui.TextViewerEditorColorUpdater;
import org.eclipse.statet.ecommons.text.ui.TextViewerJFaceUpdater;
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;
/**
* 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 TextViewerJFaceUpdater(this.sourceViewer,
this.configurator.getSourceViewerConfiguration().getPreferences() );
new TextViewerEditorColorUpdater(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();
}
}