|
From: Christopher W. <caw...@us...> - 2006-02-18 16:53:53
|
Update of /cvsroot/rubyeclipse/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/text/template/contentassist In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv28822/src/org/rubypeople/rdt/internal/ui/text/template/contentassist Added Files: VariablePosition.java TemplateProposal.java TemplateEngine.java TemplateContentAssistMessages.java MultiVariableGuess.java RubyTemplateVariableTextHover.java RubyTemplateAccess.java PositionBasedCompletionProposal.java MultiVariable.java TemplateInformationControlCreator.java TemplateContentAssistMessages.properties Log Message: overhaul our completion proposal/template code. Now we will apply formatting to templates when inserted! Plus we're closer to the way JDT does things. We still can't use the actual code formatter on the templates (because our old one does a massive replace edit which messes up variables like the cursor placement). The end result is that users should create templates with somewhat proper formatting initially. --- NEW FILE: TemplateContentAssistMessages.java --- /******************************************************************************* * Copyright (c) 2000, 2005 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.rubypeople.rdt.internal.ui.text.template.contentassist; import org.eclipse.osgi.util.NLS; /** * Helper class to get NLSed messages. */ final class TemplateContentAssistMessages extends NLS { private static final String BUNDLE_NAME= TemplateContentAssistMessages.class.getName(); private TemplateContentAssistMessages() { // Do not instantiate } public static String TemplateProposal_displayString; public static String TemplateEvaluator_error_title; static { NLS.initializeMessages(BUNDLE_NAME, TemplateContentAssistMessages.class); } } --- NEW FILE: TemplateInformationControlCreator.java --- /******************************************************************************* * Copyright (c) 2000, 2005 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.rubypeople.rdt.internal.ui.text.template.contentassist; import org.eclipse.jface.text.IInformationControl; import org.eclipse.jface.text.IInformationControlCreator; import org.eclipse.jface.text.IInformationControlCreatorExtension; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.widgets.Shell; import org.rubypeople.rdt.internal.ui.text.ruby.hover.SourceViewerInformationControl; final public class TemplateInformationControlCreator implements IInformationControlCreator, IInformationControlCreatorExtension { private SourceViewerInformationControl fControl; public TemplateInformationControlCreator() { } /* * @see org.eclipse.jface.text.IInformationControlCreator#createInformationControl(org.eclipse.swt.widgets.Shell) */ public IInformationControl createInformationControl(Shell parent) { fControl= new SourceViewerInformationControl(parent); fControl.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { fControl= null; } }); return fControl; } /* * @see org.eclipse.jface.text.IInformationControlCreatorExtension#canReuse(org.eclipse.jface.text.IInformationControl) */ public boolean canReuse(IInformationControl control) { return fControl == control && fControl != null; } /* * @see org.eclipse.jface.text.IInformationControlCreatorExtension#canReplace(org.eclipse.jface.text.IInformationControlCreator) */ public boolean canReplace(IInformationControlCreator creator) { return (creator != null && getClass() == creator.getClass()); } } --- NEW FILE: RubyTemplateAccess.java --- /******************************************************************************* * Copyright (c) 2000, 2004 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Common Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/cpl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.rubypeople.rdt.internal.ui.text.template.contentassist; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtension; import org.eclipse.core.runtime.IExtensionPoint; import org.eclipse.core.runtime.IExtensionRegistry; import org.eclipse.core.runtime.Platform; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.templates.ContextTypeRegistry; import org.eclipse.jface.text.templates.persistence.TemplatePersistenceData; import org.eclipse.jface.text.templates.persistence.TemplateStore; import org.eclipse.ui.editors.text.templates.ContributionContextTypeRegistry; import org.eclipse.ui.editors.text.templates.ContributionTemplateStore; import org.rubypeople.rdt.internal.corext.template.ruby.RubyContextType; import org.rubypeople.rdt.internal.ui.RubyPlugin; import org.rubypeople.rdt.ui.extensions.IRubyTemplateProvider; public class RubyTemplateAccess { /** Key to store custom templates. */ private static final String CUSTOM_TEMPLATES_KEY= "org.rubypeople.rdt.ui.customtemplates"; //$NON-NLS-1$ /** The shared instance. */ private static RubyTemplateAccess fgInstance; /** The template store. */ private TemplateStore fStore; /** The context type registry. */ private ContributionContextTypeRegistry fContextTypeRegistry; private RubyTemplateAccess() {} /** * Returns the shared instance. * * @return the shared instance */ public static RubyTemplateAccess getDefault() { if (fgInstance == null) { fgInstance= new RubyTemplateAccess(); } return fgInstance; } /** * Returns this plug-in's template store. * * @return the template store of this plug-in instance */ public TemplateStore getTemplateStore() { if (fStore == null) { fStore= new ContributionTemplateStore(getContextTypeRegistry(),RubyPlugin.getDefault().getPreferenceStore(), CUSTOM_TEMPLATES_KEY); try { fStore.load(); } catch (IOException e) { RubyPlugin.log(e); } // Load extension templates TemplatePersistenceData[] tempData = getExtensionTemplateData(); if(tempData != null) { for(int i = 0; i < tempData.length; i++) { fStore.add(tempData[i]); } } } return fStore; } /** * Finds all extensions to the rubyTemplateProvider extension point and return their template data. * * @return an array of TemplatePersistenceData */ private TemplatePersistenceData[] getExtensionTemplateData() { List extensions = new ArrayList(); IExtensionRegistry reg = Platform.getExtensionRegistry(); IExtensionPoint[] points = reg.getExtensionPoints(RubyPlugin.PLUGIN_ID); IExtensionPoint point = null; // Search the extension registry for the rubyTemplateProvider extension point if(points != null){ for (int i = 0; i < points.length; i++) { IExtensionPoint currentPoint = points[i]; if(currentPoint.getUniqueIdentifier().endsWith("rubyTemplateProvider")){ point = currentPoint; break; } } // Find all extensions of the point if(point != null){ IExtension[] exts = point.getExtensions(); IRubyTemplateProvider prov = null; // Get the implementing class of the extension for (int i = 0; i < exts.length; i++) { IConfigurationElement[] elem = exts[i].getConfigurationElements(); String attrs[] = elem[0].getAttributeNames(); try { Object tempProv = elem[0].createExecutableExtension("class"); if (tempProv instanceof IRubyTemplateProvider) { prov = (IRubyTemplateProvider) tempProv; extensions.add(prov); } } catch (CoreException e) { RubyPlugin.log(e); } } } } // Get the template data from the extensions if(extensions.size() > 0){ for(int i=0; i< extensions.size(); i++){ IRubyTemplateProvider currentProvider = (IRubyTemplateProvider) extensions.get(i); TemplatePersistenceData[] templates = currentProvider.getTemplateData(); if(templates != null){ return templates; } } } return null; } /** * Returns this plug-in's context type registry. * * @return the context type registry for this plug-in instance */ public ContextTypeRegistry getContextTypeRegistry() { if (fContextTypeRegistry == null) { // create and configure the contexts available in the template editor fContextTypeRegistry= new ContributionContextTypeRegistry(); fContextTypeRegistry.addContextType(new RubyContextType()); } return fContextTypeRegistry; } public IPreferenceStore getPreferenceStore() { return RubyPlugin.getDefault().getPreferenceStore(); } public void savePluginPreferences() { RubyPlugin.getDefault().savePluginPreferences(); } } --- NEW FILE: MultiVariable.java --- /******************************************************************************* * Copyright (c) 2000, 2005 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.rubypeople.rdt.internal.ui.text.template.contentassist; import java.util.HashMap; import java.util.Map; import org.eclipse.jface.text.Assert; import org.eclipse.jface.text.templates.TemplateVariable; /** * */ public class MultiVariable extends TemplateVariable { private final Map fValueMap= new HashMap(); private Object fSet; private Object fDefaultKey= null; public MultiVariable(String type, String defaultValue, int[] offsets) { super(type, defaultValue, offsets); fValueMap.put(fDefaultKey, new String[] { defaultValue }); fSet= getDefaultValue(); } /** * Sets the values of this variable under a specific set. * * @param set the set identifier for which the values are valid * @param values the possible values of this variable */ public void setValues(Object set, String[] values) { Assert.isNotNull(set); Assert.isTrue(values.length > 0); fValueMap.put(set, values); if (fDefaultKey == null) { fDefaultKey= set; fSet= getDefaultValue(); } } /* * @see org.eclipse.jface.text.templates.TemplateVariable#setValues(java.lang.String[]) */ public void setValues(String[] values) { if (fValueMap != null) { Assert.isNotNull(values); Assert.isTrue(values.length > 0); fValueMap.put(fDefaultKey, values); fSet= getDefaultValue(); } } /* * @see org.eclipse.jface.text.templates.TemplateVariable#getValues() */ public String[] getValues() { return (String[]) fValueMap.get(fDefaultKey); } /** * Returns the choices for the set identified by <code>set</code>. * * @param set the set identifier * @return the choices for this variable and the given set, or * <code>null</code> if the set is not defined. */ public String[] getValues(Object set) { return (String[]) fValueMap.get(set); } /** * @return */ public Object getSet() { return fSet; } public void setSet(Object set) { fSet= set; } } --- NEW FILE: RubyTemplateVariableTextHover.java --- /******************************************************************************* * Copyright (c) 2000, 2004 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Common Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/cpl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.rubypeople.rdt.internal.ui.text.template.contentassist; import java.util.Iterator; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextHover; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.templates.TemplateContextType; import org.eclipse.jface.text.templates.TemplateVariableResolver; import org.rubypeople.rdt.internal.corext.template.ruby.RubyContextType; public class RubyTemplateVariableTextHover implements ITextHover { public RubyTemplateVariableTextHover() { } /* (non-Javadoc) * @see org.eclipse.jface.text.ITextHover#getHoverInfo(org.eclipse.jface.text.ITextViewer, org.eclipse.jface.text.IRegion) */ public String getHoverInfo(ITextViewer textViewer, IRegion subject) { try { IDocument doc= textViewer.getDocument(); int offset= subject.getOffset(); if (offset >= 2 && "${".equals(doc.get(offset-2, 2))) { //$NON-NLS-1$ String varName= doc.get(offset, subject.getLength()); TemplateContextType contextType= RubyTemplateAccess.getDefault().getContextTypeRegistry().getContextType(RubyContextType.NAME); if (contextType != null) { Iterator iter= contextType.resolvers(); while (iter.hasNext()) { TemplateVariableResolver var= (TemplateVariableResolver) iter.next(); if (varName.equals(var.getType())) { return var.getDescription(); } } } } } catch (BadLocationException e) { } return null; } /* (non-Javadoc) * @see org.eclipse.jface.text.ITextHover#getHoverRegion(org.eclipse.jface.text.ITextViewer, int) */ public IRegion getHoverRegion(ITextViewer textViewer, int offset) { if (textViewer != null) { // FIXME Rewrite this! I just stole it from Ant! IDocument document= textViewer.getDocument(); int start= -1; int end= -1; try { int pos= offset; char c; while (pos >= 0) { c= document.getChar(pos); if (c != '.' && c != '-' && c != '/' && c != '\\' && !Character.isJavaIdentifierPart(c)) break; --pos; } start= pos; pos= offset; int length= document.getLength(); while (pos < length) { c= document.getChar(pos); if (c != '.' && c != '-' && !Character.isJavaIdentifierPart(c)) break; ++pos; } end= pos; } catch (BadLocationException x) { } if (start > -1 && end > -1) { if (start == offset && end == offset) return new Region(offset, 0); else if (start == offset) return new Region(start, end - start); else return new Region(start + 1, end - start - 1); } return null; } return null; } } --- NEW FILE: TemplateEngine.java --- /******************************************************************************* * Copyright (c) 2000, 2005 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.rubypeople.rdt.internal.ui.text.template.contentassist; import java.util.ArrayList; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.templates.GlobalTemplateVariables; import org.eclipse.jface.text.templates.Template; import org.eclipse.jface.text.templates.TemplateContextType; import org.eclipse.swt.graphics.Point; import org.rubypeople.rdt.core.IRubyScript; import org.rubypeople.rdt.internal.corext.Assert; import org.rubypeople.rdt.internal.corext.template.ruby.RubyScriptContextType; import org.rubypeople.rdt.internal.corext.template.ruby.RubyScriptContext; import org.rubypeople.rdt.internal.ui.RubyPlugin; import org.rubypeople.rdt.internal.ui.RubyPluginImages; public class TemplateEngine { private static final String $_LINE_SELECTION= "${" + GlobalTemplateVariables.LineSelection.NAME + "}"; //$NON-NLS-1$ //$NON-NLS-2$ private static final String $_WORD_SELECTION= "${" + GlobalTemplateVariables.WordSelection.NAME + "}"; //$NON-NLS-1$ //$NON-NLS-2$ /** The context type. */ private TemplateContextType fContextType; /** The result proposals. */ private ArrayList fProposals= new ArrayList(); /** * Creates the template engine for a particular context type. * See <code>TemplateContext</code> for supported context types. */ public TemplateEngine(TemplateContextType contextType) { Assert.isNotNull(contextType); fContextType= contextType; } /** * Empties the collector. */ public void reset() { fProposals.clear(); } /** * Returns the array of matching templates. */ public TemplateProposal[] getResults() { return (TemplateProposal[]) fProposals.toArray(new TemplateProposal[fProposals.size()]); } /** * Inspects the context of the compilation unit around <code>completionPosition</code> * and feeds the collector with proposals. * @param viewer the text viewer * @param completionPosition the context position in the document of the text viewer * @param compilationUnit the compilation unit (may be <code>null</code>) */ public void complete(ITextViewer viewer, int completionPosition, IRubyScript compilationUnit) { IDocument document= viewer.getDocument(); if (!(fContextType instanceof RubyScriptContextType)) return; Point selection= viewer.getSelectedRange(); // remember selected text String selectedText= null; if (selection.y != 0) { try { selectedText= document.get(selection.x, selection.y); } catch (BadLocationException e) {} } RubyScriptContext context= ((RubyScriptContextType) fContextType).createContext(document, completionPosition, selection.y, compilationUnit); context.setVariable("selection", selectedText); //$NON-NLS-1$ int start= context.getStart(); int end= context.getEnd(); IRegion region= new Region(start, end - start); Template[] templates= RubyPlugin.getDefault().getTemplateStore().getTemplates(); if (selection.y == 0) { for (int i= 0; i != templates.length; i++) if (context.canEvaluate(templates[i])) fProposals.add(new TemplateProposal(templates[i], context, region, RubyPluginImages.get(RubyPluginImages.IMG_OBJS_TEMPLATE))); } else { if (context.getKey().length() == 0) context.setForceEvaluation(true); boolean multipleLinesSelected= areMultipleLinesSelected(viewer); for (int i= 0; i != templates.length; i++) { Template template= templates[i]; if (context.canEvaluate(template) && template.getContextTypeId().equals(context.getContextType().getId()) && (!multipleLinesSelected && template.getPattern().indexOf($_WORD_SELECTION) != -1 || (multipleLinesSelected && template.getPattern().indexOf($_LINE_SELECTION) != -1))) { fProposals.add(new TemplateProposal(templates[i], context, region, RubyPluginImages.get(RubyPluginImages.IMG_OBJS_TEMPLATE))); } } } } /** * Returns <code>true</code> if one line is completely selected or if multiple lines are selected. * Being completely selected means that all characters except the new line characters are * selected. * * @return <code>true</code> if one or multiple lines are selected * @since 2.1 */ private boolean areMultipleLinesSelected(ITextViewer viewer) { if (viewer == null) return false; Point s= viewer.getSelectedRange(); if (s.y == 0) return false; try { IDocument document= viewer.getDocument(); int startLine= document.getLineOfOffset(s.x); int endLine= document.getLineOfOffset(s.x + s.y); IRegion line= document.getLineInformation(startLine); return startLine != endLine || (s.x == line.getOffset() && s.y == line.getLength()); } catch (BadLocationException x) { return false; } } } --- NEW FILE: TemplateContentAssistMessages.properties --- ############################################################################### # Copyright (c) 2000, 2005 IBM Corporation and others. # 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: # IBM Corporation - initial API and implementation ############################################################################### # template proposal # The first argument is the name and the second is the description TemplateProposal_displayString= {0} - {1} # template evaluator TemplateEvaluator_error_title=Template Evaluation Error --- NEW FILE: MultiVariableGuess.java --- /******************************************************************************* * Copyright (c) 2000, 2005 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.rubypeople.rdt.internal.ui.text.template.contentassist; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.jface.text.Assert; 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.IContextInformation; /** * Global state for templates. Selecting a proposal for the master template variable * will cause the value (and the proposals) for the slave variables to change. * * @see MultiVariable */ public class MultiVariableGuess { /** * Implementation of the <code>ICompletionProposal</code> interface and extension. */ class Proposal implements ICompletionProposal, ICompletionProposalExtension2 { /** The string to be displayed in the completion proposal popup */ private String fDisplayString; /** The replacement string */ String fReplacementString; /** The replacement offset */ private int fReplacementOffset; /** The replacement length */ private int fReplacementLength; /** The cursor position after this proposal has been applied */ private int fCursorPosition; /** The image to be displayed in the completion proposal popup */ private Image fImage; /** The context information of this proposal */ private IContextInformation fContextInformation; /** The additional info of this proposal */ private String fAdditionalProposalInfo; /** * Creates a new completion proposal based on the provided information. The replacement string is * considered being the display string too. All remaining fields are set to <code>null</code>. * * @param replacementString the actual string to be inserted into the document * @param replacementOffset the offset of the text to be replaced * @param replacementLength the length of the text to be replaced * @param cursorPosition the position of the cursor following the insert relative to replacementOffset */ public Proposal(String replacementString, int replacementOffset, int replacementLength, int cursorPosition) { this(replacementString, replacementOffset, replacementLength, cursorPosition, null, null, null, null); } /** * Creates a new completion proposal. All fields are initialized based on the provided information. * * @param replacementString the actual string to be inserted into the document * @param replacementOffset the offset of the text to be replaced * @param replacementLength the length of the text to be replaced * @param cursorPosition the position of the cursor following the insert relative to replacementOffset * @param image the image to display for this proposal * @param displayString the string to be displayed for the proposal * @param contextInformation the context information associated with this proposal * @param additionalProposalInfo the additional information associated with this proposal */ public Proposal(String replacementString, int replacementOffset, int replacementLength, int cursorPosition, Image image, String displayString, IContextInformation contextInformation, String additionalProposalInfo) { Assert.isNotNull(replacementString); Assert.isTrue(replacementOffset >= 0); Assert.isTrue(replacementLength >= 0); Assert.isTrue(cursorPosition >= 0); fReplacementString= replacementString; fReplacementOffset= replacementOffset; fReplacementLength= replacementLength; fCursorPosition= cursorPosition; fImage= image; fDisplayString= displayString; fContextInformation= contextInformation; fAdditionalProposalInfo= additionalProposalInfo; } /* * @see ICompletionProposal#apply(IDocument) */ public void apply(IDocument document) { try { document.replace(fReplacementOffset, fReplacementLength, fReplacementString); } catch (BadLocationException x) { // ignore } } /* * @see ICompletionProposal#getSelection(IDocument) */ public Point getSelection(IDocument document) { return new Point(fReplacementOffset + fCursorPosition, 0); } /* * @see ICompletionProposal#getContextInformation() */ public IContextInformation getContextInformation() { return fContextInformation; } /* * @see ICompletionProposal#getImage() */ public Image getImage() { return fImage; } /* * @see ICompletionProposal#getDisplayString() */ public String getDisplayString() { if (fDisplayString != null) return fDisplayString; return fReplacementString; } /* * @see ICompletionProposal#getAdditionalProposalInfo() */ public String getAdditionalProposalInfo() { return fAdditionalProposalInfo; } /* * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#apply(org.eclipse.jface.text.ITextViewer, char, int, int) */ public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) { apply(viewer.getDocument()); } /* * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#selected(org.eclipse.jface.text.ITextViewer, boolean) */ public void selected(ITextViewer viewer, boolean smartToggle) { } /* * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#unselected(org.eclipse.jface.text.ITextViewer) */ public void unselected(ITextViewer viewer) { } /* * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#validate(org.eclipse.jface.text.IDocument, int, org.eclipse.jface.text.DocumentEvent) */ public boolean validate(IDocument document, int offset, DocumentEvent event) { try { String content= document.get(fReplacementOffset, fReplacementLength); if (content.startsWith(fReplacementString)) return true; } catch (BadLocationException e) { // ignore concurrently modified document } return false; } } private final List fSlaves= new ArrayList(); private MultiVariable fMaster; /** * @param mv */ public MultiVariableGuess(MultiVariable mv) { fMaster= mv; } /** * @param variable * @return */ public ICompletionProposal[] getProposals(MultiVariable variable, int offset, int length) { if (variable.equals(fMaster)) { String[] choices= variable.getValues(); ICompletionProposal[] ret= new ICompletionProposal[choices.length]; for (int i= 0; i < ret.length; i++) { ret[i]= new Proposal(choices[i], offset, length, offset + length) { /* * @see org.eclipse.jface.text.link.MultiVariableGuess.Proposal#apply(org.eclipse.jface.text.IDocument) */ public void apply(IDocument document) { super.apply(document); try { Object old= fMaster.getSet(); fMaster.setSet(fReplacementString); if (!fReplacementString.equals(old)) { for (Iterator it= fSlaves.iterator(); it.hasNext();) { VariablePosition pos= (VariablePosition) it.next(); String[] values= pos.getVariable().getValues(fReplacementString); if (values != null) document.replace(pos.getOffset(), pos.getLength(), values[0]); } } } catch (BadLocationException e) { // ignore and continue } } }; } return ret; } else { String[] choices= variable.getValues(fMaster.getSet()); if (choices == null || choices.length < 2) return null; ICompletionProposal[] ret= new ICompletionProposal[choices.length]; for (int i= 0; i < ret.length; i++) { ret[i]= new Proposal(choices[i], offset, length, offset + length); } return ret; } } /** * @param position */ public void addSlave(VariablePosition position) { fSlaves.add(position); } } --- NEW FILE: VariablePosition.java --- /******************************************************************************* * Copyright (c) 2000, 2005 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.rubypeople.rdt.internal.ui.text.template.contentassist; import org.eclipse.jface.text.Assert; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.text.link.LinkedPositionGroup; import org.eclipse.jface.text.link.ProposalPosition; /** * */ public class VariablePosition extends ProposalPosition { private MultiVariableGuess fGuess; private MultiVariable fVariable; public VariablePosition(IDocument document, int offset, int length, MultiVariableGuess guess, MultiVariable variable) { this(document, offset, length, LinkedPositionGroup.NO_STOP, guess, variable); } public VariablePosition(IDocument document, int offset, int length, int sequence, MultiVariableGuess guess, MultiVariable variable) { super(document, offset, length, sequence, null); Assert.isNotNull(guess); Assert.isNotNull(variable); fVariable= variable; fGuess= guess; } /* * @see org.eclipse.jface.text.link.ProposalPosition#equals(java.lang.Object) */ public boolean equals(Object o) { if (o instanceof VariablePosition && super.equals(o)) { return fGuess.equals(((VariablePosition) o).fGuess); } return false; } /* * @see org.eclipse.jface.text.link.ProposalPosition#hashCode() */ public int hashCode() { return super.hashCode() | fGuess.hashCode(); } /* * @see org.eclipse.jface.text.link.ProposalPosition#getChoices() */ public ICompletionProposal[] getChoices() { return fGuess.getProposals(fVariable, offset, length); } /** * @return */ public MultiVariable getVariable() { return fVariable; } } --- NEW FILE: TemplateProposal.java --- /******************************************************************************* * Copyright (c) 2000, 2005 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.rubypeople.rdt.internal.ui.text.template.contentassist; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.BadPositionCategoryException; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IInformationControlCreator; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2; import org.eclipse.jface.text.contentassist.ICompletionProposalExtension3; import org.eclipse.jface.text.contentassist.ICompletionProposalExtension4; import org.eclipse.jface.text.contentassist.IContextInformation; import org.eclipse.jface.text.link.ILinkedModeListener; import org.eclipse.jface.text.link.InclusivePositionUpdater; import org.eclipse.jface.text.link.LinkedModeModel; import org.eclipse.jface.text.link.LinkedModeUI; import org.eclipse.jface.text.link.LinkedPosition; import org.eclipse.jface.text.link.LinkedPositionGroup; import org.eclipse.jface.text.link.ProposalPosition; import org.eclipse.jface.text.templates.DocumentTemplateContext; import org.eclipse.jface.text.templates.GlobalTemplateVariables; import org.eclipse.jface.text.templates.Template; import org.eclipse.jface.text.templates.TemplateBuffer; import org.eclipse.jface.text.templates.TemplateContext; import org.eclipse.jface.text.templates.TemplateException; import org.eclipse.jface.text.templates.TemplateVariable; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.texteditor.link.EditorLinkedModeUI; import org.rubypeople.rdt.internal.corext.Assert; import org.rubypeople.rdt.internal.corext.template.ruby.RubyScriptContext; import org.rubypeople.rdt.internal.corext.util.Messages; import org.rubypeople.rdt.internal.ui.RubyPlugin; import org.rubypeople.rdt.internal.ui.rubyeditor.RubyEditor; import org.rubypeople.rdt.internal.ui.util.ExceptionHandler; import org.rubypeople.rdt.ui.text.ruby.IRubyCompletionProposal; /** * A template proposal. */ public class TemplateProposal implements IRubyCompletionProposal, ICompletionProposalExtension2, ICompletionProposalExtension3, ICompletionProposalExtension4 { private final Template fTemplate; private final TemplateContext fContext; private final Image fImage; private IRegion fRegion; private int fRelevance; private IRegion fSelectedRegion; // initialized by apply() private String fDisplayString; /** * Creates a template proposal with a template and its context. * * @param template * the template * @param context * the context in which the template was requested * @param region * the region this proposal applies to * @param image * the icon of the proposal */ public TemplateProposal(Template template, TemplateContext context, IRegion region, Image image) { Assert.isNotNull(template); Assert.isNotNull(context); Assert.isNotNull(region); fTemplate = template; fContext = context; fImage = image; fRegion = region; fDisplayString = null; fRelevance = computeRelevance(); } /** * Computes the relevance to match the relevance values generated by the * core content assistant. * * @return a sensible relevance value. */ private int computeRelevance() { // see org.eclipse.jdt.internal.codeassist.RelevanceConstants final int R_DEFAULT = 0; final int R_INTERESTING = 5; final int R_CASE = 10; final int R_NON_RESTRICTED = 3; final int R_EXACT_NAME = 4; final int R_INLINE_TAG = 31; int base = R_DEFAULT + R_INTERESTING + R_NON_RESTRICTED; try { if (fContext instanceof DocumentTemplateContext) { DocumentTemplateContext templateContext = (DocumentTemplateContext) fContext; IDocument document = templateContext.getDocument(); String content = document.get(fRegion.getOffset(), fRegion .getLength()); if (fTemplate.getName().startsWith(content)) base += R_CASE; if (fTemplate.getName().equalsIgnoreCase(content)) base += R_EXACT_NAME; } } catch (BadLocationException e) { // ignore - not a case sensitive match then } // see CompletionProposalCollector.computeRelevance // just under keywords, but better than packages final int TEMPLATE_RELEVANCE = 1; return base * 16 + TEMPLATE_RELEVANCE; } /* * @see ICompletionProposal#apply(IDocument) */ public final void apply(IDocument document) { // not called anymore } /* * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#apply(org.eclipse.jface.text.ITextViewer, * char, int, int) */ public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) { try { fContext.setReadOnly(false); TemplateBuffer templateBuffer; try { templateBuffer = fContext.evaluate(fTemplate); } catch (TemplateException e1) { fSelectedRegion = fRegion; return; } int start = getReplaceOffset(); int end = getReplaceEndOffset(); end = Math.max(end, offset); // insert template string IDocument document = viewer.getDocument(); String templateString = templateBuffer.getString(); document.replace(start, end - start, templateString); // translate positions LinkedModeModel model = new LinkedModeModel(); TemplateVariable[] variables = templateBuffer.getVariables(); MultiVariableGuess guess = fContext instanceof RubyScriptContext ? ((RubyScriptContext) fContext) .getMultiVariableGuess() : null; boolean hasPositions = false; for (int i = 0; i != variables.length; i++) { TemplateVariable variable = variables[i]; if (variable.isUnambiguous()) continue; LinkedPositionGroup group = new LinkedPositionGroup(); int[] offsets = variable.getOffsets(); int length = variable.getLength(); LinkedPosition first; if (guess != null && variable instanceof MultiVariable) { first = new VariablePosition(document, offsets[0] + start, length, guess, (MultiVariable) variable); guess.addSlave((VariablePosition) first); } else { String[] values = variable.getValues(); ICompletionProposal[] proposals = new ICompletionProposal[values.length]; for (int j = 0; j < values.length; j++) { ensurePositionCategoryInstalled(document, model); Position pos = new Position(offsets[0] + start, length); document.addPosition(getCategory(), pos); proposals[j] = new PositionBasedCompletionProposal( values[j], pos, length); } if (proposals.length > 1) first = new ProposalPosition(document, offsets[0] + start, length, proposals); else first = new LinkedPosition(document, offsets[0] + start, length); } for (int j = 0; j != offsets.length; j++) if (j == 0) group.addPosition(first); else group.addPosition(new LinkedPosition(document, offsets[j] + start, length)); model.addGroup(group); hasPositions = true; } if (hasPositions) { model.forceInstall(); RubyEditor editor = getRubyEditor(); if (editor != null) { // FIXME Enable when we do marking occurences // model.addLinkingListener(new // EditorHighlightingSynchronizer(editor)); } LinkedModeUI ui = new EditorLinkedModeUI(model, viewer); ui.setExitPosition(viewer, getCaretOffset(templateBuffer) + start, 0, Integer.MAX_VALUE); ui.enter(); fSelectedRegion = ui.getSelectedRegion(); } else fSelectedRegion = new Region(getCaretOffset(templateBuffer) + start, 0); } catch (BadLocationException e) { RubyPlugin.log(e); openErrorDialog(viewer.getTextWidget().getShell(), e); fSelectedRegion = fRegion; } catch (BadPositionCategoryException e) { RubyPlugin.log(e); openErrorDialog(viewer.getTextWidget().getShell(), e); fSelectedRegion = fRegion; } } /** * Returns the currently active java editor, or <code>null</code> if it * cannot be determined. * * @return the currently active java editor, or <code>null</code> */ private RubyEditor getRubyEditor() { IEditorPart part = RubyPlugin.getActivePage().getActiveEditor(); if (part instanceof RubyEditor) return (RubyEditor) part; else return null; } /** * Returns the offset of the range in the document that will be replaced by * applying this template. * * @return the offset of the range in the document that will be replaced by * applying this template */ private int getReplaceOffset() { int start; if (fContext instanceof DocumentTemplateContext) { DocumentTemplateContext docContext = (DocumentTemplateContext) fContext; start = docContext.getStart(); } else { start = fRegion.getOffset(); } return start; } /** * Returns the end offset of the range in the document that will be replaced * by applying this template. * * @return the end offset of the range in the document that will be replaced * by applying this template */ private int getReplaceEndOffset() { int end; if (fContext instanceof DocumentTemplateContext) { DocumentTemplateContext docContext = (DocumentTemplateContext) fContext; end = docContext.getEnd(); } else { end = fRegion.getOffset() + fRegion.getLength(); } return end; } private void ensurePositionCategoryInstalled(final IDocument document, LinkedModeModel model) { if (!document.containsPositionCategory(getCategory())) { document.addPositionCategory(getCategory()); final InclusivePositionUpdater updater = new InclusivePositionUpdater( getCategory()); document.addPositionUpdater(updater); model.addLinkingListener(new ILinkedModeListener() { /* * @see org.eclipse.jface.text.link.ILinkedModeListener#left(org.eclipse.jface.text.link.LinkedModeModel, * int) */ public void left(LinkedModeModel environment, int flags) { try { document.removePositionCategory(getCategory()); } catch (BadPositionCategoryException e) { // ignore } document.removePositionUpdater(updater); } public void suspend(LinkedModeModel environment) { } public void resume(LinkedModeModel environment, int flags) { } }); } } private String getCategory() { return "TemplateProposalCategory_" + toString(); //$NON-NLS-1$ } private int getCaretOffset(TemplateBuffer buffer) { TemplateVariable[] variables = buffer.getVariables(); for (int i = 0; i != variables.length; i++) { TemplateVariable variable = variables[i]; if (variable.getType().equals(GlobalTemplateVariables.Cursor.NAME)) return variable.getOffsets()[0]; } return buffer.getString().length(); } /* * @see ICompletionProposal#getSelection(IDocument) */ public Point getSelection(IDocument document) { return new Point(fSelectedRegion.getOffset(), fSelectedRegion .getLength()); } /* * @see ICompletionProposal#getAdditionalProposalInfo() */ public String getAdditionalProposalInfo() { try { fContext.setReadOnly(true); TemplateBuffer templateBuffer; try { templateBuffer = fContext.evaluate(fTemplate); } catch (TemplateException e1) { return null; } return templateBuffer.getString(); } catch (BadLocationException e) { handleException(RubyPlugin.getActiveWorkbenchShell(), new CoreException(new Status(IStatus.ERROR, RubyPlugin .getPluginId(), IStatus.OK, "", e))); //$NON-NLS-1$ return null; } } /* * @see ICompletionProposal#getDisplayString() */ public String getDisplayString() { if (fDisplayString == null) { String[] arguments = new String[] { fTemplate.getName(), fTemplate.getDescription() }; fDisplayString = Messages .format( TemplateContentAssistMessages.TemplateProposal_displayString, arguments); } return fDisplayString; } public void setDisplayString(String displayString) { fDisplayString = displayString; } /* * @see ICompletionProposal#getImage() */ public Image getImage() { return fImage; } /* * @see ICompletionProposal#getContextInformation() */ public IContextInformation getContextInformation() { return null; } private void openErrorDialog(Shell shell, Exception e) { MessageDialog.openError(shell, TemplateContentAssistMessages.TemplateEvaluator_error_title, e .getMessage()); } private void handleException(Shell shell, CoreException e) { ExceptionHandler.handle(e, shell, TemplateContentAssistMessages.TemplateEvaluator_error_title, null); } /* * @see IRubyCompletionProposal#getRelevance() */ public int getRelevance() { return fRelevance; } public void setRelevance(int relevance) { fRelevance = relevance; } public Template getTemplate() { return fTemplate; } /* * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getInformationControlCreator() */ public IInformationControlCreator getInformationControlCreator() { return new TemplateInformationControlCreator(); } /* * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#selected(org.eclipse.jface.text.ITextViewer, * boolean) */ public void selected(ITextViewer viewer, boolean smartToggle) { } /* * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#unselected(org.eclipse.jface.text.ITextViewer) */ public void unselected(ITextViewer viewer) { } /* * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#validate(org.eclipse.jface.text.IDocument, * int, org.eclipse.jface.text.DocumentEvent) */ public boolean validate(IDocument document, int offset, DocumentEvent event) { try { int replaceOffset = getReplaceOffset(); if (offset >= replaceOffset) { String content = document.get(replaceOffset, offset - replaceOffset); return fTemplate.getName().toLowerCase().startsWith( content.toLowerCase()); } } catch (BadLocationException e) { // concurrent modification - ignore } return false; } /* * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getReplacementString() */ public CharSequence getPrefixCompletionText(IDocument document, int completionOffset) { // bug 114360 - don't make selection templates prefix-completable if (isSelectionTemplate()) return ""; //$NON-NLS-1$ return fTemplate.getName(); } /* * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getReplacementOffset() */ public int getPrefixCompletionStart(IDocument document, int completionOffset) { return getReplaceOffset(); } /* * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension4#isAutoInsertable() */ public boolean isAutoInsertable() { if (isSelectionTemplate()) return false; return fTemplate.isAutoInsertable(); } /** * Returns <code>true</code> if the proposal has a selection, e.g. will * wrap some code. * * @return <code>true</code> if the proposals completion length is non * zero * @since 3.2 */ private boolean isSelectionTemplate() { if (fContext instanceof DocumentTemplateContext) { DocumentTemplateContext ctx = (DocumentTemplateContext) fContext; if (ctx.getCompletionLength() > 0) return true; } return false; } } --- NEW FILE: PositionBasedCompletionProposal.java --- /******************************************************************************* * Copyright (c) 2000, 2005 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.rubypeople.rdt.internal.ui.text.template.contentassist; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.jface.text.Assert; 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.Position; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.text.contentassist.ICompletionProposalExtension; import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2; import org.eclipse.jface.text.contentassist.IContextInformation; /** * An enhanced implementation of the <code>ICompletionProposal</code> interface implementing all the extension interfaces. * It uses a position to track its replacement offset and length. The position must be set up externally. */ public class PositionBasedCompletionProposal implements ICompletionProposal, ICompletionProposalExtension, ICompletionProposalExtension2 { /** The string to be displayed in the completion proposal popup */ private String fDisplayString; /** The replacement string */ private String fReplacementString; /** The replacement position. */ private Position fReplacementPosition; /** The cursor position after this proposal has been applied */ private int fCursorPosition; /** The image to be displayed in the completion proposal popup */ private Image fImage; /** The context information of this proposal */ private IContextInformation fContextInformation; /** The additional info of this proposal */ private String fAdditionalProposalInfo; /** * Creates a new completion proposal based on the provided information. The replacement string is * considered being the display string too. All remaining fields are set to <code>null</code>. * * @param replacementString the actual string to be inserted into the document * @param replacementPosition the position of the text to be replaced * @param cursorPosition the position of the cursor following the insert relative to replacementOffset */ public PositionBasedCompletionProposal(String replacementString, Position replacementPosition, int cursorPosition) { this(replacementString, replacementPosition, cursorPosition, null, null, null, null); } /** * Creates a new completion proposal. All fields are initialized based on the provided information. * * @param replacementString the actual string to be inserted into the document * @param replacementPosition the position of the text to be replaced * @param cursorPosition the position of the cursor following the insert relative to replacementOffset * @param image the image to display for this proposal * @param displayString the string to be displayed for the proposal * @param contextInformation the context information associated with this proposal * @param additionalProposalInfo the additional information associated with this proposal */ public PositionBasedCompletionProposal(String replacementString, Position replacementPosition, int cursorPosition, Image image, String displayString, IContextInformation contextInformation, String additionalProposalInfo) { Assert.isNotNull(replacementString); Assert.isTrue(replacementPosition != null); fReplacementString= replacementString; fReplacementPosition= replacementPosition; fCursorPosition= cursorPosition; fImage= image; fDisplayString= displayString; fContextInformation= contextInformation; fAdditionalProposalInfo= additionalProposalInfo; } /* * @see ICompletionProposal#apply(IDocument) */ public void apply(IDocument document) { try { document.replace(fReplacementPosition.getOffset(), fReplacementPosition.getLength(), fReplacementString); } catch (BadLocationException x) { // ignore } } /* * @see ICompletionProposal#getSelection(IDocument) */ public Point getSelection(IDocument document) { return new Point(fReplacementPosition.getOffset() + fCursorPosition, 0); } /* * @see ICompletionProposal#getContextInformation() */ public IContextInformation getContextInformation() { return fContextInformation; } /* * @see ICompletionProposal#getImage() */ public Image getImage() { return fImage; } /* * @see org.eclipse.jface.text.contentassist.ICompletionProposal#getDisplayString() */ public String getDisplayString() { if (fDisplayString != null) return fDisplayString; return fReplacementString; } /* * @see ICompletionProposal#getAdditionalProposalInfo() */ public String getAdditionalProposalInfo() { return fAdditionalProposalInfo; } /* * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#apply(org.eclipse.jface.text.ITextViewer, char, int, int) */ public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) { apply(viewer.getDocument()); } /* * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#selected(org.eclipse.jface.text.ITextViewer, boolean) */ public void selected(ITextViewer viewer, boolean smartToggle) { } /* * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#unselected(org.eclipse.jface.text.ITextViewer) */ public void unselected(ITextViewer viewer) { } /* * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#validate(org.eclipse.jface.text.IDocument, int, org.eclipse.jface.text.DocumentEvent) */ public boolean validate(IDocument document, int offset, DocumentEvent event) { try { String content= document.get(fReplacementPosition.getOffset(), offset - fReplacementPosition.getOffset()); if (fReplacementString.startsWith(content)) return true; } catch (BadLocationException e) { // ignore concurrently modified document } return false; } /* * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension#apply(org.eclipse.jface.text.IDocument, char, int) */ public void apply(IDocument document, char trigger, int offset) { // not called any more } /* * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension#isValidFor(org.eclipse.jface.text.IDocument, int) */ public boolean isValidFor(IDocument document, int offset) { // not called any more return false; } /* * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension#getTriggerCharacters() */ public char[] getTriggerCharacters() { return null; } /* * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension#getContextInformationPosition() */ public int getContextInformationPosition() { return fReplacementPosition.getOffset(); } } |