/* * 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.templates; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.*; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.SourceViewer; import org.eclipse.jface.text.templates.ContextTypeRegistry; import org.eclipse.jface.text.templates.DocumentTemplateContext; import org.eclipse.jface.text.templates.Template; import org.eclipse.jface.text.templates.persistence.TemplateStore; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.ui.internal.editors.text.EditorsPlugin; import org.eclipse.ui.part.IPageSite; import org.eclipse.ui.texteditor.templates.AbstractTemplatesPage; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.core.DBeaverCore; import org.jkiss.dbeaver.model.DBPDataSource; import org.jkiss.dbeaver.registry.driver.DriverDescriptor; import org.jkiss.dbeaver.ui.DBeaverIcons; import org.jkiss.dbeaver.ui.ProxyPageSite; import org.jkiss.dbeaver.ui.UIIcon; import org.jkiss.dbeaver.ui.editors.sql.SQLEditorBase; import org.jkiss.dbeaver.ui.editors.sql.SQLEditorSourceViewer; import org.jkiss.dbeaver.ui.editors.sql.SQLEditorSourceViewerConfiguration; import org.jkiss.dbeaver.ui.preferences.PreferenceStoreDelegate; /** * The templates page for the SQL editor. */ public class SQLTemplatesPage extends AbstractTemplatesPage { private static final Log log = Log.getLog(SQLTemplatesPage.class); private static final String PREFERENCE_PAGE_ID = "org.jkiss.dbeaver.ui.editors.sql.templates.SQLTemplatesPage"; //$NON-NLS-1$ private SQLEditorBase sqlEditor; /** * Create a new AbstractTemplatesPage for the JavaEditor * * @param sqlEditor the java editor */ public SQLTemplatesPage(final SQLEditorBase sqlEditor) { super(sqlEditor, sqlEditor.getViewer()); this.sqlEditor = sqlEditor; IPageSite ps = new ProxyPageSite(sqlEditor.getSite()); init(ps); } @Override public void insertTemplate(Template template, IDocument document) { if (!sqlEditor.validateEditorInputState()) return; ISourceViewer contextViewer = sqlEditor.getViewer(); ITextSelection textSelection = (ITextSelection) contextViewer.getSelectionProvider().getSelection(); if (!isValidTemplate(document, template, textSelection.getOffset(), textSelection.getLength())) return; beginCompoundChange(contextViewer); /* * The Editor checks whether a completion for a word exists before it allows for the template to be * applied. We pickup the current text at the selection position and replace it with the first char * of the template name for this to succeed. * Another advantage by this method is that the template replaces the selected text provided the * selection by itself is not used in the template pattern. */ String savedText; try { savedText = document.get(textSelection.getOffset(), textSelection.getLength()); if (savedText.length() == 0) { String prefix = getIdentifierPart(document, template, textSelection.getOffset(), textSelection.getLength()); if (prefix.length() > 0 && !template.getName().startsWith(prefix)) { return; } if (prefix.length() > 0) { contextViewer.setSelectedRange(textSelection.getOffset() - prefix.length(), prefix.length()); textSelection = (ITextSelection) contextViewer.getSelectionProvider().getSelection(); } } document.replace(textSelection.getOffset(), textSelection.getLength(), template.getName().substring(0, 1)); } catch (BadLocationException e) { endCompoundChange(contextViewer); return; } //Position position = new Position(textSelection.getOffset() + 1, 0); Region region = new Region(textSelection.getOffset(), 0); textSelection = new TextSelection(textSelection.getOffset(), 1); contextViewer.getSelectionProvider().setSelection(textSelection); SQLContext context = getContext(document, template, textSelection.getOffset(), textSelection.getLength()); context.setVariable("selection", savedText); //$NON-NLS-1$ if (context.getKey().length() == 0) { try { document.replace(textSelection.getOffset(), 1, savedText); } catch (BadLocationException e) { endCompoundChange(contextViewer); return; } } SQLTemplateCompletionProposal proposal = new SQLTemplateCompletionProposal(template, context, region, null); sqlEditor.getSite().getPage().activate(sqlEditor); proposal.apply(sqlEditor.getViewer(), ' ', 0, region.getOffset()); final Point selection = proposal.getSelection(document); if (selection != null) { sqlEditor.getViewer().setSelectedRange(selection.x, selection.y); sqlEditor.getViewer().revealRange(selection.x, selection.y); } endCompoundChange(contextViewer); } @Override protected ContextTypeRegistry getContextTypeRegistry() { return SQLTemplatesRegistry.getInstance().getTemplateContextRegistry(); } @Override protected IPreferenceStore getTemplatePreferenceStore() { return new PreferenceStoreDelegate(DBeaverCore.getGlobalPreferenceStore()); } @Override public TemplateStore getTemplateStore() { return SQLTemplatesRegistry.getInstance().getTemplateStore(); } @Override protected boolean isValidTemplate(IDocument document, Template template, int offset, int length) { String[] contextIds = getContextTypeIds(document, offset); for (String contextId : contextIds) { if (contextId.equals(template.getContextTypeId())) { DocumentTemplateContext context = getContext(document, template, offset, length); return context.canEvaluate(template) || isTemplateAllowed(context, template); } } return false; } @Override protected SourceViewer createPatternViewer(Composite parent) { IDocument document = new Document(); SQLEditorSourceViewer viewer = new SQLEditorSourceViewer(parent, null, null, false, SWT.V_SCROLL | SWT.H_SCROLL); SQLEditorSourceViewerConfiguration configuration = new SQLEditorSourceViewerConfiguration(sqlEditor, EditorsPlugin.getDefault().getPreferenceStore()); viewer.configure(configuration); viewer.setEditable(false); viewer.setDocument(document); Control control = viewer.getControl(); GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.FILL_VERTICAL); control.setLayoutData(data); viewer.setEditable(false); return viewer; } @Override protected Image getImage(Template template) { return DBeaverIcons.getImage(UIIcon.SQL_SCRIPT); } @Override protected void updatePatternViewer(Template template) { if (template == null) { getPatternViewer().getDocument().set(""); //$NON-NLS-1$ return; } //String contextId = template.getContextTypeId(); IDocument doc = getPatternViewer().getDocument(); String start = ""; //$NON-NLS-1$ doc.set(start + template.getPattern()); int startLen = start.length(); getPatternViewer().setDocument(doc, startLen, doc.getLength() - startLen); } @Override protected String getPreferencePageId() { return PREFERENCE_PAGE_ID; } /** * Undo manager - end compound change * * @param viewer the viewer */ private void endCompoundChange(ISourceViewer viewer) { if (viewer instanceof ITextViewerExtension) ((ITextViewerExtension) viewer).getRewriteTarget().endCompoundChange(); } /** * Undo manager - begin a compound change * * @param viewer the viewer */ private void beginCompoundChange(ISourceViewer viewer) { if (viewer instanceof ITextViewerExtension) ((ITextViewerExtension) viewer).getRewriteTarget().beginCompoundChange(); } /** * Check whether the template is allowed even though the context can't evaluate it. This is * needed because the Dropping of a template is more lenient than ctl-space invoked code assist. * * @param context the template context * @param template the template * @return true if the template is allowed */ private boolean isTemplateAllowed(DocumentTemplateContext context, Template template) { int offset = context.getCompletionOffset(); try { return template != null && offset > 0 && !isTemplateNamePart(context.getDocument().getChar(offset - 1)); } catch (BadLocationException e) { log.debug(e); } return false; } /** * Checks whether the character is a valid character in Java template names * * @param ch the character * @return <code>true</code> if the character is part of a template name */ private boolean isTemplateNamePart(char ch) { return !Character.isWhitespace(ch) && ch != '(' && ch != ')' && ch != '{' && ch != '}' && ch != ';'; } /** * Get context * * @param document the document * @param template the template * @param offset the offset * @param length the length * @return the context */ private SQLContext getContext(IDocument document, Template template, final int offset, int length) { return new SQLContext( getContextTypeRegistry().getContextType(template.getContextTypeId()), document, new Position(offset, length), sqlEditor); } /** * Get the active contexts for the given position in the document. * * @param document the document * @param offset the offset * @return an array of valid context id */ @Override protected String[] getContextTypeIds(IDocument document, int offset) { DBPDataSource dataSource = sqlEditor.getDataSource(); if (dataSource == null) { return new String[]{SQLContextTypeBase.ID_SQL}; } else { DriverDescriptor driver = (DriverDescriptor)dataSource.getContainer().getDriver(); return new String[]{ SQLContextTypeBase.ID_SQL, SQLContextTypeProvider.getTypeId(driver.getProviderDescriptor()), SQLContextTypeDriver.getTypeId(driver)}; } } /** * Get the Java identifier terminated at the given offset * * @param document the document * @param template the template * @param offset the offset * @param length the length * @return the identifier part the Java identifier */ private String getIdentifierPart(IDocument document, Template template, int offset, int length) { return getContext(document, template, offset, length).getKey(); } }