diff --git a/demo/kitchen-sink/demo.js b/demo/kitchen-sink/demo.js index 49537452..48cf71b3 100644 --- a/demo/kitchen-sink/demo.js +++ b/demo/kitchen-sink/demo.js @@ -461,6 +461,14 @@ event.addListener(container, "drop", function(e) { var StatusBar = require("./statusbar").StatusBar; new StatusBar(env.editor, cmdLine.container); + +var Emmet = require("ace/ext/emmet"); +net.loadScript("https://rawgithub.com/nightwing/emmet-core/master/emmet.js", function() { + Emmet.setCore(window.emmet); + env.editor.setOption("enableEmmet", true); +}) + + require("ace/placeholder").PlaceHolder; var SnippetManager = require("ace/snippets").SnippetManager diff --git a/index.html b/index.html index 5446ac38..4b67b790 100644 --- a/index.html +++ b/index.html @@ -945,10 +945,28 @@ oop.inherits(FoldMode, BaseFoldMode); Slim Text
  • + + style="position: relative; left: -5px; width: 111px;top: 29px;"> Decor
  • +
  • + + Divshot +
  • +
  • + + Codio +
  • +
  • + + + Chocolatejs +
  • Sky Edit
  • diff --git a/lib/ace/ext/emmet.js b/lib/ace/ext/emmet.js new file mode 100644 index 00000000..e13f80fe --- /dev/null +++ b/lib/ace/ext/emmet.js @@ -0,0 +1,368 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Distributed under the BSD license: + * + * Copyright (c) 2010, Ajax.org B.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Ajax.org B.V. nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ***** END LICENSE BLOCK ***** */ + +define(function(require, exports, module) { +"use strict"; +var HashHandler = require("ace/keyboard/hash_handler").HashHandler; +var Editor = require("ace/editor").Editor; +var emmet; + +Editor.prototype.indexToPosition = function(index) { + return this.session.doc.indexToPosition(index); +}; + +Editor.prototype.positionToIndex = function(pos) { + return this.session.doc.positionToIndex(pos); +}; + +/** + * Implementation of {@link IEmmetEditor} interface for Ace + */ +function AceEmmetEditor() {} + +AceEmmetEditor.prototype = { + setupContext: function(editor) { + this.ace = editor; + this.indentation = editor.session.getTabString(); + emmet.require('resources').setVariable('indentation', this.indentation); + this.$syntax = null; + this.$syntax = this.getSyntax(); + }, + /** + * Returns character indexes of selected text: object with start + * and end properties. If there's no selection, should return + * object with start and end properties referring + * to current caret position + * @return {Object} + * @example + * var selection = editor.getSelectionRange(); + * alert(selection.start + ', ' + selection.end); + */ + getSelectionRange: function() { + // TODO should start be caret position instead? + var range = this.ace.getSelectionRange(); + return { + start: this.ace.positionToIndex(range.start), + end: this.ace.positionToIndex(range.end) + }; + }, + + /** + * Creates selection from start to end character + * indexes. If end is ommited, this method should place caret + * and start index + * @param {Number} start + * @param {Number} [end] + * @example + * editor.createSelection(10, 40); + * + * //move caret to 15th character + * editor.createSelection(15); + */ + createSelection: function(start, end) { + this.ace.selection.setRange({ + start: this.ace.indexToPosition(start), + end: this.ace.indexToPosition(end) + }); + }, + + /** + * Returns current line's start and end indexes as object with start + * and end properties + * @return {Object} + * @example + * var range = editor.getCurrentLineRange(); + * alert(range.start + ', ' + range.end); + */ + getCurrentLineRange: function() { + var row = this.ace.getCursorPosition().row; + var lineLength = this.ace.session.getLine(row).length; + var index = this.ace.positionToIndex({row: row, column: 0}); + return { + start: index, + end: index + lineLength + }; + }, + + /** + * Returns current caret position + * @return {Number|null} + */ + getCaretPos: function(){ + var pos = this.ace.getCursorPosition(); + return this.ace.positionToIndex(pos); + }, + + /** + * Set new caret position + * @param {Number} index Caret position + */ + setCaretPos: function(index){ + var pos = this.ace.indexToPosition(index); + this.ace.clearSelection(); + this.ace.selection.moveCursorToPosition(pos); + }, + + /** + * Returns content of current line + * @return {String} + */ + getCurrentLine: function() { + var row = this.ace.getCursorPosition().row; + return this.ace.session.getLine(row); + }, + + /** + * Replace editor's content or it's part (from start to + * end index). If value contains + * caret_placeholder, the editor will put caret into + * this position. If you skip start and end + * arguments, the whole target's content will be replaced with + * value. + * + * If you pass start argument only, + * the value will be placed at start string + * index of current content. + * + * If you pass start and end arguments, + * the corresponding substring of current target's content will be + * replaced with value. + * @param {String} value Content you want to paste + * @param {Number} [start] Start index of editor's content + * @param {Number} [end] End index of editor's content + * @param {Boolean} [noIndent] Do not auto indent value + */ + replaceContent: function(value, start, end, noIndent) { + if (end == null) + end = start == null ? content.length : start; + if (start == null) + start = 0; + var utils = emmet.require('utils'); + + // indent new value + if (!noIndent) { + value = utils.padString(value, utils.getLinePaddingFromPosition(this.getContent(), start)); + } + + // find new caret position + var tabstopData = emmet.require('tabStops').extract(value, { + escape: function(ch) { + return ch; + } + }); + + value = tabstopData.text; + var firstTabStop = tabstopData.tabstops[0]; + + if (firstTabStop) { + firstTabStop.start += start; + firstTabStop.end += start; + } else { + firstTabStop = { + start: value.length + start, + end: value.length + start + }; + } + + var range = this.ace.getSelectionRange(); + range.start = this.ace.indexToPosition(start); + range.end = this.ace.indexToPosition(end); + + this.ace.session.replace(range, value); + + range.start = this.ace.indexToPosition(firstTabStop.start); + range.end = this.ace.indexToPosition(firstTabStop.end); + this.ace.selection.setRange(range); + }, + + /** + * Returns editor's content + * @return {String} + */ + getContent: function(){ + return this.ace.getValue(); + }, + + /** + * Returns current editor's syntax mode + * @return {String} + */ + getSyntax: function() { + if (this.$syntax) + return this.$syntax; + var syntax = this.ace.session.$modeId.split("/").pop(); + if (syntax == 'html' || syntax == "php") { + var cursor = this.ace.getCursorPosition(); + var state = this.ace.session.getState(cursor.row); + if (typeof state != "string") + state = state[0]; + if (state) { + state = state.split("-"); + if (state.length > 1) + syntax = state[0]; + else if (syntax == "php") + syntax = "html" + } + } + return syntax; + }, + + /** + * Returns current output profile name (@see emmet#setupProfile) + * @return {String} + */ + getProfileName: function() { + switch(this.getSyntax()) { + case 'css': return css; + case 'xml': + case 'xsl': + return 'xml'; + case 'html': + var profile = emmet.require('resources').getVariable('profile'); + // no forced profile, guess from content html or xhtml? + if (!profile) + profile = this.ace.session.getLines(0,2).join("").search(/]+XHTML/i) != -1 ? 'xhtml': 'html'; + return profile; + } + return 'xhtml'; + }, + + /** + * Ask user to enter something + * @param {String} title Dialog title + * @return {String} Entered data + * @since 0.65 + */ + prompt: function(title) { + return prompt(title); + }, + + /** + * Returns current selection + * @return {String} + * @since 0.65 + */ + getSelection: function() { + return this.ace.session.getTextRange(); + }, + + /** + * Returns current editor's file path + * @return {String} + * @since 0.65 + */ + getFilePath: function() { + return ''; + } +}; + + +var keymap = { + expand_abbreviation: {"mac": "ctrl+alt+e", "win": "alt+e"}, + match_pair_outward: {"mac": "ctrl+d", "win": "ctrl+,"}, + match_pair_inward: {"mac": "ctrl+j", "win": "ctrl+shift+0"}, + matching_pair: {"mac": "ctrl+alt+j", "win": "alt+j"}, + next_edit_point: "alt+right", + prev_edit_point: "alt+left", + toggle_comment: {"mac": "command+shift+/", "win": "ctrl+shift+/"}, + split_join_tag: {"mac": "shift+command+'", "win": "shift+ctrl+`"}, + remove_tag: {"mac": "command+'", "win": "shift+ctrl+;"}, + evaluate_math_expression: {"mac": "shift+command+y", "win": "shift+ctrl+y"}, + increment_number_by_1: "ctrl+up", + decrement_number_by_1: "ctrl+down", + increment_number_by_01: "alt+up", + decrement_number_by_01: "alt+down", + increment_number_by_10: {"mac": "alt+command+up", "win": "shift+alt+up"}, + decrement_number_by_10: {"mac": "alt+command+down", "win": "shift+alt+down"}, + select_next_item: {"mac": "shift+command+.", "win": "shift+ctrl+."}, + select_previous_item: {"mac": "shift+command+,", "win": "shift+ctrl+,"}, + reflect_css_value: {"mac": "shift+command+r", "win": "shift+ctrl+r"}, + + encode_decode_data_url: {"mac": "shift+ctrl+d", "win": "ctrl+'"}, + // update_image_size: {"mac": "shift+ctrl+i", "win": "ctrl+u"}, + // expand_as_you_type: "ctrl+alt+enter", + // wrap_as_you_type: {"mac": "shift+ctrl+g", "win": "shift+ctrl+g"}, + expand_abbreviation_with_tab: "Tab" +}; + +var editorProxy = new AceEmmetEditor(); +exports.commands = new HashHandler(); +function runEmmetCommand(editor) { + editorProxy.setupContext(editor); + if (editorProxy.getSyntax() == "php") + return false; + var actions = emmet.require('actions') + + try { + var result = actions.run(this.name, editorProxy); + } catch(e) { + editor._signal("changeStatus", typeof e == "string" ? e : e.message); + } + return result; +} + +for (var command in keymap) { + exports.commands.addCommand({ + name: command, + bindKey: keymap[command], + exec: runEmmetCommand + }); +} + +var onChangeMode = function(e, target) { + var editor = target; + if (!editor) + return; + var modeId = editor.session.$modeId; + var enabled = modeId && /css|less|sass|html|php/.test(modeId); + if (e.enableEmmet === false) + enabled = false; + if (enabled) + editor.keyBinding.addKeyboardHandler(exports.commands); + else + editor.keyBinding.removeKeyboardHandler(exports.commands); +}; + + +exports.AceEmmetEditor = AceEmmetEditor +require("ace/config").defineOptions(Editor.prototype, "editor", { + enableEmmet: { + set: function(val) { + this[val ? "on" : "removeListener"]("changeMode", onChangeMode); + onChangeMode({enableEmmet: !!val}, this); + }, + value: true + } +}); + + +exports.setCore = function(e) {emmet = e;}; +}); + diff --git a/lib/ace/keyboard/hash_handler.js b/lib/ace/keyboard/hash_handler.js index fc323837..2ef110da 100644 --- a/lib/ace/keyboard/hash_handler.js +++ b/lib/ace/keyboard/hash_handler.js @@ -32,9 +32,10 @@ define(function(require, exports, module) { "use strict"; var keyUtil = require("../lib/keys"); +var useragent = require("../lib/useragent"); function HashHandler(config, platform) { - this.platform = platform; + this.platform = platform || (useragent.isMac ? "mac" : "win"); this.commands = {}; this.commmandKeyBinding = {}; diff --git a/lib/ace/lib/event_emitter.js b/lib/ace/lib/event_emitter.js index 5e4f6ec1..39d76f9a 100644 --- a/lib/ace/lib/event_emitter.js +++ b/lib/ace/lib/event_emitter.js @@ -54,17 +54,15 @@ EventEmitter._dispatchEvent = function(eventName, e) { e.stopPropagation = stopPropagation; if (!e.preventDefault) e.preventDefault = preventDefault; - if (!e.target) - e.target = this; for (var i=0; i