/* * 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.syntax; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2; import org.eclipse.jface.text.contentassist.ICompletionProposalExtension5; import org.eclipse.jface.text.contentassist.IContextInformation; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.model.DBPDataSource; import org.jkiss.dbeaver.model.DBPKeywordType; import org.jkiss.dbeaver.model.DBPNamedObject; import org.jkiss.dbeaver.model.DBUtils; import org.jkiss.dbeaver.model.runtime.DefaultProgressMonitor; import org.jkiss.dbeaver.model.sql.SQLSyntaxManager; import org.jkiss.dbeaver.model.struct.DBSObject; import org.jkiss.dbeaver.model.struct.DBSObjectContainer; import org.jkiss.dbeaver.model.struct.DBSObjectReference; import org.jkiss.dbeaver.ui.TextUtils; import org.jkiss.dbeaver.ui.editors.sql.SQLPreferenceConstants; import org.jkiss.utils.CommonUtils; import java.util.Locale; /** * SQL Completion proposal */ public class SQLCompletionProposal implements ICompletionProposal, ICompletionProposalExtension2,ICompletionProposalExtension5 { private static final Log log = Log.getLog(SQLCompletionProposal.class); private final DBPDataSource dataSource; private SQLSyntaxManager syntaxManager; /** The string to be displayed in the completion proposal popup. */ private String displayString; /** The replacement string. */ private String replacementString; private String replacementFull; private String replacementLast; /** The replacement offset. */ private int replacementOffset; /** The replacement length. */ private int replacementLength; /** The cursor position after this proposal has been applied. */ private int cursorPosition; /** The image to be displayed in the completion proposal popup. */ private Image image; /** The context information of this proposal. */ private IContextInformation contextInformation; private DBPKeywordType proposalType; private String additionalProposalInfo; private boolean simpleMode; private DBPNamedObject object; public SQLCompletionProposal( SQLCompletionAnalyzer.CompletionRequest request, String displayString, String replacementString, int cursorPosition, @Nullable Image image, IContextInformation contextInformation, DBPKeywordType proposalType, String description, DBPNamedObject object) { this.dataSource = request.editor.getDataSource(); this.syntaxManager = request.editor.getSyntaxManager(); this.displayString = displayString; this.replacementString = replacementString; this.replacementFull = DBUtils.getUnQuotedIdentifier(this.dataSource, replacementString.toLowerCase(Locale.ENGLISH)); int divPos = this.replacementFull.lastIndexOf(syntaxManager.getStructSeparator()); if (divPos == -1) { this.replacementLast = null; } else { this.replacementLast = this.replacementFull.substring(divPos + 1); } this.cursorPosition = cursorPosition; this.image = image; this.contextInformation = contextInformation; this.proposalType = proposalType; this.additionalProposalInfo = description; setPosition(request.wordDetector); this.object = object; this.simpleMode = request.simpleMode; } public DBPNamedObject getObject() { return object; } private void setPosition(SQLWordPartDetector wordDetector) { String fullWord = wordDetector.getFullWord(); int curOffset = wordDetector.getCursorOffset() - wordDetector.getStartOffset(); char structSeparator = syntaxManager.getStructSeparator(); int startOffset = fullWord.lastIndexOf(structSeparator, curOffset); int endOffset = fullWord.indexOf(structSeparator, curOffset); if (endOffset == startOffset) { startOffset = -1; } if (startOffset != -1) { startOffset += wordDetector.getStartOffset() + 1; } else { startOffset = wordDetector.getStartOffset(); } if (endOffset != -1) { endOffset += wordDetector.getStartOffset(); } else { endOffset = wordDetector.getCursorOffset();//wordDetector.getEndOffset(); } replacementOffset = startOffset; replacementLength = endOffset - startOffset; } @Override public void apply(IDocument document) { try { if (dataSource != null) { if (dataSource.getContainer().getPreferenceStore().getBoolean(SQLPreferenceConstants.INSERT_SPACE_AFTER_PROPOSALS)) { boolean insertTrailingSpace = true; if (object instanceof DBSObjectContainer) { // Do not append trailing space after schemas/catalogs/etc. } else { int docLen = document.getLength(); if (docLen <= replacementOffset + replacementLength + 2) { insertTrailingSpace = false; } else { insertTrailingSpace = document.getChar(replacementOffset + replacementLength) != ' '; } if (insertTrailingSpace) { replacementString += " "; } cursorPosition++; } } } document.replace(replacementOffset, replacementLength, replacementString); } catch (BadLocationException e) { // ignore log.debug(e); } } /* * @see ICompletionProposal#getSelection(IDocument) */ @Override public Point getSelection(IDocument document) { return new Point(replacementOffset + cursorPosition, 0); } @Override public Object getAdditionalProposalInfo(IProgressMonitor monitor) { if (additionalProposalInfo == null) { additionalProposalInfo = SQLContextInformer.readAdditionalProposalInfo(new DefaultProgressMonitor(monitor), dataSource, object, new String[] {displayString}, proposalType); } return additionalProposalInfo; } @Override public String getAdditionalProposalInfo() { return CommonUtils.toString(getAdditionalProposalInfo(new NullProgressMonitor())); } @Override public String getDisplayString() { return displayString; } @Override public Image getImage() { return image; } @Override public IContextInformation getContextInformation() { return contextInformation; } ////////////////////////////////////////////////////////////////// // ICompletionProposalExtension2 @Override public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) { apply(viewer.getDocument()); } @Override public void selected(ITextViewer viewer, boolean smartToggle) { } @Override public void unselected(ITextViewer viewer) { } @Override public boolean validate(IDocument document, int offset, DocumentEvent event) { if (event == null) { return false; } final SQLWordPartDetector wordDetector = new SQLWordPartDetector(document, syntaxManager, offset); String wordPart = wordDetector.getWordPart(); int divPos = wordPart.lastIndexOf(syntaxManager.getStructSeparator()); if (divPos != -1) { wordPart = wordPart.substring(divPos + 1); } String wordLower = wordPart.toLowerCase(Locale.ENGLISH); if (!CommonUtils.isEmpty(wordPart)) { boolean matched; if (simpleMode) { matched = replacementFull.startsWith(wordLower) && (CommonUtils.isEmpty(event.getText()) || replacementFull.contains(event.getText().toLowerCase(Locale.ENGLISH))) || (this.replacementLast != null && this.replacementLast.startsWith(wordLower)); } else { matched = (TextUtils.fuzzyScore(replacementFull, wordLower) > 0 && (CommonUtils.isEmpty(event.getText()) || TextUtils.fuzzyScore(replacementFull, event.getText()) > 0)) || (this.replacementLast != null && TextUtils.fuzzyScore(this.replacementLast, wordLower) > 0); } if (matched) { setPosition(wordDetector); return true; } } return false; } public boolean hasStructObject() { return object instanceof DBSObject || object instanceof DBSObjectReference; } public DBSObject getObjectContainer() { if (object instanceof DBSObject) { return ((DBSObject) object).getParentObject(); } else if (object instanceof DBSObjectReference) { return ((DBSObjectReference) object).getContainer(); } else { return null; } } @Override public String toString() { return displayString; } }