| /******************************************************************************* |
| * Copyright (c) 2016, 2018 Red Hat Inc. and others |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Sopot Cela (Red Hat Inc.) |
| * Lucas Bullen (Red Hat Inc.) - [Bug 522317] Support environment arguments tags in Generic TP editor |
| * - [Bug 528706] autocomplete does not respect multiline tags |
| * - [Bug 531918] filter completions |
| *******************************************************************************/ |
| package org.eclipse.pde.internal.genericeditor.target.extension.autocomplete; |
| |
| import java.util.Arrays; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import javax.xml.stream.XMLStreamException; |
| |
| import org.eclipse.jface.resource.JFaceResources; |
| import org.eclipse.jface.text.BadLocationException; |
| 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.IContentAssistProcessor; |
| import org.eclipse.jface.text.contentassist.IContextInformation; |
| import org.eclipse.jface.text.contentassist.IContextInformationValidator; |
| import org.eclipse.jface.viewers.BoldStylerProvider; |
| import org.eclipse.jface.viewers.StyledString; |
| import org.eclipse.jface.viewers.StyledString.Styler; |
| import org.eclipse.pde.internal.genericeditor.target.extension.autocomplete.processors.AttributeNameCompletionProcessor; |
| import org.eclipse.pde.internal.genericeditor.target.extension.autocomplete.processors.AttributeValueCompletionProcessor; |
| import org.eclipse.pde.internal.genericeditor.target.extension.autocomplete.processors.TagCompletionProcessor; |
| import org.eclipse.pde.internal.genericeditor.target.extension.autocomplete.processors.TagValueCompletionProcessor; |
| import org.eclipse.pde.internal.genericeditor.target.extension.model.xml.Parser; |
| |
| /** |
| * |
| * Main content assist class that is used to dispatch the specific content |
| * assist types (see COMPLETION_TYPE_* fields). Uses regex to match each type. |
| * |
| */ |
| public class TargetDefinitionContentAssist implements IContentAssistProcessor { |
| |
| private static final String PREVIOUS_TAGS_MATCH = "(\\s*<(.|\\n)*>\\s*)*"; //$NON-NLS-1$ |
| private static final String ATTRIBUTE_NAME_SEARCH_TERM_MATCH = PREVIOUS_TAGS_MATCH |
| .concat("\\s*<\\s*\\w*(\\s*\\w*\\s*=\\s*\".*?\")*\\s+(?<searchTerm>\\w*)"); //$NON-NLS-1$ |
| private static final String TAG_SEARCH_TERM_MATCH = PREVIOUS_TAGS_MATCH.concat("\\s*<\\s*(?<searchTerm>\\w*)"); //$NON-NLS-1$ |
| private static final String ATTRIBUTE_VALUE_MATCH_REGEXP = PREVIOUS_TAGS_MATCH |
| .concat("\\s*<\\s*\\w*(\\s+\\w+\\s*=\\s*\"(.|\\n)*?\")*\\s+\\w+\\s*=\\s*\"[^\"]*"); //$NON-NLS-1$ |
| private static final String ATTRIBUTE_VALUE_ACKEY_MATCH = PREVIOUS_TAGS_MATCH |
| .concat("\\s*<\\s*\\w*(\\s+\\w+\\s*=\\s*\".*?\")*\\s+(?<ackey>\\w+)\\s*=\\s*\"[^\"]*"); //$NON-NLS-1$ |
| private static final String ATTRIBUTE_VALUE_SEARCH_TERM_MATCH = PREVIOUS_TAGS_MATCH |
| .concat("\\s*<\\s*\\w*(\\s+\\w+\\s*=\\s*\".*?\")*\\s+\\w+\\s*=\\s*\"(?<searchTerm>[^\"]*)"); //$NON-NLS-1$ |
| private static final String ATTRIBUTE_NAME_MATCH_REGEXP = PREVIOUS_TAGS_MATCH |
| .concat("\\s*<\\s*\\w*(\\s*\\w+\\s*=\\s*\"(.|\\n)*?\")*\\s+\\w*"); //$NON-NLS-1$ |
| private static final String ATTRIBUTE_NAME_ACKEY_MATCH = PREVIOUS_TAGS_MATCH |
| .concat("\\s*<\\s*(?<ackey>\\w*)(\\s*\\w+\\s*=\\s*\".*?\")*\\s+\\w*"); //$NON-NLS-1$ |
| private static final String TAG_MATCH_REGEXP = PREVIOUS_TAGS_MATCH.concat("\\s*<\\s*\\w*"); //$NON-NLS-1$ |
| private static final String TAG_VALUE_MATCH_REGEXP = PREVIOUS_TAGS_MATCH.concat("\\s*<\\s*\\w+[^<]*>\\s*\\w*"); //$NON-NLS-1$ |
| private static final String TAG_VALUE_SEARCH_TERM_MATCH = PREVIOUS_TAGS_MATCH.concat("\\s*(?<searchTerm>\\w*)"); //$NON-NLS-1$ |
| private static final String TAG_VALUE_ACKEY_MATCH = PREVIOUS_TAGS_MATCH.concat("\\s*<(?<ackey>\\w*).*"); //$NON-NLS-1$ |
| |
| private static final int COMPLETION_TYPE_TAG = 0; |
| private static final int COMPLETION_TYPE_ATTRIBUTE_NAME = 1; |
| private static final int COMPLETION_TYPE_ATTRIBUTE_VALUE = 2; |
| private static final int COMPLETION_TYPE_HEADER = 4; |
| private static final int COMPLETION_TYPE_TAG_VALUE = 5; |
| private static final int COMPLETION_TYPE_UNKNOWN = 6; |
| |
| private static final Pattern TAG_SEARCH_TERM_PATTERN = Pattern.compile(TAG_SEARCH_TERM_MATCH, Pattern.DOTALL); |
| private static final Pattern ATT_NAME_SEARCH_TERM_PATTERN = Pattern.compile(ATTRIBUTE_NAME_SEARCH_TERM_MATCH, |
| Pattern.DOTALL); |
| private static final Pattern ATTR_NAME_ACKEY_MATCH = Pattern.compile(ATTRIBUTE_NAME_ACKEY_MATCH, Pattern.DOTALL); |
| private static final Pattern ATTR_VALUE_SEARCH_TERM_PATTERN = Pattern.compile(ATTRIBUTE_VALUE_SEARCH_TERM_MATCH, |
| Pattern.DOTALL); |
| private static final Pattern ATTR_VALUE_ACKEY_PATTERN = Pattern.compile(ATTRIBUTE_VALUE_ACKEY_MATCH, |
| Pattern.DOTALL); |
| private static final Pattern TAG_VALUE_SEARCH_TERM_PATTERN = Pattern.compile(TAG_VALUE_SEARCH_TERM_MATCH, |
| Pattern.DOTALL); |
| private static final Pattern TAG_VALUE_ACKEY_PATTERN = Pattern.compile(TAG_VALUE_ACKEY_MATCH, Pattern.DOTALL); |
| |
| private String searchTerm = ""; //$NON-NLS-1$ |
| private String acKey; |
| |
| @Override |
| public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) { |
| IDocument document = viewer.getDocument(); |
| String text = document.get(); |
| try { |
| Parser.getDefault().parse(document); |
| } catch (XMLStreamException e) { |
| // TODO handle parsing errors |
| } |
| |
| int completionType = detectCompletionType(document, text, offset); |
| if (completionType == COMPLETION_TYPE_UNKNOWN) { |
| return new ICompletionProposal[0]; |
| } |
| |
| if (completionType == COMPLETION_TYPE_TAG) { |
| TagCompletionProcessor processor = new TagCompletionProcessor(searchTerm, acKey, offset); |
| return processor.getCompletionProposals(); |
| } |
| |
| if (completionType == COMPLETION_TYPE_ATTRIBUTE_NAME) { |
| AttributeNameCompletionProcessor processor = new AttributeNameCompletionProcessor(searchTerm, acKey, offset, |
| text); |
| return processor.getCompletionProposals(); |
| } |
| |
| if (completionType == COMPLETION_TYPE_ATTRIBUTE_VALUE) { |
| AttributeValueCompletionProcessor processor = new AttributeValueCompletionProcessor(searchTerm, acKey, |
| offset); |
| return processor.getCompletionProposals(); |
| } |
| |
| if (completionType == COMPLETION_TYPE_TAG_VALUE) { |
| TagValueCompletionProcessor processor = new TagValueCompletionProcessor(searchTerm, acKey, offset); |
| return processor.getCompletionProposals(); |
| } |
| |
| return new ICompletionProposal[0]; |
| } |
| |
| private int detectCompletionType(IDocument doc, String text, int offset) { |
| |
| if (offset == 0) { |
| return COMPLETION_TYPE_HEADER; |
| } |
| |
| try { |
| doc.getLineInformationOfOffset(offset); |
| } catch (BadLocationException e) { |
| e.printStackTrace(); |
| return COMPLETION_TYPE_UNKNOWN; |
| } |
| int indexOfLastTagStart = text.lastIndexOf('<', offset - 1); |
| String tagText = text.substring(Math.max(0, indexOfLastTagStart), offset); |
| if (tagText.matches(TAG_MATCH_REGEXP)) { |
| Matcher matcher = TAG_SEARCH_TERM_PATTERN.matcher(tagText); |
| matcher.matches(); |
| searchTerm = matcher.group("searchTerm"); //$NON-NLS-1$ |
| return COMPLETION_TYPE_TAG; |
| } |
| |
| if (tagText.matches(ATTRIBUTE_NAME_MATCH_REGEXP)) { |
| Matcher matcher = ATT_NAME_SEARCH_TERM_PATTERN.matcher(tagText); |
| matcher.matches(); |
| searchTerm = matcher.group("searchTerm"); //$NON-NLS-1$ |
| matcher = ATTR_NAME_ACKEY_MATCH.matcher(tagText); |
| matcher.matches(); |
| acKey = matcher.group("ackey"); //$NON-NLS-1$ |
| return COMPLETION_TYPE_ATTRIBUTE_NAME; |
| } |
| |
| if (tagText.matches(ATTRIBUTE_VALUE_MATCH_REGEXP)) { |
| Matcher matcher = ATTR_VALUE_SEARCH_TERM_PATTERN.matcher(tagText); |
| matcher.matches(); |
| searchTerm = matcher.group("searchTerm"); //$NON-NLS-1$ |
| matcher = ATTR_VALUE_ACKEY_PATTERN.matcher(tagText); |
| matcher.matches(); |
| acKey = matcher.group("ackey"); //$NON-NLS-1$ |
| return COMPLETION_TYPE_ATTRIBUTE_VALUE; |
| } |
| |
| if (tagText.matches(TAG_VALUE_MATCH_REGEXP)) { |
| Matcher matcher = TAG_VALUE_SEARCH_TERM_PATTERN.matcher(tagText); |
| matcher.matches(); |
| searchTerm = matcher.group("searchTerm"); //$NON-NLS-1$ |
| matcher = TAG_VALUE_ACKEY_PATTERN.matcher(tagText); |
| matcher.matches(); |
| acKey = matcher.group("ackey"); //$NON-NLS-1$ |
| return COMPLETION_TYPE_TAG_VALUE; |
| } |
| |
| return COMPLETION_TYPE_UNKNOWN; |
| } |
| |
| @Override |
| public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) { |
| return null; |
| } |
| |
| @Override |
| public char[] getCompletionProposalAutoActivationCharacters() { |
| return new char[] { '<' }; |
| } |
| |
| @Override |
| public char[] getContextInformationAutoActivationCharacters() { |
| return null; |
| } |
| |
| @Override |
| public String getErrorMessage() { |
| return null; |
| } |
| |
| @Override |
| public IContextInformationValidator getContextInformationValidator() { |
| return null; |
| } |
| |
| private static Styler bold = new BoldStylerProvider( |
| JFaceResources.getFontRegistry().get(JFaceResources.DEFAULT_FONT)).getBoldStyler(); |
| |
| /** |
| * Uses a search term to determine if a string is a match. If it is a match, |
| * then a StyledString is generated showing how it is matched. |
| * |
| * Matches if searchTerm is empty, string contains searchTerm, or if searchTerm |
| * matches string using the camelCase technique where digits and symbols are |
| * considered as upper case letters |
| * |
| * @param string |
| * The string in question |
| * @param searchTerm |
| * The query string |
| * @return string styled showing how searchTerm is matched, or null if not |
| * matched |
| */ |
| public static StyledString getFilteredStyledString(String string, String searchTerm) { |
| if (string == null) { |
| return null; |
| } |
| if (searchTerm.isEmpty()) { |
| return new StyledString(string); |
| } else if (string.toLowerCase().contains(searchTerm.toLowerCase())) { |
| int index = string.toLowerCase().indexOf(searchTerm.toLowerCase()); |
| int len = searchTerm.length(); |
| StyledString styledString = new StyledString(string.substring(0, index)); |
| styledString.append(string.substring(index, index + len), bold); |
| return styledString.append(string.substring(index + len, string.length())); |
| } |
| int searchCharIndex = 0; |
| int subStringCharIndex = 0; |
| String[] stringParts = string.split("((?=[A-Z])|(?<=[._])|(?=[0-9])(?<![0-9]))"); |
| StyledString styledString = new StyledString(); |
| while (searchCharIndex < searchTerm.length()) { |
| for (String subString : stringParts) { |
| if (searchCharIndex == searchTerm.length()) { |
| styledString.append(subString); |
| continue; |
| } |
| while (searchCharIndex < searchTerm.length() && subStringCharIndex < subString.length()) { |
| if (subString.charAt(subStringCharIndex) == searchTerm.charAt(searchCharIndex)) { |
| searchCharIndex++; |
| subStringCharIndex++; |
| } else { |
| break; |
| } |
| } |
| if (subStringCharIndex > 0) { |
| styledString.append(subString.substring(0, subStringCharIndex), bold); |
| styledString.append(subString.substring(subStringCharIndex)); |
| subStringCharIndex = 0; |
| } else { |
| styledString.append(subString); |
| } |
| } |
| if (searchCharIndex == searchTerm.length()) { |
| // All of searchTerm has matched in the string |
| return styledString; |
| } else if (stringParts.length == 0) { |
| // Have gone through all substrings without match |
| return null; |
| } else { |
| // Try again looking beyond first substring |
| searchCharIndex = 0; |
| subStringCharIndex = 0; |
| stringParts = Arrays.copyOfRange(stringParts, 1, stringParts.length); |
| styledString = new StyledString(); |
| } |
| } |
| return null; |
| } |
| } |