├── .DS_Store ├── .classpath ├── .gitignore ├── .project ├── .settings └── org.eclipse.jdt.core.prefs ├── META-INF └── MANIFEST.MF ├── README.textile ├── build.properties ├── gson.jar ├── icons └── sample.gif ├── js.jar ├── plugin.xml └── src └── io └── emmet ├── Emmet.java ├── IEmmetEditor.java ├── IEmmetFile.java ├── IUserData.java ├── SelectionData.java ├── TabStop.java ├── TabStopGroup.java ├── TabStopStructure.java ├── actions ├── AbstractMenuItem.java ├── Action.java └── Menu.java ├── eclipse ├── AutoCompleteDialog.java ├── EclipseEmmetEditor.java ├── EclipseEmmetFile.java ├── EclipseEmmetHelper.java ├── EclipseEmmetPlugin.java ├── EclipseTemplateProcessor.java ├── EclipseUserData.java ├── EditorTypeInvestigator.java ├── EmmetContextType.java ├── MainMenuContribution.java ├── Startup.java ├── TabKeyHandler.java ├── WrapWithAbbreviationDialog.java ├── handlers │ ├── ActionRunner.java │ ├── DefaultAction.java │ ├── ExpandAbbreviationAction.java │ ├── InsertFormattedLineBreakAction.java │ └── WrapWithAbbreviationAction.java └── preferences │ ├── EclipseEmmetPreferencePage.java │ ├── EmmetAbbreviationsPreferencesPage.java │ ├── EmmetSnippetsPreferencesPage.java │ ├── EmmetVariablesPreferencePage.java │ ├── LabelFieldEditor.java │ ├── PreferenceConstants.java │ ├── PreferenceInitializer.java │ ├── SpacerFieldEditor.java │ ├── SpinnerFieldEditor.java │ ├── StatusInfo.java │ ├── TemplateContentProvider.java │ ├── TemplateHelper.java │ ├── VariablePreferencePage.java │ └── output │ ├── CSSOutputPreferencePage.java │ ├── DefaultOutputPreferencePage.java │ ├── HAMLOutputPreferencePage.java │ ├── HTMLOutputPreferencePage.java │ ├── OutputProfile.java │ ├── XMLOutputPreferencePage.java │ └── XSLOutputPreferencePage.java ├── emmet-app.js ├── file-interface.js ├── java-wrapper.js ├── json2.js └── snippets.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergeche/eclipse-zencoding/146d682fdc6914b900401a8e265659ce07cef8d1/.DS_Store -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | /src/.DS_Store -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | ru.zencoding.eclipse 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.pde.ManifestBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.pde.SchemaBuilder 20 | 21 | 22 | 23 | 24 | 25 | org.eclipse.pde.PluginNature 26 | org.eclipse.jdt.core.javanature 27 | 28 | 29 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | #Sun Oct 31 21:35:50 MSK 2010 2 | eclipse.preferences.version=1 3 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 4 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 5 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 6 | org.eclipse.jdt.core.compiler.compliance=1.6 7 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 8 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 9 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 10 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 11 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 12 | org.eclipse.jdt.core.compiler.source=1.6 13 | -------------------------------------------------------------------------------- /META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Bundle-ManifestVersion: 2 3 | Bundle-Name: Emmet for Eclipse 4 | Bundle-SymbolicName: io.emmet.eclipse;singleton:=true 5 | Bundle-Version: 1.0.0.qualifier 6 | Bundle-Activator: io.emmet.eclipse.EclipseEmmetPlugin 7 | Require-Bundle: org.eclipse.ui, 8 | org.eclipse.core.runtime, 9 | org.eclipse.ui.editors;bundle-version="3.5.0", 10 | org.eclipse.jface.text;bundle-version="3.5.0", 11 | org.eclipse.core.expressions;bundle-version="3.4.300" 12 | Bundle-ActivationPolicy: lazy 13 | Bundle-RequiredExecutionEnvironment: JavaSE-1.6 14 | Bundle-ClassPath: ., 15 | js.jar, 16 | gson.jar 17 | Import-Package: org.eclipse.core.filesystem, 18 | org.eclipse.core.resources, 19 | org.eclipse.jface.text, 20 | org.eclipse.ui, 21 | org.eclipse.ui.editors.text, 22 | org.eclipse.ui.part, 23 | org.eclipse.ui.texteditor, 24 | org.eclipse.ui.texteditor.templates 25 | Export-Package: io.emmet, 26 | io.emmet.eclipse, 27 | io.emmet.eclipse.handlers, 28 | io.emmet.eclipse.preferences, 29 | io.emmet.eclipse.preferences.output 30 | -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h1. Emmet (ex-Zen Coding) 2 | 3 | Emmet is a toolkit for high-speed HTML, XML, XSL (or any other structured code format) coding and editing. The core of this plugin is a powerful abbreviation engine which allows you to expand expressions—similar to CSS selectors—into HTML code. For example: 4 | 5 | @div#page>div.logo+ul#navigation>li*5>a@ 6 | 7 | ...can be expanded into: 8 | 9 |

10 | 
11 | 12 | 19 |
20 |
21 | 22 | "Read more about current Emmet syntax":http://code.google.com/p/zen-coding/wiki/ZenHTMLSelectorsEn 23 | 24 | h2. Installation 25 | 26 | # Go to _Help > Install New Software..._ in your Eclipse IDE 27 | # Add "http://emmet.io/eclipse/updates/":http://emmet.io/eclipse/updates/ in update sites 28 | # Check _Emmet for Eclipse_ group in available plugins list, click Next button and follow the installation instructions 29 | # Restart Eclipse 30 | 31 | h2. Plugin Overview 32 | 33 | This plugin provides the features: 34 | 35 | * Expand abbreviations by Tab key 36 | * Tab stops and linked mode support 37 | * Simple install and update process 38 | * Change action shortcuts in Eclipse's Keys preferences page 39 | * Works across all Eclipse editors 40 | * Preferences support to fine-tune output for each syntax and add new abbreviations and snippets 41 | 42 | !http://media.chikuyonok.ru/eclipse/prefs.png! 43 | 44 | *Aptana 3 users:* since Aptana 3 can also expand snippets by Tab key, there might be collisions in expanded result (for example, for @div@ tag). You can remove unused snippets for Aptana bundles in order to make Emmet plugin work properly. 45 | 46 | h3. Contributions 47 | 48 | "Django snippets":https://github.com/andreyfedoseev/zen-coding-django-snippets -------------------------------------------------------------------------------- /build.properties: -------------------------------------------------------------------------------- 1 | source.. = src/ 2 | output.. = bin/ 3 | bin.includes = plugin.xml,\ 4 | META-INF/,\ 5 | .,\ 6 | icons/,\ 7 | js.jar,\ 8 | gson.jar 9 | -------------------------------------------------------------------------------- /gson.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergeche/eclipse-zencoding/146d682fdc6914b900401a8e265659ce07cef8d1/gson.jar -------------------------------------------------------------------------------- /icons/sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergeche/eclipse-zencoding/146d682fdc6914b900401a8e265659ce07cef8d1/icons/sample.gif -------------------------------------------------------------------------------- /js.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergeche/eclipse-zencoding/146d682fdc6914b900401a8e265659ce07cef8d1/js.jar -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 10 | 11 | 12 | 16 | 17 | 21 | 22 | 26 | 27 | 31 | 32 | 36 | 37 | 41 | 42 | 46 | 47 | 51 | 52 | 56 | 57 | 61 | 62 | 66 | 67 | 71 | 72 | 76 | 77 | 81 | 82 | 86 | 87 | 91 | 92 | 96 | 97 | 101 | 102 | 106 | 107 | 111 | 112 | 116 | 117 | 121 | 122 | 126 | 127 | 128 | 129 | 131 | 136 | 137 | 142 | 143 | 148 | 149 | 154 | 155 | 160 | 161 | 166 | 167 | 172 | 173 | 178 | 179 | 184 | 185 | 190 | 191 | 196 | 197 | 202 | 203 | 204 | 206 | 208 | 212 | 213 | 214 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 226 | 230 | 231 | 236 | 237 | 242 | 243 | 244 | 249 | 250 | 251 | 252 | 257 | 258 | 263 | 264 | 269 | 270 | 275 | 276 | 281 | 282 | 287 | 288 | 289 | 291 | 293 | 294 | 295 | 296 | 297 | 301 | 305 | 309 | 313 | 317 | 321 | 322 | 330 | 338 | 346 | 347 | 348 | -------------------------------------------------------------------------------- /src/io/emmet/Emmet.java: -------------------------------------------------------------------------------- 1 | package io.emmet; 2 | 3 | import java.io.InputStream; 4 | import java.io.InputStreamReader; 5 | 6 | import org.mozilla.javascript.Context; 7 | import org.mozilla.javascript.Scriptable; 8 | import org.mozilla.javascript.ScriptableObject; 9 | 10 | public class Emmet { 11 | private volatile static Emmet singleton; 12 | private static Context cx; 13 | private static Scriptable scope; 14 | private static String snippetsJSON = "snippets.json"; 15 | private static IUserData userDataDelegate = null; 16 | 17 | 18 | private static String[] coreFiles = { 19 | "emmet-app.js", 20 | // "json2.js", 21 | "file-interface.js", 22 | "java-wrapper.js" 23 | }; 24 | 25 | private Emmet() { 26 | cx = Context.enter(); 27 | scope = cx.initStandardObjects(); 28 | try { 29 | // load core 30 | for (int i = 0; i < coreFiles.length; i++) { 31 | cx.evaluateReader(scope, getReaderForLocalFile(coreFiles[i]), coreFiles[i], 1, null); 32 | } 33 | 34 | // load default snippets 35 | execJSFunction("javaLoadSystemSnippets", readLocalFile(snippetsJSON)); 36 | 37 | if (userDataDelegate != null) { 38 | userDataDelegate.load(this); 39 | userDataDelegate.loadExtensions(this); 40 | } 41 | } catch (Exception e) { 42 | System.err.println(e.getMessage()); 43 | } 44 | } 45 | 46 | public static Emmet getSingleton() { 47 | if (singleton == null) { 48 | synchronized (Emmet.class) { 49 | if (singleton == null) { 50 | singleton = new Emmet(); 51 | } 52 | } 53 | } 54 | return singleton; 55 | } 56 | 57 | public static void setUserDataDelegate(IUserData delegate) { 58 | userDataDelegate = delegate; 59 | } 60 | 61 | public static void reset() { 62 | if (singleton == null) 63 | return; 64 | 65 | Context.exit(); 66 | cx = null; 67 | scope = null; 68 | singleton = null; 69 | } 70 | 71 | private InputStreamReader getReaderForLocalFile(String fileName) { 72 | InputStream is = this.getClass().getResourceAsStream(fileName); 73 | return new InputStreamReader(is); 74 | } 75 | 76 | private String readLocalFile(String fileName) { 77 | // using Scanner trick: 78 | // http://stackoverflow.com/a/5445161/1312205 79 | InputStream is = this.getClass().getResourceAsStream(fileName); 80 | try { 81 | return new java.util.Scanner(is).useDelimiter("\\A").next(); 82 | } catch (java.util.NoSuchElementException e) { 83 | return ""; 84 | } 85 | } 86 | 87 | /** 88 | * Executes arbitrary JS function with passed arguments. Each argument is 89 | * automatically converted to JS type 90 | * @param name JS function name. May have namespaces 91 | * (e.g. emmet.require('actions').get) 92 | * @param vargs 93 | * @return 94 | */ 95 | public Object execJSFunction(String name, Object... vargs) { 96 | // temporary register all variables 97 | Object wrappedObj; 98 | StringBuilder jsArgs = new StringBuilder(); 99 | for (int i = 0; i < vargs.length; i++) { 100 | wrappedObj = Context.javaToJS(vargs[i], scope); 101 | ScriptableObject.putProperty(scope, "__javaParam" + i, wrappedObj); 102 | if (i > 0) { 103 | jsArgs.append(','); 104 | } 105 | jsArgs.append("__javaParam" + i); 106 | } 107 | 108 | // evaluate code 109 | Object result = cx.evaluateString(scope, name + "(" + jsArgs.toString() + ");", "", 1, null); 110 | 111 | // remove temp variables 112 | for (int i = 0; i < vargs.length; i++) { 113 | ScriptableObject.deleteProperty(scope, "__javaParam" + i); 114 | } 115 | 116 | return result; 117 | } 118 | 119 | /** 120 | * Runs Emmet script on passed editor object (should be the first argument) 121 | * @return 'True' if action was successfully executed 122 | */ 123 | public boolean runAction(Object... args) { 124 | return Context.toBoolean(execJSFunction("runEmmetAction", args)); 125 | } 126 | 127 | /** 128 | * Returns preview for "Wrap with Abbreviation" action 129 | */ 130 | public String getWrapPreview(IEmmetEditor editor, String abbr) { 131 | return Context.toString(execJSFunction("previewWrapWithAbbreviation", editor, abbr)); 132 | } 133 | } -------------------------------------------------------------------------------- /src/io/emmet/IEmmetEditor.java: -------------------------------------------------------------------------------- 1 | package io.emmet; 2 | 3 | /** 4 | * Emmet editor interface that should be implemented in order to 5 | * run Emmet actions 6 | * @author Sergey Chikuyonok 7 | */ 8 | public interface IEmmetEditor { 9 | /** 10 | * Returns character indexes of selected text: object with start 11 | * and end indexes. If there's no selection, should return 12 | * list with both indexes referring to current caret position 13 | * @example 14 | *
 15 | 	 * SelectionData selection = editor.getSelectionRange();
 16 | 	 * System.out.println(selection.getStart() + ", " + selection.getEnd());
 17 | 	 * 
18 | */ 19 | public SelectionData getSelectionRange(); 20 | 21 | /** 22 | * Creates selection from start to end character 23 | * indexes. 24 | * @example 25 | *
editor.createSelection(10, 40);
26 | */ 27 | public void createSelection(int start, int end); 28 | 29 | /** 30 | * Returns current line's start and end indexes as object with start 31 | * and end properties 32 | * @return {Object} 33 | * @example 34 | *
 35 | 	 * SelectionData range = editor.getCurrentLineRange();
 36 | 	 * System.out.println(range.getStart() + ", " + range.getEnd();
 37 | 	 * 
38 | */ 39 | public SelectionData getCurrentLineRange(); 40 | 41 | /** 42 | * Returns current caret position 43 | */ 44 | public int getCaretPos(); 45 | 46 | /** 47 | * Set new caret position 48 | */ 49 | public void setCaretPos(int pos); 50 | 51 | /** 52 | * Returns content of current line 53 | */ 54 | public String getCurrentLine(); 55 | 56 | /** 57 | * Replace current editor's content. If value contains 58 | * caret_placeholder, the editor will put caret into 59 | * this position. 60 | * @param value Content you want to paste 61 | */ 62 | public void replaceContent(String value); 63 | 64 | /** 65 | * Place the value content at start string 66 | * index of current content. If value contains 67 | * caret_placeholder, the editor will put caret into 68 | * this position. 69 | * @param value Content you want to paste 70 | * @param start Start index of editor's content 71 | */ 72 | public void replaceContent(String value, int start); 73 | 74 | /** 75 | * Replace editor's content part (from start to 76 | * end index) with value. If value 77 | * contains caret_placeholder, the editor will put caret into 78 | * this position. 79 | * 80 | * @param value Content you want to paste 81 | * @param start Start index of editor's content 82 | * @param end End index of editor's content 83 | */ 84 | public void replaceContent(String value, int start, int end); 85 | 86 | /** 87 | * Replace editor's content part (from start to 88 | * end index) with value. If value 89 | * contains caret_placeholder, the editor will put caret into 90 | * this position. 91 | * 92 | * @param value Content you want to paste 93 | * @param start Start index of editor's content 94 | * @param end End index of editor's content 95 | * @param no_indent Do not indent pasted value 96 | */ 97 | public void replaceContent(String value, int start, int end, boolean no_indent); 98 | 99 | /** 100 | * Returns editor's content 101 | */ 102 | public String getContent(); 103 | 104 | /** 105 | * Returns current editor's syntax mode 106 | */ 107 | public String getSyntax(); 108 | 109 | /** 110 | * Returns current output profile name 111 | */ 112 | public String getProfileName(); 113 | 114 | /** 115 | * Ask user to enter something 116 | * @param title Dialog title 117 | * @return Entered data 118 | * @since 0.65 119 | */ 120 | public String prompt(String title); 121 | 122 | /** 123 | * Returns current selection 124 | * @since 0.65 125 | */ 126 | public String getSelection(); 127 | 128 | /** 129 | * Returns current editor's file path 130 | * @since 0.65 131 | */ 132 | public String getFilePath(); 133 | } 134 | -------------------------------------------------------------------------------- /src/io/emmet/IEmmetFile.java: -------------------------------------------------------------------------------- 1 | package io.emmet; 2 | 3 | /** 4 | * Emmet File interface is used by JS core to read and locate files 5 | * @author sergey 6 | * 7 | */ 8 | public interface IEmmetFile { 9 | /** 10 | * Read file content and return it 11 | * @param path File's relative or absolute path 12 | */ 13 | public String read(String path); 14 | 15 | /** 16 | * Locate fileName file that relates to editorFile. 17 | * File name may be absolute or relative path 18 | *

19 | * Dealing with absolute path. 20 | * Many modern editors have a "project" support as information unit, but you 21 | * should not rely on project path to find file with absolute path. First, 22 | * it requires user to create a project before using this method (and this 23 | * is not very convenient). Second, project path doesn't always points to 24 | * to website's document root folder: it may point, for example, to an 25 | * upper folder which contains server-side scripts. 26 | * 27 | * For better result, you should use the following algorithm in locating 28 | * absolute resources:
29 | * 1) Get parent folder for editorFile as a start point
30 | * 2) Append required fileName to start point and test if
31 | * file exists
32 | * 3) If it doesn't exists, move start point one level up (to parent folder) 33 | * and repeat step 2. 34 | * 35 | * @param editorFile 36 | * @param fileName 37 | * @return Returns null if fileName cannot be located 38 | */ 39 | public String locateFile(String editorFile, String fileName); 40 | 41 | /** 42 | * Creates absolute path by concatenating parent and file_name. 43 | * If parent points to file, its parent directory is used 44 | * @param parent 45 | * @param fileName 46 | */ 47 | public String createPath(String parent, String fileName); 48 | 49 | /** 50 | * Saves content as file 51 | * @param file File's absolute path 52 | * @param content File content 53 | */ 54 | public void save(String file, String content); 55 | 56 | /** 57 | * Returns file extension in lower case 58 | * @param file 59 | */ 60 | public String getExt(String file); 61 | } 62 | -------------------------------------------------------------------------------- /src/io/emmet/IUserData.java: -------------------------------------------------------------------------------- 1 | package io.emmet; 2 | 3 | public interface IUserData { 4 | public void load(Emmet ctx); 5 | public void loadExtensions(Emmet ctx); 6 | } 7 | -------------------------------------------------------------------------------- /src/io/emmet/SelectionData.java: -------------------------------------------------------------------------------- 1 | package io.emmet; 2 | 3 | 4 | public class SelectionData { 5 | private int start = 0; 6 | private int end = 0; 7 | 8 | public SelectionData() { 9 | 10 | } 11 | 12 | public SelectionData(int start, int end) { 13 | updateRange(start, end); 14 | } 15 | 16 | public void setStart(int start) { 17 | this.start = start; 18 | } 19 | 20 | public int getStart() { 21 | return start; 22 | } 23 | 24 | public void setEnd(int end) { 25 | this.end = end; 26 | } 27 | 28 | public int getEnd() { 29 | return end; 30 | } 31 | 32 | /** 33 | * Updates selection's start and end indexes 34 | */ 35 | public void updateRange(int start, int end) { 36 | this.start = Math.min(start, end); 37 | this.end = Math.max(start, end); 38 | } 39 | 40 | /** 41 | * Updates selection ranges by passing start offset and selection length 42 | * (commonly used notation in most editors) 43 | */ 44 | public void updateRangeWithLength(int start, int length) { 45 | int end = start + length; 46 | updateRange(start, end); 47 | } 48 | 49 | /** 50 | * Returns selection length 51 | */ 52 | public int getLength() { 53 | return end - start; 54 | } 55 | 56 | public String toString() { 57 | return "selection start: " + start + ", end: " + end; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/io/emmet/TabStop.java: -------------------------------------------------------------------------------- 1 | package io.emmet; 2 | 3 | 4 | public class TabStop { 5 | private int start = 0; 6 | private int end = 0; 7 | 8 | public TabStop(int start, int end) { 9 | this.start = start; 10 | this.end = end; 11 | } 12 | 13 | public void setStart(int start) { 14 | this.start = start; 15 | } 16 | 17 | public int getStart() { 18 | return start; 19 | } 20 | 21 | public void setEnd(int end) { 22 | this.end = end; 23 | } 24 | 25 | public int getEnd() { 26 | return end; 27 | } 28 | 29 | public boolean isZeroWidth() { 30 | return start == end; 31 | } 32 | 33 | public int getLength() { 34 | return end - start; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/io/emmet/TabStopGroup.java: -------------------------------------------------------------------------------- 1 | package io.emmet; 2 | 3 | 4 | import java.util.ArrayList; 5 | 6 | public class TabStopGroup { 7 | private ArrayList list; 8 | 9 | public TabStopGroup() { 10 | list = new ArrayList(); 11 | } 12 | 13 | public void addTabStop(int start, int end) { 14 | list.add(new TabStop(start, end)); 15 | } 16 | 17 | public void addTabStop(TabStop tabStop) { 18 | list.add(tabStop); 19 | } 20 | 21 | public ArrayList getTabStopList() { 22 | return list; 23 | } 24 | 25 | public int getLength() { 26 | return list.size(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/io/emmet/TabStopStructure.java: -------------------------------------------------------------------------------- 1 | package io.emmet; 2 | 3 | 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.HashMap; 7 | import java.util.Set; 8 | 9 | import org.mozilla.javascript.Context; 10 | import org.mozilla.javascript.NativeArray; 11 | import org.mozilla.javascript.NativeObject; 12 | import org.mozilla.javascript.Scriptable; 13 | import org.mozilla.javascript.ScriptableObject; 14 | 15 | /** 16 | * A coomon structure that contains list of tabstop groups and valid 17 | * text for these groups 18 | * @author sergey 19 | * 20 | */ 21 | public class TabStopStructure { 22 | /** 23 | * Valid text for current tabstob structure 24 | */ 25 | private String text = ""; 26 | private HashMap groups; 27 | 28 | public TabStopStructure(String text) { 29 | createGroups(); 30 | 31 | Emmet jse = Emmet.getSingleton(); 32 | Scriptable tabstopData = (Scriptable) jse.execJSFunction("javaExtractTabstops", text); 33 | if (tabstopData != null) { 34 | text = Context.toString(ScriptableObject.getProperty(tabstopData, "text")); 35 | NativeArray tabstops = (NativeArray) ScriptableObject.getProperty(tabstopData, "tabstops"); 36 | NativeObject tabstopItem; 37 | for (int i = 0; i < tabstops.getLength(); i++) { 38 | tabstopItem = (NativeObject) ScriptableObject.getProperty(tabstops, i); 39 | addTabStopToGroup( 40 | Context.toString(ScriptableObject.getProperty(tabstopItem, "group")), 41 | (int) Context.toNumber(ScriptableObject.getProperty(tabstopItem, "start")), 42 | (int) Context.toNumber(ScriptableObject.getProperty(tabstopItem, "end"))); 43 | } 44 | } 45 | setText(text); 46 | } 47 | 48 | private void createGroups() { 49 | groups = new HashMap(); 50 | } 51 | 52 | public void setText(String text) { 53 | this.text = text; 54 | } 55 | 56 | public String getText() { 57 | return text; 58 | } 59 | 60 | public void addTabStopToGroup(String groupName, int start, int end) { 61 | if (!groups.containsKey(groupName)) { 62 | groups.put(groupName, new TabStopGroup()); 63 | } 64 | 65 | getTabStopGroup(groupName).addTabStop(start, end); 66 | } 67 | 68 | public HashMap getGroups() { 69 | return groups; 70 | } 71 | 72 | /** 73 | * Returns total amount of tabstops in current structure 74 | * @return 75 | */ 76 | public int getTabStopsCount() { 77 | int result = 0; 78 | for (TabStopGroup item : groups.values()) { 79 | result += item.getLength(); 80 | } 81 | 82 | return result; 83 | } 84 | 85 | public String[] getSortedGroupKeys() { 86 | Set keySet = groups.keySet(); 87 | String[] keys = keySet.toArray(new String[keySet.size()]); 88 | Arrays.sort(keys); 89 | return keys; 90 | } 91 | 92 | public TabStop getFirstTabStop() { 93 | String[] names = getSortedGroupKeys(); 94 | return (names.length > 0) ? getTabStop(names[0], 0) : null; 95 | } 96 | 97 | public TabStopGroup getTabStopGroup(String groupName) { 98 | return (TabStopGroup) groups.get(groupName); 99 | } 100 | 101 | public TabStop getTabStop(String groupName, int index) { 102 | ArrayList tabStops = getTabStopGroup(groupName).getTabStopList(); 103 | return (index < tabStops.size()) ? tabStops.get(index) : null; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/io/emmet/actions/AbstractMenuItem.java: -------------------------------------------------------------------------------- 1 | package io.emmet.actions; 2 | 3 | public abstract class AbstractMenuItem { 4 | public abstract String getType(); 5 | public abstract String getName(); 6 | } 7 | -------------------------------------------------------------------------------- /src/io/emmet/actions/Action.java: -------------------------------------------------------------------------------- 1 | package io.emmet.actions; 2 | 3 | import org.mozilla.javascript.Context; 4 | import org.mozilla.javascript.NativeObject; 5 | import org.mozilla.javascript.ScriptableObject; 6 | 7 | public class Action extends AbstractMenuItem { 8 | private String type = "action"; 9 | private String name = null; 10 | private String id = null; 11 | 12 | public Action(NativeObject item) { 13 | this.name = Context.toString(ScriptableObject.getProperty(item, "label")); 14 | this.id = Context.toString(ScriptableObject.getProperty(item, "name")); 15 | } 16 | 17 | @Override 18 | public String getType() { 19 | return type; 20 | } 21 | 22 | @Override 23 | public String getName() { 24 | return name; 25 | } 26 | 27 | public String getId() { 28 | return id; 29 | } 30 | } -------------------------------------------------------------------------------- /src/io/emmet/actions/Menu.java: -------------------------------------------------------------------------------- 1 | package io.emmet.actions; 2 | 3 | import io.emmet.Emmet; 4 | 5 | import java.util.ArrayList; 6 | 7 | import org.mozilla.javascript.Context; 8 | import org.mozilla.javascript.NativeArray; 9 | import org.mozilla.javascript.NativeObject; 10 | import org.mozilla.javascript.ScriptableObject; 11 | 12 | 13 | public class Menu extends AbstractMenuItem { 14 | private String type = "menu"; 15 | private String name = null; 16 | private ArrayList items; 17 | 18 | public static Menu create() { 19 | // get all actions from Emmet core 20 | Emmet jse = Emmet.getSingleton(); 21 | Object actions = jse.execJSFunction("require('actions').getMenu"); 22 | 23 | return new Menu("Emmet", itemsFromJSArray((NativeArray) actions)); 24 | } 25 | 26 | private static ArrayList itemsFromJSArray(NativeArray ar) { 27 | ArrayList list = new ArrayList(); 28 | 29 | NativeObject menuItem; 30 | for (int i = 0; i < ar.getLength(); i++) { 31 | menuItem = (NativeObject) ScriptableObject.getProperty(ar, i); 32 | if (Context.toString(ScriptableObject.getProperty(menuItem, "type")).equals("action")) { 33 | list.add(new Action(menuItem)); 34 | } else { 35 | list.add(new Menu(menuItem)); 36 | } 37 | } 38 | 39 | return list; 40 | } 41 | 42 | public Menu(String name, ArrayList items) { 43 | this.name = name; 44 | this.items = items; 45 | } 46 | 47 | public Menu(NativeObject item) { 48 | this.name = Context.toString(ScriptableObject.getProperty(item, "name")); 49 | this.items = itemsFromJSArray((NativeArray) ScriptableObject.getProperty(item, "items")); 50 | } 51 | 52 | @Override 53 | public String getType() { 54 | return type; 55 | } 56 | 57 | @Override 58 | public String getName() { 59 | return name; 60 | } 61 | 62 | public ArrayList getItems() { 63 | return items; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/AutoCompleteDialog.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse; 2 | 3 | import java.util.ArrayList; 4 | 5 | import org.eclipse.jface.bindings.keys.KeyStroke; 6 | import org.eclipse.jface.bindings.keys.ParseException; 7 | import org.eclipse.jface.dialogs.Dialog; 8 | import org.eclipse.jface.dialogs.IDialogConstants; 9 | import org.eclipse.jface.fieldassist.ContentProposalAdapter; 10 | import org.eclipse.jface.fieldassist.SimpleContentProposalProvider; 11 | import org.eclipse.jface.fieldassist.TextContentAdapter; 12 | import org.eclipse.swt.SWT; 13 | import org.eclipse.swt.graphics.Point; 14 | import org.eclipse.swt.layout.GridData; 15 | import org.eclipse.swt.widgets.Button; 16 | import org.eclipse.swt.widgets.Composite; 17 | import org.eclipse.swt.widgets.Control; 18 | import org.eclipse.swt.widgets.Label; 19 | import org.eclipse.swt.widgets.Shell; 20 | import org.eclipse.swt.widgets.Text; 21 | 22 | /** 23 | * Input dialog with autocomplete feature 24 | * @author sergey 25 | */ 26 | public class AutoCompleteDialog extends Dialog { 27 | /** 28 | * The title of the dialog. 29 | */ 30 | private String title; 31 | 32 | /** 33 | * The message to display, or null if none. 34 | */ 35 | private String message; 36 | 37 | /** 38 | * The input value; the empty string by default. 39 | */ 40 | private String value = "";//$NON-NLS-1$ 41 | 42 | /** 43 | * Ok button widget. 44 | */ 45 | private Button okButton; 46 | 47 | /** 48 | * Input text widget. 49 | */ 50 | private Text text; 51 | 52 | /** 53 | * Maximum proposals to be displayed in content assist 54 | */ 55 | private int maxProposals = 10; 56 | 57 | private ArrayList proposals; 58 | 59 | /** 60 | * Creates an input dialog with OK and Cancel buttons. Note that the dialog 61 | * will have no visual representation (no widgets) until it is told to open. 62 | *

63 | * Note that the open method blocks for input dialogs. 64 | *

65 | * 66 | * @param parentShell 67 | * the parent shell, or null to create a top-level 68 | * shell 69 | * @param dialogTitle 70 | * the dialog title, or null if none 71 | * @param dialogMessage 72 | * the dialog message, or null if none 73 | * @param initialValue 74 | * the initial input value, or null if none 75 | * (equivalent to the empty string) 76 | */ 77 | public AutoCompleteDialog(Shell parentShell, String dialogTitle, 78 | String dialogMessage, String initialValue) { 79 | super(parentShell); 80 | title = dialogTitle; 81 | message = dialogMessage; 82 | if (initialValue == null) { 83 | value = "";//$NON-NLS-1$ 84 | } else { 85 | value = initialValue; 86 | } 87 | 88 | setProposals(new ArrayList()); 89 | } 90 | 91 | /* 92 | * (non-Javadoc) Method declared on Dialog. 93 | */ 94 | protected void buttonPressed(int buttonId) { 95 | if (buttonId == IDialogConstants.OK_ID) { 96 | value = text.getText(); 97 | } else { 98 | value = null; 99 | } 100 | super.buttonPressed(buttonId); 101 | } 102 | 103 | /* 104 | * (non-Javadoc) 105 | * 106 | * @see org.eclipse.jface.window.Window#configureShell(org.eclipse.swt.widgets.Shell) 107 | */ 108 | protected void configureShell(Shell shell) { 109 | super.configureShell(shell); 110 | if (title != null) { 111 | shell.setText(title); 112 | } 113 | } 114 | 115 | /* 116 | * (non-Javadoc) 117 | * 118 | * @see org.eclipse.jface.dialogs.Dialog#createButtonsForButtonBar(org.eclipse.swt.widgets.Composite) 119 | */ 120 | protected void createButtonsForButtonBar(Composite parent) { 121 | // create OK and Cancel buttons by default 122 | okButton = createButton(parent, IDialogConstants.OK_ID, 123 | IDialogConstants.OK_LABEL, true); 124 | createButton(parent, IDialogConstants.CANCEL_ID, 125 | IDialogConstants.CANCEL_LABEL, false); 126 | //do this here because setting the text will set enablement on the ok 127 | // button 128 | text.setFocus(); 129 | if (value != null) { 130 | text.setText(value); 131 | text.selectAll(); 132 | } 133 | } 134 | 135 | /* 136 | * (non-Javadoc) Method declared on Dialog. 137 | */ 138 | protected Control createDialogArea(Composite parent) { 139 | // create composite 140 | Composite composite = (Composite) super.createDialogArea(parent); 141 | // create message 142 | if (message != null) { 143 | Label label = new Label(composite, SWT.WRAP); 144 | label.setText(message); 145 | GridData data = new GridData(GridData.GRAB_HORIZONTAL 146 | | GridData.GRAB_VERTICAL | GridData.HORIZONTAL_ALIGN_FILL 147 | | GridData.VERTICAL_ALIGN_CENTER); 148 | data.widthHint = convertHorizontalDLUsToPixels(IDialogConstants.MINIMUM_MESSAGE_AREA_WIDTH); 149 | label.setLayoutData(data); 150 | label.setFont(parent.getFont()); 151 | } 152 | text = new Text(composite, getInputTextStyle()); 153 | text.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL 154 | | GridData.HORIZONTAL_ALIGN_FILL)); 155 | 156 | // Help the user with the possible inputs 157 | if (proposals != null) { 158 | try { 159 | String[] props = new String[Math.min(maxProposals, getProposals().size())]; 160 | props = getProposals().subList(0, props.length).toArray(props); 161 | 162 | SimpleContentProposalProvider provider = new SimpleContentProposalProvider(props); 163 | provider.setFiltering(true); 164 | 165 | ContentProposalAdapter adapter = new ContentProposalAdapter(text, 166 | new TextContentAdapter(), 167 | provider, 168 | KeyStroke.getInstance("ARROW_DOWN"), null); 169 | 170 | adapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE); 171 | 172 | composite.pack(); 173 | adapter.setPopupSize(new Point(text.getBounds().width, 100)); 174 | } catch (ParseException e) { 175 | e.printStackTrace(); 176 | } 177 | } 178 | 179 | applyDialogFont(composite); 180 | return composite; 181 | } 182 | 183 | /** 184 | * Returns the ok button. 185 | * 186 | * @return the ok button 187 | */ 188 | protected Button getOkButton() { 189 | return okButton; 190 | } 191 | 192 | /** 193 | * Returns the text area. 194 | * 195 | * @return the text area 196 | */ 197 | protected Text getText() { 198 | return text; 199 | } 200 | 201 | /** 202 | * Returns the string typed into this input dialog. 203 | * 204 | * @return the input string 205 | */ 206 | public String getValue() { 207 | return value; 208 | } 209 | 210 | /** 211 | * Returns the style bits that should be used for the input text field. 212 | * Defaults to a single line entry. Subclasses may override. 213 | * 214 | * @return the integer style bits that should be used when creating the 215 | * input text 216 | */ 217 | protected int getInputTextStyle() { 218 | return SWT.SINGLE | SWT.BORDER; 219 | } 220 | 221 | protected int getShellStyle() { 222 | return SWT.SHEET | SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | getDefaultOrientation(); 223 | } 224 | 225 | public void setMaxProposals(int maxProposals) { 226 | this.maxProposals = maxProposals; 227 | } 228 | 229 | public int getMaxProposals() { 230 | return maxProposals; 231 | } 232 | 233 | public void setProposals(ArrayList proposals) { 234 | this.proposals = proposals; 235 | } 236 | 237 | public ArrayList getProposals() { 238 | return proposals; 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/EclipseEmmetEditor.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse; 2 | 3 | import io.emmet.IEmmetEditor; 4 | import io.emmet.SelectionData; 5 | import io.emmet.TabStop; 6 | import io.emmet.TabStopGroup; 7 | import io.emmet.TabStopStructure; 8 | 9 | import java.util.ArrayList; 10 | import java.util.HashMap; 11 | import java.util.regex.Matcher; 12 | import java.util.regex.Pattern; 13 | 14 | import org.eclipse.jface.text.BadLocationException; 15 | import org.eclipse.jface.text.IDocument; 16 | import org.eclipse.jface.text.IRegion; 17 | import org.eclipse.jface.text.ITextSelection; 18 | import org.eclipse.jface.text.ITextViewer; 19 | import org.eclipse.jface.text.TextSelection; 20 | import org.eclipse.jface.text.TextUtilities; 21 | import org.eclipse.jface.text.link.LinkedModeModel; 22 | import org.eclipse.jface.text.link.LinkedModeUI; 23 | import org.eclipse.jface.text.link.LinkedPosition; 24 | import org.eclipse.jface.text.link.LinkedPositionGroup; 25 | import org.eclipse.jface.viewers.ISelection; 26 | import org.eclipse.jface.viewers.ISelectionProvider; 27 | import org.eclipse.jface.window.Window; 28 | import org.eclipse.swt.widgets.Display; 29 | import org.eclipse.swt.widgets.Shell; 30 | import org.eclipse.ui.IEditorPart; 31 | 32 | 33 | public class EclipseEmmetEditor implements IEmmetEditor { 34 | 35 | private IEditorPart editor; 36 | private IDocument doc; 37 | private String caretPlaceholder = "${0}"; 38 | 39 | private static Pattern whitespaceBegin = Pattern.compile("^(\\s+)"); 40 | 41 | private static String DIALOG_PROMPT = "prompt"; 42 | private static String DIALOG_WRAP_WITH_ABBREVIATION = "wrap"; 43 | 44 | private HashMap> proposals; 45 | 46 | public EclipseEmmetEditor() { 47 | 48 | } 49 | 50 | public EclipseEmmetEditor(IEditorPart editor) { 51 | setContext(editor); 52 | } 53 | 54 | public void setContext(IEditorPart editor) { 55 | this.editor = editor; 56 | doc = EclipseEmmetHelper.getDocument(editor); 57 | if (proposals == null) { 58 | proposals = new HashMap>(); 59 | } 60 | } 61 | 62 | public boolean isValid() { 63 | return editor != null && doc != null; 64 | } 65 | 66 | @Override 67 | public SelectionData getSelectionRange() { 68 | ISelectionProvider sp = editor.getEditorSite().getSelectionProvider(); 69 | ISelection selection = sp.getSelection(); 70 | 71 | SelectionData result = new SelectionData(); 72 | 73 | if (selection instanceof ITextSelection) { 74 | ITextSelection txSel = (ITextSelection) selection; 75 | result.updateRangeWithLength(txSel.getOffset(), txSel.getLength()); 76 | } 77 | 78 | return result; 79 | } 80 | 81 | @Override 82 | public void createSelection(int start, int end) { 83 | editor.getEditorSite().getSelectionProvider().setSelection(new TextSelection(start, end - start)); 84 | } 85 | 86 | @Override 87 | public SelectionData getCurrentLineRange() { 88 | return getLineRangeFromPosition(getCaretPos()); 89 | } 90 | 91 | public SelectionData getLineRangeFromPosition(int pos) { 92 | SelectionData result = new SelectionData(); 93 | 94 | try { 95 | IRegion lineInfo = doc.getLineInformationOfOffset(pos); 96 | result.updateRangeWithLength(lineInfo.getOffset(), lineInfo.getLength()); 97 | } catch (BadLocationException e) { } 98 | 99 | return result; 100 | } 101 | 102 | @Override 103 | public int getCaretPos() { 104 | return getSelectionRange().getStart(); 105 | } 106 | 107 | @Override 108 | public void setCaretPos(int pos) { 109 | createSelection(pos, pos); 110 | } 111 | 112 | @Override 113 | public String getCurrentLine() { 114 | return getLineFromRange(getCurrentLineRange()); 115 | } 116 | 117 | public String getLineFromRange(SelectionData range) { 118 | try { 119 | return doc.get(range.getStart(), range.getLength()); 120 | } catch (BadLocationException e) { 121 | return ""; 122 | } 123 | } 124 | 125 | @Override 126 | public void replaceContent(String value) { 127 | replaceContent(value, 0, doc.getLength(), false); 128 | } 129 | 130 | @Override 131 | public void replaceContent(String value, int start) { 132 | replaceContent(value, start, start, false); 133 | } 134 | 135 | @Override 136 | public void replaceContent(String value, int start, int end) { 137 | replaceContent(value, start, end, false); 138 | } 139 | 140 | @Override 141 | public void replaceContent(String value, int start, int end, boolean noIndent) { 142 | String newValue = value; 143 | 144 | if (!noIndent) { 145 | String line = getLineFromRange(getLineRangeFromPosition(start)); 146 | String padding = getStringPadding(line); 147 | newValue = padString(value, padding); 148 | } 149 | 150 | TabStopStructure tabStops = new TabStopStructure(newValue); 151 | newValue = tabStops.getText(); 152 | 153 | try { 154 | doc.replace(start, end - start, newValue); 155 | 156 | int totalLinks = tabStops.getTabStopsCount(); 157 | 158 | if (totalLinks < 1) { 159 | tabStops.addTabStopToGroup("carets", newValue.length(), newValue.length()); 160 | } 161 | 162 | String[] tabGroups = tabStops.getSortedGroupKeys(); 163 | TabStop firstTabStop = tabStops.getFirstTabStop(); 164 | 165 | if (totalLinks > 1 || firstTabStop != null && firstTabStop.getStart() != firstTabStop.getEnd()) { 166 | ITextViewer viewer = EclipseEmmetHelper.getTextViewer(editor); 167 | LinkedModeModel model = new LinkedModeModel(); 168 | int exitPos = -1; 169 | 170 | for (int i = 0; i < tabGroups.length; i++) { 171 | TabStopGroup tabGroup = tabStops.getTabStopGroup(tabGroups[i]); 172 | LinkedPositionGroup group = null; 173 | 174 | if (tabGroups[i].equals("carets") || tabGroups[i].equals("0")) { 175 | int caretCount = tabGroup.getTabStopList().size(); 176 | for (int j = 0; j < caretCount; j++) { 177 | TabStop ts = tabGroup.getTabStopList().get(j); 178 | group = new LinkedPositionGroup(); 179 | group.addPosition(new LinkedPosition(doc, start + ts.getStart(), ts.getLength())); 180 | model.addGroup(group); 181 | if (j == caretCount - 1) { 182 | exitPos = start + ts.getStart(); 183 | } 184 | } 185 | } else { 186 | group = new LinkedPositionGroup(); 187 | 188 | for (int j = 0; j < tabGroup.getTabStopList().size(); j++) { 189 | TabStop ts = tabGroup.getTabStopList().get(j); 190 | group.addPosition(new LinkedPosition(doc, start + ts.getStart(), ts.getLength())); 191 | } 192 | 193 | model.addGroup(group); 194 | } 195 | } 196 | 197 | model.forceInstall(); 198 | LinkedModeUI linkUI = new LinkedModeUI(model, viewer); 199 | if (exitPos != -1) { 200 | linkUI.setExitPosition(viewer, exitPos, 0, Integer.MAX_VALUE); 201 | } 202 | 203 | // Aptana has a buggy linked mode implementation, use simple 204 | // mode for it 205 | linkUI.setSimpleMode(isApatana()); 206 | linkUI.enter(); 207 | } else { 208 | setCaretPos(start + firstTabStop.getStart()); 209 | } 210 | } catch (Exception e) { 211 | e.printStackTrace(); 212 | } 213 | } 214 | 215 | public String getCurrentLinePadding() { 216 | return getStringPadding(getCurrentLine()); 217 | } 218 | 219 | /** 220 | * Returns whitespace padding from the beginning of the text 221 | * @param text 222 | * @return 223 | */ 224 | private String getStringPadding(String text) { 225 | Matcher matcher = whitespaceBegin.matcher(text); 226 | if (matcher.find()) { 227 | return matcher.group(0); 228 | } else { 229 | return ""; 230 | } 231 | } 232 | 233 | /** 234 | * Repeats string howMany times 235 | */ 236 | public String repeatString(String str, int howMany) { 237 | StringBuilder result = new StringBuilder(); 238 | 239 | for (int i = 0; i < howMany; i++) { 240 | result.append(str); 241 | } 242 | 243 | return result.toString(); 244 | } 245 | 246 | public String getNewline() { 247 | return TextUtilities.getDefaultLineDelimiter(doc); 248 | } 249 | 250 | /** 251 | * Indents text with padding 252 | * @param {String} text Text to indent 253 | * @param {String|Number} pad Padding size (number) or padding itself (string) 254 | * @return {String} 255 | */ 256 | public String padString(String text, String pad) { 257 | StringBuilder result = new StringBuilder(); 258 | String newline = getNewline(); 259 | String lines[] = text.split("\\r\\n|\\n\\r|\\r|\\n", -1); 260 | 261 | if (lines.length > 0) { 262 | result.append(lines[0]); 263 | for (int i = 1; i < lines.length; i++) { 264 | result.append(newline + pad + lines[i]); 265 | } 266 | } else { 267 | result.append(text); 268 | } 269 | 270 | return result.toString(); 271 | } 272 | 273 | @Override 274 | public String getContent() { 275 | return doc.get(); 276 | } 277 | 278 | @Override 279 | public String getSyntax() { 280 | String syntax = EditorTypeInvestigator.getSyntax(this); 281 | if (syntax == null) 282 | syntax = EditorTypeInvestigator.TYPE_HTML; 283 | return syntax; 284 | } 285 | 286 | @Override 287 | public String getProfileName() { 288 | return EditorTypeInvestigator.getOutputProfile(this); 289 | } 290 | 291 | public String prompt(String type, String title) { 292 | 293 | final Display currentDisplay = Display.getCurrent(); 294 | String defaultValueArg = ""; 295 | 296 | /** 297 | * Answer 298 | */ 299 | class Answer { 300 | public String result = ""; 301 | } 302 | 303 | final String message = title; 304 | final String dialogType = type; 305 | final String defaultValue = defaultValueArg; 306 | final Answer a = new Answer(); 307 | 308 | if (currentDisplay != null) { 309 | currentDisplay.syncExec(new Runnable() { 310 | 311 | public void run() { 312 | Shell shell = currentDisplay.getActiveShell(); 313 | 314 | if (shell != null) { 315 | AutoCompleteDialog dialog = dialogFactory(dialogType, message, defaultValue); 316 | int dialogResult = dialog.open(); 317 | if (dialogResult == Window.OK) { 318 | a.result = dialog.getValue(); 319 | addProposal(message, a.result); 320 | } else { 321 | a.result = ""; 322 | } 323 | } 324 | } 325 | }); 326 | } 327 | 328 | return a.result; 329 | } 330 | 331 | @Override 332 | public String prompt(String title) { 333 | return prompt(DIALOG_PROMPT, title); 334 | } 335 | 336 | public String promptWrap(String title) { 337 | return prompt(DIALOG_WRAP_WITH_ABBREVIATION, title); 338 | } 339 | 340 | private AutoCompleteDialog dialogFactory(String type, String message, String defaultValue) { 341 | AutoCompleteDialog dialog; 342 | if (type == DIALOG_WRAP_WITH_ABBREVIATION) { 343 | dialog = new WrapWithAbbreviationDialog(null, "Emmet Prompt", message, defaultValue); 344 | } else { 345 | dialog = new AutoCompleteDialog(null, "Emmet Prompt", message, defaultValue); 346 | } 347 | 348 | dialog.setProposals(getProposals(message)); 349 | return dialog; 350 | } 351 | 352 | private ArrayList getProposals(String title) { 353 | if (proposals.containsKey(title)) 354 | return proposals.get(title); 355 | 356 | return null; 357 | } 358 | 359 | private void addProposal(String title, String value) { 360 | if (!value.equals("")) { 361 | if (!proposals.containsKey(title)) 362 | proposals.put(title, new ArrayList()); 363 | 364 | ArrayList props = proposals.get(title); 365 | if (!props.contains(value)) 366 | props.add(0, value); 367 | } 368 | } 369 | 370 | @Override 371 | public String getSelection() { 372 | SelectionData selection = getSelectionRange(); 373 | try { 374 | return doc.get(selection.getStart(), selection.getLength()); 375 | } catch (BadLocationException e) { 376 | return ""; 377 | } 378 | } 379 | 380 | @Override 381 | public String getFilePath() { 382 | return EclipseEmmetHelper.getURI(editor).substring(5); 383 | } 384 | 385 | public IEditorPart getEditor() { 386 | return editor; 387 | } 388 | 389 | public IDocument getDocument() { 390 | return doc; 391 | } 392 | 393 | public String getCaretPlaceholder() { 394 | return caretPlaceholder; 395 | } 396 | 397 | public boolean isApatana() { 398 | return getEditor().toString().toLowerCase().indexOf(".aptana.") != -1; 399 | } 400 | 401 | public void print(String msg) { 402 | System.out.println("ZC: " + msg); 403 | } 404 | 405 | /** 406 | * Removes caret placeholders and tabstops from text 407 | * @param text 408 | * @return 409 | */ 410 | public String cleanText(String text) { 411 | TabStopStructure tss = new TabStopStructure(text); 412 | return tss.getText(); 413 | } 414 | } 415 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/EclipseEmmetFile.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse; 2 | 3 | import io.emmet.IEmmetFile; 4 | 5 | import java.io.File; 6 | import java.io.FileInputStream; 7 | import java.io.FileOutputStream; 8 | import java.io.IOException; 9 | 10 | 11 | public class EclipseEmmetFile implements IEmmetFile { 12 | 13 | @Override 14 | public String read(String path) { 15 | File f = new File(path); 16 | StringBuffer strContent = new StringBuffer(""); 17 | 18 | if (f.exists() && f.isFile() && f.canRead()) { 19 | int ch; 20 | FileInputStream fin; 21 | try { 22 | fin = new FileInputStream(f); 23 | while( (ch = fin.read()) != -1) 24 | strContent.append((char)ch); 25 | 26 | fin.close(); 27 | } catch (Exception e) { 28 | return ""; 29 | } 30 | } 31 | 32 | return strContent.toString(); 33 | } 34 | 35 | @Override 36 | public String locateFile(String editorFile, String fileName) { 37 | File f = new File(editorFile); 38 | String result = null; 39 | File tmp; 40 | 41 | // traverse upwards to find image uri 42 | while (f.getParent() != null) { 43 | tmp = new File(this.createPath(f.getParent(), fileName)); 44 | if (tmp.exists()) { 45 | try { 46 | result = tmp.getCanonicalPath(); 47 | } catch (IOException e) {} 48 | 49 | break; 50 | } 51 | 52 | f = new File(f.getParent()); 53 | } 54 | 55 | return result; 56 | } 57 | 58 | @Override 59 | public String createPath(String parent, String fileName) { 60 | File f = new File(parent); 61 | String result = null; 62 | 63 | if (f.exists()) { 64 | if (f.isFile()) { 65 | parent = f.getParent(); 66 | } 67 | 68 | File reqFile = new File(parent, fileName); 69 | try { 70 | result = reqFile.getCanonicalPath(); 71 | } catch (IOException e) { 72 | 73 | } 74 | } 75 | 76 | return result; 77 | } 78 | 79 | @Override 80 | public void save(String file, String content) { 81 | File f = new File(file); 82 | 83 | if (file.indexOf('/') != -1) { 84 | File f_parent = new File(f.getParent()); 85 | f_parent.mkdirs(); 86 | } 87 | 88 | FileOutputStream stream = null; 89 | try { 90 | if (!f.exists()) 91 | f.createNewFile(); 92 | 93 | stream = new FileOutputStream(file); 94 | 95 | for (int i = 0; i < content.length(); i++) { 96 | stream.write(content.codePointAt(i)); 97 | } 98 | 99 | stream.write(content.getBytes()); 100 | stream.flush(); 101 | stream.close(); 102 | } catch (IOException e) { 103 | e.printStackTrace(); 104 | } 105 | } 106 | 107 | @Override 108 | public String getExt(String file) { 109 | int ix = file.lastIndexOf('.'); 110 | if (ix != -1) { 111 | return file.substring(ix + 1).toLowerCase(); 112 | } else { 113 | return ""; 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/EclipseEmmetHelper.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.lang.reflect.Field; 6 | import java.lang.reflect.Method; 7 | import java.net.URI; 8 | import java.net.URISyntaxException; 9 | 10 | import org.eclipse.core.resources.IFile; 11 | import org.eclipse.core.runtime.IPath; 12 | import org.eclipse.jface.text.IDocument; 13 | import org.eclipse.jface.text.ITextViewer; 14 | import org.eclipse.ui.IEditorInput; 15 | import org.eclipse.ui.IEditorPart; 16 | import org.eclipse.ui.IPathEditorInput; 17 | import org.eclipse.ui.IStorageEditorInput; 18 | import org.eclipse.ui.PlatformUI; 19 | import org.eclipse.ui.part.FileEditorInput; 20 | import org.eclipse.ui.part.MultiPageEditorPart; 21 | import org.eclipse.ui.texteditor.AbstractTextEditor; 22 | import org.eclipse.ui.texteditor.DocumentProviderRegistry; 23 | import org.eclipse.ui.texteditor.IDocumentProvider; 24 | import org.eclipse.ui.texteditor.ITextEditor; 25 | 26 | /** 27 | * Helper object that provides some commonly used functions for Emmet. 28 | * 29 | * File location methods are taken from Aptana project: 30 | * {@link https://github.com/aptana/} 31 | * @author sergey 32 | * 33 | */ 34 | public class EclipseEmmetHelper { 35 | 36 | private static final String FILE_COLON = "file:"; //$NON-NLS-1$ 37 | private static final String FILE_SLASH = FILE_COLON + "/"; //$NON-NLS-1$ 38 | private static final String FILE_SLASH_SLASH = FILE_SLASH + "/"; //$NON-NLS-1$ 39 | 40 | public static IEditorPart getActiveEditor() { 41 | IEditorPart editor = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor(); 42 | return getTextEditor(editor); 43 | } 44 | 45 | /** 46 | * Returns text editor object from given editor part. If given part is multipage editor, 47 | * it tries to find text editor in it 48 | * @param editor 49 | * @return 50 | */ 51 | public static IEditorPart getTextEditor(IEditorPart editor) { 52 | if (editor instanceof MultiPageEditorPart) { 53 | Object currentPage = ((MultiPageEditorPart) editor).getSelectedPage(); 54 | if (currentPage instanceof ITextEditor) 55 | editor = (ITextEditor) currentPage; 56 | else 57 | editor = null; 58 | } 59 | 60 | return editor; 61 | } 62 | 63 | public static IDocument getActiveDocument() { 64 | return getDocument(getActiveEditor()); 65 | } 66 | 67 | public static IDocument getDocument(IEditorPart editor) { 68 | if (editor != null) { 69 | IDocumentProvider dp = null; 70 | if (editor instanceof ITextEditor) 71 | dp = ((ITextEditor) editor).getDocumentProvider(); 72 | 73 | if (dp == null) 74 | dp = DocumentProviderRegistry.getDefault().getDocumentProvider(editor.getEditorInput()); 75 | 76 | if (dp != null) 77 | return (IDocument) dp.getDocument(editor.getEditorInput()); 78 | } 79 | 80 | return null; 81 | } 82 | 83 | @SuppressWarnings({ "rawtypes", "unchecked" }) 84 | public static ITextViewer getTextViewer(IEditorPart editor) { 85 | Field svField; 86 | ITextViewer viewer = null; 87 | 88 | if (editor instanceof AbstractTextEditor) { 89 | try { 90 | svField = AbstractTextEditor.class.getDeclaredField("fSourceViewer"); 91 | svField.setAccessible(true); 92 | viewer = (ITextViewer) svField.get((AbstractTextEditor) editor); 93 | } catch (Exception e) { } 94 | } 95 | 96 | if (viewer == null) { 97 | Class editorClass = editor.getClass(); 98 | try { 99 | Method getViewer = editorClass.getMethod("getViewer", new Class[]{}); 100 | viewer = (ITextViewer) getViewer.invoke(editor); 101 | } catch (Exception e) { } 102 | } 103 | 104 | return viewer; 105 | } 106 | 107 | /** 108 | * Calls IFile.getLocation if it exists and uses an Eclipse internal mechanism if the file is deleted. Look at the 109 | * implementation of IFile.getLocation to see why this is necessary. Basically getLocation() returns null if the 110 | * enclosing project doesn't exist so this allows the location of a deleted file to be found. 111 | * 112 | * @param file 113 | * @return - Absolute OS string of file location 114 | */ 115 | public static String getStringOfIFileLocation(IFile file) { 116 | String location = null; 117 | IPath path = getPathOfIFileLocation(file); 118 | if (path != null) { 119 | location = path.makeAbsolute().toOSString(); 120 | } 121 | return location; 122 | } 123 | 124 | /** 125 | * @see com.aptana.ide.core.ui.CoreUIUtils#getStringOfIFileLocation(IFile file) 126 | * @param file 127 | * @return - path of IFile 128 | */ 129 | public static IPath getPathOfIFileLocation(IFile file) { 130 | IPath location = null; 131 | if (file != null) { 132 | if (file.exists() && file.getProject() != null 133 | && file.getProject().exists()) { 134 | location = file.getLocation(); 135 | } 136 | } 137 | return location; 138 | } 139 | 140 | /** 141 | * Returns the current path to the source file from an editor input. 142 | * 143 | * @param input 144 | * the editor input 145 | * @return the path, or null if not found 146 | */ 147 | public static String getPathFromEditorInput(IEditorInput input) { 148 | try { 149 | if (input instanceof FileEditorInput) { 150 | IFile file = ((FileEditorInput) input).getFile(); 151 | return getStringOfIFileLocation(file); 152 | } else if (input instanceof IStorageEditorInput) { 153 | IStorageEditorInput sei = (IStorageEditorInput) input; 154 | try { 155 | return sei.getStorage().getFullPath().toOSString(); 156 | } catch (Exception e) { 157 | if (input instanceof IPathEditorInput) { 158 | IPathEditorInput pin = (IPathEditorInput) input; 159 | return pin.getPath().toOSString(); 160 | } 161 | } 162 | } else if (input instanceof IPathEditorInput) { 163 | IPathEditorInput pin = (IPathEditorInput) input; 164 | return pin.getPath().toOSString(); 165 | } 166 | } catch (Exception e) { 167 | return null; 168 | } 169 | 170 | return null; 171 | } 172 | 173 | /** 174 | * Returns the URI for the current editor (effectively the file path transformed into file://) 175 | * 176 | * @param editor 177 | * @return String 178 | */ 179 | public static String getURI(IEditorPart editor) { 180 | if (editor != null && editor.getEditorInput() != null) { 181 | return getURI(editor.getEditorInput()); 182 | } else { 183 | return null; 184 | } 185 | } 186 | 187 | /** 188 | * Returns a valid URI from the passed in editor input. This assumed that the editor input represents a file on disk 189 | * 190 | * @param input 191 | * @return String 192 | */ 193 | public static String getURI(IEditorInput input) { 194 | String s = getPathFromEditorInput(input); 195 | if (s == null) { 196 | try { 197 | Method method = input.getClass().getMethod("getURI"); //$NON-NLS-1$ 198 | return ((URI) method.invoke(input)).toString(); 199 | } catch (Exception e) { 200 | 201 | } 202 | return null; 203 | } 204 | return getURI(new File(s)); 205 | } 206 | 207 | /** 208 | * Returns a URI from a file 209 | * 210 | * @param file 211 | * the file to pull from 212 | * @return the string path to the file 213 | */ 214 | public static String getURI(File file) { 215 | return getURI(file, true); 216 | } 217 | 218 | /** 219 | * Returns a URI from a file 220 | * 221 | * @param file 222 | * the file to pull from 223 | * @param urlEncode 224 | * do we url encode the file name 225 | * @return the string path to the file 226 | */ 227 | public static String getURI(File file, boolean urlEncode) { 228 | String filePath = null; 229 | 230 | String path = file.getPath(); 231 | if (path.startsWith("file:\\")) //$NON-NLS-1$ 232 | { 233 | filePath = path.replaceAll("file:\\\\", FILE_SLASH_SLASH); //$NON-NLS-1$ 234 | } else if (path.startsWith("http:\\")) //$NON-NLS-1$ 235 | { 236 | filePath = path.replaceAll("http:\\\\", "http://"); //$NON-NLS-1$ //$NON-NLS-2$ 237 | } else { 238 | try { 239 | filePath = file.getCanonicalPath(); 240 | } catch (IOException e) { 241 | filePath = file.getAbsolutePath(); 242 | } 243 | 244 | if (filePath.startsWith("\\\\")) //$NON-NLS-1$ 245 | { 246 | filePath = filePath.substring(2); 247 | } 248 | filePath = appendProtocol(filePath); 249 | } 250 | 251 | filePath = filePath.replaceAll("\\\\", "/"); //$NON-NLS-1$ //$NON-NLS-2$ 252 | 253 | if (urlEncode) { 254 | filePath = urlEncodeFilename(filePath.toCharArray()); 255 | } 256 | 257 | URI uri; 258 | try { 259 | if (urlEncode) { 260 | uri = new URI(filePath).normalize(); 261 | return uri.toString(); 262 | } else { 263 | return filePath; 264 | } 265 | } catch (URISyntaxException e) { 266 | return filePath; 267 | } 268 | } 269 | 270 | /** 271 | * Appends the file:// protocol, if none found 272 | * 273 | * @param path 274 | * @return String 275 | */ 276 | public static String appendProtocol(String path) { 277 | if (path.indexOf("://") < 0) //$NON-NLS-1$ 278 | { 279 | return FILE_SLASH_SLASH + path; 280 | } 281 | return path; 282 | } 283 | 284 | /** 285 | * This method encodes the URL, removes the spaces and brackets from the URL and replaces the same with 286 | * "%20" and "%5B" and "%5D" and "%7B" "%7D". 287 | * 288 | * @param input 289 | * @return String 290 | * @since 3.0.2 291 | */ 292 | public static String urlEncodeFilename(char[] input) { 293 | 294 | if (input == null) { 295 | return null; 296 | } 297 | 298 | StringBuffer retu = new StringBuffer(input.length); 299 | for (int i = 0; i < input.length; i++) { 300 | if (input[i] == ' ') { 301 | retu.append("%20"); //$NON-NLS-1$ 302 | } else if (input[i] == '[') { 303 | retu.append("%5B"); //$NON-NLS-1$ 304 | } else if (input[i] == ']') { 305 | retu.append("%5D"); //$NON-NLS-1$ 306 | } else if (input[i] == '{') { 307 | retu.append("%7B"); //$NON-NLS-1$ 308 | } else if (input[i] == '}') { 309 | retu.append("%7D"); //$NON-NLS-1$ 310 | } else if (input[i] == '`') { 311 | retu.append("%60"); //$NON-NLS-1$ 312 | } else if (input[i] == '+') { 313 | retu.append("%2B"); //$NON-NLS-1$ 314 | } else { 315 | retu.append(input[i]); 316 | } 317 | } 318 | return retu.toString(); 319 | } 320 | 321 | /** 322 | * Returns string representation of passed editor 323 | * @param editor 324 | * @return 325 | */ 326 | public static String getEditorString(EclipseEmmetEditor editor) { 327 | return editor.getEditor().toString().toLowerCase(); 328 | } 329 | 330 | /** 331 | * Test if current editor belongs to Aptana 332 | * @param editor 333 | * @return 334 | */ 335 | public static boolean isApatana(EclipseEmmetEditor editor) { 336 | return getEditorString(editor).indexOf(".aptana.") != -1; 337 | } 338 | 339 | } 340 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/EclipseEmmetPlugin.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse; 2 | 3 | import io.emmet.Emmet; 4 | import io.emmet.eclipse.preferences.PreferenceConstants; 5 | 6 | import org.eclipse.jface.resource.ImageDescriptor; 7 | import org.eclipse.jface.util.IPropertyChangeListener; 8 | import org.eclipse.jface.util.PropertyChangeEvent; 9 | import org.eclipse.ui.plugin.AbstractUIPlugin; 10 | import org.osgi.framework.BundleContext; 11 | 12 | 13 | /** 14 | * The activator class controls the plug-in life cycle 15 | */ 16 | public class EclipseEmmetPlugin extends AbstractUIPlugin { 17 | 18 | // The plug-in ID 19 | public static final String PLUGIN_ID = "io.emmet.eclipse"; //$NON-NLS-1$ 20 | 21 | // The shared instance 22 | private static EclipseEmmetPlugin plugin; 23 | 24 | /** 25 | * The constructor 26 | */ 27 | public EclipseEmmetPlugin() { 28 | } 29 | 30 | /* 31 | * (non-Javadoc) 32 | * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext) 33 | */ 34 | public void start(BundleContext context) throws Exception { 35 | super.start(context); 36 | plugin = this; 37 | 38 | Emmet.setUserDataDelegate(new EclipseUserData()); 39 | 40 | // XXX maybe there's a better place for such listener? 41 | getDefault().getPreferenceStore().addPropertyChangeListener(new IPropertyChangeListener() { 42 | @Override 43 | public void propertyChange(PropertyChangeEvent event) { 44 | if (event.getProperty() == PreferenceConstants.P_EXTENSIONS_PATH) { 45 | Emmet.reset(); 46 | } 47 | } 48 | }); 49 | } 50 | 51 | /* 52 | * (non-Javadoc) 53 | * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) 54 | */ 55 | public void stop(BundleContext context) throws Exception { 56 | plugin = null; 57 | super.stop(context); 58 | } 59 | 60 | /** 61 | * Returns the shared instance 62 | * 63 | * @return the shared instance 64 | */ 65 | public static EclipseEmmetPlugin getDefault() { 66 | return plugin; 67 | } 68 | 69 | /** 70 | * Returns an image descriptor for the image file at the given 71 | * plug-in relative path 72 | * 73 | * @param path the path 74 | * @return the image descriptor 75 | */ 76 | public static ImageDescriptor getImageDescriptor(String path) { 77 | return imageDescriptorFromPlugin(PLUGIN_ID, path); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/EclipseTemplateProcessor.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse; 2 | 3 | /** 4 | * Processes Eclipse template and converts it to Emmet abbreviation/snippet 5 | * @author sergey 6 | * 7 | */ 8 | public class EclipseTemplateProcessor { 9 | /** 10 | * Convert Eclipse template to Emmet entry 11 | * @param template 12 | * @return 13 | */ 14 | public static String process(String template) { 15 | StringBuffer result = new StringBuffer(); 16 | 17 | char ch; 18 | char nextCh; 19 | int i = 0; 20 | int len = template.length(); 21 | int varEnd; 22 | String varName; 23 | 24 | while (i < len) { 25 | ch = template.charAt(i); 26 | nextCh = (i < len - 1) ? template.charAt(i + 1) : '\0'; 27 | 28 | if (ch == '$') { 29 | if (nextCh == '$') { // escaping dollar sign 30 | result.append("\\$"); 31 | i++; 32 | } else if (nextCh == '{') { // variable start 33 | varEnd = template.indexOf('}', i); 34 | if (varEnd != -1) { 35 | varName = template.substring(i + 2, varEnd); 36 | if (varName.equals("cursor")) { 37 | result.append('|'); 38 | } else { 39 | // Leave variables as is because filters can provide 40 | // value substitutions 41 | result.append("${" + varName + "}"); 42 | } 43 | i = varEnd; 44 | } else { 45 | result.append(ch); 46 | } 47 | 48 | } else { // just a dollar sign, escape it 49 | result.append("\\$"); 50 | } 51 | } else { 52 | result.append(ch); 53 | } 54 | 55 | i++; 56 | } 57 | 58 | return result.toString(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/EclipseUserData.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse; 2 | 3 | import io.emmet.IUserData; 4 | import io.emmet.Emmet; 5 | import io.emmet.eclipse.preferences.PreferenceConstants; 6 | import io.emmet.eclipse.preferences.TemplateHelper; 7 | import io.emmet.eclipse.preferences.output.OutputProfile; 8 | 9 | import java.io.File; 10 | import java.util.ArrayList; 11 | import java.util.HashMap; 12 | 13 | import org.eclipse.jface.preference.IPreferenceStore; 14 | import org.eclipse.jface.text.templates.Template; 15 | import org.eclipse.jface.text.templates.persistence.TemplateStore; 16 | 17 | 18 | import com.google.gson.Gson; 19 | 20 | public class EclipseUserData implements IUserData { 21 | 22 | @Override 23 | public void load(Emmet ctx) { 24 | // since JSON with variable data types and fields in Java is pretty hard, 25 | // we will collect user data into a simple hash array and then convert to 26 | // desired structure in JS 27 | HashMap userData = new HashMap(); 28 | userData.put("variables", getTemplates("variables")); 29 | userData.put("snippets", getTemplates("snippets")); 30 | userData.put("abbreviations", getTemplates("abbreviations")); 31 | userData.put("profiles", OutputProfile.allProfiles()); 32 | 33 | Gson gson = new Gson(); 34 | String payload = gson.toJson(userData); 35 | ctx.execJSFunction("javaLoadUserData", payload); 36 | } 37 | 38 | /** 39 | * Loads Emmet extensions from folder 40 | * @param cx 41 | * @param scope 42 | */ 43 | @Override 44 | public void loadExtensions(Emmet ctx) { 45 | IPreferenceStore store = EclipseEmmetPlugin.getDefault().getPreferenceStore(); 46 | String extensionsPath = store.getString(PreferenceConstants.P_EXTENSIONS_PATH); 47 | if (extensionsPath != null && extensionsPath.length() > 0) { 48 | File extDir = new File(extensionsPath); 49 | if (extDir.exists() && extDir.isDirectory()) { 50 | File[] files = extDir.listFiles(); 51 | ArrayList extFiles = new ArrayList(); 52 | 53 | try { 54 | for (File f : files) { 55 | extFiles.add(f.getCanonicalPath()); 56 | } 57 | } catch (Exception e) {} 58 | 59 | Gson gson = new Gson(); 60 | ctx.execJSFunction("javaLoadExtensions", gson.toJson(extFiles)); 61 | } 62 | } 63 | } 64 | 65 | private ArrayList> getTemplates(String type) { 66 | TemplateStore storage = storeFactory(type); 67 | Template[] templates = storage.getTemplates(); 68 | ArrayList> output = new ArrayList>(); 69 | 70 | for (Template template : templates) { 71 | ArrayList templateItem = new ArrayList(); 72 | 73 | 74 | String ctxId = template.getContextTypeId(); 75 | if (ctxId.lastIndexOf('.') != -1) { 76 | String syntax = ctxId.substring(ctxId.lastIndexOf('.') + 1); 77 | templateItem.add(syntax); 78 | } 79 | 80 | templateItem.add(template.getName()); 81 | templateItem.add(EclipseTemplateProcessor.process(template.getPattern())); 82 | output.add(templateItem); 83 | } 84 | 85 | return output; 86 | } 87 | 88 | private TemplateStore storeFactory(String type) { 89 | if (type.equals("variables")) { 90 | return TemplateHelper.getVariableStore(); 91 | } 92 | 93 | return TemplateHelper.getTemplateStore(type); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/EditorTypeInvestigator.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | import org.eclipse.jface.text.IDocument; 6 | import org.eclipse.jface.text.ITypedRegion; 7 | import org.eclipse.ui.IEditorPart; 8 | import org.eclipse.ui.PlatformUI; 9 | import org.eclipse.ui.texteditor.ITextEditor; 10 | 11 | 12 | /** 13 | * Tries to investigate editor's type and syntax profile 14 | * @author sergey 15 | * 16 | */ 17 | public class EditorTypeInvestigator { 18 | public static String TYPE_HTML = "html"; 19 | public static String TYPE_XML = "xml"; 20 | public static String TYPE_CSS = "css"; 21 | public static String TYPE_HAML = "haml"; 22 | public static String TYPE_XSL = "xsl"; 23 | 24 | public static String PROFILE_XML = "xml"; 25 | public static String PROFILE_XHTML = "xhtml"; 26 | public static String PROFILE_HTML = "html"; 27 | public static String PROFILE_DEFAULT = "default"; 28 | 29 | private EditorTypeInvestigator() { 30 | 31 | } 32 | 33 | /** 34 | * Returns current editor's syntax mode 35 | */ 36 | public static String getSyntax(EclipseEmmetEditor editor) { 37 | String result = null; 38 | 39 | IDocument doc = editor.getDocument(); 40 | String className = PlatformUI.getWorkbench().getActiveWorkbenchWindow() 41 | .getActivePage().getActiveEditor().getSite().getId().toLowerCase(); 42 | 43 | // try to get current partition (true Eclipse) 44 | try { 45 | ITypedRegion[] regions = doc.computePartitioning(editor.getCaretPos(), 0); 46 | if (regions.length > 0) { 47 | result = guessSyntaxFromString(regions[0].getType()); 48 | } 49 | } catch (Exception e) { } 50 | 51 | if (result == null) { 52 | // try Aptana 2 way 53 | IEditorPart ed = editor.getEditor(); 54 | if (ed instanceof ITextEditor) { 55 | Class editorClass = ed.getClass(); 56 | try { 57 | Method getFileContext = editorClass.getMethod("getFileContext", new Class[]{}); 58 | Object fileContext = getFileContext.invoke(ed); 59 | if (fileContext != null) { 60 | Class fcClass = fileContext.getClass(); 61 | Method getPartition = fcClass.getMethod("getPartitionAtOffset", new Class[]{Integer.TYPE}); 62 | ITypedRegion region = (ITypedRegion) getPartition.invoke(fileContext, new Object[]{editor.getCaretPos()}); 63 | result = guessSyntaxFromString(region.getType()); 64 | } 65 | 66 | } catch (Exception e) { } 67 | } 68 | } 69 | 70 | if (result == null) { 71 | // try to guess syntax from editor class 72 | result = guessSyntaxFromString(className); 73 | } 74 | 75 | // if (result == null) 76 | // result = TYPE_HTML; // fallback to HTML 77 | 78 | // in case of WTP's XML editor, we have to check editor class too 79 | if (result == TYPE_XML && guessSyntaxFromString(className) == TYPE_XSL) 80 | result = TYPE_XSL; 81 | 82 | return result; 83 | } 84 | 85 | private static String guessSyntaxFromString(String str) { 86 | // System.out.println("Guess syntax from " + str); 87 | if (str.indexOf("xsl") != -1) 88 | return TYPE_XSL; 89 | else if (str.indexOf("xml") != -1) 90 | return TYPE_XML; 91 | else if (str.indexOf("haml") != -1) 92 | return TYPE_HAML; 93 | else if (str.indexOf("sass") != -1) 94 | return TYPE_CSS; 95 | else if (str.indexOf("css") != -1) 96 | return TYPE_CSS; 97 | else if (str.indexOf(".less.") != -1) 98 | return TYPE_CSS; 99 | else if (str.indexOf("html") != -1) 100 | return TYPE_HTML; 101 | 102 | return null; 103 | } 104 | 105 | /** 106 | * Returns current output profile name 107 | */ 108 | public static String getOutputProfile(EclipseEmmetEditor editor) { 109 | String syntax = getSyntax(editor); 110 | if (syntax != null) { 111 | if (syntax.equals(TYPE_XML) || syntax.equals(TYPE_XSL)) 112 | return PROFILE_XML; 113 | return syntax; 114 | } 115 | 116 | // TODO more intelligent output profile guessing 117 | return PROFILE_DEFAULT; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/EmmetContextType.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse; 2 | 3 | import org.eclipse.jface.text.templates.GlobalTemplateVariables; 4 | import org.eclipse.jface.text.templates.TemplateContextType; 5 | 6 | public class EmmetContextType extends TemplateContextType { 7 | 8 | public static final String CTX_HTML = "io.emmet.eclipse.templates.html"; 9 | public static final String CTX_CSS = "io.emmet.eclipse.templates.css"; 10 | public static final String CTX_XML = "io.emmet.eclipse.templates.xml"; 11 | public static final String CTX_XSL = "io.emmet.eclipse.templates.xsl"; 12 | public static final String CTX_HAML = "io.emmet.eclipse.templates.haml"; 13 | 14 | public static final String CTX_VARIABLE = "io.emmet.eclipse.variable"; 15 | 16 | 17 | 18 | public EmmetContextType() { 19 | addResolver(new GlobalTemplateVariables.Cursor()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/MainMenuContribution.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse; 2 | 3 | import io.emmet.actions.AbstractMenuItem; 4 | import io.emmet.actions.Action; 5 | import io.emmet.actions.Menu; 6 | import io.emmet.eclipse.handlers.DefaultAction; 7 | import io.emmet.eclipse.handlers.ExpandAbbreviationAction; 8 | import io.emmet.eclipse.handlers.WrapWithAbbreviationAction; 9 | 10 | import org.eclipse.core.commands.AbstractHandler; 11 | import org.eclipse.core.commands.Command; 12 | import org.eclipse.jface.action.IContributionItem; 13 | import org.eclipse.jface.action.MenuManager; 14 | import org.eclipse.ui.commands.ICommandService; 15 | import org.eclipse.ui.handlers.IHandlerService; 16 | import org.eclipse.ui.menus.CommandContributionItem; 17 | import org.eclipse.ui.menus.CommandContributionItemParameter; 18 | import org.eclipse.ui.menus.ExtensionContributionFactory; 19 | import org.eclipse.ui.menus.IContributionRoot; 20 | import org.eclipse.ui.services.IServiceLocator; 21 | 22 | 23 | public class MainMenuContribution extends ExtensionContributionFactory { 24 | 25 | @Override 26 | public void createContributionItems(IServiceLocator serviceLocator, 27 | final IContributionRoot additions) { 28 | 29 | Menu rootMenu = Menu.create(); 30 | for (AbstractMenuItem item : rootMenu.getItems()) { 31 | additions.addContributionItem(createContributionItem(serviceLocator, item), null); 32 | } 33 | } 34 | 35 | private IContributionItem createContributionItem(IServiceLocator serviceLocator, AbstractMenuItem item) { 36 | if (item instanceof Menu) 37 | return createContributionItem(serviceLocator, (Menu) item); 38 | 39 | return createContributionItem(serviceLocator, (Action) item); 40 | } 41 | 42 | private IContributionItem createContributionItem(IServiceLocator serviceLocator, Action item) { 43 | ICommandService commandService = (ICommandService) serviceLocator.getService(ICommandService.class); 44 | 45 | Command command = commandService.getCommand("io.emmet.eclipse.commands." + item.getId()); 46 | command.define(item.getName(), "", commandService.getCategory("io.emmet.eclipse.commands.category")); 47 | 48 | IHandlerService handlerService = (IHandlerService) serviceLocator.getService(IHandlerService.class); 49 | handlerService.activateHandler(command.getId(), handlerFactory(item.getId())); 50 | 51 | CommandContributionItemParameter p = new CommandContributionItemParameter( 52 | serviceLocator, "", command.getId(), CommandContributionItem.STYLE_PUSH); 53 | 54 | p.label = item.getName(); 55 | 56 | CommandContributionItem contribItem = new CommandContributionItem(p); 57 | contribItem.setVisible(true); 58 | return contribItem; 59 | } 60 | 61 | private IContributionItem createContributionItem(IServiceLocator serviceLocator, Menu item) { 62 | MenuManager submenu = new MenuManager(item.getName(), null); 63 | 64 | for (AbstractMenuItem subitem : item.getItems()) { 65 | submenu.add(createContributionItem(serviceLocator, subitem)); 66 | } 67 | 68 | return submenu; 69 | } 70 | 71 | private AbstractHandler handlerFactory(String actionId) { 72 | if (actionId.equals("expand_abbreviation")) 73 | return new ExpandAbbreviationAction(); 74 | 75 | if (actionId.equals("wrap_with_abbreviation")) 76 | return new WrapWithAbbreviationAction(); 77 | 78 | return new DefaultAction(); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/Startup.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse; 2 | 3 | import org.eclipse.ui.IStartup; 4 | import org.eclipse.ui.IWorkbench; 5 | import org.eclipse.ui.IWorkbenchWindow; 6 | import org.eclipse.ui.PlatformUI; 7 | 8 | public class Startup implements IStartup { 9 | 10 | @Override 11 | public void earlyStartup() { 12 | final IWorkbench workbench = PlatformUI.getWorkbench(); 13 | workbench.getDisplay().asyncExec(new Runnable() { 14 | public void run() { 15 | IWorkbenchWindow window = workbench.getActiveWorkbenchWindow(); 16 | if (window != null) { 17 | TabKeyHandler.setup(window.getActivePage()); 18 | } 19 | } 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/TabKeyHandler.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse; 2 | 3 | import io.emmet.eclipse.handlers.ExpandAbbreviationAction; 4 | import io.emmet.eclipse.handlers.InsertFormattedLineBreakAction; 5 | import io.emmet.eclipse.preferences.PreferenceConstants; 6 | 7 | import java.util.HashMap; 8 | 9 | import org.eclipse.jface.preference.IPreferenceStore; 10 | import org.eclipse.jface.text.IDocument; 11 | import org.eclipse.jface.text.ITextViewer; 12 | import org.eclipse.jface.text.link.LinkedModeModel; 13 | import org.eclipse.swt.custom.StyledText; 14 | import org.eclipse.swt.custom.VerifyKeyListener; 15 | import org.eclipse.swt.events.VerifyEvent; 16 | import org.eclipse.ui.IEditorPart; 17 | import org.eclipse.ui.IEditorReference; 18 | import org.eclipse.ui.IPartListener; 19 | import org.eclipse.ui.IWorkbenchPage; 20 | import org.eclipse.ui.IWorkbenchPart; 21 | import org.eclipse.ui.PlatformUI; 22 | import org.eclipse.ui.texteditor.AbstractTextEditor; 23 | import org.eclipse.ui.texteditor.ITextEditor; 24 | 25 | 26 | /** 27 | * Handles Tab key press 28 | * @author sergey 29 | * 30 | */ 31 | public class TabKeyHandler { 32 | private static HashMap installedEditors = new HashMap(); 33 | private static HashMap keyListeners = new HashMap(); 34 | private static boolean inited = false; 35 | private static boolean enabled = true; 36 | 37 | /** 38 | * Tries to install key listener on editor's widget 39 | */ 40 | public static void install(IWorkbenchPart part) { 41 | IEditorPart editor; 42 | if (isEnabled() && part instanceof IEditorPart) { 43 | editor = EclipseEmmetHelper.getTextEditor((IEditorPart) part); 44 | if (editor instanceof ITextEditor) 45 | install((ITextEditor) editor); 46 | } 47 | } 48 | 49 | /** 50 | * Tries to install key listener on editor's widget 51 | */ 52 | public static void install(ITextEditor editor) { 53 | if (editor == null || !isEnabled()) 54 | return; 55 | 56 | Integer id = getEditorId(editor); 57 | if (!installedEditors.containsKey(id)) { 58 | // install key listener for Tab key 59 | try { 60 | ITextViewer textViewer = EclipseEmmetHelper.getTextViewer(editor); 61 | StyledText widget = textViewer.getTextWidget(); 62 | widget.addVerifyKeyListener(getKeyListener(editor)); 63 | installedEditors.put(id, editor); 64 | } catch (Exception e) { 65 | e.printStackTrace(); 66 | } 67 | } 68 | } 69 | 70 | /** 71 | * Uninstalls Tab key listener from editor 72 | * @param editor 73 | */ 74 | public static void uninstall(AbstractTextEditor editor) { 75 | if (editor == null) 76 | return; 77 | 78 | Integer id = getEditorId(editor); 79 | if (installedEditors.containsKey(id)) { 80 | try { 81 | StyledText widget = EclipseEmmetHelper.getTextViewer(editor).getTextWidget(); 82 | widget.removeVerifyKeyListener(getKeyListener(editor)); 83 | installedEditors.remove(id); 84 | keyListeners.remove(id); 85 | } catch (Exception e) { 86 | e.printStackTrace(); 87 | } 88 | } 89 | } 90 | 91 | public static void uninstall(IWorkbenchPart part) { 92 | IEditorPart editor; 93 | if (part instanceof IEditorPart) { 94 | editor = EclipseEmmetHelper.getTextEditor((IEditorPart) part); 95 | if (editor instanceof AbstractTextEditor) 96 | uninstall((AbstractTextEditor) editor); 97 | } 98 | } 99 | 100 | /** 101 | * Returns unique editor ID 102 | * @param editor 103 | * @return 104 | */ 105 | public static Integer getEditorId(ITextEditor editor) { 106 | return editor.hashCode(); 107 | } 108 | 109 | public static VerifyKeyListener getKeyListener(final ITextEditor editor) { 110 | Integer id = getEditorId(editor); 111 | if (!keyListeners.containsKey(id)) { 112 | keyListeners.put(id, new VerifyKeyListener() { 113 | 114 | @Override 115 | public void verifyKey(VerifyEvent event) { 116 | IDocument document = EclipseEmmetHelper.getDocument(editor); 117 | if (document == null) { 118 | return; 119 | } 120 | 121 | if (LinkedModeModel.hasInstalledModel(document)) { 122 | return; 123 | } 124 | 125 | if (event.doit) { 126 | if (event.keyCode == 9) { // Tab key 127 | event.doit = !ExpandAbbreviationAction.expand(); 128 | } else if (event.keyCode == 13) { // Enter key 129 | event.doit = !InsertFormattedLineBreakAction.execute(); 130 | } 131 | } 132 | 133 | } 134 | }); 135 | } 136 | 137 | return keyListeners.get(id); 138 | } 139 | 140 | /** 141 | * Setup global editor listener which adds Tab key listeners to newly 142 | * created editors 143 | */ 144 | public static void setup(IWorkbenchPage page) { 145 | if (!inited) { 146 | inited = true; 147 | 148 | // get user preference 149 | IPreferenceStore store = EclipseEmmetPlugin.getDefault().getPreferenceStore(); 150 | setEnabled(store.getBoolean(PreferenceConstants.P_TAB_EXPAND)); 151 | 152 | page.addPartListener(new IPartListener() { 153 | 154 | @Override 155 | public void partOpened(IWorkbenchPart part) { 156 | if (isEnabled()) 157 | install(part); 158 | } 159 | 160 | @Override 161 | public void partDeactivated(IWorkbenchPart part) { 162 | 163 | } 164 | 165 | @Override 166 | public void partClosed(IWorkbenchPart part) { 167 | uninstall(part); 168 | } 169 | 170 | @Override 171 | public void partBroughtToTop(IWorkbenchPart part) { 172 | 173 | } 174 | 175 | @Override 176 | public void partActivated(IWorkbenchPart part) { 177 | if (isEnabled()) 178 | install(part); 179 | } 180 | }); 181 | } 182 | } 183 | 184 | /** 185 | * Try to install tab expander for all opened editors 186 | */ 187 | public static void installForAll() { 188 | IEditorReference[] editors = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getEditorReferences(); 189 | for (int i = 0; i < editors.length; i++) { 190 | IEditorReference editor = editors[i]; 191 | install(editor.getEditor(false)); 192 | } 193 | } 194 | 195 | /** 196 | * Try to uninstall tab expander from all opened editors 197 | */ 198 | public static void uninstallFromAll() { 199 | IEditorReference[] editors = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getEditorReferences(); 200 | for (int i = 0; i < editors.length; i++) { 201 | IEditorReference editor = editors[i]; 202 | uninstall(editor.getEditor(false)); 203 | } 204 | } 205 | 206 | public static void setEnabled(boolean enabled) { 207 | TabKeyHandler.enabled = enabled; 208 | if (enabled) 209 | installForAll(); 210 | else 211 | uninstallFromAll(); 212 | } 213 | 214 | public static boolean isEnabled() { 215 | return enabled; 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/WrapWithAbbreviationDialog.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse; 2 | 3 | import io.emmet.Emmet; 4 | import io.emmet.eclipse.handlers.ActionRunner; 5 | 6 | import java.util.Timer; 7 | import java.util.TimerTask; 8 | 9 | import org.eclipse.swt.SWT; 10 | import org.eclipse.swt.graphics.Color; 11 | import org.eclipse.swt.layout.GridData; 12 | import org.eclipse.swt.widgets.Composite; 13 | import org.eclipse.swt.widgets.Control; 14 | import org.eclipse.swt.widgets.Event; 15 | import org.eclipse.swt.widgets.Listener; 16 | import org.eclipse.swt.widgets.Shell; 17 | import org.eclipse.swt.widgets.Text; 18 | 19 | 20 | public class WrapWithAbbreviationDialog extends AutoCompleteDialog { 21 | private Text wrapPreview; 22 | private static int timerDelay = 200; 23 | private String previewPlaceholder = "Enter abbreviation to get live preview"; 24 | private String errorPlaceholder = "Invalid abbreviation"; 25 | 26 | public WrapWithAbbreviationDialog(Shell parentShell, String dialogTitle, 27 | String dialogMessage, String initialValue) { 28 | super(parentShell, dialogTitle, dialogMessage, initialValue); 29 | } 30 | 31 | @Override 32 | protected Control createDialogArea(Composite parent) { 33 | Composite composite = (Composite) super.createDialogArea(parent); 34 | 35 | wrapPreview = new Text(composite, SWT.MULTI | SWT.BORDER | SWT.READ_ONLY | SWT.H_SCROLL | SWT.V_SCROLL); 36 | GridData wrapData = new GridData(GridData.FILL_BOTH); 37 | wrapData.heightHint = convertHeightInCharsToPixels(12); 38 | wrapPreview.setLayoutData(wrapData); 39 | 40 | setupUpdateListener(composite); 41 | 42 | applyDialogFont(composite); 43 | return composite; 44 | } 45 | 46 | private void setupUpdateListener(final Composite composite) { 47 | final Color defaultColor = wrapPreview.getForeground(); 48 | final Color disabledColor = composite.getDisplay().getSystemColor(SWT.COLOR_GRAY); 49 | final Color errorColor = composite.getDisplay().getSystemColor(SWT.COLOR_RED); 50 | final Text text = getText(); 51 | 52 | final Runnable task = new Runnable() { 53 | @Override 54 | public void run() { 55 | String curText = text.getText(); 56 | if (curText.equals("")) { 57 | wrapPreview.setForeground(disabledColor); 58 | wrapPreview.setText(previewPlaceholder); 59 | } else { 60 | EclipseEmmetEditor editor = ActionRunner.getSingleton().getEditor(); 61 | String result = Emmet.getSingleton().getWrapPreview(editor, curText); 62 | 63 | if (result == null || result.equals("") || result.equals("null")) { 64 | wrapPreview.setForeground(errorColor); 65 | wrapPreview.setText(errorPlaceholder); 66 | } else { 67 | wrapPreview.setForeground(defaultColor); 68 | wrapPreview.setText(editor.cleanText(result)); 69 | } 70 | } 71 | } 72 | }; 73 | 74 | Listener listener = new Listener () { 75 | private String lastText = ""; 76 | private Timer timer; 77 | public void handleEvent (Event e) { 78 | String curText = text.getText(); 79 | if (!curText.equals(lastText)) { 80 | if (timer != null) { 81 | timer.cancel(); 82 | } 83 | timer = new Timer(); 84 | timer.schedule(new TimerTask() { 85 | @Override 86 | public void run() { 87 | composite.getDisplay().syncExec(task); 88 | } 89 | }, timerDelay); 90 | lastText = curText; 91 | } 92 | } 93 | }; 94 | 95 | text.addListener(SWT.KeyUp, listener); 96 | task.run(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/handlers/ActionRunner.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.handlers; 2 | 3 | import io.emmet.Emmet; 4 | import io.emmet.eclipse.EclipseEmmetHelper; 5 | import io.emmet.eclipse.EclipseEmmetEditor; 6 | 7 | import org.eclipse.ui.IEditorPart; 8 | 9 | 10 | public class ActionRunner { 11 | private volatile static ActionRunner singleton; 12 | private EclipseEmmetEditor emmetEditor; 13 | private Emmet js; 14 | 15 | private ActionRunner() { 16 | emmetEditor = new EclipseEmmetEditor(); 17 | js = Emmet.getSingleton(); 18 | } 19 | 20 | public static ActionRunner getSingleton() { 21 | if (singleton == null) { 22 | synchronized (ActionRunner.class) { 23 | if (singleton == null) 24 | singleton = new ActionRunner(); 25 | } 26 | } 27 | return singleton; 28 | } 29 | 30 | 31 | 32 | /** 33 | * Runs Emmet action, automatically setting up context editor 34 | * @param actionName Action name to perform 35 | * @return 36 | */ 37 | public boolean run(String actionName) { 38 | EclipseEmmetEditor editor = getEditor(); 39 | if (editor != null) { 40 | try { 41 | return js.runAction(editor, actionName); 42 | } catch (Exception e) { 43 | e.printStackTrace(); 44 | } 45 | } 46 | 47 | return false; 48 | } 49 | 50 | public EclipseEmmetEditor getEditor() { 51 | IEditorPart editor = EclipseEmmetHelper.getActiveEditor(); 52 | if (editor != null) { 53 | emmetEditor.setContext(editor); 54 | return emmetEditor; 55 | } 56 | 57 | return null; 58 | } 59 | } -------------------------------------------------------------------------------- /src/io/emmet/eclipse/handlers/DefaultAction.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.handlers; 2 | 3 | import org.eclipse.core.commands.AbstractHandler; 4 | import org.eclipse.core.commands.ExecutionEvent; 5 | import org.eclipse.core.commands.ExecutionException; 6 | 7 | public class DefaultAction extends AbstractHandler { 8 | 9 | @Override 10 | public Object execute(ExecutionEvent event) throws ExecutionException { 11 | String commandId = event.getCommand().getId(); 12 | String commandName = commandId.substring(commandId.lastIndexOf('.') + 1); 13 | ActionRunner.getSingleton().run(commandName); 14 | return null; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/handlers/ExpandAbbreviationAction.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.handlers; 2 | 3 | import io.emmet.Emmet; 4 | import io.emmet.eclipse.EclipseEmmetEditor; 5 | import io.emmet.eclipse.TabKeyHandler; 6 | 7 | import org.eclipse.core.commands.AbstractHandler; 8 | import org.eclipse.core.commands.ExecutionEvent; 9 | import org.eclipse.core.commands.ExecutionException; 10 | 11 | 12 | /** 13 | * Our sample handler extends AbstractHandler, an IHandler base class. 14 | * @see org.eclipse.core.commands.IHandler 15 | * @see org.eclipse.core.commands.AbstractHandler 16 | */ 17 | public class ExpandAbbreviationAction extends AbstractHandler { 18 | /** 19 | * The constructor. 20 | */ 21 | public ExpandAbbreviationAction() { 22 | } 23 | 24 | /** 25 | * the command has been executed, so extract extract the needed information 26 | * from the application context. 27 | */ 28 | public Object execute(ExecutionEvent event) throws ExecutionException { 29 | expand(); 30 | return null; 31 | } 32 | 33 | public static boolean expand() { 34 | ActionRunner runner = ActionRunner.getSingleton(); 35 | EclipseEmmetEditor editor = runner.getEditor(); 36 | Emmet js = Emmet.getSingleton(); 37 | String profileName = "eclipse"; 38 | 39 | if (editor != null) { 40 | try { 41 | // force tab key handler installation 42 | TabKeyHandler.install(editor.getEditor()); 43 | 44 | // expand abbreviation with current profile 45 | return js.runAction(editor, "expand_abbreviation", editor.getSyntax(), profileName); 46 | } catch (Exception e) { 47 | e.printStackTrace(); 48 | } 49 | 50 | } 51 | 52 | return false; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/handlers/InsertFormattedLineBreakAction.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.handlers; 2 | 3 | import io.emmet.Emmet; 4 | import io.emmet.SelectionData; 5 | import io.emmet.eclipse.EclipseEmmetHelper; 6 | import io.emmet.eclipse.EclipseEmmetPlugin; 7 | import io.emmet.eclipse.EclipseEmmetEditor; 8 | import io.emmet.eclipse.EditorTypeInvestigator; 9 | import io.emmet.eclipse.preferences.PreferenceConstants; 10 | 11 | public class InsertFormattedLineBreakAction { 12 | 13 | public static boolean execute() { 14 | if (!isEnabled()) return false; 15 | 16 | EclipseEmmetEditor editor = ActionRunner.getSingleton().getEditor(); 17 | 18 | if (editor != null && shouldHandle(editor)) { 19 | try { 20 | Boolean result = Emmet.getSingleton().runAction(editor, "insert_formatted_line_break_only"); 21 | if (!result) { 22 | String curPadding = editor.getCurrentLinePadding(); 23 | String content = editor.getContent(); 24 | int caretPos = editor.getCaretPos(); 25 | int c_len = content.length(); 26 | String nl = editor.getNewline(); 27 | 28 | String nextNl = editor.getDocument().getLineDelimiter( editor.getDocument().getLineOfOffset(caretPos) ); 29 | 30 | if (nextNl != null) 31 | nl = nextNl; 32 | 33 | // check out next line padding 34 | SelectionData lineRange = editor.getCurrentLineRange(); 35 | StringBuilder nextPadding = new StringBuilder(); 36 | 37 | for (int i = lineRange.getEnd() + nl.length(); i < c_len; i++) { 38 | char ch = content.charAt(i); 39 | if (ch == ' ' || ch == '\t') 40 | nextPadding.append(ch); 41 | else 42 | break; 43 | } 44 | 45 | if (nextPadding.length() > curPadding.length()) { 46 | editor.replaceContent(nl + nextPadding.toString(), caretPos, caretPos, true); 47 | result = true; 48 | } 49 | } 50 | 51 | return result; 52 | } catch (Exception e) { 53 | e.printStackTrace(); 54 | } 55 | } 56 | 57 | return false; 58 | } 59 | 60 | public static boolean isEnabled() { 61 | return EclipseEmmetPlugin.getDefault().getPreferenceStore() 62 | .getBoolean(PreferenceConstants.P_UPGRADE_EDITORS); 63 | } 64 | 65 | /** 66 | * Check if newline insertion should be handled for passed editor 67 | * @param editor 68 | * @return 69 | */ 70 | public static boolean shouldHandle(EclipseEmmetEditor editor) { 71 | String ed = EclipseEmmetHelper.getEditorString(editor); 72 | return ed.indexOf("org.eclipse.wst.sse") != -1 73 | || ed.indexOf("org.eclipse.wst.xsl") != -1 74 | || (EclipseEmmetHelper.isApatana(editor) 75 | && editor.getSyntax() == EditorTypeInvestigator.TYPE_CSS); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/handlers/WrapWithAbbreviationAction.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.handlers; 2 | 3 | import io.emmet.Emmet; 4 | import io.emmet.eclipse.EclipseEmmetEditor; 5 | 6 | import org.eclipse.core.commands.AbstractHandler; 7 | import org.eclipse.core.commands.ExecutionEvent; 8 | import org.eclipse.core.commands.ExecutionException; 9 | 10 | 11 | public class WrapWithAbbreviationAction extends AbstractHandler { 12 | 13 | @Override 14 | public Object execute(ExecutionEvent event) throws ExecutionException { 15 | ActionRunner runner = ActionRunner.getSingleton(); 16 | EclipseEmmetEditor editor = runner.getEditor(); 17 | Emmet js = Emmet.getSingleton(); 18 | String profileName = "eclipse"; 19 | 20 | if (editor != null) { 21 | try { 22 | String abbr = editor.promptWrap("Enter abbreviation:"); 23 | 24 | if (abbr != null && !abbr.equals("")) { 25 | // expand abbreviation with current profile 26 | return js.runAction(editor, "wrap_with_abbreviation", 27 | abbr, editor.getSyntax(), profileName); 28 | } 29 | 30 | } catch (Exception e) { 31 | e.printStackTrace(); 32 | } 33 | } 34 | 35 | return null; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/preferences/EclipseEmmetPreferencePage.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.preferences; 2 | 3 | import io.emmet.Emmet; 4 | import io.emmet.eclipse.EclipseEmmetPlugin; 5 | import io.emmet.eclipse.TabKeyHandler; 6 | 7 | import org.eclipse.jface.dialogs.Dialog; 8 | import org.eclipse.jface.preference.BooleanFieldEditor; 9 | import org.eclipse.jface.preference.DirectoryFieldEditor; 10 | import org.eclipse.jface.preference.FieldEditorPreferencePage; 11 | import org.eclipse.jface.preference.IPreferenceStore; 12 | import org.eclipse.swt.SWT; 13 | import org.eclipse.swt.events.SelectionAdapter; 14 | import org.eclipse.swt.events.SelectionEvent; 15 | import org.eclipse.swt.layout.GridLayout; 16 | import org.eclipse.swt.widgets.Button; 17 | import org.eclipse.swt.widgets.Composite; 18 | import org.eclipse.ui.IWorkbench; 19 | import org.eclipse.ui.IWorkbenchPreferencePage; 20 | 21 | 22 | /** 23 | * This class represents a preference page that 24 | * is contributed to the Preferences dialog. By 25 | * subclassing FieldEditorPreferencePage, we 26 | * can use the field support built into JFace that allows 27 | * us to create a page that is small and knows how to 28 | * save, restore and apply itself. 29 | *

30 | * This page is used to modify preferences only. They 31 | * are stored in the preference store that belongs to 32 | * the main plug-in class. That way, preferences can 33 | * be accessed directly via the preference store. 34 | */ 35 | 36 | public class EclipseEmmetPreferencePage 37 | extends FieldEditorPreferencePage 38 | implements IWorkbenchPreferencePage { 39 | 40 | public EclipseEmmetPreferencePage() { 41 | super(GRID); 42 | setPreferenceStore(EclipseEmmetPlugin.getDefault().getPreferenceStore()); 43 | setDescription("Common Emmet preferences"); 44 | 45 | 46 | } 47 | 48 | /** 49 | * Creates the field editors. Field editors are abstractions of 50 | * the common GUI blocks needed to manipulate various types 51 | * of preferences. Each field editor knows how to save and 52 | * restore itself. 53 | */ 54 | public void createFieldEditors() { 55 | addField( 56 | new BooleanFieldEditor( 57 | PreferenceConstants.P_TAB_EXPAND, 58 | "&Expand abbreviations by Tab key", 59 | getFieldEditorParent())); 60 | 61 | addField( 62 | new BooleanFieldEditor( 63 | PreferenceConstants.P_UPGRADE_EDITORS, 64 | "&Upgrade web editors", 65 | getFieldEditorParent())); 66 | 67 | addField(new LabelFieldEditor( 68 | "This option provides better newline insertion behaviour for \n" + 69 | "Web editors (Eclipse WTP's HTML, XML, XSL and CSS; Aptana's CSS editor).\n" + 70 | "For CSS editor, you can specify 'close_css_brace' variable \n" + 71 | "(see Variable section) with the value that will be automatically \n" + 72 | "inserted instead of closing brace of CSS rule defition.", getFieldEditorParent())); 73 | 74 | addField(new DirectoryFieldEditor( 75 | PreferenceConstants.P_EXTENSIONS_PATH, 76 | "E&xtensions path", 77 | getFieldEditorParent())); 78 | 79 | } 80 | 81 | /* (non-Javadoc) 82 | * @see org.eclipse.ui.IWorkbenchPreferencePage#init(org.eclipse.ui.IWorkbench) 83 | */ 84 | public void init(IWorkbench workbench) { 85 | } 86 | 87 | private void updatePreferences() { 88 | IPreferenceStore store = EclipseEmmetPlugin.getDefault().getPreferenceStore(); 89 | TabKeyHandler.setEnabled(store.getBoolean(PreferenceConstants.P_TAB_EXPAND)); 90 | } 91 | 92 | @Override 93 | public boolean performOk() { 94 | boolean result = super.performOk(); 95 | if (result) { 96 | updatePreferences(); 97 | } 98 | return result; 99 | } 100 | 101 | @Override 102 | protected void performDefaults() { 103 | super.performDefaults(); 104 | updatePreferences(); 105 | } 106 | 107 | protected void contributeButtons(Composite parent) { 108 | Button resetButton = new Button(parent, SWT.PUSH); 109 | resetButton.setText("Reload engine"); 110 | Dialog.applyDialogFont(resetButton); 111 | 112 | resetButton.addSelectionListener(new SelectionAdapter() { 113 | public void widgetSelected(SelectionEvent e) { 114 | Emmet.reset(); 115 | } 116 | }); 117 | 118 | ((GridLayout) parent.getLayout()).numColumns++; 119 | } 120 | 121 | } -------------------------------------------------------------------------------- /src/io/emmet/eclipse/preferences/EmmetAbbreviationsPreferencesPage.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.preferences; 2 | 3 | import io.emmet.Emmet; 4 | import io.emmet.eclipse.EclipseEmmetPlugin; 5 | 6 | import org.eclipse.ui.IWorkbenchPreferencePage; 7 | import org.eclipse.ui.texteditor.templates.TemplatePreferencePage; 8 | 9 | 10 | public class EmmetAbbreviationsPreferencesPage extends TemplatePreferencePage implements 11 | IWorkbenchPreferencePage { 12 | 13 | public EmmetAbbreviationsPreferencesPage() { 14 | setPreferenceStore(EclipseEmmetPlugin.getDefault().getPreferenceStore()); 15 | setTemplateStore(TemplateHelper.getTemplateStore("abbreviations")); 16 | setContextTypeRegistry(TemplateHelper.getContextTypeRegistry()); 17 | setDescription("Abbreviations for Emmet are building blocks for (X)HTML tags. " + 18 | "Abbreviation should look like opening XHTML tag, e.g.:\n" + 19 | "

\n\n" + 20 | "The forward slash at the of tag definition means that a self-closing " + 21 | "form of this element is preffered, e.g.:\n" + 22 | ""); 23 | } 24 | 25 | @Override 26 | protected boolean isShowFormatterSetting() { 27 | return false; 28 | } 29 | 30 | @Override 31 | public boolean performOk() { 32 | Emmet.reset(); 33 | return super.performOk(); 34 | } 35 | 36 | @Override 37 | protected void performDefaults() { 38 | Emmet.reset(); 39 | super.performDefaults(); 40 | } 41 | } -------------------------------------------------------------------------------- /src/io/emmet/eclipse/preferences/EmmetSnippetsPreferencesPage.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.preferences; 2 | 3 | import io.emmet.Emmet; 4 | import io.emmet.eclipse.EclipseEmmetPlugin; 5 | 6 | import org.eclipse.ui.IWorkbenchPreferencePage; 7 | import org.eclipse.ui.texteditor.templates.TemplatePreferencePage; 8 | 9 | 10 | public class EmmetSnippetsPreferencesPage extends TemplatePreferencePage 11 | implements IWorkbenchPreferencePage { 12 | 13 | public EmmetSnippetsPreferencesPage() { 14 | setPreferenceStore(EclipseEmmetPlugin.getDefault().getPreferenceStore()); 15 | setTemplateStore(TemplateHelper.getTemplateStore("snippets")); 16 | setContextTypeRegistry(TemplateHelper.getContextTypeRegistry()); 17 | setDescription("Snippets for Emmet are used for describing arbitrary code blocks."); 18 | } 19 | 20 | @Override 21 | protected boolean isShowFormatterSetting() { 22 | return false; 23 | } 24 | 25 | @Override 26 | public boolean performOk() { 27 | Emmet.reset(); 28 | return super.performOk(); 29 | } 30 | 31 | @Override 32 | protected void performDefaults() { 33 | Emmet.reset(); 34 | super.performDefaults(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/preferences/EmmetVariablesPreferencePage.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.preferences; 2 | 3 | import io.emmet.Emmet; 4 | import io.emmet.eclipse.EclipseEmmetPlugin; 5 | 6 | import org.eclipse.ui.IWorkbenchPreferencePage; 7 | 8 | 9 | public class EmmetVariablesPreferencePage extends VariablePreferencePage 10 | implements IWorkbenchPreferencePage { 11 | 12 | public EmmetVariablesPreferencePage() { 13 | setPreferenceStore(EclipseEmmetPlugin.getDefault().getPreferenceStore()); 14 | setTemplateStore(TemplateHelper.getVariableStore()); 15 | setDescription("Variables for Emmet"); 16 | } 17 | 18 | @Override 19 | public boolean performOk() { 20 | Emmet.reset(); 21 | return super.performOk(); 22 | } 23 | 24 | @Override 25 | protected void performDefaults() { 26 | Emmet.reset(); 27 | super.performDefaults(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/preferences/LabelFieldEditor.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.preferences; 2 | 3 | import org.eclipse.jface.preference.FieldEditor; 4 | import org.eclipse.swt.layout.GridData; 5 | import org.eclipse.swt.widgets.Composite; 6 | import org.eclipse.swt.widgets.Label; 7 | 8 | public class LabelFieldEditor extends FieldEditor { 9 | private Label label; 10 | 11 | // All labels can use the same preference name since they don't 12 | // store any preference. 13 | public LabelFieldEditor(String value, Composite parent) { 14 | super("label", value, parent); 15 | } 16 | 17 | // Adjusts the field editor to be displayed correctly 18 | // for the given number of columns. 19 | protected void adjustForNumColumns(int numColumns) { 20 | ((GridData) label.getLayoutData()).horizontalSpan = numColumns; 21 | } 22 | 23 | // Fills the field editor's controls into the given parent. 24 | protected void doFillIntoGrid(Composite parent, int numColumns) { 25 | label = getLabelControl(parent); 26 | 27 | GridData gridData = new GridData(); 28 | gridData.horizontalSpan = numColumns; 29 | gridData.horizontalAlignment = GridData.FILL; 30 | gridData.grabExcessHorizontalSpace = false; 31 | gridData.verticalAlignment = GridData.CENTER; 32 | gridData.grabExcessVerticalSpace = false; 33 | 34 | label.setLayoutData(gridData); 35 | } 36 | 37 | // Returns the number of controls in the field editor. 38 | public int getNumberOfControls() { 39 | return 1; 40 | } 41 | 42 | // Labels do not persist any preferences, so these methods are empty. 43 | protected void doLoad() { 44 | } 45 | 46 | protected void doLoadDefault() { 47 | } 48 | 49 | protected void doStore() { 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/preferences/PreferenceConstants.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.preferences; 2 | 3 | /** 4 | * Constant definitions for plug-in preferences 5 | */ 6 | public class PreferenceConstants { 7 | 8 | public static final String P_TAB_EXPAND = "tabExpand"; 9 | public static final String P_UPGRADE_EDITORS = "upgradeEditors"; 10 | public static final String P_EXTENSIONS_PATH = "extensionsPath"; 11 | 12 | public static final String P_FILTERS = "filters"; 13 | 14 | public static final String P_PROFILE_TAG_CASE = "profileTagCase"; 15 | public static final String P_PROFILE_ATTR_CASE = "profileAttrCase"; 16 | public static final String P_PROFILE_ATTR_QUOTES = "profileAttrQuotes"; 17 | public static final String P_PROFILE_TAG_NEWLINE = "profileTagNewline"; 18 | public static final String P_PROFILE_PLACE_CURSOR = "profilePlaceCursor"; 19 | public static final String P_PROFILE_INDENT = "profileIndent"; 20 | public static final String P_PROFILE_INLINE_BREAK = "profileInlineBreak"; 21 | public static final String P_PROFILE_SELF_CLOSING_TAG = "profileSelfClosingTag"; 22 | } 23 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/preferences/PreferenceInitializer.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.preferences; 2 | 3 | import io.emmet.eclipse.EclipseEmmetPlugin; 4 | import io.emmet.eclipse.preferences.output.OutputProfile; 5 | 6 | import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer; 7 | import org.eclipse.jface.preference.IPreferenceStore; 8 | 9 | 10 | /** 11 | * Class used to initialize default preference values. 12 | */ 13 | public class PreferenceInitializer extends AbstractPreferenceInitializer { 14 | 15 | /* 16 | * (non-Javadoc) 17 | * 18 | * @see org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer#initializeDefaultPreferences() 19 | */ 20 | public void initializeDefaultPreferences() { 21 | IPreferenceStore store = EclipseEmmetPlugin.getDefault().getPreferenceStore(); 22 | store.setDefault(PreferenceConstants.P_TAB_EXPAND, true); 23 | setupDefaultOutputProfiles(); 24 | } 25 | 26 | private void setupDefaultOutputProfiles() { 27 | OutputProfile html = new OutputProfile(); 28 | 29 | OutputProfile xml = new OutputProfile(); 30 | xml.setTagCase(OutputProfile.LEAVE); 31 | xml.setAttrCase(OutputProfile.LEAVE); 32 | xml.setTagNewline(OutputProfile.TRUE); 33 | xml.setSelfClosing(OutputProfile.TRUE); 34 | 35 | setupOutputPrefrences("default", html); 36 | setupOutputPrefrences("html", html); 37 | setupOutputPrefrences("css", html); 38 | 39 | html.setFilters("haml"); 40 | setupOutputPrefrences("haml", html); 41 | 42 | setupOutputPrefrences("xml", xml); 43 | 44 | xml.setFilters("html, xsl"); 45 | setupOutputPrefrences("xsl", xml); 46 | } 47 | 48 | private void setupOutputPrefrences(String suffix, OutputProfile profile) { 49 | IPreferenceStore store = EclipseEmmetPlugin.getDefault().getPreferenceStore(); 50 | store.setDefault(getPrefName(PreferenceConstants.P_PROFILE_TAG_CASE, suffix), profile.getTagCase()); 51 | store.setDefault(getPrefName(PreferenceConstants.P_PROFILE_ATTR_CASE, suffix), profile.getAttrCase()); 52 | store.setDefault(getPrefName(PreferenceConstants.P_PROFILE_ATTR_QUOTES, suffix), profile.getAttrQuotes()); 53 | store.setDefault(getPrefName(PreferenceConstants.P_PROFILE_TAG_NEWLINE, suffix), profile.getTagNewline()); 54 | store.setDefault(getPrefName(PreferenceConstants.P_PROFILE_PLACE_CURSOR, suffix), profile.isPlaceCaret()); 55 | store.setDefault(getPrefName(PreferenceConstants.P_PROFILE_INDENT, suffix), profile.isIndentTags()); 56 | store.setDefault(getPrefName(PreferenceConstants.P_PROFILE_INLINE_BREAK, suffix), profile.getInlineBreak()); 57 | store.setDefault(getPrefName(PreferenceConstants.P_PROFILE_SELF_CLOSING_TAG, suffix), profile.getSelfClosing()); 58 | store.setDefault(getPrefName(PreferenceConstants.P_FILTERS, suffix), profile.getFilters()); 59 | } 60 | 61 | public static String getPrefName(String prefix, String suffix) { 62 | return prefix + "_" + suffix; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/preferences/SpacerFieldEditor.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.preferences; 2 | 3 | import org.eclipse.swt.widgets.Composite; 4 | 5 | public class SpacerFieldEditor extends LabelFieldEditor { 6 | // Implemented as an empty label field editor. 7 | public SpacerFieldEditor(Composite parent) { 8 | super("", parent); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/preferences/SpinnerFieldEditor.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.preferences; 2 | 3 | import org.eclipse.core.runtime.Assert; 4 | import org.eclipse.jface.preference.FieldEditor; 5 | import org.eclipse.jface.resource.JFaceResources; 6 | import org.eclipse.swt.SWT; 7 | import org.eclipse.swt.events.DisposeEvent; 8 | import org.eclipse.swt.events.DisposeListener; 9 | import org.eclipse.swt.events.FocusAdapter; 10 | import org.eclipse.swt.events.FocusEvent; 11 | import org.eclipse.swt.events.KeyAdapter; 12 | import org.eclipse.swt.events.KeyEvent; 13 | import org.eclipse.swt.graphics.GC; 14 | import org.eclipse.swt.graphics.Point; 15 | import org.eclipse.swt.layout.GridData; 16 | import org.eclipse.swt.widgets.Composite; 17 | import org.eclipse.swt.widgets.Spinner; 18 | 19 | public class SpinnerFieldEditor extends FieldEditor { 20 | private Spinner spinner; 21 | 22 | /** 23 | * Validation strategy constant (value 0) indicating that the 24 | * editor should perform validation after every key stroke. 25 | * 26 | * @see #setValidateStrategy 27 | */ 28 | public static final int VALIDATE_ON_KEY_STROKE = 0; 29 | 30 | /** 31 | * Validation strategy constant (value 1) indicating that the 32 | * editor should perform validation only when the text widget loses focus. 33 | * 34 | * @see #setValidateStrategy 35 | */ 36 | public static final int VALIDATE_ON_FOCUS_LOST = 1; 37 | 38 | /** 39 | * Text limit constant (value -1) indicating unlimited text 40 | * limit and width. 41 | */ 42 | public static int UNLIMITED = -1; 43 | 44 | /** 45 | * Cached valid state. 46 | */ 47 | private boolean isValid; 48 | 49 | /** 50 | * Old text value. 51 | * 52 | * @since 3.4 this field is protected. 53 | */ 54 | protected int oldValue; 55 | 56 | /** 57 | * Width of text field in characters; initially unlimited. 58 | */ 59 | private int widthInChars = UNLIMITED; 60 | 61 | /** 62 | * Text limit of text field in characters; initially unlimited. 63 | */ 64 | private int textLimit = UNLIMITED; 65 | 66 | private int increment = 1; 67 | private int minValue = 0; 68 | private int maxValue = Integer.MAX_VALUE; 69 | 70 | /** 71 | * The error message, or null if none. 72 | */ 73 | private String errorMessage; 74 | 75 | /** 76 | * The validation strategy; VALIDATE_ON_KEY_STROKE by default. 77 | */ 78 | private int validateStrategy = VALIDATE_ON_KEY_STROKE; 79 | 80 | public SpinnerFieldEditor(String name, String labelText, int width, Composite parent) { 81 | init(name, labelText); 82 | isValid = false; 83 | errorMessage = JFaceResources.getString("IntegerFieldEditor.errorMessage");//$NON-NLS-1$ 84 | widthInChars = width; 85 | createControl(parent); 86 | } 87 | 88 | @Override 89 | protected void adjustForNumColumns(int numColumns) { 90 | GridData gd = (GridData) spinner.getLayoutData(); 91 | gd.horizontalSpan = numColumns - 1; 92 | gd.grabExcessHorizontalSpace = false; 93 | } 94 | 95 | /** 96 | * Checks whether the text input field contains a valid value or not. 97 | * 98 | * @return true if the field value is valid, 99 | * and false if invalid 100 | */ 101 | protected boolean checkState() { 102 | if (spinner == null) { 103 | return false; 104 | } 105 | 106 | String numberString = spinner.getText(); 107 | try { 108 | int number = Integer.valueOf(numberString).intValue(); 109 | if (number >= minValue && number <= maxValue) { 110 | clearErrorMessage(); 111 | return true; 112 | } 113 | 114 | showErrorMessage(); 115 | return false; 116 | 117 | } catch (NumberFormatException e1) { 118 | showErrorMessage(); 119 | } 120 | 121 | return false; 122 | } 123 | 124 | /** 125 | * Hook for subclasses to do specific state checks. 126 | *

127 | * The default implementation of this framework method does nothing and 128 | * returns true. Subclasses should override this method to 129 | * specific state checks. 130 | *

131 | * 132 | * @return true if the field value is valid, and 133 | * false if invalid 134 | */ 135 | protected boolean doCheckState() { 136 | return true; 137 | } 138 | 139 | @Override 140 | protected void doFillIntoGrid(Composite parent, int numColumns) { 141 | getLabelControl(parent); 142 | 143 | spinner = getSpinnerControl(parent); 144 | GridData gd = new GridData(); 145 | gd.horizontalSpan = numColumns - 1; 146 | if (widthInChars != UNLIMITED) { 147 | GC gc = new GC(spinner); 148 | try { 149 | Point extent = gc.textExtent("X");//$NON-NLS-1$ 150 | gd.widthHint = widthInChars * extent.x; 151 | } finally { 152 | gc.dispose(); 153 | } 154 | } else { 155 | gd.horizontalAlignment = GridData.FILL; 156 | gd.grabExcessHorizontalSpace = true; 157 | } 158 | spinner.setLayoutData(gd); 159 | 160 | } 161 | 162 | @Override 163 | protected void doLoad() { 164 | if (spinner != null) { 165 | int value = getPreferenceStore().getInt(getPreferenceName()); 166 | spinner.setSelection(value); 167 | oldValue = value; 168 | } 169 | } 170 | 171 | @Override 172 | protected void doLoadDefault() { 173 | if (spinner != null) { 174 | int value = getPreferenceStore().getDefaultInt(getPreferenceName()); 175 | spinner.setSelection(value); 176 | } 177 | valueChanged(); 178 | } 179 | 180 | @Override 181 | protected void doStore() { 182 | getPreferenceStore().setValue(getPreferenceName(), spinner.getSelection()); 183 | } 184 | 185 | /** 186 | * Returns the error message that will be displayed when and if 187 | * an error occurs. 188 | * 189 | * @return the error message, or null if none 190 | */ 191 | public String getErrorMessage() { 192 | return errorMessage; 193 | } 194 | 195 | @Override 196 | public int getNumberOfControls() { 197 | return 2; 198 | } 199 | 200 | /** 201 | * Returns the field editor's value. 202 | * 203 | * @return the current value 204 | */ 205 | public int getIntValue() { 206 | if (spinner != null) { 207 | return spinner.getSelection(); 208 | } 209 | 210 | return getPreferenceStore().getInt(getPreferenceName()); 211 | } 212 | 213 | /** 214 | * Returns this field editor's text control. 215 | * 216 | * @return the text control, or null if no 217 | * text field is created yet 218 | */ 219 | protected Spinner getSpinnerControl() { 220 | return spinner; 221 | } 222 | 223 | /** 224 | * Returns this field editor's text control. 225 | *

226 | * The control is created if it does not yet exist 227 | *

228 | * 229 | * @param parent 230 | * the parent 231 | * @return the text control 232 | */ 233 | public Spinner getSpinnerControl(Composite parent) { 234 | if (spinner == null) { 235 | spinner = new Spinner(parent, SWT.SINGLE | SWT.BORDER); 236 | spinner.setFont(parent.getFont()); 237 | switch (validateStrategy) { 238 | case VALIDATE_ON_KEY_STROKE: 239 | spinner.addKeyListener(new KeyAdapter() { 240 | 241 | /* 242 | * (non-Javadoc) 243 | * @see org.eclipse.swt.events.KeyAdapter#keyReleased(org.eclipse.swt.events.KeyEvent) 244 | */ 245 | public void keyReleased(KeyEvent e) { 246 | valueChanged(); 247 | } 248 | }); 249 | spinner.addFocusListener(new FocusAdapter() { 250 | // Ensure that the value is checked on focus loss in case we 251 | // missed a keyRelease or user hasn't released key. 252 | // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=214716 253 | public void focusLost(FocusEvent e) { 254 | valueChanged(); 255 | } 256 | }); 257 | 258 | break; 259 | case VALIDATE_ON_FOCUS_LOST: 260 | spinner.addKeyListener(new KeyAdapter() { 261 | public void keyPressed(KeyEvent e) { 262 | clearErrorMessage(); 263 | } 264 | }); 265 | spinner.addFocusListener(new FocusAdapter() { 266 | public void focusGained(FocusEvent e) { 267 | refreshValidState(); 268 | } 269 | 270 | public void focusLost(FocusEvent e) { 271 | valueChanged(); 272 | clearErrorMessage(); 273 | } 274 | }); 275 | break; 276 | default: 277 | Assert.isTrue(false, "Unknown validate strategy");//$NON-NLS-1$ 278 | } 279 | spinner.addDisposeListener(new DisposeListener() { 280 | public void widgetDisposed(DisposeEvent event) { 281 | spinner = null; 282 | } 283 | }); 284 | if (textLimit > 0) {// Only set limits above 0 - see SWT spec 285 | spinner.setTextLimit(textLimit); 286 | } 287 | 288 | spinner.setIncrement(getIncrement()); 289 | spinner.setMinimum(getMinValue()); 290 | spinner.setMaximum(getMaxValue()); 291 | } else { 292 | checkParent(spinner, parent); 293 | } 294 | return spinner; 295 | } 296 | 297 | /* 298 | * (non-Javadoc) Method declared on FieldEditor. 299 | */ 300 | public boolean isValid() { 301 | return isValid; 302 | } 303 | 304 | /* 305 | * (non-Javadoc) Method declared on FieldEditor. 306 | */ 307 | protected void refreshValidState() { 308 | isValid = checkState(); 309 | } 310 | 311 | /** 312 | * Sets the error message that will be displayed when and if an error 313 | * occurs. 314 | * 315 | * @param message 316 | * the error message 317 | */ 318 | public void setErrorMessage(String message) { 319 | errorMessage = message; 320 | } 321 | 322 | /* (non-Javadoc) 323 | * Method declared on FieldEditor. 324 | */ 325 | public void setFocus() { 326 | if (spinner != null) { 327 | spinner.setFocus(); 328 | } 329 | } 330 | 331 | /** 332 | * Sets this field editor's value. 333 | * 334 | * @param value the new value, or null meaning the empty string 335 | */ 336 | public void setIntValue(int value) { 337 | if (spinner != null) { 338 | if (spinner == null) { 339 | value = 0;//$NON-NLS-1$ 340 | } 341 | 342 | oldValue = spinner.getSelection(); 343 | if (oldValue != value) { 344 | spinner.setSelection(value); 345 | valueChanged(); 346 | } 347 | } 348 | } 349 | 350 | /** 351 | * Sets this text field's text limit. 352 | * 353 | * @param limit the limit on the number of character in the text 354 | * input field, or UNLIMITED for no limit 355 | 356 | */ 357 | public void setTextLimit(int limit) { 358 | textLimit = limit; 359 | if (spinner != null) { 360 | spinner.setTextLimit(limit); 361 | } 362 | } 363 | 364 | /** 365 | * Sets the strategy for validating the text. 366 | *

367 | * Calling this method has no effect after createPartControl 368 | * is called. Thus this method is really only useful for subclasses to call 369 | * in their constructor. However, it has public visibility for backward 370 | * compatibility. 371 | *

372 | * 373 | * @param value either VALIDATE_ON_KEY_STROKE to perform 374 | * on the fly checking (the default), or VALIDATE_ON_FOCUS_LOST to 375 | * perform validation only after the text has been typed in 376 | */ 377 | public void setValidateStrategy(int value) { 378 | Assert.isTrue(value == VALIDATE_ON_FOCUS_LOST 379 | || value == VALIDATE_ON_KEY_STROKE); 380 | validateStrategy = value; 381 | } 382 | 383 | /** 384 | * Shows the error message set via setErrorMessage. 385 | */ 386 | public void showErrorMessage() { 387 | showErrorMessage(errorMessage); 388 | } 389 | 390 | /** 391 | * Informs this field editor's listener, if it has one, about a change to 392 | * the value (VALUE property) provided that the old and new 393 | * values are different. 394 | *

395 | * This hook is not called when the text is initialized (or reset 396 | * to the default value) from the preference store. 397 | *

398 | */ 399 | protected void valueChanged() { 400 | setPresentsDefaultValue(false); 401 | boolean oldState = isValid; 402 | refreshValidState(); 403 | 404 | if (isValid != oldState) { 405 | fireStateChanged(IS_VALID, oldState, isValid); 406 | } 407 | 408 | int newValue = spinner.getSelection(); 409 | if (newValue != oldValue) { 410 | fireValueChanged(VALUE, oldValue, newValue); 411 | oldValue = newValue; 412 | } 413 | } 414 | 415 | /* 416 | * @see FieldEditor.setEnabled(boolean,Composite). 417 | */ 418 | public void setEnabled(boolean enabled, Composite parent) { 419 | super.setEnabled(enabled, parent); 420 | getSpinnerControl(parent).setEnabled(enabled); 421 | } 422 | 423 | public void setIncrement(int increment) { 424 | this.increment = increment; 425 | } 426 | 427 | public int getIncrement() { 428 | return increment; 429 | } 430 | 431 | public int getMinValue() { 432 | return minValue; 433 | } 434 | 435 | public int getMaxValue() { 436 | return maxValue; 437 | } 438 | 439 | /** 440 | * Sets the range of valid values for this field. 441 | * 442 | * @param min the minimum allowed value (inclusive) 443 | * @param max the maximum allowed value (inclusive) 444 | */ 445 | public void setValidRange(int min, int max) { 446 | minValue = min; 447 | maxValue = max; 448 | setErrorMessage(JFaceResources.format( 449 | "IntegerFieldEditor.errorMessageRange", //$NON-NLS-1$ 450 | new Object[] { new Integer(min), new Integer(max) })); 451 | } 452 | } 453 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/preferences/StatusInfo.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.preferences; 2 | 3 | import io.emmet.eclipse.EclipseEmmetPlugin; 4 | 5 | import org.eclipse.core.runtime.Assert; 6 | import org.eclipse.core.runtime.IStatus; 7 | 8 | 9 | /** 10 | * A settable IStatus. 11 | * Can be an error, warning, info or OKk. For error, info and warning states, 12 | * a message describes the problem. 13 | * 14 | * @since 3.0 15 | */ 16 | class StatusInfo implements IStatus { 17 | 18 | private String fStatusMessage; 19 | private int fSeverity; 20 | 21 | /** 22 | * Creates a status set to OK (no message) 23 | */ 24 | public StatusInfo() { 25 | this(OK, null); 26 | } 27 | 28 | /** 29 | * Creates a status . 30 | * @param severity The status severity: ERROR, WARNING, INFO and OK. 31 | * @param message The message of the status. Applies only for ERROR, 32 | * WARNING and INFO. 33 | */ 34 | public StatusInfo(int severity, String message) { 35 | fStatusMessage= message; 36 | fSeverity= severity; 37 | } 38 | 39 | /** 40 | * Returns if the status' severity is OK. 41 | * 42 | * @return true if the status' severity is OK 43 | */ 44 | public boolean isOK() { 45 | return fSeverity == IStatus.OK; 46 | } 47 | 48 | /** 49 | * Returns if the status' severity is WARNING. 50 | * 51 | * @return true if the status' severity is WARNING 52 | */ 53 | public boolean isWarning() { 54 | return fSeverity == IStatus.WARNING; 55 | } 56 | 57 | /** 58 | * Returns if the status' severity is INFO. 59 | * 60 | * @return true if the status' severity is INFO 61 | */ 62 | public boolean isInfo() { 63 | return fSeverity == IStatus.INFO; 64 | } 65 | 66 | /** 67 | * Returns if the status' severity is ERROR. 68 | * 69 | * @return true if the status' severity is ERROR 70 | */ 71 | public boolean isError() { 72 | return fSeverity == IStatus.ERROR; 73 | } 74 | 75 | /** 76 | * Returns the message. 77 | * 78 | * @return the message 79 | * @see IStatus#getMessage() 80 | */ 81 | public String getMessage() { 82 | return fStatusMessage; 83 | } 84 | 85 | /** 86 | * Sets the status to ERROR. 87 | * @param errorMessage the error message (can be empty, but not null) 88 | */ 89 | public void setError(String errorMessage) { 90 | Assert.isNotNull(errorMessage); 91 | fStatusMessage= errorMessage; 92 | fSeverity= IStatus.ERROR; 93 | } 94 | 95 | /** 96 | * Sets the status to WARNING. 97 | * @param warningMessage the warning message (can be empty, but not null) 98 | */ 99 | public void setWarning(String warningMessage) { 100 | Assert.isNotNull(warningMessage); 101 | fStatusMessage= warningMessage; 102 | fSeverity= IStatus.WARNING; 103 | } 104 | 105 | /** 106 | * Sets the status to INFO. 107 | * @param infoMessage the info message (can be empty, but not null) 108 | */ 109 | public void setInfo(String infoMessage) { 110 | Assert.isNotNull(infoMessage); 111 | fStatusMessage= infoMessage; 112 | fSeverity= IStatus.INFO; 113 | } 114 | 115 | /** 116 | * Sets the status to OK. 117 | */ 118 | public void setOK() { 119 | fStatusMessage= null; 120 | fSeverity= IStatus.OK; 121 | } 122 | 123 | /* 124 | * @see IStatus#matches(int) 125 | */ 126 | public boolean matches(int severityMask) { 127 | return (fSeverity & severityMask) != 0; 128 | } 129 | 130 | /** 131 | * Returns always false. 132 | * @see IStatus#isMultiStatus() 133 | */ 134 | public boolean isMultiStatus() { 135 | return false; 136 | } 137 | 138 | /* 139 | * @see IStatus#getSeverity() 140 | */ 141 | public int getSeverity() { 142 | return fSeverity; 143 | } 144 | 145 | /* 146 | * @see IStatus#getPlugin() 147 | */ 148 | public String getPlugin() { 149 | return EclipseEmmetPlugin.PLUGIN_ID; 150 | } 151 | 152 | /** 153 | * Returns always null. 154 | * @see IStatus#getException() 155 | */ 156 | public Throwable getException() { 157 | return null; 158 | } 159 | 160 | /** 161 | * Returns always the error severity. 162 | * @see IStatus#getCode() 163 | */ 164 | public int getCode() { 165 | return fSeverity; 166 | } 167 | 168 | /** 169 | * Returns always null. 170 | * @see IStatus#getChildren() 171 | */ 172 | public IStatus[] getChildren() { 173 | return new IStatus[0]; 174 | } 175 | 176 | } -------------------------------------------------------------------------------- /src/io/emmet/eclipse/preferences/TemplateContentProvider.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.preferences; 2 | 3 | import org.eclipse.jface.text.templates.persistence.TemplateStore; 4 | import org.eclipse.jface.viewers.IStructuredContentProvider; 5 | import org.eclipse.jface.viewers.Viewer; 6 | 7 | /** 8 | * A content provider for the template preference page's table viewer. 9 | * 10 | * @since 3.0 11 | */ 12 | class TemplateContentProvider implements IStructuredContentProvider { 13 | 14 | /** The template store. */ 15 | private TemplateStore fStore; 16 | 17 | /* 18 | * @see IStructuredContentProvider#getElements(Object) 19 | */ 20 | public Object[] getElements(Object input) { 21 | return fStore.getTemplateData(false); 22 | } 23 | 24 | /* 25 | * @see IContentProvider#inputChanged(Viewer, Object, Object) 26 | */ 27 | public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { 28 | fStore= (TemplateStore) newInput; 29 | } 30 | 31 | /* 32 | * @see IContentProvider#dispose() 33 | */ 34 | public void dispose() { 35 | fStore= null; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/preferences/TemplateHelper.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.preferences; 2 | 3 | import io.emmet.eclipse.EclipseEmmetPlugin; 4 | import io.emmet.eclipse.EmmetContextType; 5 | 6 | import java.io.IOException; 7 | import java.util.HashMap; 8 | 9 | import org.eclipse.jface.text.templates.ContextTypeRegistry; 10 | import org.eclipse.jface.text.templates.persistence.TemplateStore; 11 | import org.eclipse.ui.editors.text.templates.ContributionContextTypeRegistry; 12 | import org.eclipse.ui.editors.text.templates.ContributionTemplateStore; 13 | 14 | 15 | public class TemplateHelper { 16 | /** The template store list. */ 17 | private static HashMap fStoreList = new HashMap(); 18 | 19 | private static TemplateStore fVariableStore; 20 | 21 | /** The context type registry. */ 22 | private static ContributionContextTypeRegistry fRegistry; 23 | 24 | /** The context type registry. */ 25 | private static ContributionContextTypeRegistry fVarsRegistry; 26 | 27 | /** Key to store custom templates. */ 28 | public static final String CUSTOM_TEMPLATES_KEY = "io.emmet.eclipse.preferences.EmmetTemplatesPreferencesPage"; 29 | 30 | /** 31 | * Returns this plug-in's template store. 32 | * 33 | * @return the template store of this plug-in instance 34 | */ 35 | public static TemplateStore getTemplateStore(String type) { 36 | if (!fStoreList.containsKey(type)) { 37 | ContributionTemplateStore store = new ContributionTemplateStore(TemplateHelper.getContextTypeRegistry(), 38 | EclipseEmmetPlugin.getDefault().getPreferenceStore(), CUSTOM_TEMPLATES_KEY + "." + type); 39 | try { 40 | store.load(); 41 | fStoreList.put(type, store); 42 | } catch (IOException e) { 43 | e.printStackTrace(); 44 | throw new RuntimeException(e); 45 | } 46 | 47 | } 48 | 49 | return fStoreList.get(type); 50 | } 51 | 52 | /** 53 | * Returns this plug-in's variable store. 54 | * 55 | * @return the template store of this plug-in instance 56 | */ 57 | public static TemplateStore getVariableStore() { 58 | if (fVariableStore == null) { 59 | fVariableStore = new ContributionTemplateStore(TemplateHelper.getVariableContextTypeRegistry(), 60 | EclipseEmmetPlugin.getDefault().getPreferenceStore(), CUSTOM_TEMPLATES_KEY + ".variable"); 61 | try { 62 | fVariableStore .load(); 63 | } catch (IOException e) { 64 | e.printStackTrace(); 65 | throw new RuntimeException(e); 66 | } 67 | 68 | } 69 | 70 | return fVariableStore; 71 | } 72 | 73 | /** 74 | * Returns this plug-in's context type registry. 75 | * 76 | * @return the context type registry for this plug-in instance 77 | */ 78 | public static ContextTypeRegistry getContextTypeRegistry() { 79 | if (fRegistry == null) { 80 | // create and configure the contexts available in the template editor 81 | fRegistry = new ContributionContextTypeRegistry(); 82 | fRegistry.addContextType(EmmetContextType.CTX_HTML); 83 | fRegistry.addContextType(EmmetContextType.CTX_CSS); 84 | fRegistry.addContextType(EmmetContextType.CTX_XML); 85 | fRegistry.addContextType(EmmetContextType.CTX_XSL); 86 | fRegistry.addContextType(EmmetContextType.CTX_HAML); 87 | } 88 | 89 | return fRegistry; 90 | } 91 | 92 | /** 93 | * Returns this plug-in's context type registry. 94 | * 95 | * @return the context type registry for this plug-in instance 96 | */ 97 | public static ContextTypeRegistry getVariableContextTypeRegistry() { 98 | if (fVarsRegistry == null) { 99 | // create and configure the contexts available in the template editor 100 | fVarsRegistry = new ContributionContextTypeRegistry(); 101 | fVarsRegistry.addContextType(EmmetContextType.CTX_VARIABLE); 102 | } 103 | 104 | return fVarsRegistry; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/preferences/VariablePreferencePage.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.preferences; 2 | 3 | import io.emmet.eclipse.EclipseEmmetPlugin; 4 | import io.emmet.eclipse.EmmetContextType; 5 | 6 | import java.io.IOException; 7 | import java.text.Collator; 8 | import java.util.Iterator; 9 | 10 | import org.eclipse.core.runtime.IStatus; 11 | import org.eclipse.core.runtime.Status; 12 | import org.eclipse.jface.dialogs.Dialog; 13 | import org.eclipse.jface.dialogs.IDialogConstants; 14 | import org.eclipse.jface.dialogs.IDialogSettings; 15 | import org.eclipse.jface.dialogs.MessageDialog; 16 | import org.eclipse.jface.dialogs.StatusDialog; 17 | import org.eclipse.jface.layout.TableColumnLayout; 18 | import org.eclipse.jface.preference.PreferencePage; 19 | import org.eclipse.jface.resource.JFaceResources; 20 | import org.eclipse.jface.text.templates.Template; 21 | import org.eclipse.jface.text.templates.persistence.TemplatePersistenceData; 22 | import org.eclipse.jface.text.templates.persistence.TemplateStore; 23 | import org.eclipse.jface.viewers.ColumnWeightData; 24 | import org.eclipse.jface.viewers.DoubleClickEvent; 25 | import org.eclipse.jface.viewers.IDoubleClickListener; 26 | import org.eclipse.jface.viewers.ISelectionChangedListener; 27 | import org.eclipse.jface.viewers.IStructuredSelection; 28 | import org.eclipse.jface.viewers.ITableLabelProvider; 29 | import org.eclipse.jface.viewers.LabelProvider; 30 | import org.eclipse.jface.viewers.SelectionChangedEvent; 31 | import org.eclipse.jface.viewers.StructuredSelection; 32 | import org.eclipse.jface.viewers.TableViewer; 33 | import org.eclipse.jface.viewers.Viewer; 34 | import org.eclipse.jface.viewers.ViewerComparator; 35 | import org.eclipse.jface.window.Window; 36 | import org.eclipse.swt.SWT; 37 | import org.eclipse.swt.events.FocusEvent; 38 | import org.eclipse.swt.events.FocusListener; 39 | import org.eclipse.swt.events.ModifyEvent; 40 | import org.eclipse.swt.events.ModifyListener; 41 | import org.eclipse.swt.graphics.GC; 42 | import org.eclipse.swt.graphics.Image; 43 | import org.eclipse.swt.layout.GridData; 44 | import org.eclipse.swt.layout.GridLayout; 45 | import org.eclipse.swt.widgets.Button; 46 | import org.eclipse.swt.widgets.Composite; 47 | import org.eclipse.swt.widgets.Control; 48 | import org.eclipse.swt.widgets.Event; 49 | import org.eclipse.swt.widgets.Label; 50 | import org.eclipse.swt.widgets.Listener; 51 | import org.eclipse.swt.widgets.Shell; 52 | import org.eclipse.swt.widgets.Table; 53 | import org.eclipse.swt.widgets.TableColumn; 54 | import org.eclipse.swt.widgets.Text; 55 | import org.eclipse.swt.widgets.Widget; 56 | import org.eclipse.ui.IWorkbench; 57 | import org.eclipse.ui.IWorkbenchPreferencePage; 58 | 59 | 60 | public abstract class VariablePreferencePage extends PreferencePage implements 61 | IWorkbenchPreferencePage { 62 | 63 | private static final String CONTEXT_TYPE_ID = EmmetContextType.CTX_VARIABLE; 64 | 65 | private static final String columnValue = "Value"; 66 | 67 | private static final String columnName = "Name"; 68 | 69 | /** 70 | * Dialog to edit a template. Clients will usually instantiate, but 71 | * may also extend. 72 | * 73 | * @since 3.3 74 | */ 75 | protected static class EditVariableDialog extends StatusDialog { 76 | 77 | private final Template fOriginalTemplate; 78 | 79 | private Text fNameText; 80 | private Text fDescriptionText; 81 | private boolean fIsNameModifiable; 82 | 83 | private StatusInfo fValidationStatus; 84 | private boolean fSuppressError= true; // #4354 85 | 86 | private Template fNewTemplate; 87 | 88 | 89 | /** 90 | * Creates a new dialog. 91 | * 92 | * @param parent the shell parent of the dialog 93 | * @param template the template to edit 94 | * @param edit whether this is a new template or an existing being edited 95 | * @param isNameModifiable whether the name of the template may be modified 96 | */ 97 | public EditVariableDialog(Shell parent, Template template, boolean edit, boolean isNameModifiable) { 98 | super(parent); 99 | 100 | String title= edit 101 | ? "Edit variable" 102 | : "New variable"; 103 | setTitle(title); 104 | 105 | fOriginalTemplate= template; 106 | fIsNameModifiable= isNameModifiable; 107 | 108 | fValidationStatus= new StatusInfo(); 109 | } 110 | 111 | /* 112 | * @see org.eclipse.jface.dialogs.Dialog#isResizable() 113 | * @since 3.4 114 | */ 115 | protected boolean isResizable() { 116 | return true; 117 | } 118 | 119 | /* 120 | * @see org.eclipse.ui.texteditor.templates.StatusDialog#create() 121 | */ 122 | public void create() { 123 | super.create(); 124 | // update initial OK button to be disabled for new templates 125 | boolean valid= fNameText == null || fNameText.getText().trim().length() != 0; 126 | if (!valid) { 127 | StatusInfo status = new StatusInfo(); 128 | status.setError("You must provide variable name"); 129 | updateButtonsEnableState(status); 130 | } 131 | } 132 | 133 | /* 134 | * @see Dialog#createDialogArea(Composite) 135 | */ 136 | protected Control createDialogArea(Composite ancestor) { 137 | Composite parent= new Composite(ancestor, SWT.NONE); 138 | GridLayout layout= new GridLayout(); 139 | layout.numColumns= 2; 140 | layout.marginHeight= convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN); 141 | layout.marginWidth= convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN); 142 | layout.verticalSpacing= convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING); 143 | layout.horizontalSpacing= convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING); 144 | parent.setLayout(layout); 145 | parent.setLayoutData(new GridData(GridData.FILL_BOTH)); 146 | 147 | ModifyListener listener= new ModifyListener() { 148 | public void modifyText(ModifyEvent e) { 149 | doTextWidgetChanged(e.widget); 150 | } 151 | }; 152 | 153 | if (fIsNameModifiable) { 154 | createLabel(parent, "Name"); 155 | 156 | Composite composite= new Composite(parent, SWT.NONE); 157 | composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 158 | layout= new GridLayout(); 159 | layout.numColumns= 2; 160 | layout.marginWidth= 0; 161 | layout.marginHeight= 0; 162 | composite.setLayout(layout); 163 | 164 | fNameText= createText(composite); 165 | fNameText.addModifyListener(listener); 166 | fNameText.addFocusListener(new FocusListener() { 167 | 168 | public void focusGained(FocusEvent e) { 169 | } 170 | 171 | public void focusLost(FocusEvent e) { 172 | if (fSuppressError) { 173 | fSuppressError= false; 174 | updateButtons(); 175 | } 176 | } 177 | }); 178 | } 179 | 180 | createLabel(parent, "Value"); 181 | 182 | int descFlags= fIsNameModifiable ? SWT.BORDER : SWT.BORDER | SWT.READ_ONLY; 183 | fDescriptionText= new Text(parent, descFlags ); 184 | fDescriptionText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 185 | fDescriptionText.addModifyListener(listener); 186 | 187 | // Composite composite= new Composite(parent, SWT.NONE); 188 | // layout= new GridLayout(); 189 | // layout.marginWidth= 0; 190 | // layout.marginHeight= 0; 191 | // composite.setLayout(layout); 192 | // composite.setLayoutData(new GridData()); 193 | 194 | fDescriptionText.setText(fOriginalTemplate.getPattern()); 195 | 196 | if (fIsNameModifiable) { 197 | fNameText.setText(fOriginalTemplate.getName()); 198 | fNameText.addModifyListener(listener); 199 | } else { 200 | fDescriptionText.setFocus(); 201 | } 202 | 203 | applyDialogFont(parent); 204 | return parent; 205 | } 206 | 207 | private void doTextWidgetChanged(Widget w) { 208 | if (w == fNameText) { 209 | fSuppressError= false; 210 | updateButtons(); 211 | } else if (w == fDescriptionText) { 212 | // oh, nothing 213 | } 214 | } 215 | 216 | private String getContextId() { 217 | return fOriginalTemplate.getContextTypeId(); 218 | } 219 | 220 | private static Label createLabel(Composite parent, String name) { 221 | Label label= new Label(parent, SWT.NULL); 222 | label.setText(name); 223 | label.setLayoutData(new GridData()); 224 | 225 | return label; 226 | } 227 | 228 | private static Text createText(Composite parent) { 229 | Text text= new Text(parent, SWT.BORDER); 230 | text.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 231 | 232 | return text; 233 | } 234 | 235 | private void updateButtons() { 236 | StatusInfo status; 237 | 238 | boolean valid= fNameText == null || fNameText.getText().trim().length() != 0; 239 | if (!valid) { 240 | status = new StatusInfo(); 241 | if (!fSuppressError) 242 | status.setError("You have to provide variable name"); 243 | } else { 244 | status= fValidationStatus; 245 | } 246 | updateStatus(status); 247 | } 248 | 249 | /* 250 | * @since 3.1 251 | */ 252 | protected void okPressed() { 253 | String name= fNameText == null ? fOriginalTemplate.getName() : fNameText.getText(); 254 | fNewTemplate= new Template(name, name, getContextId(), fDescriptionText.getText(), false); 255 | super.okPressed(); 256 | } 257 | 258 | /** 259 | * Returns the created template. 260 | * 261 | * @return the created template 262 | * @since 3.1 263 | */ 264 | public Template getTemplate() { 265 | return fNewTemplate; 266 | } 267 | 268 | /* 269 | * @see org.eclipse.jface.dialogs.Dialog#getDialogBoundsSettings() 270 | * @since 3.2 271 | */ 272 | protected IDialogSettings getDialogBoundsSettings() { 273 | String sectionName= getClass().getName() + "_dialogBounds"; //$NON-NLS-1$ 274 | IDialogSettings settings= EclipseEmmetPlugin.getDefault().getDialogSettings(); 275 | IDialogSettings section= settings.getSection(sectionName); 276 | if (section == null) 277 | section= settings.addNewSection(sectionName); 278 | return section; 279 | } 280 | 281 | } 282 | 283 | /** 284 | * Label provider for templates. 285 | */ 286 | private class TemplateLabelProvider extends LabelProvider implements ITableLabelProvider { 287 | 288 | /* 289 | * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnImage(java.lang.Object, int) 290 | */ 291 | public Image getColumnImage(Object element, int columnIndex) { 292 | return null; 293 | } 294 | 295 | /* 296 | * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnText(java.lang.Object, int) 297 | */ 298 | public String getColumnText(Object element, int columnIndex) { 299 | TemplatePersistenceData data = (TemplatePersistenceData) element; 300 | Template template= data.getTemplate(); 301 | 302 | switch (columnIndex) { 303 | case 0: 304 | return template.getName(); 305 | case 1: 306 | return template.getPattern(); 307 | default: 308 | return ""; //$NON-NLS-1$ 309 | } 310 | } 311 | } 312 | 313 | /** The table presenting the templates. */ 314 | private TableViewer fTableViewer; 315 | 316 | /* buttons */ 317 | private Button fAddButton; 318 | private Button fEditButton; 319 | private Button fRemoveButton; 320 | 321 | /** The store for our templates. */ 322 | private TemplateStore fTemplateStore; 323 | 324 | /* 325 | * @see PreferencePage#createContents(Composite) 326 | */ 327 | protected Control createContents(Composite ancestor) { 328 | Composite parent= new Composite(ancestor, SWT.NONE); 329 | GridLayout layout= new GridLayout(); 330 | layout.numColumns= 2; 331 | layout.marginHeight= 0; 332 | layout.marginWidth= 0; 333 | parent.setLayout(layout); 334 | 335 | Composite innerParent= new Composite(parent, SWT.NONE); 336 | GridLayout innerLayout= new GridLayout(); 337 | innerLayout.numColumns= 2; 338 | innerLayout.marginHeight= 0; 339 | innerLayout.marginWidth= 0; 340 | innerParent.setLayout(innerLayout); 341 | GridData gd= new GridData(GridData.FILL_BOTH); 342 | gd.horizontalSpan= 2; 343 | innerParent.setLayoutData(gd); 344 | 345 | Composite tableComposite= new Composite(innerParent, SWT.NONE); 346 | GridData data= new GridData(GridData.FILL_BOTH); 347 | data.widthHint= 360; 348 | data.heightHint= convertHeightInCharsToPixels(10); 349 | tableComposite.setLayoutData(data); 350 | 351 | TableColumnLayout columnLayout = new TableColumnLayout(); 352 | tableComposite.setLayout(columnLayout); 353 | Table table= new Table(tableComposite, SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL); 354 | 355 | table.setHeaderVisible(true); 356 | table.setLinesVisible(true); 357 | 358 | GC gc= new GC(getShell()); 359 | gc.setFont(JFaceResources.getDialogFont()); 360 | 361 | TableColumn column1= new TableColumn(table, SWT.NONE); 362 | column1.setText(columnName); 363 | int minWidth= computeMinimumColumnWidth(gc, columnName); 364 | columnLayout.setColumnData(column1, new ColumnWeightData(1, minWidth, true)); 365 | 366 | TableColumn column2= new TableColumn(table, SWT.NONE); 367 | column2.setText(columnValue); 368 | minWidth= computeMinimumColumnWidth(gc, columnValue); 369 | columnLayout.setColumnData(column2, new ColumnWeightData(3, minWidth, true)); 370 | 371 | gc.dispose(); 372 | 373 | fTableViewer= new TableViewer(table); 374 | fTableViewer.setLabelProvider(new TemplateLabelProvider()); 375 | fTableViewer.setContentProvider(new TemplateContentProvider()); 376 | 377 | fTableViewer.setComparator(new ViewerComparator() { 378 | public int compare(Viewer viewer, Object object1, Object object2) { 379 | if ((object1 instanceof TemplatePersistenceData) && (object2 instanceof TemplatePersistenceData)) { 380 | Template left= ((TemplatePersistenceData) object1).getTemplate(); 381 | Template right= ((TemplatePersistenceData) object2).getTemplate(); 382 | int result= Collator.getInstance().compare(left.getName(), right.getName()); 383 | if (result != 0) 384 | return result; 385 | return Collator.getInstance().compare(left.getDescription(), right.getDescription()); 386 | } 387 | return super.compare(viewer, object1, object2); 388 | } 389 | 390 | public boolean isSorterProperty(Object element, String property) { 391 | return true; 392 | } 393 | }); 394 | 395 | fTableViewer.addDoubleClickListener(new IDoubleClickListener() { 396 | public void doubleClick(DoubleClickEvent e) { 397 | edit(); 398 | } 399 | }); 400 | 401 | fTableViewer.addSelectionChangedListener(new ISelectionChangedListener() { 402 | public void selectionChanged(SelectionChangedEvent e) { 403 | selectionChanged1(); 404 | } 405 | }); 406 | 407 | Composite buttons= new Composite(innerParent, SWT.NONE); 408 | buttons.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING)); 409 | layout= new GridLayout(); 410 | layout.marginHeight= 0; 411 | layout.marginWidth= 0; 412 | buttons.setLayout(layout); 413 | 414 | fAddButton= new Button(buttons, SWT.PUSH); 415 | fAddButton.setText("New..."); 416 | fAddButton.setLayoutData(getButtonGridData(fAddButton)); 417 | fAddButton.addListener(SWT.Selection, new Listener() { 418 | public void handleEvent(Event e) { 419 | add(); 420 | } 421 | }); 422 | 423 | fEditButton= new Button(buttons, SWT.PUSH); 424 | fEditButton.setText("Edit..."); 425 | fEditButton.setLayoutData(getButtonGridData(fEditButton)); 426 | fEditButton.addListener(SWT.Selection, new Listener() { 427 | public void handleEvent(Event e) { 428 | edit(); 429 | } 430 | }); 431 | 432 | fRemoveButton= new Button(buttons, SWT.PUSH); 433 | fRemoveButton.setText("Remove"); 434 | fRemoveButton.setLayoutData(getButtonGridData(fRemoveButton)); 435 | fRemoveButton.addListener(SWT.Selection, new Listener() { 436 | public void handleEvent(Event e) { 437 | remove(); 438 | } 439 | }); 440 | 441 | 442 | fTableViewer.setInput(fTemplateStore); 443 | 444 | updateButtons(); 445 | Dialog.applyDialogFont(parent); 446 | innerParent.layout(); 447 | 448 | return parent; 449 | } 450 | 451 | /** 452 | * Return the grid data for the button. 453 | * 454 | * @param button the button 455 | * @return the grid data 456 | */ 457 | private static GridData getButtonGridData(Button button) { 458 | GridData data= new GridData(GridData.FILL_HORIZONTAL); 459 | return data; 460 | } 461 | 462 | @Override 463 | public void init(IWorkbench workbench) { 464 | 465 | } 466 | 467 | private void edit() { 468 | IStructuredSelection selection= (IStructuredSelection) fTableViewer.getSelection(); 469 | 470 | Object[] objects= selection.toArray(); 471 | if ((objects == null) || (objects.length != 1)) 472 | return; 473 | 474 | TemplatePersistenceData data= (TemplatePersistenceData) selection.getFirstElement(); 475 | edit(data); 476 | } 477 | 478 | private void edit(TemplatePersistenceData data) { 479 | Template oldTemplate= data.getTemplate(); 480 | Template newTemplate= editTemplate(new Template(oldTemplate), true, true); 481 | if (newTemplate != null) { 482 | 483 | if (!newTemplate.getName().equals(oldTemplate.getName()) && 484 | MessageDialog.openQuestion(getShell(), 485 | "New variable", 486 | "New variable message")) 487 | { 488 | data= new TemplatePersistenceData(newTemplate, true); 489 | fTemplateStore.add(data); 490 | fTableViewer.refresh(); 491 | } else { 492 | data.setTemplate(newTemplate); 493 | fTableViewer.refresh(data); 494 | } 495 | selectionChanged1(); 496 | fTableViewer.setSelection(new StructuredSelection(data)); 497 | } 498 | } 499 | 500 | /** 501 | * Creates the edit dialog. Subclasses may override this method to provide a 502 | * custom dialog. 503 | * 504 | * @param template the template being edited 505 | * @param edit whether the dialog should be editable 506 | * @param isNameModifiable whether the template name may be modified 507 | * @return the created or modified template, or null if the edition failed 508 | * @since 3.1 509 | */ 510 | protected Template editTemplate(Template template, boolean edit, boolean isNameModifiable) { 511 | EditVariableDialog dialog= new EditVariableDialog(getShell(), template, edit, isNameModifiable); 512 | if (dialog.open() == Window.OK) { 513 | return dialog.getTemplate(); 514 | } 515 | return null; 516 | } 517 | 518 | private void selectionChanged1() { 519 | updateButtons(); 520 | } 521 | 522 | /** 523 | * Updates the buttons. 524 | */ 525 | protected void updateButtons() { 526 | IStructuredSelection selection= (IStructuredSelection) fTableViewer.getSelection(); 527 | int selectionCount= selection.size(); 528 | int itemCount= fTableViewer.getTable().getItemCount(); 529 | 530 | fEditButton.setEnabled(selectionCount == 1); 531 | fRemoveButton.setEnabled(selectionCount > 0 && selectionCount <= itemCount); 532 | } 533 | 534 | private void add() { 535 | Template template= new Template("", "", CONTEXT_TYPE_ID, "", true); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ 536 | 537 | Template newTemplate= editTemplate(template, false, true); 538 | if (newTemplate != null) { 539 | TemplatePersistenceData data= new TemplatePersistenceData(newTemplate, true); 540 | fTemplateStore.add(data); 541 | fTableViewer.refresh(); 542 | fTableViewer.setSelection(new StructuredSelection(data)); 543 | } 544 | } 545 | 546 | private void remove() { 547 | IStructuredSelection selection= (IStructuredSelection) fTableViewer.getSelection(); 548 | 549 | @SuppressWarnings("rawtypes") 550 | Iterator elements= selection.iterator(); 551 | while (elements.hasNext()) { 552 | TemplatePersistenceData data= (TemplatePersistenceData) elements.next(); 553 | fTemplateStore.delete(data); 554 | } 555 | 556 | fTableViewer.refresh(); 557 | } 558 | 559 | /** 560 | * Sets the template store. 561 | * 562 | * @param store the new template store 563 | */ 564 | public void setTemplateStore(TemplateStore store) { 565 | fTemplateStore= store; 566 | } 567 | 568 | /** 569 | * Returns the template store. 570 | * 571 | * @return the template store 572 | */ 573 | public TemplateStore getTemplateStore() { 574 | return fTemplateStore; 575 | } 576 | 577 | private int computeMinimumColumnWidth(GC gc, String string) { 578 | return gc.stringExtent(string).x + 10; // pad 10 to accommodate table header trimmings 579 | } 580 | 581 | /* 582 | * @see PreferencePage#performDefaults() 583 | */ 584 | protected void performDefaults() { 585 | fTemplateStore.restoreDefaults(false); 586 | fTableViewer.refresh(); 587 | } 588 | 589 | /* 590 | * @see PreferencePage#performOk() 591 | */ 592 | public boolean performOk() { 593 | try { 594 | fTemplateStore.save(); 595 | } catch (IOException e) { 596 | openWriteErrorDialog(e); 597 | } 598 | 599 | return super.performOk(); 600 | } 601 | 602 | private void openWriteErrorDialog(IOException ex) { 603 | IStatus status= new Status(IStatus.ERROR, EclipseEmmetPlugin.PLUGIN_ID, IStatus.OK, "Failed to write templates.", ex); //$NON-NLS-1$ 604 | EclipseEmmetPlugin.getDefault().getLog().log(status); 605 | String title= "Error while saving variables"; 606 | String message= "Error occured while saving variable preverences"; 607 | MessageDialog.openError(getShell(), title, message); 608 | } 609 | } 610 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/preferences/output/CSSOutputPreferencePage.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.preferences.output; 2 | 3 | public class CSSOutputPreferencePage extends DefaultOutputPreferencePage { 4 | 5 | public CSSOutputPreferencePage() { 6 | super(); 7 | setDescription("Output preferences for CSS syntax"); 8 | setPrefSuffix("css"); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/preferences/output/DefaultOutputPreferencePage.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.preferences.output; 2 | 3 | import io.emmet.Emmet; 4 | import io.emmet.eclipse.EclipseEmmetPlugin; 5 | import io.emmet.eclipse.preferences.PreferenceConstants; 6 | import io.emmet.eclipse.preferences.PreferenceInitializer; 7 | import io.emmet.eclipse.preferences.SpacerFieldEditor; 8 | import io.emmet.eclipse.preferences.SpinnerFieldEditor; 9 | 10 | import org.eclipse.jface.preference.BooleanFieldEditor; 11 | import org.eclipse.jface.preference.FieldEditorPreferencePage; 12 | import org.eclipse.jface.preference.RadioGroupFieldEditor; 13 | import org.eclipse.jface.preference.StringFieldEditor; 14 | import org.eclipse.ui.IWorkbench; 15 | import org.eclipse.ui.IWorkbenchPreferencePage; 16 | 17 | 18 | public class DefaultOutputPreferencePage extends FieldEditorPreferencePage 19 | implements IWorkbenchPreferencePage { 20 | 21 | private String prefSuffix = ""; 22 | 23 | public DefaultOutputPreferencePage() { 24 | super(FLAT); 25 | setPreferenceStore(EclipseEmmetPlugin.getDefault().getPreferenceStore()); 26 | setDescription("Default output preferences for unknown syntaxes (like JavaScript, Python, etc.)"); 27 | setPrefSuffix("default"); 28 | } 29 | 30 | public String getPrefName(String prefix) { 31 | return PreferenceInitializer.getPrefName(prefix, getPrefSuffix()); 32 | } 33 | 34 | /* (non-Javadoc) 35 | * @see org.eclipse.ui.IWorkbenchPreferencePage#init(org.eclipse.ui.IWorkbench) 36 | */ 37 | @Override 38 | public void init(IWorkbench workbench) { 39 | } 40 | 41 | @Override 42 | protected void createFieldEditors() { 43 | 44 | addField(new RadioGroupFieldEditor( 45 | getPrefName(PreferenceConstants.P_PROFILE_TAG_CASE), 46 | "Tag case:", 47 | 3, 48 | new String[][] { 49 | { "&Lowercase", OutputProfile.LOWERCASE }, 50 | { "&Uppercase", OutputProfile.UPPERCASE }, 51 | { "&As is", OutputProfile.LEAVE } 52 | }, getFieldEditorParent(), true)); 53 | 54 | addField(new RadioGroupFieldEditor( 55 | getPrefName(PreferenceConstants.P_PROFILE_ATTR_CASE), 56 | "Attribute case:", 57 | 3, 58 | new String[][] { 59 | { "L&owercase", OutputProfile.LOWERCASE }, 60 | { "U&ppercase", OutputProfile.UPPERCASE }, 61 | { "A&s is", OutputProfile.LEAVE } 62 | }, getFieldEditorParent(), true)); 63 | 64 | addField(new RadioGroupFieldEditor( 65 | getPrefName(PreferenceConstants.P_PROFILE_ATTR_QUOTES), 66 | "Attribute quotes:", 67 | 2, 68 | new String[][] { 69 | { "S&ingle", OutputProfile.SINGE_QUOTES }, 70 | { "&Double", OutputProfile.DOUBLE_QUOTES } 71 | }, getFieldEditorParent(), true)); 72 | 73 | addField(new RadioGroupFieldEditor( 74 | getPrefName(PreferenceConstants.P_PROFILE_TAG_NEWLINE), 75 | "Each tag on new line:", 76 | 3, 77 | new String[][] { 78 | { "Yes", OutputProfile.TRUE }, 79 | { "No", OutputProfile.FALSE }, 80 | { "Decide", OutputProfile.DECIDE } 81 | }, getFieldEditorParent(), true)); 82 | 83 | addField(new BooleanFieldEditor( 84 | getPrefName(PreferenceConstants.P_PROFILE_PLACE_CURSOR), 85 | "Place caret placeholders in expanded abbreviations", 86 | getFieldEditorParent())); 87 | 88 | addField(new BooleanFieldEditor( 89 | getPrefName(PreferenceConstants.P_PROFILE_INDENT), 90 | "Indent tags", 91 | getFieldEditorParent())); 92 | 93 | SpinnerFieldEditor inlineBreak = new SpinnerFieldEditor( 94 | getPrefName(PreferenceConstants.P_PROFILE_INLINE_BREAK), 95 | "How many inline elements should be to force line break", 96 | 6, 97 | getFieldEditorParent()); 98 | 99 | inlineBreak.setValidRange(0, 99); 100 | addField(inlineBreak); 101 | 102 | addField(new RadioGroupFieldEditor( 103 | getPrefName(PreferenceConstants.P_PROFILE_SELF_CLOSING_TAG), 104 | "Self-closing style for writing empty elements:", 105 | 1, 106 | new String[][] { 107 | { "Disabled (
)", OutputProfile.FALSE }, 108 | { "Enabled (
)", OutputProfile.TRUE }, 109 | { "XHTML-style (
)", OutputProfile.XHTML_STYLE } 110 | }, getFieldEditorParent(), true)); 111 | 112 | 113 | addField(new SpacerFieldEditor(getFieldEditorParent())); 114 | 115 | addField( 116 | new StringFieldEditor(getPrefName(PreferenceConstants.P_FILTERS), "Applied &filters:", getFieldEditorParent())); 117 | 118 | } 119 | 120 | public void setPrefSuffix(String prefSuffix) { 121 | this.prefSuffix = prefSuffix; 122 | } 123 | 124 | public String getPrefSuffix() { 125 | return prefSuffix; 126 | } 127 | 128 | @Override 129 | public boolean performOk() { 130 | Emmet.reset(); 131 | return super.performOk(); 132 | } 133 | 134 | @Override 135 | protected void performDefaults() { 136 | Emmet.reset(); 137 | super.performDefaults(); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/preferences/output/HAMLOutputPreferencePage.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.preferences.output; 2 | 3 | public class HAMLOutputPreferencePage extends DefaultOutputPreferencePage { 4 | 5 | public HAMLOutputPreferencePage() { 6 | super(); 7 | setDescription("Output preferences for HAML syntax"); 8 | setPrefSuffix("haml"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/preferences/output/HTMLOutputPreferencePage.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.preferences.output; 2 | 3 | public class HTMLOutputPreferencePage extends DefaultOutputPreferencePage { 4 | public HTMLOutputPreferencePage() { 5 | super(); 6 | setDescription("Output preferences for HTML syntax"); 7 | setPrefSuffix("html"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/preferences/output/OutputProfile.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.preferences.output; 2 | 3 | import io.emmet.eclipse.EclipseEmmetPlugin; 4 | import io.emmet.eclipse.preferences.PreferenceConstants; 5 | import io.emmet.eclipse.preferences.PreferenceInitializer; 6 | 7 | import java.util.HashMap; 8 | 9 | import org.eclipse.jface.preference.IPreferenceStore; 10 | 11 | 12 | public class OutputProfile { 13 | public static final String LOWERCASE = "lower"; 14 | public static final String UPPERCASE = "upper"; 15 | public static final String LEAVE = "leave"; 16 | public static final String SINGE_QUOTES = "single"; 17 | public static final String DOUBLE_QUOTES = "double"; 18 | public static final String TRUE = "true"; 19 | public static final String FALSE = "false"; 20 | public static final String DECIDE = "decide"; 21 | public static final String XHTML_STYLE = "xhtml"; 22 | 23 | public static String[] syntaxes = {"html", "xml", "xsl", "css", "haml"}; 24 | 25 | private String tagCase = LOWERCASE; 26 | private String attrCase = LOWERCASE; 27 | private String attrQuotes = DOUBLE_QUOTES; 28 | private String tagNewline = DECIDE; 29 | private boolean placeCaret = true; 30 | private boolean indentTags = true; 31 | private int inlineBreak = 3; 32 | private String selfClosing = XHTML_STYLE; 33 | private String filters = ""; 34 | 35 | /** 36 | * Creates output profile object from stored preferences 37 | * @param suffix Syntax suffix (html, css, etc.) 38 | * @return 39 | */ 40 | public static OutputProfile createFromPreferences(String suffix) { 41 | OutputProfile profile = new OutputProfile(); 42 | IPreferenceStore store = EclipseEmmetPlugin.getDefault().getPreferenceStore(); 43 | 44 | profile.setTagCase(store.getString(getPrefName(PreferenceConstants.P_PROFILE_TAG_CASE, suffix))); 45 | profile.setAttrCase(store.getString(getPrefName(PreferenceConstants.P_PROFILE_ATTR_CASE, suffix))); 46 | profile.setAttrQuotes(store.getString(getPrefName(PreferenceConstants.P_PROFILE_ATTR_QUOTES, suffix))); 47 | profile.setTagNewline(store.getString(getPrefName(PreferenceConstants.P_PROFILE_TAG_NEWLINE, suffix))); 48 | profile.setPlaceCaret(store.getBoolean(getPrefName(PreferenceConstants.P_PROFILE_PLACE_CURSOR, suffix))); 49 | profile.setIndentTags(store.getBoolean(getPrefName(PreferenceConstants.P_PROFILE_INDENT, suffix))); 50 | profile.setInlineBreak(store.getInt(getPrefName(PreferenceConstants.P_PROFILE_INLINE_BREAK, suffix))); 51 | profile.setSelfClosing(store.getString(getPrefName(PreferenceConstants.P_PROFILE_SELF_CLOSING_TAG, suffix))); 52 | profile.setFilters(store.getString(getPrefName(PreferenceConstants.P_FILTERS, suffix))); 53 | 54 | return profile; 55 | } 56 | 57 | public static HashMap allProfiles() { 58 | HashMap profiles = new HashMap(); 59 | for (String syntax : syntaxes) { 60 | profiles.put(syntax, createFromPreferences(syntax)); 61 | } 62 | 63 | return profiles; 64 | } 65 | 66 | private static String getPrefName(String prefix, String suffix) { 67 | return PreferenceInitializer.getPrefName(prefix, suffix); 68 | } 69 | 70 | public String getAttrCase() { 71 | return attrCase; 72 | } 73 | 74 | public void setAttrCase(String attrCase) { 75 | this.attrCase = attrCase; 76 | } 77 | 78 | public String getAttrQuotes() { 79 | return attrQuotes; 80 | } 81 | 82 | public void setAttrQuotes(String attrQuotes) { 83 | this.attrQuotes = attrQuotes; 84 | } 85 | 86 | public String getTagNewline() { 87 | return tagNewline; 88 | } 89 | 90 | public void setTagNewline(String tagNewline) { 91 | this.tagNewline = tagNewline; 92 | } 93 | 94 | public boolean isPlaceCaret() { 95 | return placeCaret; 96 | } 97 | 98 | public void setPlaceCaret(boolean placeCaret) { 99 | this.placeCaret = placeCaret; 100 | } 101 | 102 | public boolean isIndentTags() { 103 | return indentTags; 104 | } 105 | 106 | public void setIndentTags(boolean indentTags) { 107 | this.indentTags = indentTags; 108 | } 109 | 110 | public int getInlineBreak() { 111 | return inlineBreak; 112 | } 113 | 114 | public void setInlineBreak(int inlineBreak) { 115 | this.inlineBreak = inlineBreak; 116 | } 117 | 118 | public String getSelfClosing() { 119 | return selfClosing; 120 | } 121 | 122 | public void setSelfClosing(String selfClosing) { 123 | this.selfClosing = selfClosing; 124 | } 125 | 126 | public void setTagCase(String tagCase) { 127 | this.tagCase = tagCase; 128 | } 129 | 130 | public String getTagCase() { 131 | return tagCase; 132 | } 133 | 134 | public void setFilters(String filters) { 135 | this.filters = filters; 136 | } 137 | 138 | public String getFilters() { 139 | return filters; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/preferences/output/XMLOutputPreferencePage.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.preferences.output; 2 | 3 | public class XMLOutputPreferencePage extends DefaultOutputPreferencePage { 4 | public XMLOutputPreferencePage() { 5 | super(); 6 | setDescription("Output preferences for XML syntax"); 7 | setPrefSuffix("xml"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/io/emmet/eclipse/preferences/output/XSLOutputPreferencePage.java: -------------------------------------------------------------------------------- 1 | package io.emmet.eclipse.preferences.output; 2 | 3 | public class XSLOutputPreferencePage extends DefaultOutputPreferencePage { 4 | public XSLOutputPreferencePage() { 5 | super(); 6 | setDescription("Output preferences for XSL syntax"); 7 | setPrefSuffix("xsl"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/io/emmet/file-interface.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Zen Coding file I/O interface implementation using Java classes 3 | * (for Mozilla Rhino) 4 | * 5 | * @author Sergey Chikuyonok (serge.che@gmail.com) 6 | * @link http://chikuyonok.ru 7 | * @version 0.65 8 | */ 9 | emmet.define('file', function(require, _) { 10 | return { 11 | /** 12 | * Read file content and return it 13 | * @param {String} path File's relative or absolute path 14 | * @return {String} 15 | * @memberOf __emmetFileJava 16 | */ 17 | read: function(path) { 18 | var File = Packages.java.io.File; 19 | var f = new File(path), 20 | input_stream, c, content = []; 21 | 22 | if (f.exists() && f.isFile() && f.canRead()) { 23 | input_stream = new Packages.java.io.FileInputStream(f); 24 | while ((c = input_stream.read()) != -1) { 25 | content.push(String.fromCharCode(c)); 26 | } 27 | 28 | input_stream.close(); 29 | } 30 | 31 | return content.join(''); 32 | }, 33 | 34 | /** 35 | * Locate file_name file that relates to editor_file. 36 | * File name may be absolute or relative path 37 | * 38 | * Dealing with absolute path. 39 | * Many modern editors has a "project" support as information unit, but you 40 | * should not rely on project path to find file with absolute path. First, 41 | * it requires user to create a project before using this method (and this 42 | * is not acutually Zen). Second, project path doesn't always points to 43 | * to website's document root folder: it may point, for example, to an 44 | * upper folder which contains server-side scripts. 45 | * 46 | * For better result, you should use the following algorithm in locating 47 | * absolute resources: 48 | * 1) Get parent folder for editor_file as a start point 49 | * 2) Append required file_name to start point and test if 50 | * file exists 51 | * 3) If it doesn't exists, move start point one level up (to parent folder) 52 | * and repeat step 2. 53 | * 54 | * @param {String} editor_file 55 | * @param {String} file_name 56 | * @return {String|null} Returns null if file_name cannot be located 57 | */ 58 | locateFile: function(editor_file, file_name) { 59 | var File = Packages.java.io.File; 60 | var f = new File(editor_file), 61 | result = '', 62 | tmp; 63 | 64 | // traverse upwards to find image uri 65 | while (f.getParent()) { 66 | tmp = new File(this.createPath(f.getParent(), file_name)); 67 | if (tmp.exists()) { 68 | result = tmp.getCanonicalPath(); 69 | break; 70 | } 71 | 72 | f = new File(f.getParent()); 73 | } 74 | 75 | return result; 76 | }, 77 | 78 | /** 79 | * Creates absolute path by concatenating parent and file_name. 80 | * If parent points to file, its parent directory is used 81 | * @param {String} parent 82 | * @param {String} file_name 83 | * @return {String} 84 | */ 85 | createPath: function(parent, file_name) { 86 | var File = Packages.java.io.File, 87 | f = new File(parent), 88 | result = ''; 89 | 90 | if (f.exists()) { 91 | if (f.isFile()) { 92 | parent = f.getParent(); 93 | } 94 | 95 | var req_file = new File(parent, file_name); 96 | result = req_file.getCanonicalPath(); 97 | } 98 | 99 | return result; 100 | }, 101 | 102 | /** 103 | * Saves content as file 104 | * @param {String} file File's asolute path 105 | * @param {String} content File content 106 | */ 107 | save: function(file, content) { 108 | content = content || ''; 109 | file = String(file); 110 | 111 | var File = Packages.java.io.File, 112 | f = new File(file); 113 | 114 | if (file.indexOf('/') != -1) { 115 | var f_parent = new File(f.getParent()); 116 | f_parent.mkdirs(); 117 | } 118 | 119 | var stream = new Packages.java.io.FileOutputStream(file); 120 | for (var i = 0, il = content.length; i < il; i++) { 121 | stream.write(content.charCodeAt(i)); 122 | } 123 | 124 | stream.close(); 125 | }, 126 | 127 | /** 128 | * Returns file extension in lower case 129 | * @param {String} file 130 | * @return {String} 131 | */ 132 | getExt: function(file) { 133 | var m = (file || '').match(/\.([\w\-]+)$/); 134 | return m ? m[1].toLowerCase() : ''; 135 | } 136 | }; 137 | }); 138 | -------------------------------------------------------------------------------- /src/io/emmet/java-wrapper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Short-hand functions for Java wrapper 3 | * @author Sergey Chikuyonok (serge.che@gmail.com) 4 | * @link http://chikuyonok.ru 5 | */ 6 | 7 | function require(name) { 8 | return emmet.require(name); 9 | } 10 | 11 | /** 12 | * Runs Emmet action 13 | * @param {IEmmetEditor} editor 14 | * @param {String} actionName 15 | * @return {Boolean} 16 | */ 17 | function runEmmetAction(editor, actionName){ 18 | var args = [editor]; 19 | for (var i = 2, il = arguments.length; i < il; i++) { 20 | args.push(arguments[i]); 21 | } 22 | 23 | return require('actions').run(actionName, args); 24 | } 25 | 26 | function tryBoolean(val) { 27 | var strVal = String(val || '').toLowerCase(); 28 | if (strVal == 'true') 29 | return true; 30 | if (strVal == 'false') 31 | return false; 32 | 33 | var intVal = parseInt(strVal, 10); 34 | if (!isNaN(intVal)) 35 | return intVal; 36 | 37 | return strVal; 38 | } 39 | 40 | function previewWrapWithAbbreviation(editor, abbr) { 41 | abbr = String(abbr); 42 | if (!abbr) 43 | return null; 44 | 45 | var editorUtils = require('editorUtils'); 46 | var utils = require('utils'); 47 | var info = editorUtils.outputInfo(editor); 48 | 49 | var range = editor.getSelectionRange(), 50 | startOffset = range.start, 51 | endOffset = range.end; 52 | 53 | if (startOffset == endOffset) { 54 | // no selection, find tag pair 55 | range = require('html_matcher')(info.content, startOffset, info.profile); 56 | 57 | if (!range || range[0] == -1) // nothing to wrap 58 | return null; 59 | 60 | var narrowedSel = utils.narrowToNonSpace(info.content, range[0], range[1] - range[0]); 61 | startOffset = narrowedSel.start; 62 | endOffset = narrowedSel.end; 63 | } 64 | 65 | var newContent = utils.escapeText(info.content.substring(startOffset, endOffset)); 66 | return require('wrapWithAbbreviation').wrap(abbr, editorUtils.unindent(editor, newContent), info.syntax, info.profile) 67 | || null; 68 | } 69 | 70 | function strToJSON(data) { 71 | try { 72 | return (new Function('return ' + String(data)))(); 73 | } catch(e) { 74 | log('Error while parsing JSON: ' + e); 75 | return {}; 76 | } 77 | } 78 | 79 | function javaLoadSystemSnippets(data) { 80 | if (data) { 81 | require('resources').setVocabulary(strToJSON(data), 'system'); 82 | } 83 | } 84 | 85 | function javaLoadUserData(payload) { 86 | payload = strToJSON(payload); 87 | var profileMap = { 88 | 'tagCase': 'tag_case', 89 | 'attrCase': 'attr_case', 90 | 'attrQuotes': 'attr_quotes', 91 | 'tagNewline': 'tag_nl', 92 | 'placeCaret': 'place_cursor', 93 | 'indentTags': 'indent', 94 | 'inlineBreak': 'inline_break', 95 | 'selfClosing': 'self_closing_tag', 96 | 'filters': 'filters' 97 | }; 98 | 99 | var validPayload = {}; 100 | 101 | // prepare profiles 102 | if ('profiles' in payload) { 103 | validPayload.syntaxProfiles = {}; 104 | _.each(payload.profiles, function(profile, syntax) { 105 | var p = {}; 106 | _.each(profile, function(v, k) { 107 | p[profileMap[k]] = v; 108 | }); 109 | 110 | validPayload.syntaxProfiles[syntax] = p; 111 | }); 112 | } 113 | 114 | // prepare snippets 115 | var snippets = {}; 116 | var addSnippets = function(data, type) { 117 | if (!data) 118 | return; 119 | 120 | _.each(data, function(item) { 121 | if (!(item[0] in snippets)) { 122 | snippets[item[0]] = {}; 123 | } 124 | 125 | var syntaxCtx = snippets[item[0]]; 126 | if (!syntaxCtx[type]) { 127 | syntaxCtx[type] = {}; 128 | } 129 | 130 | syntaxCtx[type][item[1]] = item[2]; 131 | }); 132 | }; 133 | 134 | addSnippets(payload.snippets, 'snippets'); 135 | addSnippets(payload.abbreviations, 'abbreviations'); 136 | validPayload.snippets = snippets; 137 | 138 | // prepare variables 139 | if ('variables' in payload) { 140 | validPayload.variables = {}; 141 | _.each(payload.variables, function(item) { 142 | validPayload.variables[item[1]] = item[2]; 143 | }); 144 | } 145 | 146 | require('bootstrap').loadUserData(validPayload); 147 | } 148 | 149 | function javaLoadExtensions(payload) { 150 | require('bootstrap').loadExtensions(strToJSON(payload)); 151 | } 152 | 153 | function javaExtractTabstops(text) { 154 | return require('tabStops').extract(text, { 155 | escape: function(ch) { 156 | return ch; 157 | } 158 | }); 159 | } 160 | 161 | function log(message) { 162 | java.lang.System.out.println('JS: ' + message); 163 | } -------------------------------------------------------------------------------- /src/io/emmet/json2.js: -------------------------------------------------------------------------------- 1 | /* 2 | json2.js 3 | 2011-10-19 4 | 5 | Public Domain. 6 | 7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | 9 | See http://www.JSON.org/js.html 10 | 11 | 12 | This code should be minified before deployment. 13 | See http://javascript.crockford.com/jsmin.html 14 | 15 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 16 | NOT CONTROL. 17 | 18 | 19 | This file creates a global JSON object containing two methods: stringify 20 | and parse. 21 | 22 | JSON.stringify(value, replacer, space) 23 | value any JavaScript value, usually an object or array. 24 | 25 | replacer an optional parameter that determines how object 26 | values are stringified for objects. It can be a 27 | function or an array of strings. 28 | 29 | space an optional parameter that specifies the indentation 30 | of nested structures. If it is omitted, the text will 31 | be packed without extra whitespace. If it is a number, 32 | it will specify the number of spaces to indent at each 33 | level. If it is a string (such as '\t' or ' '), 34 | it contains the characters used to indent at each level. 35 | 36 | This method produces a JSON text from a JavaScript value. 37 | 38 | When an object value is found, if the object contains a toJSON 39 | method, its toJSON method will be called and the result will be 40 | stringified. A toJSON method does not serialize: it returns the 41 | value represented by the name/value pair that should be serialized, 42 | or undefined if nothing should be serialized. The toJSON method 43 | will be passed the key associated with the value, and this will be 44 | bound to the value 45 | 46 | For example, this would serialize Dates as ISO strings. 47 | 48 | Date.prototype.toJSON = function (key) { 49 | function f(n) { 50 | // Format integers to have at least two digits. 51 | return n < 10 ? '0' + n : n; 52 | } 53 | 54 | return this.getUTCFullYear() + '-' + 55 | f(this.getUTCMonth() + 1) + '-' + 56 | f(this.getUTCDate()) + 'T' + 57 | f(this.getUTCHours()) + ':' + 58 | f(this.getUTCMinutes()) + ':' + 59 | f(this.getUTCSeconds()) + 'Z'; 60 | }; 61 | 62 | You can provide an optional replacer method. It will be passed the 63 | key and value of each member, with this bound to the containing 64 | object. The value that is returned from your method will be 65 | serialized. If your method returns undefined, then the member will 66 | be excluded from the serialization. 67 | 68 | If the replacer parameter is an array of strings, then it will be 69 | used to select the members to be serialized. It filters the results 70 | such that only members with keys listed in the replacer array are 71 | stringified. 72 | 73 | Values that do not have JSON representations, such as undefined or 74 | functions, will not be serialized. Such values in objects will be 75 | dropped; in arrays they will be replaced with null. You can use 76 | a replacer function to replace those with JSON values. 77 | JSON.stringify(undefined) returns undefined. 78 | 79 | The optional space parameter produces a stringification of the 80 | value that is filled with line breaks and indentation to make it 81 | easier to read. 82 | 83 | If the space parameter is a non-empty string, then that string will 84 | be used for indentation. If the space parameter is a number, then 85 | the indentation will be that many spaces. 86 | 87 | Example: 88 | 89 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 90 | // text is '["e",{"pluribus":"unum"}]' 91 | 92 | 93 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 94 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 95 | 96 | text = JSON.stringify([new Date()], function (key, value) { 97 | return this[key] instanceof Date ? 98 | 'Date(' + this[key] + ')' : value; 99 | }); 100 | // text is '["Date(---current time---)"]' 101 | 102 | 103 | JSON.parse(text, reviver) 104 | This method parses a JSON text to produce an object or array. 105 | It can throw a SyntaxError exception. 106 | 107 | The optional reviver parameter is a function that can filter and 108 | transform the results. It receives each of the keys and values, 109 | and its return value is used instead of the original value. 110 | If it returns what it received, then the structure is not modified. 111 | If it returns undefined then the member is deleted. 112 | 113 | Example: 114 | 115 | // Parse the text. Values that look like ISO date strings will 116 | // be converted to Date objects. 117 | 118 | myData = JSON.parse(text, function (key, value) { 119 | var a; 120 | if (typeof value === 'string') { 121 | a = 122 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 123 | if (a) { 124 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 125 | +a[5], +a[6])); 126 | } 127 | } 128 | return value; 129 | }); 130 | 131 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 132 | var d; 133 | if (typeof value === 'string' && 134 | value.slice(0, 5) === 'Date(' && 135 | value.slice(-1) === ')') { 136 | d = new Date(value.slice(5, -1)); 137 | if (d) { 138 | return d; 139 | } 140 | } 141 | return value; 142 | }); 143 | 144 | 145 | This is a reference implementation. You are free to copy, modify, or 146 | redistribute. 147 | */ 148 | 149 | /*jslint evil: true, regexp: true */ 150 | 151 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 152 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 153 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 154 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 155 | test, toJSON, toString, valueOf 156 | */ 157 | 158 | 159 | // Create a JSON object only if one does not already exist. We create the 160 | // methods in a closure to avoid creating global variables. 161 | 162 | var JSON; 163 | if (!JSON) { 164 | JSON = {}; 165 | } 166 | 167 | (function () { 168 | 'use strict'; 169 | 170 | function f(n) { 171 | // Format integers to have at least two digits. 172 | return n < 10 ? '0' + n : n; 173 | } 174 | 175 | if (typeof Date.prototype.toJSON !== 'function') { 176 | 177 | Date.prototype.toJSON = function (key) { 178 | 179 | return isFinite(this.valueOf()) 180 | ? this.getUTCFullYear() + '-' + 181 | f(this.getUTCMonth() + 1) + '-' + 182 | f(this.getUTCDate()) + 'T' + 183 | f(this.getUTCHours()) + ':' + 184 | f(this.getUTCMinutes()) + ':' + 185 | f(this.getUTCSeconds()) + 'Z' 186 | : null; 187 | }; 188 | 189 | String.prototype.toJSON = 190 | Number.prototype.toJSON = 191 | Boolean.prototype.toJSON = function (key) { 192 | return this.valueOf(); 193 | }; 194 | } 195 | 196 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 197 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 198 | gap, 199 | indent, 200 | meta = { // table of character substitutions 201 | '\b': '\\b', 202 | '\t': '\\t', 203 | '\n': '\\n', 204 | '\f': '\\f', 205 | '\r': '\\r', 206 | '"' : '\\"', 207 | '\\': '\\\\' 208 | }, 209 | rep; 210 | 211 | 212 | function quote(string) { 213 | 214 | // If the string contains no control characters, no quote characters, and no 215 | // backslash characters, then we can safely slap some quotes around it. 216 | // Otherwise we must also replace the offending characters with safe escape 217 | // sequences. 218 | 219 | escapable.lastIndex = 0; 220 | return escapable.test(string) ? '"' + string.replace(escapable, function (a) { 221 | var c = meta[a]; 222 | return typeof c === 'string' 223 | ? c 224 | : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 225 | }) + '"' : '"' + string + '"'; 226 | } 227 | 228 | 229 | function str(key, holder) { 230 | 231 | // Produce a string from holder[key]. 232 | 233 | var i, // The loop counter. 234 | k, // The member key. 235 | v, // The member value. 236 | length, 237 | mind = gap, 238 | partial, 239 | value = holder[key]; 240 | 241 | // If the value has a toJSON method, call it to obtain a replacement value. 242 | 243 | if (value && typeof value === 'object' && 244 | typeof value.toJSON === 'function') { 245 | value = value.toJSON(key); 246 | } 247 | 248 | // If we were called with a replacer function, then call the replacer to 249 | // obtain a replacement value. 250 | 251 | if (typeof rep === 'function') { 252 | value = rep.call(holder, key, value); 253 | } 254 | 255 | // What happens next depends on the value's type. 256 | 257 | switch (typeof value) { 258 | case 'string': 259 | return quote(value); 260 | 261 | case 'number': 262 | 263 | // JSON numbers must be finite. Encode non-finite numbers as null. 264 | 265 | return isFinite(value) ? String(value) : 'null'; 266 | 267 | case 'boolean': 268 | case 'null': 269 | 270 | // If the value is a boolean or null, convert it to a string. Note: 271 | // typeof null does not produce 'null'. The case is included here in 272 | // the remote chance that this gets fixed someday. 273 | 274 | return String(value); 275 | 276 | // If the type is 'object', we might be dealing with an object or an array or 277 | // null. 278 | 279 | case 'object': 280 | 281 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 282 | // so watch out for that case. 283 | 284 | if (!value) { 285 | return 'null'; 286 | } 287 | 288 | // Make an array to hold the partial results of stringifying this object value. 289 | 290 | gap += indent; 291 | partial = []; 292 | 293 | // Is the value an array? 294 | 295 | if (Object.prototype.toString.apply(value) === '[object Array]') { 296 | 297 | // The value is an array. Stringify every element. Use null as a placeholder 298 | // for non-JSON values. 299 | 300 | length = value.length; 301 | for (i = 0; i < length; i += 1) { 302 | partial[i] = str(i, value) || 'null'; 303 | } 304 | 305 | // Join all of the elements together, separated with commas, and wrap them in 306 | // brackets. 307 | 308 | v = partial.length === 0 309 | ? '[]' 310 | : gap 311 | ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' 312 | : '[' + partial.join(',') + ']'; 313 | gap = mind; 314 | return v; 315 | } 316 | 317 | // If the replacer is an array, use it to select the members to be stringified. 318 | 319 | if (rep && typeof rep === 'object') { 320 | length = rep.length; 321 | for (i = 0; i < length; i += 1) { 322 | if (typeof rep[i] === 'string') { 323 | k = rep[i]; 324 | v = str(k, value); 325 | if (v) { 326 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 327 | } 328 | } 329 | } 330 | } else { 331 | 332 | // Otherwise, iterate through all of the keys in the object. 333 | 334 | for (k in value) { 335 | if (Object.prototype.hasOwnProperty.call(value, k)) { 336 | v = str(k, value); 337 | if (v) { 338 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 339 | } 340 | } 341 | } 342 | } 343 | 344 | // Join all of the member texts together, separated with commas, 345 | // and wrap them in braces. 346 | 347 | v = partial.length === 0 348 | ? '{}' 349 | : gap 350 | ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' 351 | : '{' + partial.join(',') + '}'; 352 | gap = mind; 353 | return v; 354 | } 355 | } 356 | 357 | // If the JSON object does not yet have a stringify method, give it one. 358 | 359 | if (typeof JSON.stringify !== 'function') { 360 | JSON.stringify = function (value, replacer, space) { 361 | 362 | // The stringify method takes a value and an optional replacer, and an optional 363 | // space parameter, and returns a JSON text. The replacer can be a function 364 | // that can replace values, or an array of strings that will select the keys. 365 | // A default replacer method can be provided. Use of the space parameter can 366 | // produce text that is more easily readable. 367 | 368 | var i; 369 | gap = ''; 370 | indent = ''; 371 | 372 | // If the space parameter is a number, make an indent string containing that 373 | // many spaces. 374 | 375 | if (typeof space === 'number') { 376 | for (i = 0; i < space; i += 1) { 377 | indent += ' '; 378 | } 379 | 380 | // If the space parameter is a string, it will be used as the indent string. 381 | 382 | } else if (typeof space === 'string') { 383 | indent = space; 384 | } 385 | 386 | // If there is a replacer, it must be a function or an array. 387 | // Otherwise, throw an error. 388 | 389 | rep = replacer; 390 | if (replacer && typeof replacer !== 'function' && 391 | (typeof replacer !== 'object' || 392 | typeof replacer.length !== 'number')) { 393 | throw new Error('JSON.stringify'); 394 | } 395 | 396 | // Make a fake root object containing our value under the key of ''. 397 | // Return the result of stringifying the value. 398 | 399 | return str('', {'': value}); 400 | }; 401 | } 402 | 403 | 404 | // If the JSON object does not yet have a parse method, give it one. 405 | 406 | if (typeof JSON.parse !== 'function') { 407 | JSON.parse = function (text, reviver) { 408 | 409 | // The parse method takes a text and an optional reviver function, and returns 410 | // a JavaScript value if the text is a valid JSON text. 411 | 412 | var j; 413 | 414 | function walk(holder, key) { 415 | 416 | // The walk method is used to recursively walk the resulting structure so 417 | // that modifications can be made. 418 | 419 | var k, v, value = holder[key]; 420 | if (value && typeof value === 'object') { 421 | for (k in value) { 422 | if (Object.prototype.hasOwnProperty.call(value, k)) { 423 | v = walk(value, k); 424 | if (v !== undefined) { 425 | value[k] = v; 426 | } else { 427 | delete value[k]; 428 | } 429 | } 430 | } 431 | } 432 | return reviver.call(holder, key, value); 433 | } 434 | 435 | 436 | // Parsing happens in four stages. In the first stage, we replace certain 437 | // Unicode characters with escape sequences. JavaScript handles many characters 438 | // incorrectly, either silently deleting them, or treating them as line endings. 439 | 440 | text = String(text); 441 | cx.lastIndex = 0; 442 | if (cx.test(text)) { 443 | text = text.replace(cx, function (a) { 444 | return '\\u' + 445 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 446 | }); 447 | } 448 | 449 | // In the second stage, we run the text against regular expressions that look 450 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 451 | // because they can cause invocation, and '=' because it can cause mutation. 452 | // But just to be safe, we want to reject all unexpected forms. 453 | 454 | // We split the second stage into 4 regexp operations in order to work around 455 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 456 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 457 | // replace all simple value tokens with ']' characters. Third, we delete all 458 | // open brackets that follow a colon or comma or that begin the text. Finally, 459 | // we look to see that the remaining characters are only whitespace or ']' or 460 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 461 | 462 | if (/^[\],:{}\s]*$/ 463 | .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') 464 | .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') 465 | .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 466 | 467 | // In the third stage we use the eval function to compile the text into a 468 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 469 | // in JavaScript: it can begin a block or an object literal. We wrap the text 470 | // in parens to eliminate the ambiguity. 471 | 472 | j = eval('(' + text + ')'); 473 | 474 | // In the optional fourth stage, we recursively walk the new structure, passing 475 | // each name/value pair to a reviver function for possible transformation. 476 | 477 | return typeof reviver === 'function' 478 | ? walk({'': j}, '') 479 | : j; 480 | } 481 | 482 | // If the text is not JSON parseable, then a SyntaxError is thrown. 483 | 484 | throw new SyntaxError('JSON.parse'); 485 | }; 486 | } 487 | }()); --------------------------------------------------------------------------------