/******************************************************************************* * Copyright (c) 2008 Phil Muldoon <pkmuldoon@picobot.org>. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Phil Muldoon <pkmuldoon@picobot.org> - initial API and implementation. *******************************************************************************/ package org.eclipse.linuxtools.internal.systemtap.ui.ide.editors.stp; import java.util.ArrayList; import java.util.Arrays; import java.util.regex.Pattern; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.BadPartitioningException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentExtension3; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextHover; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.ITypedRegion; import org.eclipse.jface.text.contentassist.CompletionProposal; import org.eclipse.jface.text.contentassist.ContextInformation; 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.linuxtools.internal.systemtap.ui.ide.editors.stp.proposals.STPFunctionCompletionProposal; import org.eclipse.linuxtools.internal.systemtap.ui.ide.editors.stp.proposals.STPProbeCompletionProposal; import org.eclipse.linuxtools.internal.systemtap.ui.ide.editors.stp.proposals.STPProbevarCompletionProposal; import org.eclipse.linuxtools.internal.systemtap.ui.ide.structures.ManpageCacher; import org.eclipse.linuxtools.internal.systemtap.ui.ide.structures.TapsetItemType; import org.eclipse.linuxtools.systemtap.structures.TreeNode; public class STPCompletionProcessor implements IContentAssistProcessor, ITextHover { private final IContextInformation[] NO_CONTEXTS = new IContextInformation[0]; private final char[] PROPOSAL_ACTIVATION_CHARS = new char[] { '.' }; private ICompletionProposal[] NO_COMPLETIONS = new ICompletionProposal[0]; private static final String GLOBAL_KEYWORD = "global "; //$NON-NLS-1$ private static final String PROBE_KEYWORD = "probe "; //$NON-NLS-1$ private static final String FUNCTION_KEYWORD = "function "; //$NON-NLS-1$ private static final String[][] GLOBAL_KEYWORDS = { { GLOBAL_KEYWORD, Messages.STPCompletionProcessor_global }, { PROBE_KEYWORD, Messages.STPCompletionProcessor_probe }, { FUNCTION_KEYWORD, Messages.STPCompletionProcessor_function } }; private STPMetadataSingleton stpMetadataSingleton; private static class Token implements IRegion { String tokenString; int offset; public Token(String string, int n) { this.tokenString = string; this.offset = n; } @Override public int getLength() { return this.tokenString.length(); } @Override public int getOffset() { return this.offset; } } public STPCompletionProcessor() { this.stpMetadataSingleton = STPMetadataSingleton.getInstance(); } @Override public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) { return computeCompletionProposals(viewer.getDocument(), offset); } public ICompletionProposal[] computeCompletionProposals(IDocument document, int offset) { ITypedRegion partition = null; boolean useGlobal = false; try { partition = ((IDocumentExtension3) document).getPartition(STPProbeScanner.STP_PROBE_PARTITIONING, offset, false); if (partition.getOffset() == offset) { if (partition.getType() != IDocument.DEFAULT_CONTENT_TYPE && partition.getType() != STPProbeScanner.STP_PROBE) { if (offset > 0) { ITypedRegion prevPartition = ((IDocumentExtension3) document).getPartition(STPProbeScanner.STP_PROBE_PARTITIONING, offset - 1, false); useGlobal = prevPartition.getType() == IDocument.DEFAULT_CONTENT_TYPE; } else { useGlobal = true; } } } } catch (BadLocationException|BadPartitioningException e) { return NO_COMPLETIONS; } String prefix = ""; //$NON-NLS-1$ String prePrefix = ""; //$NON-NLS-1$ // Get completion hint from document try { prefix = getPrefix(document, offset); Token previousToken = getPrecedingToken(document, offset - prefix.length() - 1); while (previousToken.tokenString.equals("=") || //$NON-NLS-1$ previousToken.tokenString.equals(",") ) { //$NON-NLS-1$ previousToken = getPrecedingToken(document, previousToken.offset - 1); previousToken = getPrecedingToken(document, previousToken.offset - 1); } prePrefix = previousToken.tokenString; } catch (BadLocationException e) { return NO_COMPLETIONS; } if (prePrefix.startsWith("probe")) { //$NON-NLS-1$ return getProbeCompletionList(prefix, offset); } // If inside a probe return probe variable completions and functions // which can be called. if (partition.getType() == STPProbeScanner.STP_PROBE) { ICompletionProposal[] variableCompletions = getProbeVariableCompletions(document, offset, prefix); ICompletionProposal[] functionCompletions = getFunctionCompletions(offset, prefix); ArrayList<ICompletionProposal> completions = new ArrayList<>( variableCompletions.length + functionCompletions.length); completions.addAll(Arrays.asList(variableCompletions)); completions.addAll(Arrays.asList(functionCompletions)); return completions.toArray(new ICompletionProposal[0]); } else if (partition.getType() == IDocument.DEFAULT_CONTENT_TYPE || useGlobal) { // In the global scope return global keyword completion. return getGlobalKeywordCompletion(prefix, offset); } return NO_COMPLETIONS; } private ICompletionProposal[] getFunctionCompletions(int offset, String prefix) { TreeNode[] completionData = stpMetadataSingleton.getFunctionCompletions(prefix); ICompletionProposal[] result = new ICompletionProposal[completionData.length]; for (int i = 0; i < completionData.length; i++) { result[i] = new STPFunctionCompletionProposal( completionData[i], prefix.length(), offset); } return result; } private ICompletionProposal[] getProbeVariableCompletions(IDocument document, int offset, String prefix) { try { String probeName = getProbe(document, offset); TreeNode[] completionData = stpMetadataSingleton .getProbeVariableCompletions(probeName, prefix); ICompletionProposal[] result = new ICompletionProposal[completionData.length]; for (int i = 0; i < completionData.length; i++) { result[i] = new STPProbevarCompletionProposal( completionData[i], prefix.length(), offset, probeName); } return result; } catch (BadLocationException|BadPartitioningException e) { return NO_COMPLETIONS; } } /** * Returns the full name of the probe surrounding the given * offset. This function assumes that the given offset is inside * of a {@link STPPartitionScanner#STP_PROBE} section. * @param document * @param offset * @return the probe name * @throws BadLocationException * @throws BadPartitioningException */ private String getProbe(IDocument document, int offset) throws BadLocationException, BadPartitioningException { String probePoint = null; ITypedRegion partition = ((IDocumentExtension3)document).getPartition(STPProbeScanner.STP_PROBE_PARTITIONING, offset, false); String probe = document.get(partition.getOffset(), partition.getLength()); // make sure that we are inside a probe if (probe.startsWith(PROBE_KEYWORD)) { probePoint = probe.substring(PROBE_KEYWORD.length(), probe.indexOf('{')); probePoint = probePoint.trim(); } return probePoint; } private ICompletionProposal[] getProbeCompletionList(String prefix, int offset) { prefix = canonicalizePrefix(prefix); TreeNode[] completionData = stpMetadataSingleton.getProbeCompletions(prefix); ICompletionProposal[] result = new ICompletionProposal[completionData.length]; for (int i = 0; i < completionData.length; i++) { result[i] = new STPProbeCompletionProposal( completionData[i], prefix.length(), offset); } return result; } /** * Returns a standardized version of the given prefix so that completion matching * can be performed. * For example for process("/some/long/path") this returns process(string); * @param prefix * @return */ private String canonicalizePrefix(String prefix) { if (prefix.isEmpty()) { return ""; //$NON-NLS-1$ } prefix = prefix.replaceAll("(?s)\\(\\s*\".*\"\\s*\\)", "(string)"); //$NON-NLS-1$ //$NON-NLS-2$ prefix = prefix.replaceAll("(?s)\\(\\s*\\d*\\s*\\)", "(number)"); //$NON-NLS-1$ //$NON-NLS-2$ return prefix; } private ICompletionProposal[] getGlobalKeywordCompletion(String prefix, int offset) { ArrayList<ICompletionProposal> completions = new ArrayList<>(); int prefixLength = prefix.length(); for (String[] keyword : GLOBAL_KEYWORDS) { if (keyword[0].startsWith(prefix)) { CompletionProposal proposal = new CompletionProposal( keyword[0].substring(prefixLength), offset, 0, keyword[0].length() - prefixLength, null, keyword[0], new ContextInformation("contextDisplayString", "informationDisplayString"), //$NON-NLS-1$ //$NON-NLS-2$ keyword[1]); completions.add(proposal); } } return completions.toArray(new ICompletionProposal[0]); } /** * Returns the token preceding the current completion position. * @param doc The document for the which the completion is requested. * @param prefix The prefix for which the user has requested completion. * @param offset Current offset in the document * @return The preceding token. * @throws BadLocationException */ private Token getPrecedingToken(IDocument doc, int offset) throws BadLocationException { // Skip trailing space int n = offset; while (n >= 0 && Character.isSpaceChar(doc.getChar(n))) { n--; } char c = doc.getChar(n); if (isTokenDelimiter(c)) { return new Token(Character.toString(c), n); } int end = n; while (n >= 0 && !isTokenDelimiter(doc.getChar(n))) { n--; } return new Token(doc.get(n+1, end-n), n+1); } private Token getCurrentToken(IDocument doc, int offset) throws BadLocationException { char c = doc.getChar(offset); if (isDelimiter(c)) { return new Token(Character.toString(c), offset); } int start = offset; while (start >= 0 && !isDelimiter(doc.getChar(start))) { start--; } int end = offset; while (end < doc.getLength() && !isDelimiter(doc.getChar(end))) { end++; } start++; return new Token(doc.get(start, end-start), start); } private boolean isFunctionRegion(IDocument doc, IRegion region) throws BadLocationException { int start = region.getLength() + region.getOffset(); while (isTokenDelimiter(doc.getChar(start))) { start++; } return doc.getChar(start) == '('; } /** * * Return the word the user wants to submit for completion proposals. * * @param doc - document to insert completion. * @param offset - offset of where completion hint was first generated. * @return - word to generate completion proposals. * * @throws BadLocationException */ private String getPrefix(IDocument doc, int offset) throws BadLocationException { for (int n = offset - 1; n >= 0; n--) { char c = doc.getChar(n); if (isTokenDelimiter(c)) { return doc.get(n + 1, offset - n - 1); } } return ""; //$NON-NLS-1$ } private boolean isTokenDelimiter(char c) { if (Character.isWhitespace(c)) { return true; } switch (c) { case '\n': case '\0': case ',': case '{': case '}': case ']': case '[': return true; } return false; } private boolean isDelimiter(char c) { if (isTokenDelimiter(c)) { return true; } switch (c) { case '.': case '$': return false; } return Pattern.matches("\\W", Character.toString(c)); //$NON-NLS-1$ } @Override public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) { return NO_CONTEXTS; } @Override public char[] getCompletionProposalAutoActivationCharacters() { return PROPOSAL_ACTIVATION_CHARS; } @Override public char[] getContextInformationAutoActivationCharacters() { return PROPOSAL_ACTIVATION_CHARS; } @Override public IContextInformationValidator getContextInformationValidator() { return null; } @Override public String getErrorMessage() { // TODO: When does this trigger? return "Error."; //$NON-NLS-1$ } @Override public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) { String documentation = null; try { String keyword = textViewer.getDocument().get(hoverRegion.getOffset(), hoverRegion.getLength()); int offset = hoverRegion.getOffset(); IDocument document = textViewer.getDocument(); if (getPrecedingToken(document, offset - 1).tokenString.equals(PROBE_KEYWORD.trim())) { documentation = ManpageCacher.getDocumentation(TapsetItemType.PROBE, keyword); } else { ITypedRegion partition = ((IDocumentExtension3)document).getPartition(STPProbeScanner.STP_PROBE_PARTITIONING, offset, false); if (partition.getType() == STPProbeScanner.STP_PROBE) { if (isFunctionRegion(document, hoverRegion)) { documentation = ManpageCacher.getDocumentation(TapsetItemType.FUNCTION, keyword); } else { String probe = getProbe(document, offset); if (stpMetadataSingleton.isVariableInProbe(probe, keyword)) { documentation = ManpageCacher.getDocumentation(TapsetItemType.PROBEVAR, probe, keyword); } } } } } catch (BadLocationException|BadPartitioningException e) { // Bad hover location/scenario; just ignore it. } return documentation; } @Override public IRegion getHoverRegion(ITextViewer textViewer, int offset) { try { return getCurrentToken(textViewer.getDocument(), offset); } catch (BadLocationException e) { // Bad hover location; just ignore it. } return null; } }