/* * DBeaver - Universal Database Manager * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) * * 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 org.jkiss.dbeaver.ui.editors.sql; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.*; import org.eclipse.jface.text.contentassist.IContentAssistProcessor; import org.eclipse.jface.text.contentassist.IContentAssistant; import org.eclipse.jface.text.formatter.ContentFormatter; import org.eclipse.jface.text.formatter.IContentFormatter; import org.eclipse.jface.text.formatter.IFormattingStrategy; import org.eclipse.jface.text.hyperlink.*; import org.eclipse.jface.text.information.IInformationPresenter; import org.eclipse.jface.text.information.IInformationProvider; import org.eclipse.jface.text.information.InformationPresenter; import org.eclipse.jface.text.presentation.IPresentationReconciler; import org.eclipse.jface.text.presentation.PresentationReconciler; import org.eclipse.jface.text.rules.BufferedRuleBasedScanner; import org.eclipse.jface.text.rules.DefaultDamagerRepairer; import org.eclipse.jface.text.rules.Token; import org.eclipse.jface.text.source.IAnnotationHover; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.editors.text.TextSourceViewerConfiguration; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.core.DBeaverUI; import org.jkiss.dbeaver.model.preferences.DBPPreferenceListener; import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore; import org.jkiss.dbeaver.model.sql.SQLConstants; import org.jkiss.dbeaver.model.sql.SQLDialect; import org.jkiss.dbeaver.ui.editors.sql.indent.SQLAutoIndentStrategy; import org.jkiss.dbeaver.ui.editors.sql.indent.SQLCommentAutoIndentStrategy; import org.jkiss.dbeaver.ui.editors.sql.indent.SQLStringAutoIndentStrategy; import org.jkiss.dbeaver.ui.editors.sql.syntax.*; import org.jkiss.dbeaver.ui.editors.sql.util.SQLAnnotationHover; import org.jkiss.dbeaver.ui.editors.sql.syntax.SQLInformationProvider; import org.jkiss.utils.ArrayUtils; /** * This class defines the editor add-ons; content assist, content formatter, * highlighting, auto-indent strategy, double click strategy. */ public class SQLEditorSourceViewerConfiguration extends TextSourceViewerConfiguration { /** * The editor with which this configuration is associated. */ private SQLEditorBase editor; private SQLRuleManager ruleManager; private IContentAssistProcessor completionProcessor; private IHyperlinkDetector hyperlinkDetector; /** * This class implements a single token scanner. */ static class SingleTokenScanner extends BufferedRuleBasedScanner { public SingleTokenScanner(TextAttribute attribute) { setDefaultReturnToken(new Token(attribute)); } } /** * Constructs an instance of this class with the given SQLEditor to * configure. * * @param editor the SQLEditor to configure */ public SQLEditorSourceViewerConfiguration( SQLEditorBase editor, IPreferenceStore preferenceStore) { super(preferenceStore); this.editor = editor; this.ruleManager = editor.getRuleManager(); this.completionProcessor = new SQLCompletionProcessor(editor); this.hyperlinkDetector = new SQLHyperlinkDetector(editor, editor.getSyntaxManager()); } @Override public IUndoManager getUndoManager(ISourceViewer sourceViewer) { return new TextViewerUndoManager(200); } /** * Returns the annotation hover which will provide the information to be * shown in a hover popup window when requested for the given * source viewer. * * @see org.eclipse.jface.text.source.SourceViewerConfiguration#getAnnotationHover(org.eclipse.jface.text.source.ISourceViewer) */ @Override public IAnnotationHover getAnnotationHover(ISourceViewer sourceViewer) { return new SQLAnnotationHover(getSQLEditor()); } @Nullable @Override public IAutoEditStrategy[] getAutoEditStrategies(ISourceViewer sourceViewer, String contentType) { if (IDocument.DEFAULT_CONTENT_TYPE.equals(contentType)) { return new IAutoEditStrategy[] { new SQLAutoIndentStrategy(SQLPartitionScanner.SQL_PARTITIONING, editor.getSyntaxManager()) } ; } else if (SQLPartitionScanner.CONTENT_TYPE_SQL_COMMENT.equals(contentType) || SQLPartitionScanner.CONTENT_TYPE_SQL_MULTILINE_COMMENT.equals(contentType)) { return new IAutoEditStrategy[] { new SQLCommentAutoIndentStrategy(SQLPartitionScanner.SQL_PARTITIONING) } ; } else if (SQLPartitionScanner.CONTENT_TYPE_SQL_STRING.equals(contentType)) { return new IAutoEditStrategy[] { new SQLStringAutoIndentStrategy(SQLPartitionScanner.CONTENT_TYPE_SQL_STRING) }; } return new IAutoEditStrategy[0]; } /** * Returns the configured partitioning for the given source viewer. The partitioning is * used when the querying content types from the source viewer's input document. * * @see org.eclipse.jface.text.source.SourceViewerConfiguration#getConfiguredDocumentPartitioning(org.eclipse.jface.text.source.ISourceViewer) */ @Override public String getConfiguredDocumentPartitioning(ISourceViewer sourceViewer) { return SQLPartitionScanner.SQL_PARTITIONING; } /** * Creates, initializes, and returns the ContentAssistant to use with this editor. */ @Override public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) { DBPPreferenceStore store = editor.getActivePreferenceStore(); final DBPPreferenceStore configStore = store; final SQLContentAssistant assistant = new SQLContentAssistant(); assistant.setDocumentPartitioning(getConfiguredDocumentPartitioning(sourceViewer)); // Set content assist processors for various content types. if (completionProcessor != null) { assistant.setContentAssistProcessor(completionProcessor, IDocument.DEFAULT_CONTENT_TYPE); } // Configure how content assist information will appear. assistant.enableAutoActivation(store.getBoolean(SQLPreferenceConstants.ENABLE_AUTO_ACTIVATION)); assistant.setAutoActivationDelay(store.getInt(SQLPreferenceConstants.AUTO_ACTIVATION_DELAY)); assistant.setProposalPopupOrientation(IContentAssistant.PROPOSAL_OVERLAY); assistant.setInformationControlCreator(getInformationControlCreator(sourceViewer)); //In the future, a preference page will be added to customize foreground and background. Color foreground = new Color(DBeaverUI.getDisplay(), 0, 0, 0); Color background = new Color(DBeaverUI.getDisplay(), 255, 255, 255); assistant.setContextInformationPopupOrientation(IContentAssistant.CONTEXT_INFO_ABOVE); assistant.setContextInformationPopupForeground(foreground); assistant.setContextInformationPopupBackground(background); //Set auto insert mode. assistant.enableAutoInsert(store.getBoolean(SQLPreferenceConstants.INSERT_SINGLE_PROPOSALS_AUTO)); assistant.setShowEmptyList(true); final DBPPreferenceListener prefListener = new DBPPreferenceListener() { @Override public void preferenceChange(PreferenceChangeEvent event) { switch (event.getProperty()) { case SQLPreferenceConstants.ENABLE_AUTO_ACTIVATION: assistant.enableAutoActivation(configStore.getBoolean(SQLPreferenceConstants.ENABLE_AUTO_ACTIVATION)); break; case SQLPreferenceConstants.AUTO_ACTIVATION_DELAY: assistant.setAutoActivationDelay(configStore.getInt(SQLPreferenceConstants.AUTO_ACTIVATION_DELAY)); break; case SQLPreferenceConstants.INSERT_SINGLE_PROPOSALS_AUTO: assistant.enableAutoInsert(configStore.getBoolean(SQLPreferenceConstants.INSERT_SINGLE_PROPOSALS_AUTO)); break; } } }; configStore.addPropertyChangeListener(prefListener); editor.getTextViewer().getControl().addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent e) { configStore.removePropertyChangeListener(prefListener); } }); return assistant; } @Override public IInformationControlCreator getInformationControlCreator(ISourceViewer sourceViewer) { return new IInformationControlCreator() { @Override public IInformationControl createInformationControl(Shell parent) { return new DefaultInformationControl(parent, true); } }; } /** * Creates, configures, and returns the ContentFormatter to use. * * @see org.eclipse.jface.text.source.SourceViewerConfiguration#getContentFormatter(ISourceViewer) */ @Override public IContentFormatter getContentFormatter(ISourceViewer sourceViewer) { ContentFormatter formatter = new ContentFormatter(); formatter.setDocumentPartitioning(SQLPartitionScanner.SQL_PARTITIONING); IFormattingStrategy formattingStrategy = new SQLFormattingStrategy(sourceViewer, this, editor.getSyntaxManager()); for (String ct : SQLPartitionScanner.SQL_CONTENT_TYPES) { formatter.setFormattingStrategy(formattingStrategy, ct); } formatter.enablePartitionAwareFormatting(false); return formatter; } /** * Returns the double-click strategy ready to be used in this viewer when double clicking * onto text of the given content type. (Note: the same double-click strategy * object is used for all content types.) * * @see org.eclipse.jface.text.source.SourceViewerConfiguration#getDoubleClickStrategy(ISourceViewer, String) */ @Override public ITextDoubleClickStrategy getDoubleClickStrategy(ISourceViewer sourceViewer, String contentType) { return new SQLDoubleClickStrategy(); } /** * Creates, configures, and returns a presentation reconciler to help with * document changes. * * @see org.eclipse.jface.text.source.SourceViewerConfiguration#getPresentationReconciler(ISourceViewer) */ @Override public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) { // Create a presentation reconciler to handle handle document changes. PresentationReconciler reconciler = new PresentationReconciler(); String docPartitioning = getConfiguredDocumentPartitioning(sourceViewer); reconciler.setDocumentPartitioning(docPartitioning); // Add a "damager-repairer" for changes in default text (SQL code). DefaultDamagerRepairer dr = new DefaultDamagerRepairer(ruleManager); reconciler.setDamager(dr, IDocument.DEFAULT_CONTENT_TYPE); reconciler.setRepairer(dr, IDocument.DEFAULT_CONTENT_TYPE); // rule for multiline comments // We just need a scanner that does nothing but returns a token with // the corresponding text attributes addContentTypeDamageRepairer(reconciler, SQLPartitionScanner.CONTENT_TYPE_SQL_MULTILINE_COMMENT, SQLConstants.CONFIG_COLOR_COMMENT); // Add a "damager-repairer" for changes within one-line SQL comments. addContentTypeDamageRepairer(reconciler, SQLPartitionScanner.CONTENT_TYPE_SQL_COMMENT, SQLConstants.CONFIG_COLOR_COMMENT); // Add a "damager-repairer" for changes within quoted literals. addContentTypeDamageRepairer(reconciler, SQLPartitionScanner.CONTENT_TYPE_SQL_STRING, SQLConstants.CONFIG_COLOR_STRING); // Add a "damager-repairer" for changes within quoted literals. addContentTypeDamageRepairer(reconciler, SQLPartitionScanner.CONTENT_TYPE_SQL_QUOTED, SQLConstants.CONFIG_COLOR_DATATYPE); return reconciler; } private void addContentTypeDamageRepairer(PresentationReconciler reconciler, String contentType, String colorId) { DefaultDamagerRepairer dr = new DefaultDamagerRepairer( new SingleTokenScanner( new TextAttribute(ruleManager.getColor(colorId)))); reconciler.setDamager(dr, contentType); reconciler.setRepairer(dr, contentType); } /** * Returns the SQLEditor associated with this object. * * @return the SQLEditor that this object configures */ public SQLEditorBase getSQLEditor() { return editor; } /** * Returns the text hover which will provide the information to be shown * in a text hover popup window when requested for the given source viewer and * the given content type. * * @see org.eclipse.jface.text.source.SourceViewerConfiguration#getTextHover(org.eclipse.jface.text.source.ISourceViewer, java.lang.String) */ @Override public ITextHover getTextHover(ISourceViewer sourceViewer, String contentType) { //return new BestMatchHover(this.getSQLEditor()); return new SQLAnnotationHover(this.getSQLEditor()); } @Override public String[] getConfiguredContentTypes(ISourceViewer sourceViewer) { return SQLPartitionScanner.SQL_CONTENT_TYPES; } @Override public String[] getDefaultPrefixes(ISourceViewer sourceViewer, String contentType) { SQLDialect dialect = editor.getSQLDialect(); return ArrayUtils.add(String.class, dialect.getSingleLineComments(), ""); } @Override public IInformationPresenter getInformationPresenter(ISourceViewer sourceViewer) { InformationPresenter presenter = new InformationPresenter(getInformationControlCreator(sourceViewer)); presenter.setDocumentPartitioning(getConfiguredDocumentPartitioning(sourceViewer)); // Register information provider IInformationProvider provider = new SQLInformationProvider(getSQLEditor()); String[] contentTypes = getConfiguredContentTypes(sourceViewer); for (String contentType : contentTypes) { presenter.setInformationProvider(provider, contentType); } presenter.setSizeConstraints(60, 10, true, true); return presenter; } @Override public IHyperlinkPresenter getHyperlinkPresenter(ISourceViewer sourceViewer) { return new MultipleHyperlinkPresenter(new RGB(0, 0, 255)) { }; } @Nullable @Override public IHyperlinkDetector[] getHyperlinkDetectors(ISourceViewer sourceViewer) { if (sourceViewer == null) { return null; } return new IHyperlinkDetector[]{ hyperlinkDetector, new URLHyperlinkDetector()}; } void onDataSourceChange() { if (hyperlinkDetector instanceof IHyperlinkDetectorExtension) { ((IHyperlinkDetectorExtension)hyperlinkDetector).dispose(); } } }