Java tutorial
/** * Aptana Studio * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions). * Please see the license.html included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package com.aptana.editor.common.scripting.snippets; import java.text.MessageFormat; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.runtime.Platform; 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.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.ICompletionProposalExtension6; 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.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.TemplateProposal; import org.eclipse.jface.text.templates.TemplateVariable; import org.eclipse.jface.viewers.StyledString; import org.eclipse.jface.viewers.StyledString.Styler; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Shell; import com.aptana.core.logging.IdeLog; import com.aptana.core.util.StringUtil; import com.aptana.editor.common.CommonEditorPlugin; import com.aptana.editor.common.contentassist.ICommonCompletionProposal; import com.aptana.editor.common.util.EditorUtil; import com.aptana.ui.epl.scripting.snippets.PositionBasedCompletionProposal; public class SnippetTemplateProposal extends TemplateProposal implements ICommonCompletionProposal, ICompletionProposalExtension6, Comparable<ICompletionProposal> { protected ICompletionProposal[] templateProposals; protected char triggerChar; protected char[] triggerChars; private final Template fTemplate; private final TemplateContext fContext; private final IRegion fRegion; private int fRelevance; private ICompletionProposal delegateTemplateProposal; private IRegion fSelectedRegion; // initialized by apply() private InclusivePositionUpdater fUpdater; private StyledString styledDisplayString; private StyledString styledActivationString; private Styler styler; /** * Cached value for replacement offset. */ private Integer fReplaceOffset; public SnippetTemplateProposal(Template template, TemplateContext context, IRegion region, Image image, int relevance) { super(template, context, region, image, relevance); fTemplate = template; fContext = context; fRegion = region; fTemplate.getContextTypeId(); EditorUtil.getSpaceIndentSize(); } /* * (non-Javadoc) * @see org.eclipse.jface.text.templates.TemplateProposal#getAdditionalProposalInfo() */ public String getAdditionalProposalInfo() { return getTemplate().getPattern(); } @Override public void apply(final ITextViewer viewer, char trigger, int stateMask, final int offset) { delegateTemplateProposal = null; if (contains(triggerChars, trigger)) { if (triggerChar == trigger) { doApply(viewer, trigger, stateMask, offset); } else { delegateTemplateProposal = templateProposals[trigger - '1']; ((ICompletionProposalExtension2) templateProposals[trigger - '1']).apply(viewer, trigger, stateMask, offset); } } else { doApply(viewer, trigger, stateMask, offset); } } protected void doApply(final ITextViewer viewer, char trigger, int stateMask, final int offset) { IDocument document = viewer.getDocument(); try { fContext.setReadOnly(false); int start; TemplateBuffer templateBuffer; { int oldReplaceOffset = getReplaceOffset(document, fTemplate); if (fTemplate instanceof SnippetTemplate) { // Reset indented pattern ((SnippetTemplate) fTemplate).setIndentedPattern(null); IRegion lineInformationOfStart = document.getLineInformationOfOffset(oldReplaceOffset); int lineInformationOfStartOffset = lineInformationOfStart.getOffset(); if (oldReplaceOffset > lineInformationOfStartOffset) { // Get the text from beginning of line to the replacement start offset String prefix = document.get(lineInformationOfStartOffset, oldReplaceOffset - lineInformationOfStartOffset); // Is there any leading white space? if (prefix.matches("\\s+.*")) //$NON-NLS-1$ { // Yes. Prefix each line in the template's pattern with the same white space String indentedPattern = fTemplate.getPattern().replaceAll("(\r\n|\r|\n)", //$NON-NLS-1$ "$1" + prefix.replaceFirst("(\\s+).*", "$1")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ // We need to convert so that the pattern uses the same type of indent (spaces vs tabs) indentedPattern = conformIndents(prefix, indentedPattern); ((SnippetTemplate) fTemplate).setIndentedPattern(indentedPattern); } } } try { // this may already modify the document templateBuffer = fContext.evaluate(fTemplate); } catch (TemplateException e) { IdeLog.logWarning(CommonEditorPlugin.getDefault(), MessageFormat .format("Error in template {0}. {1}", fTemplate.toString(), e.getMessage())); //$NON-NLS-1$ fSelectedRegion = fRegion; return; } start = getReplaceOffset(document, fTemplate); int shift = start - oldReplaceOffset; int end = Math.max(getReplaceEndOffset(), offset + shift); // insert template string String templateString = templateBuffer.getString(); document.replace(start, end - start, templateString); } // translate positions LinkedModeModel model = new LinkedModeModel(); TemplateVariable[] variables = templateBuffer.getVariables(); // If there is no tab stop variable in this template use // default sequence number int defaultSequenceNumber = LinkedPositionGroup.NO_STOP; for (TemplateVariable templateVariable : variables) { String type = templateVariable.getType(); if (TabStopVariableResolver.VARIABLE_TYPE.equals(type)) { try { Integer.parseInt(templateVariable.getName()); // The non tab stop variables are visited after // visiting the tab stop variables defaultSequenceNumber = Integer.MAX_VALUE - 1; break; } catch (NumberFormatException nfe) { // ignore } } } 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(); int sequenceNumber = defaultSequenceNumber; String type = variable.getType(); if (TabStopVariableResolver.VARIABLE_TYPE.equals(type)) { try { sequenceNumber = Integer.parseInt(variable.getName()); } catch (NumberFormatException nfe) { // ignore } } LinkedPosition first; { 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, sequenceNumber, proposals); } else { first = new LinkedPosition(document, offsets[0] + start, length, sequenceNumber); } } 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(); LinkedModeUI ui = new LinkedModeUI(model, viewer); // Do not cycle ui.setCyclingMode(LinkedModeUI.CYCLE_NEVER); ui.setExitPosition(viewer, getCaretOffset(templateBuffer) + start, 0, Integer.MAX_VALUE); ui.enter(); fSelectedRegion = ui.getSelectedRegion(); } else { ensurePositionCategoryRemoved(document); fSelectedRegion = new Region(getCaretOffset(templateBuffer) + start, 0); } } catch (BadLocationException e) { openErrorDialog(viewer.getTextWidget().getShell(), e); ensurePositionCategoryRemoved(document); fSelectedRegion = fRegion; } catch (BadPositionCategoryException e) { openErrorDialog(viewer.getTextWidget().getShell(), e); fSelectedRegion = fRegion; } if (fSelectedRegion == null) { fSelectedRegion = fRegion; // default case } } /* * @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(document, fTemplate); if (offset >= replaceOffset) { String content = document.get(replaceOffset, offset - replaceOffset); return fTemplate.getName().startsWith(content); } } catch (BadLocationException e) { // concurrent modification - ignore } return false; } private synchronized int getReplaceOffset(IDocument document, Template template) { // Cache this value, so we don't need to compute it multiple times... if (fReplaceOffset == null) { if (template instanceof CommandTemplate) { try { CommandTemplate ct = (CommandTemplate) template; // Need to get correct offset based on prefix chopping! int fullPrefixOffset = getReplaceOffset(); String prefix = document.get(fullPrefixOffset, getReplaceEndOffset() - fullPrefixOffset); final String origPrefix = prefix; while (!ct.matches(prefix)) { prefix = SnippetsCompletionProcessor.narrowPrefix(prefix); } if (prefix.length() == 0) { fReplaceOffset = fullPrefixOffset; } else { fReplaceOffset = fullPrefixOffset + (origPrefix.length() - prefix.length()); } } catch (BadLocationException e) { // ignore fReplaceOffset = getReplaceOffset(); } } else { fReplaceOffset = getReplaceOffset(); } } return fReplaceOffset; } /** * Given the prefix text in the editor, modify the snippet patterns' indents to use same type of indentation (tabs * vs spaces) * * @param prefix * @param indentedPattern * @return */ protected String conformIndents(String prefix, String indentedPattern) { boolean useTabs = prefix.contains("\t"); //$NON-NLS-1$ int indentSize = EditorUtil.getSpaceIndentSize(); Pattern p = Pattern.compile("(\r\n|\r|\n)(\\s*)"); //$NON-NLS-1$ Matcher m = p.matcher(indentedPattern); int startIndex = 0; StringBuilder builder = new StringBuilder(); while (m.find(startIndex)) { builder.append(indentedPattern.substring(startIndex, m.start(1))); startIndex = m.end(2); String indent = EditorUtil.convertIndent(m.group(2), indentSize, useTabs); builder.append(m.group(1)); builder.append(indent); } builder.append(indentedPattern.substring(startIndex)); indentedPattern = builder.toString(); return indentedPattern; } private void ensurePositionCategoryInstalled(final IDocument document, LinkedModeModel model) { if (!document.containsPositionCategory(getCategory())) { document.addPositionCategory(getCategory()); fUpdater = new InclusivePositionUpdater(getCategory()); document.addPositionUpdater(fUpdater); 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) { ensurePositionCategoryRemoved(document); } public void suspend(LinkedModeModel environment) { } public void resume(LinkedModeModel environment, int flags) { } }); } } private void ensurePositionCategoryRemoved(IDocument document) { if (document.containsPositionCategory(getCategory())) { try { document.removePositionCategory(getCategory()); } catch (BadPositionCategoryException e) { // ignore } document.removePositionUpdater(fUpdater); } } private String getCategory() { return "SnippetTemplateProposalCategory_" + 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(); } private void openErrorDialog(Shell shell, Exception e) { MessageDialog.openError(shell, Messages.SnippetTemplateProposal_TITLE_SnippetTemplateProposalError, e.getMessage()); } /* * @see ICompletionProposal#getSelection(IDocument) */ public Point getSelection(IDocument document) { if (delegateTemplateProposal == null) { return new Point(fSelectedRegion.getOffset(), fSelectedRegion.getLength()); } return delegateTemplateProposal.getSelection(document); } @Override public char[] getTriggerCharacters() { return triggerChars; } @Override public String getDisplayString() { return getStyledDisplayString().getString().trim(); } public String getActivationString() { return getStyledActivationString().getString().trim(); } public StyledString getStyledDisplayString() { if (styledDisplayString == null) { Template template = getTemplate(); styledDisplayString = new StyledString(template.getDescription(), styler); } return styledDisplayString; } public StyledString getStyledActivationString() { if (styledActivationString == null) { Template template = getTemplate(); styledActivationString = new StyledString(String.format("%1$10.10s ", //$NON-NLS-1$ template.getName() + " \u00bb") //$NON-NLS-1$ + ((triggerChar == '\000') ? " " : String.valueOf(triggerChar)) //$NON-NLS-1$ // Need padding on windows to work around the width computation + (Platform.OS_WIN32.equals(Platform.getOS()) ? " " : ""), //$NON-NLS-1$ //$NON-NLS-2$ styler); } return styledActivationString; } Template getTemplateSuper() { return super.getTemplate(); } public void setTriggerChar(char triggerChar) { this.triggerChar = triggerChar; } void setStyler(Styler styler) { this.styler = styler; } private static final String TRIGGER_CHARS = "123456789"; //$NON-NLS-1$ void setTemplateProposals(ICompletionProposal[] templateProposals) { this.templateProposals = templateProposals; triggerChars = new char[Math.min(templateProposals.length, TRIGGER_CHARS.length())]; TRIGGER_CHARS.getChars(0, triggerChars.length, triggerChars, 0); } protected static boolean contains(char[] characters, char c) { if (characters == null) return false; for (int i = 0; i < characters.length; i++) { if (c == characters[i]) return true; } return false; } /* * (non-Javadoc) * @see com.aptana.editor.common.contentassist.ICommonCompletionProposal#getFileLocation() */ public String getFileLocation() { return getActivationString(); } /* * (non-Javadoc) * @see com.aptana.editor.common.contentassist.ICommonCompletionProposal#getUserAgentImages() */ public Image[] getUserAgentImages() { return null; } /* * (non-Javadoc) * @see com.aptana.editor.common.contentassist.ICommonCompletionProposal#isDefaultSelection() */ public boolean isDefaultSelection() { return false; } /* * (non-Javadoc) * @see com.aptana.editor.common.contentassist.ICommonCompletionProposal#setIsSuggestedSelection() */ public boolean isSuggestedSelection() { return false; } /* * (non-Javadoc) * @see com.aptana.editor.common.contentassist.ICommonCompletionProposal#setIsSuggestedSelection() */ public void setIsDefaultSelection(boolean isDefault) { } /* * (non-Javadoc) * @see com.aptana.editor.common.contentassist.ICommonCompletionProposal#setIsSuggestedSelection() */ public void setIsSuggestedSelection(boolean isSuggested) { } /* * (non-Javadoc) * @see com.aptana.editor.common.contentassist.ICommonCompletionProposal#getExtraInfo() */ public String getExtraInfo() { return getActivationString(); } /* * (non-Javadoc) * @see java.lang.Comparable#compareTo(java.lang.Object) */ public int compareTo(ICompletionProposal o) { if (this == o) { return 0; } Template t = getTemplate(); if (t == null) { return -1; } if (o instanceof SnippetTemplateProposal) { Template t2 = ((SnippetTemplateProposal) o).getTemplate(); if (t2 == null) { return 1; } return StringUtil.compareCaseInsensitive(t.getName(), t2.getName()); } else { return StringUtil.compareCaseInsensitive(t.getName(), o.getDisplayString()); } } /* * (non-Javadoc) * @see com.aptana.editor.common.contentassist.ICommonCompletionProposal#getRelevance() */ public int getRelevance() { return fRelevance; } /* * (non-Javadoc) * @see com.aptana.editor.common.contentassist.ICommonCompletionProposal#setRelevance(int) */ public void setRelevance(int relevance) { fRelevance = relevance; } /* * (non-Javadoc) * @see * com.aptana.editor.common.contentassist.ICommonCompletionProposal#isTriggerEnabled(org.eclipse.jface.text.IDocument * , int) */ public boolean validateTrigger(IDocument document, int offset, KeyEvent keyEvent) { try { int replaceOffset = getReplaceOffset(document, fTemplate); if (offset >= replaceOffset) { String content = document.get(replaceOffset, offset - replaceOffset); return fTemplate.getName().equals(content); } } catch (BadLocationException e) { // concurrent modification - ignore } return false; } }