/** * This file Copyright (c) 2005-2008 Aptana, Inc. This program is * dual-licensed under both the Aptana Public License and the GNU General * Public license. You may elect to use one or the other of these licenses. * * This program is distributed in the hope that it will be useful, but * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or * NONINFRINGEMENT. Redistribution, except as permitted by whichever of * the GPL or APL you select, is prohibited. * * 1. For the GPL license (GPL), you can redistribute and/or modify this * program under the terms of the GNU General Public License, * Version 3, as published by the Free Software Foundation. You should * have received a copy of the GNU General Public License, Version 3 along * with this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Aptana provides a special exception to allow redistribution of this file * with certain other free and open source software ("FOSS") code and certain additional terms * pursuant to Section 7 of the GPL. You may view the exception and these * terms on the web at http://www.aptana.com/legal/gpl/. * * 2. For the Aptana Public License (APL), this program and the * accompanying materials are made available under the terms of the APL * v1.0 which accompanies this distribution, and is available at * http://www.aptana.com/legal/apl/. * * You may view the GPL, Aptana's exception and additional terms, and the * APL in the file titled license.html at the root of the corresponding * plugin containing this source file. * * Any modifications to this file must keep this entire header intact. */ package com.aptana.ide.snippets; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextSelection; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.texteditor.IDocumentProvider; import org.eclipse.ui.texteditor.ITextEditor; import com.aptana.ide.core.FileUtils; import com.aptana.ide.editors.unified.IUnifiedEditor; /** * @author Kevin Lindsey */ public class Snippet { private static final Pattern MULTILINE_COMMENT_PATTERN = Pattern.compile("^/\\*(.*?)\\*/\\s*", Pattern.DOTALL); //$NON-NLS-1$ private static final Pattern XML_COMMENT_PATTERN = Pattern.compile("^<!--(.*?)-->\\s*", Pattern.DOTALL); //$NON-NLS-1$ private static final Pattern KEY_VALUE_PATTERN = Pattern.compile("((?:\\w|[-()])+)\\s*:\\s*(.*)$", //$NON-NLS-1$ Pattern.MULTILINE); private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\$\\{((?:\\w|-)+)\\}"); //$NON-NLS-1$ private static final String CATEGORY_KEY = "category"; //$NON-NLS-1$ private static final String ICON_KEY = "icon"; //$NON-NLS-1$ private static final String TOOLBAR_KEY = "toolbar"; //$NON-NLS-1$ private static final String TOOLTIP_KEY = "tooltip"; //$NON-NLS-1$ private static final String LANGUAGE_KEY = "language"; //$NON-NLS-1$ private static final String NAME_KEY = "name"; //$NON-NLS-1$ private static final String PROMPT_START = "prompt("; //$NON-NLS-1$ private static final String PROMPT_END = ")"; //$NON-NLS-1$ private Map<String, String> _metadata; private List<SnippetVariable> _prompts; private File _file; private String _content; /** * @return icon path; */ public String getIcon() { return this._metadata.get(ICON_KEY); } /** * @return icon path; */ public String getLanguage() { return this._metadata.get(LANGUAGE_KEY); } /** * @return should this item be contributed to toolbar */ public boolean isToolbar() { String string = this._metadata.get(TOOLBAR_KEY); if (string != null) { return Boolean.parseBoolean(string); } return false; } /** * Snippet * * @param category * @param name * @param content */ public Snippet(String category, String name, String content) { this(); this.setValue(CATEGORY_KEY, category); this.setValue(NAME_KEY, name); this._content = content; } /** * setValue * * @param key * @param value */ private void setValue(String key, String value) { if (key.startsWith(PROMPT_START) && key.endsWith(PROMPT_END)) { String name = key.substring(PROMPT_START.length(), key.length() - PROMPT_END.length()); if (this._prompts == null) { this._prompts = new ArrayList<SnippetVariable>(); } this._prompts.add(new SnippetVariable(name, "", value)); //$NON-NLS-1$ } else { this._metadata.put(key, value); } } /** * Snippet */ private Snippet() { this._metadata = new HashMap<String, String>(); } /** * fromString * * @param text * @return Snippet */ public static Snippet fromString(String text) { Snippet result = null; if (text != null) { Matcher m = MULTILINE_COMMENT_PATTERN.matcher(text); String metadata = null; String content = null; if (m.find()) { metadata = m.group(1); content = text.substring(m.end()); } else { m = XML_COMMENT_PATTERN.matcher(text); if (m.find()) { metadata = m.group(1); content = text.substring(m.end()); } } if (metadata != null) { Snippet candidate = new Snippet(); m = KEY_VALUE_PATTERN.matcher(metadata); while (m.find()) { candidate.setValue(m.group(1).toLowerCase(Locale.getDefault()), m.group(2)); } candidate._content = content; if (candidate.isValid()) { result = candidate; } } } return result; } /** * fromFile * * @param file * @return Snippet */ public static Snippet fromFile(File file) { String text = null; try { // get file contents as text text = FileUtils.readContent(file); } catch (IOException e) { } // get resulting snippet after processing the file content Snippet result = fromString(text); if (result != null) { // associate this file with this snippet result._file = file; } // return result; return result; } /** * getCategory * * @return String */ public String getCategory() { return this._metadata.get(CATEGORY_KEY); } /** * getContent * * @return String */ public String getRawContent() { return this._content; } /** * getExpandedContent * * @param selectedText * @return */ public String getExpandedContent(String selectedText) { return this.getExpandedContent(selectedText, new IntegerHolder()); } /** * getExpandedContent * * @param selectedText * @param cursorPosition * @return String */ public String getExpandedContent(String selectedText, IntegerHolder cursorPosition) { boolean expand = true; String result = null; // build lookup table Map<String, String> valuesByName = new HashMap<String, String>(); // add selection text valuesByName.put("selection", selectedText); //$NON-NLS-1$ valuesByName.put("cursor", "" + (char) 2); //$NON-NLS-1$ //$NON-NLS-2$ // prompt for variable values, if needed if (this._prompts != null && this._prompts.size() > 0) { // ask user for values SnippetDialog dialog = new SnippetDialog(Display.getCurrent().getActiveShell(), this); dialog.open(); if (dialog.OK) { // move variable values into lookup table for (int i = 0; i < this._prompts.size(); i++) { SnippetVariable variable = this._prompts.get(i); valuesByName.put(variable.getName(), variable.getValue()); } } else { // let later code know that user canceled this action expand = false; } } if (expand) { // replace all variable instances StringBuffer buffer = new StringBuffer(); Matcher m = VARIABLE_PATTERN.matcher(this._content); while (m.find()) { String key = m.group(1); String replacement = ""; //$NON-NLS-1$ if (valuesByName.containsKey(key)) { replacement = valuesByName.get(key); } // Fix for #5050. We were selecting items with their own regular expressions inside [IM] String quotedReplacement = Matcher.quoteReplacement(replacement); m.appendReplacement(buffer, quotedReplacement); } m.appendTail(buffer); result = buffer.toString(); int indexOf = result.indexOf(2); if (indexOf != -1) { cursorPosition.cursorPosition = indexOf; result = result.substring(0, indexOf) + result.substring(indexOf + 1); } } return result; } /** * Return the file with which this snippet is associated * * @return File or null */ public File getFile() { return this._file; } /** * getName * * @return String */ public String getName() { return this._metadata.get(NAME_KEY); } /** * getVariables * * @return an array of variables */ public SnippetVariable[] getVariables() { SnippetVariable[] result; if (this._prompts != null) { result = this._prompts.toArray(new SnippetVariable[this._prompts.size()]); } else { result = new SnippetVariable[0]; } return result; } /** * isValid * * @return boolean */ private boolean isValid() { return this.hasKey(CATEGORY_KEY) && this.hasKey(NAME_KEY) && this._content != null; } /** * hasKey * * @param key * @return boolean */ private boolean hasKey(String key) { return this._metadata.containsKey(key); } static class IntegerHolder { int cursorPosition = -1; } void apply(final ITextEditor editor) { // get current selection ITextSelection ts = (ITextSelection) editor.getSelectionProvider().getSelection(); final int selectionOffset = ts.getOffset(); final int selectionLength = ts.getLength(); // get document IDocumentProvider dp = editor.getDocumentProvider(); IDocument doc = dp.getDocument(editor.getEditorInput()); // get selected text String selectedText = ""; //$NON-NLS-1$ try { selectedText = doc.get(selectionOffset, selectionLength); } catch (BadLocationException e1) { } // get content after all variables have been expanded final IntegerHolder cursorPosition = new IntegerHolder(); String content = getExpandedContent(selectedText, cursorPosition); // NOTE: null is returned as the content when a user cancels a // snippet that uses a dialog to fill in values if (content != null) { final int contentLength = content.length(); try { // replace the current selection doc.replace(selectionOffset, selectionLength, content); final IWorkbench workbench = PlatformUI.getWorkbench(); Display display = workbench.getDisplay(); display.asyncExec(new Runnable() { public void run() { if (cursorPosition.cursorPosition != -1) { ((IUnifiedEditor) editor).getViewer().getTextWidget().setCaretOffset( selectionOffset + cursorPosition.cursorPosition); } else { editor.selectAndReveal(selectionOffset, contentLength); } } }); } catch (BadLocationException e) { // e.printStackTrace(); } } } /** * @return tooltip */ public String getTooltip() { String string = this._metadata.get(TOOLTIP_KEY); return string != null ? string : getName(); } }