From c673190bf79d1e6c04aa372f0b39e31445031f17 Mon Sep 17 00:00:00 2001 From: nightwing Date: Tue, 22 Jul 2014 03:20:43 +0400 Subject: [PATCH 01/25] allow binding multiple commands to same key --- lib/ace/commands/command_manager.js | 18 ++-- lib/ace/editor.js | 49 +++------ lib/ace/ext/language_tools.js | 4 +- lib/ace/keyboard/emacs.js | 4 +- lib/ace/keyboard/hash_handler.js | 149 +++++++++++++++++----------- lib/ace/lib/keys.js | 3 + 6 files changed, 122 insertions(+), 105 deletions(-) diff --git a/lib/ace/commands/command_manager.js b/lib/ace/commands/command_manager.js index 72a9942d..7b017ed2 100644 --- a/lib/ace/commands/command_manager.js +++ b/lib/ace/commands/command_manager.js @@ -2,13 +2,12 @@ define(function(require, exports, module) { "use strict"; var oop = require("../lib/oop"); -var HashHandler = require("../keyboard/hash_handler").HashHandler; +var MultiHashHandler = require("../keyboard/hash_handler").MultiHashHandler; var EventEmitter = require("../lib/event_emitter").EventEmitter; /** * @class CommandManager * - * **/ /** @@ -19,20 +18,27 @@ var EventEmitter = require("../lib/event_emitter").EventEmitter; **/ var CommandManager = function(platform, commands) { - HashHandler.call(this, commands, platform); + MultiHashHandler.call(this, commands, platform); this.byName = this.commands; this.setDefaultHandler("exec", function(e) { return e.command.exec(e.editor, e.args || {}); }); }; -oop.inherits(CommandManager, HashHandler); +oop.inherits(CommandManager, MultiHashHandler); (function() { oop.implement(this, EventEmitter); this.exec = function(command, editor, args) { + if (Array.isArray(command)) { + for (var i = command.length; i--; ) { + if (this.exec(command[i], editor, args)) return true; + } + return false; + } + if (typeof command === 'string') command = this.commands[command]; @@ -43,10 +49,10 @@ oop.inherits(CommandManager, HashHandler); return false; var e = {editor: editor, command: command, args: args}; - var retvalue = this._emit("exec", e); + e.returnValue = this._emit("exec", e); this._signal("afterExec", e); - return retvalue === false ? false : true; + return e.returnValue === false ? false : true; }; this.toggleRecording = function(editor) { diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 6a68e5de..6c1a9200 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -114,30 +114,8 @@ var Editor = function(renderer, session) { function last(a) {return a[a.length - 1]} this.selections = []; - this.commands.on("exec", function(e) { - this.startOperation(e); - - var command = e.command; - if (command.aceCommandGroup == "fileJump") { - var prev = this.prevOp; - if (!prev || prev.command.aceCommandGroup != "fileJump") { - this.lastFileJumpPos = last(this.selections); - } - } else { - this.lastFileJumpPos = null; - } - }.bind(this), true); - - this.commands.on("afterExec", function(e) { - var command = e.command; - - if (command.aceCommandGroup == "fileJump") { - if (this.lastFileJumpPos && !this.curOp.selectionChanged) { - this.selection.fromJSON(this.lastFileJumpPos); - } - } - this.endOperation(e); - }.bind(this), true); + this.commands.on("exec", this.startOperation.bind(this), true); + this.commands.on("afterExec", this.endOperation.bind(this), true); this.$opResetTimer = lang.delayedCall(this.endOperation.bind(this)); @@ -172,18 +150,16 @@ var Editor = function(renderer, session) { scrollTop: this.renderer.scrollTop }; - var command = this.curOp.command; - if (command && command.scrollIntoView) - this.$blockScrolling++; - - this.selections.push(this.selection.toJSON()); + // this.selections.push(this.selection.toJSON()); }; - this.endOperation = function() { + this.endOperation = function(e) { if (this.curOp) { + if (e && e.returnValue === false) + return this.curOp = null; + var command = this.curOp.command; if (command && command.scrollIntoView) { - this.$blockScrolling--; switch (command.scrollIntoView) { case "center": this.renderer.scrollCursorIntoView(null, 0.5); @@ -254,19 +230,19 @@ var Editor = function(renderer, session) { * @param {String} keyboardHandler The new key handler * **/ - this.setKeyboardHandler = function(keyboardHandler) { - if (!keyboardHandler) { - this.keyBinding.setKeyboardHandler(null); - } else if (typeof keyboardHandler === "string") { + this.setKeyboardHandler = function(keyboardHandler, cb) { + if (keyboardHandler && typeof keyboardHandler === "string") { this.$keybindingId = keyboardHandler; var _self = this; config.loadModule(["keybinding", keyboardHandler], function(module) { if (_self.$keybindingId == keyboardHandler) _self.keyBinding.setKeyboardHandler(module && module.handler); + cb && cb(); }); } else { this.$keybindingId = null; this.keyBinding.setKeyboardHandler(keyboardHandler); + cb && cb(); } }; @@ -925,9 +901,8 @@ var Editor = function(renderer, session) { this.insert(e.text, true); }; - this.execCommand = function(command, args) { - this.commands.exec(command, this, args); + return this.commands.exec(command, this, args); }; /** diff --git a/lib/ace/ext/language_tools.js b/lib/ace/ext/language_tools.js index d8b2a1dd..9d10695d 100644 --- a/lib/ace/ext/language_tools.js +++ b/lib/ace/ext/language_tools.js @@ -80,9 +80,7 @@ exports.snippetCompleter = snippetCompleter; var expandSnippet = { name: "expandSnippet", exec: function(editor) { - var success = snippetManager.expandWithTab(editor); - if (!success) - editor.execCommand("indent"); + return snippetManager.expandWithTab(editor); }, bindKey: "Tab" }; diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index f1adb9ef..c15f07d0 100644 --- a/lib/ace/keyboard/emacs.js +++ b/lib/ace/keyboard/emacs.js @@ -64,7 +64,7 @@ exports.handler.attach = function(editor) { initialized = true; dom.importCssString('\ .emacs-mode .ace_cursor{\ - border: 2px rgba(50,250,50,0.8) solid!important;\ + border: 1px rgba(50,250,50,0.8) solid!important;\ -moz-box-sizing: border-box!important;\ -webkit-box-sizing: border-box!important;\ box-sizing: border-box!important;\ @@ -195,6 +195,8 @@ exports.handler.onPaste = function(e, editor) { }; exports.handler.bindKey = function(key, command) { + if (typeof key == "object") + key = key[this.platform]; if (!key) return; diff --git a/lib/ace/keyboard/hash_handler.js b/lib/ace/keyboard/hash_handler.js index cb02f170..06badccd 100644 --- a/lib/ace/keyboard/hash_handler.js +++ b/lib/ace/keyboard/hash_handler.js @@ -33,37 +33,25 @@ define(function(require, exports, module) { var keyUtil = require("../lib/keys"); var useragent = require("../lib/useragent"); +var KEY_MODS = keyUtil.KEY_MODS; function HashHandler(config, platform) { this.platform = platform || (useragent.isMac ? "mac" : "win"); this.commands = {}; this.commandKeyBinding = {}; - - // todo remove this after a while - if (this.__defineGetter__ && this.__defineSetter__ && typeof console != "undefined" && console.error) { - var warned = false; - var warn = function() { - if (!warned) { - warned = true; - console.error("commmandKeyBinding has too many m's. use commandKeyBinding"); - } - }; - this.__defineGetter__("commmandKeyBinding", function() { - warn(); - return this.commandKeyBinding; - }); - this.__defineSetter__("commmandKeyBinding", function(val) { - warn(); - return this.commandKeyBinding = val; - }); - } else { - this.commmandKeyBinding = this.commandKeyBinding; - } - this.addCommands(config); -}; + this.$singleCommand = true; +} + +function MultiHashHandler(config, platform) { + HashHandler.call(this, config, platform); + this.$singleCommand = false; +} + +MultiHashHandler.prototype = HashHandler.prototype; (function() { + this.addCommand = function(command) { if (this.commands[command.name]) @@ -75,37 +63,76 @@ function HashHandler(config, platform) { this._buildKeyHash(command); }; - this.removeCommand = function(command) { - var name = (typeof command === 'string' ? command : command.name); + this.removeCommand = function(command, keepCommand) { + var name = command && (typeof command === 'string' ? command : command.name); command = this.commands[name]; - delete this.commands[name]; + if (!keepCommand) + delete this.commands[name]; // exhaustive search is brute force but since removeCommand is // not a performance critical operation this should be OK var ckb = this.commandKeyBinding; - for (var hashId in ckb) { - for (var key in ckb[hashId]) { - if (ckb[hashId][key] == command) - delete ckb[hashId][key]; + for (var keyId in ckb) { + var cmdGroup = ckb[keyId]; + if (cmdGroup == command) { + delete ckb[keyId]; + } else if (Array.isArray(cmdGroup)) { + var i = cmdGroup.indexOf(command); + if (i != -1) { + cmdGroup.splice(i, 1); + if (cmdGroup.length == 1) + ckb[keyId] = cmdGroup[0]; + } } } }; - this.bindKey = function(key, command) { - if(!key) + this.bindKey = function(key, command, asDefault) { + if (typeof key == "object") + key = key[this.platform]; + if (!key) return; - if (typeof command == "function") { - this.addCommand({exec: command, bindKey: key, name: command.name || key}); - return; - } - - var ckb = this.commandKeyBinding; + if (typeof command == "function") + return this.addCommand({exec: command, bindKey: key, name: command.name || key}); + key.split("|").forEach(function(keyPart) { - var binding = this.parseKeys(keyPart, command); - var hashId = binding.hashId; - (ckb[hashId] || (ckb[hashId] = {}))[binding.key] = command; + var chain = ""; + if (keyPart.indexOf(" ") != -1) { + var parts = keyPart.split(/\s+/); + keyPart = parts.pop(); + parts.forEach(function(keyPart) { + var binding = this.parseKeys(keyPart); + var id = KEY_MODS[binding.hashId] + binding.key; + chain += (chain ? " " : "") + id; + this._addCommandToBinding(chain, "chainKeys"); + }, this); + chain += " "; + } + var binding = this.parseKeys(keyPart); + var id = KEY_MODS[binding.hashId] + binding.key; + this._addCommandToBinding(chain + id, command, asDefault); }, this); }; + + this._addCommandToBinding = function(keyId, command, asDefault) { + var ckb = this.commandKeyBinding, i; + if (!command) { + delete ckb[keyId]; + } else if (!ckb[keyId] || this.$singleCommand) { + ckb[keyId] = command; + } else { + if (!Array.isArray(ckb[keyId])) { + ckb[keyId] = [ckb[keyId]]; + } else if ((i = ckb[keyId].indexOf(command)) != -1) { + ckb[keyId].splice(i, 1); + } + + if (asDefault || command.isDefault) + ckb[keyId].unshift(command); + else + ckb[keyId].push(command); + } + }; this.addCommands = function(commands) { commands && Object.keys(commands).forEach(function(name) { @@ -142,21 +169,12 @@ function HashHandler(config, platform) { }; this._buildKeyHash = function(command) { - var binding = command.bindKey; - if (!binding) - return; - - var key = typeof binding == "string" ? binding: binding[this.platform]; - this.bindKey(key, command); + this.bindKey(command.bindKey, command); }; // accepts keys in the form ctrl+Enter or ctrl-Enter // keys without modifiers or shift only this.parseKeys = function(keys) { - // todo support keychains - if (keys.indexOf(" ") != -1) - keys = keys.split(/\s+/).pop(); - var parts = keys.toLowerCase().split(/[\-\+]([\-\+])?/).filter(function(x){return x}); var key = parts.pop(); @@ -173,7 +191,7 @@ function HashHandler(config, platform) { var modifier = keyUtil.KEY_MODS[parts[i]]; if (modifier == null) { if (typeof console != "undefined") - console.error("invalid modifier " + parts[i] + " in " + keys); + console.error("invalid modifier " + parts[i] + " in " + keys); return false; } hashId |= modifier; @@ -182,17 +200,32 @@ function HashHandler(config, platform) { }; this.findKeyCommand = function findKeyCommand(hashId, keyString) { - var ckbr = this.commandKeyBinding; - return ckbr[hashId] && ckbr[hashId][keyString]; + var key = KEY_MODS[hashId] + keyString; + return this.commandKeyBinding[key]; }; this.handleKeyboard = function(data, hashId, keyString, keyCode) { - return { - command: this.findKeyCommand(hashId, keyString) - }; + var key = KEY_MODS[hashId] + keyString; + var command = this.commandKeyBinding[key]; + if (data.$keyChain) { + data.$keyChain += " " + key; + command = this.commandKeyBinding[data.$keyChain] || command; + } + + if (command) { + if (command == "chainKeys" || command[command.length - 1] == "chainKeys") { + data.$keyChain = data.$keyChain || key; + return {command: "null"}; + } + } + + if (data.$keyChain && keyCode > 0) + data.$keyChain = ""; + return {command: command}; }; -}).call(HashHandler.prototype) +}).call(HashHandler.prototype); exports.HashHandler = HashHandler; +exports.MultiHashHandler = MultiHashHandler; }); diff --git a/lib/ace/lib/keys.js b/lib/ace/lib/keys.js index e708a5d4..e3c19214 100644 --- a/lib/ace/lib/keys.js +++ b/lib/ace/lib/keys.js @@ -143,6 +143,9 @@ var Keys = (function() { } })(); + ret.KEY_MODS[0] = ""; + ret.KEY_MODS[-1] = "input"; + return ret; })(); oop.mixin(exports, Keys); From b2612c6039e74972cff414eb17f2346993609fed Mon Sep 17 00:00:00 2001 From: nightwing Date: Tue, 29 Jul 2014 17:40:43 +0400 Subject: [PATCH 02/25] cleanup emmet extension --- lib/ace/ext/emmet.js | 82 +++++++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 31 deletions(-) diff --git a/lib/ace/ext/emmet.js b/lib/ace/ext/emmet.js index e547ae4d..75eefa3f 100644 --- a/lib/ace/ext/emmet.js +++ b/lib/ace/ext/emmet.js @@ -34,15 +34,7 @@ var HashHandler = require("ace/keyboard/hash_handler").HashHandler; var Editor = require("ace/editor").Editor; var snippetManager = require("ace/snippets").snippetManager; var Range = require("ace/range").Range; -var emmet; - -Editor.prototype.indexToPosition = function(index) { - return this.session.doc.indexToPosition(index); -}; - -Editor.prototype.positionToIndex = function(pos) { - return this.session.doc.positionToIndex(pos); -}; +var emmet, emmetPath; /** * Implementation of {@link IEmmetEditor} interface for Ace @@ -72,9 +64,10 @@ AceEmmetEditor.prototype = { getSelectionRange: function() { // TODO should start be caret position instead? var range = this.ace.getSelectionRange(); + var doc = this.ace.session.doc; return { - start: this.ace.positionToIndex(range.start), - end: this.ace.positionToIndex(range.end) + start: doc.positionToIndex(range.start), + end: doc.positionToIndex(range.end) }; }, @@ -91,9 +84,10 @@ AceEmmetEditor.prototype = { * editor.createSelection(15); */ createSelection: function(start, end) { + var doc = this.ace.session.doc; this.ace.selection.setRange({ - start: this.ace.indexToPosition(start), - end: this.ace.indexToPosition(end) + start: doc.indexToPosition(start), + end: doc.indexToPosition(end) }); }, @@ -106,9 +100,10 @@ AceEmmetEditor.prototype = { * 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}); + var ace = this.ace; + var row = ace.getCursorPosition().row; + var lineLength = ace.session.getLine(row).length; + var index = ace.session.doc.positionToIndex({row: row, column: 0}); return { start: index, end: index + lineLength @@ -121,7 +116,7 @@ AceEmmetEditor.prototype = { */ getCaretPos: function(){ var pos = this.ace.getCursorPosition(); - return this.ace.positionToIndex(pos); + return this.ace.session.doc.positionToIndex(pos); }, /** @@ -129,7 +124,7 @@ AceEmmetEditor.prototype = { * @param {Number} index Caret position */ setCaretPos: function(index){ - var pos = this.ace.indexToPosition(index); + var pos = this.ace.session.doc.indexToPosition(index); this.ace.selection.moveToPosition(pos); }, @@ -169,14 +164,15 @@ AceEmmetEditor.prototype = { start = 0; var editor = this.ace; - var range = Range.fromPoints(editor.indexToPosition(start), editor.indexToPosition(end)); + var doc = editor.session.doc; + var range = Range.fromPoints(doc.indexToPosition(start), doc.indexToPosition(end)); editor.session.remove(range); range.end = range.start; //editor.selection.setRange(range); value = this.$updateTabstops(value); - snippetManager.insertSnippet(editor, value) + snippetManager.insertSnippet(editor, value); }, /** @@ -292,7 +288,7 @@ AceEmmetEditor.prototype = { lastZero = range.create(data.start, result); } - return result + return result; }, escape: function(ch) { if (ch == '$') return '\\$'; @@ -363,12 +359,17 @@ exports.runEmmetCommand = function(editor) { }, 0); } + var pos = editor.selection.lead; + var token = editor.session.getTokenAt(pos.row, pos.column); + if (token && /\btag\b/.test(token.type)) + return false; + try { var result = actions.run(this.action, editorProxy); } catch(e) { editor._signal("changeStatus", typeof e == "string" ? e : e.message); console.log(e); - result = false + result = false; } return result; }; @@ -383,21 +384,36 @@ for (var command in keymap) { }); } +exports.updateCommands = function(editor, enabled) { + if (enabled) { + editor.keyBinding.addKeyboardHandler(exports.commands); + } else { + editor.keyBinding.removeKeyboardHandler(exports.commands); + } +}; + +exports.isSupportedMode = function(modeId) { + return modeId && /css|less|scss|sass|stylus|html|php|twig/.test(modeId); +}; + var onChangeMode = function(e, target) { var editor = target; if (!editor) return; - var modeId = editor.session.$modeId; - var enabled = modeId && /css|less|scss|sass|stylus|html|php/.test(modeId); + var enabled = exports.isSupportedMode(editor.session.$modeId); if (e.enableEmmet === false) enabled = false; - if (enabled) - editor.keyBinding.addKeyboardHandler(exports.commands); - else - editor.keyBinding.removeKeyboardHandler(exports.commands); + if (enabled) { + if (typeof emmetPath == "string") { + require("ace/config").loadModule(emmetPath, function() { + + }); + emmetPath = null; + } + } + exports.updateCommands(editor, enabled); }; - exports.AceEmmetEditor = AceEmmetEditor; require("ace/config").defineOptions(Editor.prototype, "editor", { enableEmmet: { @@ -409,7 +425,11 @@ require("ace/config").defineOptions(Editor.prototype, "editor", { } }); - -exports.setCore = function(e) {emmet = e;}; +exports.setCore = function(e) { + if (typeof e == "string") + emmetPath = e; + else + emmet = e; +}; }); From a00ce9cd72d5b2f417fb8ed5d9282bfaa5a54182 Mon Sep 17 00:00:00 2001 From: nightwing Date: Fri, 1 Aug 2014 20:47:49 +0400 Subject: [PATCH 03/25] update keyboard shortcuts extension --- .../get_editor_keyboard_shortcuts.js | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/lib/ace/ext/menu_tools/get_editor_keyboard_shortcuts.js b/lib/ace/ext/menu_tools/get_editor_keyboard_shortcuts.js index e412bfba..99e006b0 100644 --- a/lib/ace/ext/menu_tools/get_editor_keyboard_shortcuts.js +++ b/lib/ace/ext/menu_tools/get_editor_keyboard_shortcuts.js @@ -69,29 +69,20 @@ module.exports.getEditorKeybordShortcuts = function(editor) { editor.keyBinding.$handlers.forEach(function(handler) { var ckb = handler.commandKeyBinding; for (var i in ckb) { - var modifier = parseInt(i); - if (modifier == -1) { - modifier = ""; - } else if(isNaN(modifier)) { - modifier = i; - } else { - modifier = "" + - (modifier & KEY_MODS.command ? "Cmd-" : "") + - (modifier & KEY_MODS.ctrl ? "Ctrl-" : "") + - (modifier & KEY_MODS.alt ? "Alt-" : "") + - (modifier & KEY_MODS.shift ? "Shift-" : ""); - } - for (var key in ckb[i]) { - var command = ckb[i][key] + var key = i.replace(/(^|-)\w/g, function(x) { return x.toUpperCase(); }); + var commands = ckb[i]; + if (!Array.isArray(commands)) + commands = [commands]; + commands.forEach(function(command) { if (typeof command != "string") command = command.name if (commandMap[command]) { - commandMap[command].key += "|" + modifier + key; + commandMap[command].key += "|" + key; } else { - commandMap[command] = {key: modifier+key, command: command}; + commandMap[command] = {key: key, command: command}; keybindings.push(commandMap[command]); - } - } + } + }); } }); return keybindings; From dfee7408020c6c86b41983d78b10b856720202df Mon Sep 17 00:00:00 2001 From: nightwing Date: Fri, 1 Aug 2014 22:33:32 +0400 Subject: [PATCH 04/25] update command_manager test --- lib/ace/commands/command_manager_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ace/commands/command_manager_test.js b/lib/ace/commands/command_manager_test.js index 76d973bb..902600be 100644 --- a/lib/ace/commands/command_manager_test.js +++ b/lib/ace/commands/command_manager_test.js @@ -188,7 +188,7 @@ module.exports = { assert.equal(command, "cm2"); var command = this.cm.findKeyCommand(0, "return"); - assert.equal(command, "cm3"); + assert.equal(command + "", ["cm4", "cm3"] + ""); } }; From 953e452f705a9e4af080d09825c7f5b49a0c9001 Mon Sep 17 00:00:00 2001 From: nightwing Date: Thu, 11 Sep 2014 05:00:49 +0400 Subject: [PATCH 05/25] add doc tooltip --- lib/ace/autocomplete.js | 89 ++++++++++++++++++++++++++++++++++- lib/ace/editor.js | 8 ++-- lib/ace/ext/language_tools.js | 12 ++++- lib/ace/keyboard/textinput.js | 8 ++-- 4 files changed, 106 insertions(+), 11 deletions(-) diff --git a/lib/ace/autocomplete.js b/lib/ace/autocomplete.js index 78015bd4..b4bac72b 100644 --- a/lib/ace/autocomplete.js +++ b/lib/ace/autocomplete.js @@ -36,6 +36,7 @@ var AcePopup = require("./autocomplete/popup").AcePopup; var util = require("./autocomplete/util"); var event = require("./lib/event"); var lang = require("./lib/lang"); +var dom = require("./lib/dom"); var snippetManager = require("./snippets").snippetManager; var Autocomplete = function() { @@ -52,6 +53,8 @@ var Autocomplete = function() { this.changeTimer = lang.delayedCall(function() { this.updateCompletions(true); }.bind(this)); + + this.tooltipTimer = lang.delayedCall(this.updateDocTooltip.bind(this), 50); }; (function() { @@ -64,6 +67,13 @@ var Autocomplete = function() { e.stop(); }.bind(this)); this.popup.focus = this.editor.focus.bind(this.editor); + this.popup.on("select", this.tooltipTimer.bind(null, null)); + this.popup.on("changeHoverMarker", this.tooltipTimer.bind(null, null)); + return this.popup; + }; + + this.getPopup = function() { + return this.popup || this.$init(); }; this.openPopup = function(editor, prefix, keepPopupPosition) { @@ -101,6 +111,7 @@ var Autocomplete = function() { this.editor.off("mousedown", this.mousedownListener); this.editor.off("mousewheel", this.mousewheelListener); this.changeTimer.cancel(); + this.hideDocTooltip(); if (this.popup && this.popup.isOpen) { this.gatherCompletionsId += 1; @@ -124,12 +135,17 @@ var Autocomplete = function() { this.detach(); }; - this.blurListener = function() { + this.blurListener = function(e) { // we have to check if activeElement is a child of popup because // on IE preventDefault doesn't stop scrollbar from being focussed var el = document.activeElement; - if (el != this.editor.textInput.getElement() && el.parentNode != this.popup.container) + var text = this.editor.textInput.getElement() + if (el != text && el.parentNode != this.popup.container + && el != this.tooltipNode && e.relatedTarget != this.tooltipNode + && e.relatedTarget != text + ) { this.detach(); + } }; this.mousedownListener = function(e) { @@ -178,6 +194,7 @@ var Autocomplete = function() { this.detach(); }; + this.commands = { "Up": function(editor) { editor.completer.goTo("up"); }, "Down": function(editor) { editor.completer.goTo("down"); }, @@ -310,6 +327,74 @@ var Autocomplete = function() { this.cancelContextMenu = function() { this.editor.$mouseHandler.cancelContextMenu(); }; + + this.updateDocTooltip = function() { + var popup = this.popup; + var all = popup.data; + var selected = all && (all[popup.getHoveredRow()] || all[popup.getRow()]); + var doc = null; + if (!selected || !this.editor || !this.popup.isOpen) + return this.hideDocTooltip(); + this.editor.completers.some(function(completer) { + if (completer.getDocTooltip) + doc = completer.getDocTooltip(selected); + return doc; + }); + if (!doc) + doc = selected; + + if (typeof doc == "string") + doc = {tooltipText: doc} + if (!doc || !(doc.docHTML || doc.docText)) + return this.hideDocTooltip(); + this.showDocTooltip(doc); + }; + + this.showDocTooltip = function(item) { + if (!this.tooltipNode) { + this.tooltipNode = dom.createElement("pre"); + this.tooltipNode.className = "ace_tooltip ace_doc-tooltip"; + this.tooltipNode.style.margin = 0; + this.tooltipNode.style.pointerEvents = "auto"; + this.tooltipNode.tabIndex = -1; + this.tooltipNode.onblur = this.blurListener.bind(this); + } + + var tooltipNode = this.tooltipNode; + if (item.docHTML) { + tooltipNode.innerHTML = item.docHTML; + } else if (item.docText) { + tooltipNode.textContent = item.docText; + } + + if (!tooltipNode.parentNode) + document.body.appendChild(tooltipNode); + var popup = this.popup; + var rect = popup.container.getBoundingClientRect(); + tooltipNode.style.top = popup.container.style.top; + tooltipNode.style.bottom = popup.container.style.bottom; + + if (window.innerWidth - rect.right < 320) { + tooltipNode.style.right = window.innerWidth - rect.left + "px"; + tooltipNode.style.left = ""; + } else { + tooltipNode.style.left = (rect.right + 1) + "px"; + tooltipNode.style.right = ""; + } + // tooltipNode.style.height = rect.height + "px"; + tooltipNode.style.display = "block"; + }; + + this.hideDocTooltip = function() { + this.tooltipTimer.cancel(); + if (!this.tooltipNode) return; + var el = this.tooltipNode; + if (!this.editor.isFocused() && document.activeElement == el) + this.editor.focus(); + this.tooltipNode = null; + if (el.parentNode) + el.parentNode.removeChild(el); + }; }).call(Autocomplete.prototype); diff --git a/lib/ace/editor.js b/lib/ace/editor.js index f2b9744c..95dee68d 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -662,13 +662,13 @@ var Editor = function(renderer, session) { * * **/ - this.onFocus = function() { + this.onFocus = function(e) { if (this.$isFocused) return; this.$isFocused = true; this.renderer.showCursor(); this.renderer.visualizeFocus(); - this._emit("focus"); + this._emit("focus", e); }; /** @@ -677,13 +677,13 @@ var Editor = function(renderer, session) { * * **/ - this.onBlur = function() { + this.onBlur = function(e) { if (!this.$isFocused) return; this.$isFocused = false; this.renderer.hideCursor(); this.renderer.visualizeBlur(); - this._emit("blur"); + this._emit("blur", e); }; this.$cursorChange = function() { diff --git a/lib/ace/ext/language_tools.js b/lib/ace/ext/language_tools.js index d8b2a1dd..3b4dfd7a 100644 --- a/lib/ace/ext/language_tools.js +++ b/lib/ace/ext/language_tools.js @@ -34,6 +34,7 @@ define(function(require, exports, module) { var snippetManager = require("../snippets").snippetManager; var Autocomplete = require("../autocomplete").Autocomplete; var config = require("../config"); +var lang = require("../lib/lang"); var util = require("../autocomplete/util"); var textCompleter = require("../autocomplete/text_completer"); @@ -59,11 +60,20 @@ var snippetCompleter = { completions.push({ caption: caption, snippet: s.content, - meta: s.tabTrigger && !s.name ? s.tabTrigger + "\u21E5 " : "snippet" + meta: s.tabTrigger && !s.name ? s.tabTrigger + "\u21E5 " : "snippet", + type: "snippet" }); } }, this); callback(null, completions); + }, + getDocTooltip: function(item) { + if (item.type == "snippet" && !item.docHTML) { + item.docHTML = [ + "", lang.escapeHTML(item.caption), "", "
", + lang.escapeHTML(item.snippet) + ].join(""); + } } }; diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index d2293024..1e6b057e 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -66,13 +66,13 @@ var TextInput = function(parentNode, host) { // ie9 throws error if document.activeElement is accessed too soon try { var isFocused = document.activeElement === text; } catch(e) {} - event.addListener(text, "blur", function() { - host.onBlur(); + event.addListener(text, "blur", function(e) { + host.onBlur(e); isFocused = false; }); - event.addListener(text, "focus", function() { + event.addListener(text, "focus", function(e) { isFocused = true; - host.onFocus(); + host.onFocus(e); resetSelection(); }); this.focus = function() { text.focus(); }; From 9b8385b07db5d429f46b03ef0272fa1013fef4ef Mon Sep 17 00:00:00 2001 From: takapa Date: Tue, 30 Sep 2014 19:55:06 +0100 Subject: [PATCH 06/25] Update language_tools.js Added removeCompleters() function to language tools exports as it is not possible to access the completers array directly. This is currently utilized by Ace GWT to cleardown existing completers (for entirely programmatic completers without snippets or keyword completions) but I feel it belongs in the mainline (I have to manage a fork at the moment and there seems to be no other workaround). --- lib/ace/ext/language_tools.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/ace/ext/language_tools.js b/lib/ace/ext/language_tools.js index d8b2a1dd..cad80605 100644 --- a/lib/ace/ext/language_tools.js +++ b/lib/ace/ext/language_tools.js @@ -67,6 +67,10 @@ var snippetCompleter = { } }; +exports.removeCompleters = function() { + completers = []; +}; + var completers = [snippetCompleter, textCompleter, keyWordCompleter]; exports.addCompleter = function(completer) { completers.push(completer); From 6e9ce4dbc8c3b67d4bdb989795adfdc8356d952c Mon Sep 17 00:00:00 2001 From: nightwing Date: Sun, 5 Oct 2014 00:06:08 +0400 Subject: [PATCH 07/25] fix #2173 AutoScrollEditorIntoView doesn't work for search --- lib/ace/editor.js | 4 +++- lib/ace/ext/searchbox.js | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/ace/editor.js b/lib/ace/editor.js index f2b9744c..7183eb9e 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -2553,7 +2553,9 @@ var Editor = function(renderer, session) { rect = self.renderer.container.getBoundingClientRect(); }); var onAfterRender = this.renderer.on("afterRender", function() { - if (shouldScroll && rect && self.isFocused()) { + if (shouldScroll && rect && (self.isFocused() + || self.searchBox && self.searchBox.isFocused()) + ) { var renderer = self.renderer; var pos = renderer.$cursorLayer.$pixelPos; var config = renderer.layerConfig; diff --git a/lib/ace/ext/searchbox.js b/lib/ace/ext/searchbox.js index 0369f2d0..b316aa75 100644 --- a/lib/ace/ext/searchbox.js +++ b/lib/ace/ext/searchbox.js @@ -281,6 +281,10 @@ var SearchBox = function(editor, range, showReplaceForm) { this.editor.keyBinding.addKeyboardHandler(this.$closeSearchBarKb); }; + this.isFocused = function() { + var el = document.activeElement; + return el == this.searchInput || el == this.replaceInput; + } }).call(SearchBox.prototype); exports.SearchBox = SearchBox; From 6867d1f4c65a33cb37637d43e4d3fddca003aa3c Mon Sep 17 00:00:00 2001 From: nightwing Date: Sun, 5 Oct 2014 21:56:43 +0400 Subject: [PATCH 08/25] fix several highlighter issues --- lib/ace/mode/_test/highlight_rules_test.js | 75 ++++++++++++++++++---- lib/ace/mode/actionscript.js | 4 -- lib/ace/mode/autohotkey.js | 7 +- lib/ace/mode/c_cpp_highlight_rules.js | 24 +++++-- lib/ace/mode/coffee_highlight_rules.js | 2 +- lib/ace/mode/d.js | 2 +- lib/ace/mode/forth.js | 6 +- lib/ace/mode/gitignore.js | 1 + lib/ace/mode/makefile.js | 5 -- lib/ace/mode/rust.js | 4 -- lib/ace/mode/stylus.js | 5 +- lib/ace/mode/textile.js | 1 + 12 files changed, 89 insertions(+), 47 deletions(-) diff --git a/lib/ace/mode/_test/highlight_rules_test.js b/lib/ace/mode/_test/highlight_rules_test.js index 57273292..647ca3c3 100644 --- a/lib/ace/mode/_test/highlight_rules_test.js +++ b/lib/ace/mode/_test/highlight_rules_test.js @@ -1,20 +1,66 @@ var fs = require("fs"); +var path = require("path"); if (!fs.existsSync) - fs.existsSync = require("path").existsSync; + fs.existsSync = path.existsSync; require("amd-loader"); var cwd = __dirname + "/"; +var root = path.normalize(cwd + Array(5).join("../")); + +function jsFileList(path, filter) { + if (!filter) filter = /_test/; + return fs.readdirSync(path).map(function(x) { + if (x.slice(-3) == ".js" && !filter.test(x) && !/\s/.test(x)) + return x.slice(0, -3); + }).filter(Boolean); +} + +function modeList() { + return jsFileList(cwd + "../", /_highlight_rules|_test|_worker|xml_util|_outdent|behaviour|completions/); +} + +function checkModes() { + modeList().forEach(function(modeName) { + try { + var Mode = require("../" + modeName).Mode; + } catch(e) { + console.warn("Can't load mode :" + modeName, e); + return; + } + var m = new Mode(); + if (!m.lineCommentStart && !m.blockComment) + console.warn("missing comment in " + modeName); + if (!m.$id) + console.warn("missing id in " + modeName); + var tokenizer = (new Mode).getTokenizer(); + if (m.lineCommentStart) { + if (Array.isArray(m.lineCommentStart)) { + m.lineCommentStart.forEach(function(x) { + testLineComment(tokenizer, x, modeName) + }); + } else { + testLineComment(tokenizer, m.lineCommentStart, modeName) + } + } + // if (m.blockComment) { + // var tokens = tok.getLineTokens(m.lineCommentStart, "start"); + // if (!/comment/.test(tokens[0])) + // console.warn("broken lineCommentStart in " + modeName); + // } + }); + + function testLineComment(tokenizer, commentStart, modeName) { + var tokens = tokenizer.getLineTokens(commentStart + " ", "start").tokens; + if (!/comment/.test(tokens[0].type)) + console.warn("broken lineCommentStart in " + modeName); + } +} function generateTestData() { - var root = Array(5).join("../") + "/demo/kitchen-sink/docs"; - var docs = fs.readdirSync(cwd + root); + var docs = jsFileList(cwd + root); var specialDocs = fs.readdirSync(cwd); - var modes = fs.readdirSync(cwd + "../").filter(function(x){ - return !/(_highlight_rules|behaviour|worker)\.js$/.test(x) && /\.js$/.test(x); - }).map(function(x) { - return x.replace(/\.js$/, ""); - }); + var modes = modeList(); console.log("Docs:", docs); console.log("Modes:", modes); @@ -61,7 +107,7 @@ function generateTestData() { return tmp.join(",\n "); }); - jsonStr = "[[\n " + data.join("\n],[\n ") + "\n]]"; + var jsonStr = "[[\n " + data.join("\n],[\n ") + "\n]]"; fs.writeFileSync(cwd + "tokens_" + modeName + ".json", jsonStr, "utf8"); }); } @@ -91,7 +137,7 @@ function testMode(modeName, i) { lineData.state = lineData.shift(); var line = null; if (typeof lineData[lineData.length - 1] == "string") - line = lineData.pop() + line = lineData.pop(); lineData.forEach(function(x) { lineData.types.push(x[0]); lineData.values.push(x[1]); @@ -103,14 +149,13 @@ function testMode(modeName, i) { var values = tokens.tokens.map(function(x) {return x.value;}); var types = tokens.tokens.map(function(x) {return x.type;}); - var success = true; var err = testEqual([ JSON.stringify(lineData.state), JSON.stringify(tokens.state), lineData.types, types, lineData.values, values]); if (err) { - console.log(line) + console.log(line); throw "error"; } @@ -150,10 +195,12 @@ function padNumber(num, digits) { // cli var arg = process.argv[2]; if (!arg) - test() + test(); else if (/--?g(en)?/.test(arg)) generateTestData(process.argv.splice(3)); +else if (/--?c(heck)?/.test(arg)) + checkModes(process.argv.splice(3)); else if (/\d+/.test(arg)) test(parseInt(process.argv[2],10) || 0); else - testMode(arg, -1) \ No newline at end of file + testMode(arg, -1); \ No newline at end of file diff --git a/lib/ace/mode/actionscript.js b/lib/ace/mode/actionscript.js index 93297e42..7daf2941 100644 --- a/lib/ace/mode/actionscript.js +++ b/lib/ace/mode/actionscript.js @@ -27,10 +27,6 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * - * Contributor(s): - * - * - * * ***** END LICENSE BLOCK ***** */ /* diff --git a/lib/ace/mode/autohotkey.js b/lib/ace/mode/autohotkey.js index 21d3c802..d7093fd5 100644 --- a/lib/ace/mode/autohotkey.js +++ b/lib/ace/mode/autohotkey.js @@ -26,11 +26,6 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * - * Contributor(s): - * - * - * * ***** END LICENSE BLOCK ***** */ /* @@ -53,7 +48,7 @@ var Mode = function() { oop.inherits(Mode, TextMode); (function() { - this.lineCommentStart = "/\\*"; + this.lineCommentStart = ";"; this.blockComment = {start: "/*", end: "*/"}; this.$id = "ace/mode/autohotkey"; }).call(Mode.prototype); diff --git a/lib/ace/mode/c_cpp_highlight_rules.js b/lib/ace/mode/c_cpp_highlight_rules.js index 008da9d5..8aad5046 100644 --- a/lib/ace/mode/c_cpp_highlight_rules.js +++ b/lib/ace/mode/c_cpp_highlight_rules.js @@ -50,11 +50,12 @@ var c_cppHighlightRules = function() { // regexp must not have capturing parentheses. Use (?:) instead. // regexps are ordered -> the first match is used - this.$rules = { + this.$rules = { "start" : [ { token : "comment", - regex : "\\/\\/.*$" + regex : "//", + next : "singleLineComment" }, DocCommentHighlightRules.getStartRule("doc-start"), { @@ -121,14 +122,26 @@ var c_cppHighlightRules = function() { regex : ".+" } ], + "singleLineComment" : [ + { + token : "comment", + regex : /\\$/, + next : "singleLineComment" + }, { + token : "comment", + regex : /$/, + next : "start" + }, { + defaultToken: "comment" + } + ], "qqstring" : [ { token : "string", regex : '(?:(?:\\\\.)|(?:[^"\\\\]))*?"', next : "start" }, { - token : "string", - regex : '.+' + defaultToken : "string" } ], "qstring" : [ @@ -137,8 +150,7 @@ var c_cppHighlightRules = function() { regex : "(?:(?:\\\\.)|(?:[^'\\\\]))*?'", next : "start" }, { - token : "string", - regex : '.+' + defaultToken : "string" } ], "directive" : [ diff --git a/lib/ace/mode/coffee_highlight_rules.js b/lib/ace/mode/coffee_highlight_rules.js index a6d33abb..94c9476f 100644 --- a/lib/ace/mode/coffee_highlight_rules.js +++ b/lib/ace/mode/coffee_highlight_rules.js @@ -142,7 +142,7 @@ define(function(require, exports, module) { } if (val == "}" && stack.length) { stack.shift(); - this.next = stack.shift(); + this.next = stack.shift() || ""; if (this.next.indexOf("string") != -1) return "paren.string"; } diff --git a/lib/ace/mode/d.js b/lib/ace/mode/d.js index 7c3a53c1..03c723e0 100644 --- a/lib/ace/mode/d.js +++ b/lib/ace/mode/d.js @@ -47,7 +47,7 @@ var Mode = function() { oop.inherits(Mode, TextMode); (function() { - this.lineCommentStart = "/\\+"; + this.lineCommentStart = "//"; this.blockComment = {start: "/*", end: "*/"}; this.$id = "ace/mode/d"; }).call(Mode.prototype); diff --git a/lib/ace/mode/forth.js b/lib/ace/mode/forth.js index 31e8d512..ce590576 100644 --- a/lib/ace/mode/forth.js +++ b/lib/ace/mode/forth.js @@ -27,10 +27,6 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * - * Contributor(s): - * - * - * * ***** END LICENSE BLOCK ***** */ /* @@ -53,7 +49,7 @@ var Mode = function() { oop.inherits(Mode, TextMode); (function() { - this.lineCommentStart = "(?<=^|\\s)\\.?\\( [^)]*\\)"; + this.lineCommentStart = "--"; this.blockComment = {start: "/*", end: "*/"}; this.$id = "ace/mode/forth"; }).call(Mode.prototype); diff --git a/lib/ace/mode/gitignore.js b/lib/ace/mode/gitignore.js index fd9b04f4..fa263985 100644 --- a/lib/ace/mode/gitignore.js +++ b/lib/ace/mode/gitignore.js @@ -12,6 +12,7 @@ var Mode = function() { oop.inherits(Mode, TextMode); (function() { + this.lineCommentStart = "#"; this.$id = "ace/mode/gitignore"; }).call(Mode.prototype); diff --git a/lib/ace/mode/makefile.js b/lib/ace/mode/makefile.js index 673f8927..efeca6ee 100644 --- a/lib/ace/mode/makefile.js +++ b/lib/ace/mode/makefile.js @@ -26,11 +26,6 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * - * Contributor(s): - * - * - * * ***** END LICENSE BLOCK ***** */ /* diff --git a/lib/ace/mode/rust.js b/lib/ace/mode/rust.js index 28485e97..a8840307 100644 --- a/lib/ace/mode/rust.js +++ b/lib/ace/mode/rust.js @@ -27,10 +27,6 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * - * Contributor(s): - * - * - * * ***** END LICENSE BLOCK ***** */ /* diff --git a/lib/ace/mode/stylus.js b/lib/ace/mode/stylus.js index ebeb1fca..2c9c3cf8 100644 --- a/lib/ace/mode/stylus.js +++ b/lib/ace/mode/stylus.js @@ -51,7 +51,10 @@ var Mode = function() { }; oop.inherits(Mode, TextMode); -(function() { +(function() { + this.lineCommentStart = "//"; + this.blockComment = {start: "/*", end: "*/"}; + this.$id = "ace/mode/stylus"; }).call(Mode.prototype); diff --git a/lib/ace/mode/textile.js b/lib/ace/mode/textile.js index d5525969..9a1ea809 100644 --- a/lib/ace/mode/textile.js +++ b/lib/ace/mode/textile.js @@ -43,6 +43,7 @@ var Mode = function() { oop.inherits(Mode, TextMode); (function() { + this.type = "text"; this.getNextLineIndent = function(state, line, tab) { if (state == "intag") return tab; From 14fc8adac2faeb342a1c45e0abb0a14918329994 Mon Sep 17 00:00:00 2001 From: nightwing Date: Sun, 5 Oct 2014 21:57:50 +0400 Subject: [PATCH 09/25] cleanup mode_creator tool --- tool/mode_creator.js | 56 ++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/tool/mode_creator.js b/tool/mode_creator.js index 3eda8c8c..489edaaa 100644 --- a/tool/mode_creator.js +++ b/tool/mode_creator.js @@ -2,6 +2,11 @@ define(function(require, exports, module) { /** creates globals intentionally to make things easily accessible from console **/ +require("ace/ext/language_tools"); +require("ace/config").setDefaultValues("editor", { + enableBasicAutocompletion: true, + enableSnippets: true +}); var net = require("ace/lib/net"); var Range = require("ace/range").Range; var util = require("demo/kitchen-sink/util"); @@ -79,28 +84,28 @@ document.getElementById("syncToMode").onclick = function() { docEl.value = modelist.modesByName[modeEl.value].desc; docEl.onchange(); run(); -} +}; document.getElementById("perfTest").onclick = function() { - var lines = editor2.session.doc.getAllLines() + var lines = editor2.session.doc.getAllLines(); if (!lines.length) - return + return; while (lines.length < 1000) { - lines = lines.concat(lines) + lines = lines.concat(lines); } var tk = new Tokenizer(currentRules); var testPerf = function(lines, tk){ - var state = "start" + var state = "start"; for (var i=0, l = lines.length; i Date: Sun, 5 Oct 2014 21:59:39 +0400 Subject: [PATCH 10/25] fix gutter colors in theme importer --- lib/ace/theme/cobalt.css | 2 +- lib/ace/theme/idle_fingers.css | 2 +- lib/ace/theme/tomorrow_night_bright.css | 13 +- tool/templates/theme.css | 4 +- tool/tmtheme.js | 31 +- tool/tmthemes/Katzenmilch.tmTheme | 399 +++++++++++ tool/tmthemes/Kuroir Theme.tmTheme | 916 ++++++++++++++++++++++++ 7 files changed, 1351 insertions(+), 16 deletions(-) create mode 100644 tool/tmthemes/Katzenmilch.tmTheme create mode 100644 tool/tmthemes/Kuroir Theme.tmTheme diff --git a/lib/ace/theme/cobalt.css b/lib/ace/theme/cobalt.css index 22df0460..16568fc3 100644 --- a/lib/ace/theme/cobalt.css +++ b/lib/ace/theme/cobalt.css @@ -1,6 +1,6 @@ .ace-cobalt .ace_gutter { background: #011e3a; - color: #fff + color: rgb(128,145,160) } .ace-cobalt .ace_print-margin { diff --git a/lib/ace/theme/idle_fingers.css b/lib/ace/theme/idle_fingers.css index 91318b30..f507ec10 100644 --- a/lib/ace/theme/idle_fingers.css +++ b/lib/ace/theme/idle_fingers.css @@ -1,6 +1,6 @@ .ace-idle-fingers .ace_gutter { background: #3b3b3b; - color: #fff + color: rgb(153,153,153) } .ace-idle-fingers .ace_print-margin { diff --git a/lib/ace/theme/tomorrow_night_bright.css b/lib/ace/theme/tomorrow_night_bright.css index 183e0eaf..5c896fb9 100644 --- a/lib/ace/theme/tomorrow_night_bright.css +++ b/lib/ace/theme/tomorrow_night_bright.css @@ -34,12 +34,13 @@ margin: -1px 0 0 -1px; border: 1px solid #888888 } + .ace-tomorrow-night-bright .ace_marker-layer .ace_highlight { - border: 1px solid rgb(110, 119, 0); - border-bottom: 0; - box-shadow: inset 0 -1px rgb(110, 119, 0); - margin: -1px 0 0 -1px; - background: rgba(255, 235, 0, 0.1); + border: 1px solid rgb(110, 119, 0); + border-bottom: 0; + box-shadow: inset 0 -1px rgb(110, 119, 0); + margin: -1px 0 0 -1px; + background: rgba(255, 235, 0, 0.1) } .ace-tomorrow-night-bright .ace_marker-layer .ace_active-line { @@ -132,7 +133,7 @@ } .ace-tomorrow-night-bright .ace_c9searchresults.ace_keyword { - color: #C2C280; + color: #C2C280 } .ace-tomorrow-night-bright .ace_indent-guide { diff --git a/tool/templates/theme.css b/tool/templates/theme.css index 39bb2437..8515d0c3 100644 --- a/tool/templates/theme.css +++ b/tool/templates/theme.css @@ -1,8 +1,8 @@ /* THIS THEME WAS AUTOGENERATED BY Theme.tmpl.css (UUID: %uuid%) */ .%cssClass% .ace_gutter { - background: #e8e8e8; - color: #333; + background: %gutterBg%; + color: %gutterFg%; } .%cssClass% .ace_print-margin { diff --git a/tool/tmtheme.js b/tool/tmtheme.js index e9e34f39..02d9542e 100755 --- a/tool/tmtheme.js +++ b/tool/tmtheme.js @@ -115,7 +115,7 @@ function extractStyles(theme) { var aceScope = supportedScopes[scope]; if (aceScope) { - colors[aceScope] = style; + colors[aceScope] = style; } else if (style) { unsupportedScopes[scope] = (unsupportedScopes[scope] || 0) + 1; @@ -134,25 +134,44 @@ function extractStyles(theme) { colors.fold = foldSource.match(/\:([^;]+)/)[1]; } } + + colors.gutterBg = colors.background + colors.gutterFg = mix(colors.foreground, colors.background, 0.5) if (!colors.selected_word_highlight) colors.selected_word_highlight = "border: 1px solid " + colors.selection + ";"; colors.isDark = (luma(colors.background) < 0.5) + ""; - + return colors; }; -function luma(color) { +function mix(c1, c2, a1, a2) { + c1 = rgbColor(c1); + c2 = rgbColor(c2); + if (a2 === undefined) + a2 = 1 - a1 + return "rgb(" + [ + Math.round(a1*c1[0] + a2*c2[0]), + Math.round(a1*c1[1] + a2*c2[1]), + Math.round(a1*c1[2] + a2*c2[2]) + ].join(",") + ")"; +} + +function rgbColor(color) { + if (typeof color == "object") + return color; if (color[0]=="#") - var rgb = color.match(/^#(..)(..)(..)/).slice(1).map(function(c) { + return color.match(/^#(..)(..)(..)/).slice(1).map(function(c) { return parseInt(c, 16); }); else - var rgb = color.match(/\(([^,]+),([^,]+),([^,]+)/).slice(1).map(function(c) { + return color.match(/\(([^,]+),([^,]+),([^,]+)/).slice(1).map(function(c) { return parseInt(c, 10); }); - +} +function luma(color) { + var rgb = rgbColor(color); return (0.21 * rgb[0] + 0.72 * rgb[1] + 0.07 * rgb[2]) / 255; } diff --git a/tool/tmthemes/Katzenmilch.tmTheme b/tool/tmthemes/Katzenmilch.tmTheme new file mode 100644 index 00000000..3878ffe9 --- /dev/null +++ b/tool/tmthemes/Katzenmilch.tmTheme @@ -0,0 +1,399 @@ + + + + + name + Katzen-Milch + comment + Those silly germans and their cat milk! Ghee wizz! + settings + + + settings + + background + #f3f2f3 + caret + #100011 + foreground + #0f0009ff + invisibles + #000000 + lineHighlight + #ffffff + selection + #6405D044 + selectionBorder + #8425f0 + bracketContentsOptions + underline + tagsForeground + #0f0009ff + tagsOptions + underline + + + + name + Parenthesis + scope + punctuation.definition.list + settings + + fontStyle + + foreground + #940494 + background + #4444940a + + + + name + Comment + scope + comment + settings + + fontStyle + italic + foreground + #404f50aa + background + #5f0fff02 + + + + name + String + scope + string + settings + + foreground + #5a5f9b + background + #aaafdb09 + + + + name + Number + scope + constant.numeric + settings + + foreground + #4f827bee + background + #77c2bb0f + + + + name + User-defined Constant + scope + constant.character, constant.other + settings + + foreground + #025f69ff + background + #7f229910 + + + + name + Built-in Constant + scope + constant.language + settings + + fontStyle + + foreground + #7D7e52 + background + #bDbe820f + + + + name + Storage Modifier + scope + storage.modifier + settings + + fontStyle + bold + foreground + #7B5D8f + background + #9B9FfD0a + + + + name + Storage + scope + storage + settings + + fontStyle + bold + foreground + #7B5cbfff + background + #8B5Ddf0d + + + + name + Function Name + scope + entity.name.function + settings + + fontStyle + + foreground + #025f49f7 + background + #22ff491f + + + + name + Support Function + scope + support.function + settings + + foreground + #9D7e62 + background + #bDbe820a + + + + name + Misc Function + scope + entity.name.function.misc + settings + + foreground + #939469 + background + #E3E4A90a + + + + name + Predicate Function + scope + entity.name.function.predicate + settings + + foreground + #856F63 + background + #A5DF930a + + + + name + Input/Output Function + scope + entity.name.function.io + settings + + foreground + #aF938C + background + #DFB3AC0a + + + + name + External Symbol + scope + variable.other.external-symbol + settings + + foreground + #7BaFaD + background + #BBDFDD0a + + + + name + Variable + scope + variable.language, variable.other + settings + + foreground + #316fcf + background + #3aafff0a + + + + name + Parameter Variable + scope + variable.parameter + settings + + fontStyle + italic + foreground + #33969fdd + background + #05d6f90b + + + + name + Keyword + scope + keyword + settings + + foreground + #674Aa8 + background + #A3AAD80e + + + + name + Class Name + scope + entity.name.class + settings + + fontStyle + bold + foreground + #B9986F + background + #B998DF22 + + + + name + Structure Name + scope + entity.name.structure + settings + + foreground + #22af9d + background + #B998DF0a + + + + name + Type Name + scope + entity.name.type + settings + + foreground + #af47a9 + background + #af77a90d + + + + name + Class name + scope + entity.name.class, entity.name.type.class + settings + + foreground + #cc4357 + background + #ffddff92 + + + + name + Support Class + scope + support.class + settings + + foreground + #ef6aa7ff + background + #ef6aa710 + + + + name + Invalid + scope + invalid + settings + + background + #CC1B27 + foreground + #DFDFD5 + + + + + name + ♦ String embedded-source + scope + string source + settings + + fontStyle + italic + foreground + #13499fdd + background + #0099ff0a + + + + name + Tag name + scope + entity.name.tag + settings + + foreground + #3976a2 + background + #49a6d20a + + + + name + Tag attribute + scope + entity.other.attribute-name + settings + + fontStyle + + foreground + #4946c2ee + background + #4986c209 + + + + + diff --git a/tool/tmthemes/Kuroir Theme.tmTheme b/tool/tmthemes/Kuroir Theme.tmTheme new file mode 100644 index 00000000..36acd588 --- /dev/null +++ b/tool/tmthemes/Kuroir Theme.tmTheme @@ -0,0 +1,916 @@ + + + + + author + Stanley Rost + comment + Kuroir + name + Kuroir Theme + settings + + + settings + + background + #E8E9E8 + caret + #202020 + foreground + #363636 + invisibles + #0000004A + lineHighlight + #CBDC2F38 + selection + #F5AA0091 + + bracketsForeground + #C41717 + bracketsOptions + foreground underline + + bracketContentsForeground + #C41717 + bracketContentsOptions + foreground underline background + + guide + #8F8F8F + activeGuide + #FA2828 + + tagsOptions + stippled_underline + + + + name + Comment + scope + comment + settings + + background + #DCDCDC8F + fontStyle + + foreground + #949494E8 + + + + name + Regions + scope + comment.line.region + settings + + background + #E9D6DC85 + fontStyle + + foreground + #A54776 + + + + name + Line Marker + scope + comment.line.marker.php + settings + + background + #E9E4BE + foreground + #668D68 + + + + name + Todo + scope + comment.line.todo.php + settings + + background + #D9EAB8 + fontStyle + + foreground + #456E48 + + + + name + FIXME + scope + comment.line.fixme.php + settings + + background + #E1D0CA + fontStyle + + foreground + #880006 + + + + name + Constant + scope + constant + settings + + foreground + #CD6839 + + + + name + Entity + scope + entity + settings + + background + #E8E9E8 + fontStyle + + foreground + #8B4726 + + + + name + Storage + scope + storage + settings + + fontStyle + + foreground + #A52A2A + + + + name + Keyword Control + scope + keyword.control + settings + + foreground + #CD3700 + + + + name + Library Function + scope + support.function - variable, keyword.other.special-method.ruby + settings + + foreground + #B03060 + + + + name + Comparison + scope + keyword.operator.comparison,keyword.operator.logical + settings + + foreground + #B83126 + + + + name + String + scope + string + settings + + fontStyle + + foreground + #639300 + + + + name + R Interpolation + scope + string.quoted.double.ruby source.ruby.embedded.source + settings + + foreground + #007E69 + + + + name + Support + scope + support + settings + + fontStyle + + foreground + #104E8B + + + + name + Variable + scope + variable + settings + + foreground + #009ACD + + + + name + Invalid Deprecated + scope + invalid.deprecated + settings + + background + #E8E9E8 + fontStyle + italic underline + foreground + #FD1732 + + + + name + Invalid Illegal + scope + invalid.illegal + settings + + background + #FF060026 + foreground + #FD1224 + + + + name + Embedded Source (Bright) + scope + text source + settings + + background + #77ADE900 + foreground + #7B211A + + + + name + Entity inherited-class + scope + entity.other.inherited-class + settings + + fontStyle + italic + foreground + #005273 + + + + name + String.regexp + scope + string.regexp + settings + + background + #C9D4BE + foreground + #417E00 + + + + name + Support.function + scope + support.function + settings + + fontStyle + + foreground + #005273 + + + + name + Support.constant + scope + support.constant + settings + + fontStyle + + foreground + #CF6A4C + + + + name + j Entity Name Type + scope + entity.name.type + settings + + fontStyle + underline + + + + name + j Cast + scope + meta.cast + settings + + fontStyle + italic + foreground + #676767 + + + + name + Doctype/XML Processing + scope + meta.sgml.html meta.doctype, meta.sgml.html meta.doctype entity, meta.sgml.html meta.doctype string, meta.xml-processing, meta.xml-processing entity, meta.xml-processing string + settings + + foreground + #494949 + + + + name + Meta.tag.all + scope + meta.tag, meta.tag entity + settings + + foreground + #005273 + + + + name + Meta.tag.inline + scope + source entity.name.tag, source entity.other.attribute-name, meta.tag.inline, meta.tag.inline entity + settings + + foreground + #005273 + + + + name + Namespaces + scope + entity.name.tag.namespace, entity.other.attribute-name.namespace + settings + + foreground + #B85423 + + + + name + css tag-name + scope + entity.name.tag.css + settings + + foreground + #B83126 + + + + name + css:pseudo-class + scope + meta.selector.css entity.other.attribute-name.tag.pseudo-class + settings + + foreground + #B12E25 + + + + name + css#id + scope + meta.selector.css entity.other.attribute-name.id,entity.other.attribute-name.id.css + settings + + foreground + #B8002D + + + + name + css.class + scope + meta.selector.css entity.other.attribute-name.class, entity.other.attribute-name.class.css + settings + + foreground + #B8012D + + + + name + css property-name: + scope + support.type.property-name.css, meta.property-name + settings + + foreground + #005273 + + + + name + css @at-rule + scope + meta.preprocessor.at-rule keyword.control.at-rule + settings + + foreground + #8693A5 + + + + name + css property-value; + scope + meta.property-value + settings + + foreground + #417E00 + + + + name + css property-value color + scope + constant.other.color + settings + + foreground + #B8860B + + + + name + css !important / !default + scope + keyword.other.important,keyword.other.default + settings + + foreground + #EE3A8C + + + + name + css additional-constants + scope + meta.property-value support.constant.named-color.css, meta.property-value constant + settings + + foreground + #417E00 + + + + + + name + css constructor.argument + scope + meta.constructor.argument.css + settings + + foreground + #417E00 + + + + name + css constant.numeric + scope + constant.numeric + settings + + foreground + #9A5925 + + + + name + css keyword.unit + scope + keyword.other + settings + + foreground + #9F5E3D + + + + name + css keyword.unit + scope + source.scss support.function.misc + settings + + foreground + #1B76B0 + + + + name + diff.header + scope + meta.diff, meta.diff.header + settings + + background + #82000E + fontStyle + italic + foreground + #F8BEBE + + + + name + diff.deleted + scope + markup.deleted + settings + + background + #420E09 + foreground + #F8F8F8 + + + + name + diff.changed + scope + markup.changed + settings + + background + #4A410D + foreground + #F8F8F8 + + + + name + diff.inserted + scope + markup.inserted + settings + + background + #253B22 + foreground + #F8F8F8 + + + + name + Markup: Italic + scope + markup.italic + settings + + fontStyle + italic + foreground + #CD2626 + + + + name + Markup: Bold + scope + markup.bold + settings + + fontStyle + bold + foreground + #8B1A1A + + + + name + Markup: Underline + scope + markup.underline + settings + + fontStyle + underline + foreground + #E18964 + + + + name + Markup: Quote + scope + markup.quote + settings + + background + #FEE09C12 + fontStyle + italic + foreground + #8B7765 + + + + name + Markup: Heading + scope + markup.heading, markup.heading entity + settings + + background + #BF61330D + fontStyle + + foreground + #B8012D + + + + name + Markup: List + scope + markup.list + settings + + foreground + #8F5B26 + + + + name + Markup: Raw + scope + markup.raw + settings + + background + #B1B3BA08 + fontStyle + + foreground + #578BB3 + + + + name + Markup: Comment + scope + markup comment + settings + + fontStyle + italic + foreground + #F67B37 + + + + name + Markup: Separator + scope + meta.separator + settings + + background + #242424 + foreground + #60A633 + + + + name + Markup: Other + scope + markup.other + settings + + background + #B1B3BA08 + fontStyle + + foreground + #578BB3 + + + + name + Log Entry + scope + meta.line.entry.logfile, meta.line.exit.logfile + settings + + background + #EEEEEE29 + + + + name + Log Entry Error + scope + meta.line.error.logfile + settings + + background + #751012 + + + + name + SCSS Punctuation End Comments + scope + punctuation.definition.end + settings + + background + #DCDCDC8F + fontStyle + + + + + name + -------------------------------- + settings + + + + name + HTML Attribute name + scope + entity.other.attribute-name.html + settings + + foreground + #629F9E + + + + name + JS String + scope + string.quoted.double.js, string.quoted.single.js + settings + + foreground + #79A316 + + + + name + JS Function + scope + entity.name.function.js + settings + + foreground + #488C45 + fontStyle + italic + + + + name + JS Embedded code general + scope + source.js.embedded.html + settings + + foreground + #666 + + + + name + JS Storage Type + scope + storage.type.js + settings + + foreground + #BB3182 + + + + name + JS Support Class + scope + support.class.js + settings + + foreground + #338FD5 + + + + name + JS Control Keyword + scope + keyword.control.js, keyword.operator.js + settings + + foreground + #A99904 + fontStyle + italic + + + + + name + Matching Pairs + scope + entity.name.class + settings + + background + #D7D7A7 + foreground + #616838 + fontStyle + + + + + name + Active Guide + scope + active_guide + settings + + background + #968F96 + fontStyle + + + + + name + Highlight Matching Word + scope + highlight_matching_word + settings + + background + #CBDC2F38 + fontStyle + + + + + uuid + 467560D0-6ACE-4409-82FD-4791420837AC + + From 6c0bc56500777a111ea85b885cafa124f68d38ae Mon Sep 17 00:00:00 2001 From: nightwing Date: Sun, 5 Oct 2014 22:01:01 +0400 Subject: [PATCH 11/25] improve error handling in worker --- lib/ace/worker/worker.js | 19 +++++++++++++++---- lib/ace/worker/worker_client.js | 18 ++++++++++++------ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/lib/ace/worker/worker.js b/lib/ace/worker/worker.js index e642a6ae..928000dd 100644 --- a/lib/ace/worker/worker.js +++ b/lib/ace/worker/worker.js @@ -17,7 +17,13 @@ window.window = window; window.ace = window; window.onerror = function(message, file, line, col, err) { - console.error("Worker " + (err ? err.stack : message)); + postMessage({type: "error", data: { + message: message, + file: file, + line: line, + col: col, + stack: err.stack + }}); }; window.normalizeModule = function(parentId, moduleName) { @@ -84,15 +90,20 @@ window.define = function(id, deps, factory) { deps = []; id = window.require.id; } + + if (typeof factory != "function") { + window.require.modules[id] = { + exports: factory, + initialized: true + }; + return; + } if (!deps.length) // If there is no dependencies, we inject 'require', 'exports' and // 'module' as dependencies, to provide CommonJS compatibility. deps = ['require', 'exports', 'module']; - if (id.indexOf("text!") === 0) - return; - var req = function(childId) { return window.require(id, childId); }; diff --git a/lib/ace/worker/worker_client.js b/lib/ace/worker/worker_client.js index e2798c13..ad445287 100644 --- a/lib/ace/worker/worker_client.js +++ b/lib/ace/worker/worker_client.js @@ -92,14 +92,9 @@ var WorkerClient = function(topLevelNamespaces, mod, classname, workerUrl) { this.onMessage = function(e) { var msg = e.data; switch(msg.type) { - case "log": - window.console && console.log && console.log.apply(console, msg.data); - break; - case "event": this._signal(msg.name, {data: msg.data}); break; - case "call": var callback = this.callbacks[msg.id]; if (callback) { @@ -107,8 +102,18 @@ var WorkerClient = function(topLevelNamespaces, mod, classname, workerUrl) { delete this.callbacks[msg.id]; } break; + case "error": + this.reportError(msg.data); + break; + case "log": + window.console && console.log && console.log.apply(console, msg.data); + break; } }; + + this.reportError = function(err) { + window.console && console.error && console.error(err); + }; this.$normalizePath = function(path) { return net.qualifyURL(path); @@ -119,7 +124,8 @@ var WorkerClient = function(topLevelNamespaces, mod, classname, workerUrl) { this.deltaQueue = null; this.$worker.terminate(); this.$worker = null; - this.$doc.removeEventListener("change", this.changeListener); + if (this.$doc) + this.$doc.off("change", this.changeListener); this.$doc = null; }; From a1042e0d949f234c4f62cac8b46e8ee3d945ea16 Mon Sep 17 00:00:00 2001 From: nightwing Date: Sun, 5 Oct 2014 22:02:14 +0400 Subject: [PATCH 12/25] fix reattaching linewidgets to editor --- demo/kitchen-sink/inline_editor.js | 3 --- demo/kitchen-sink/layout.js | 1 - lib/ace/line_widgets.js | 24 ++++++++++++------------ 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/demo/kitchen-sink/inline_editor.js b/demo/kitchen-sink/inline_editor.js index 0c050aed..3f814c21 100644 --- a/demo/kitchen-sink/inline_editor.js +++ b/demo/kitchen-sink/inline_editor.js @@ -96,9 +96,6 @@ require("ace/commands/default_commands").commands.push({ editor.keyBinding.addKeyboardHandler(kb); inlineEditor.keyBinding.addKeyboardHandler(kb); - editor.on("changeSession", function(e) { - w.el.parentNode && w.el.parentNode.removeChild(w.el); - }); inlineEditor.setTheme("ace/theme/solarized_light"); } }); diff --git a/demo/kitchen-sink/layout.js b/demo/kitchen-sink/layout.js index e43915bb..1332eef3 100644 --- a/demo/kitchen-sink/layout.js +++ b/demo/kitchen-sink/layout.js @@ -116,7 +116,6 @@ exports.singleLineEditor = function(el) { renderer.setStyle("ace_one-line"); var editor = new Editor(renderer); - new MultiSelect(editor); editor.session.setUndoManager(new UndoManager()); editor.setShowPrintMargin(false); diff --git a/lib/ace/line_widgets.js b/lib/ace/line_widgets.js index 9e384e0e..d4080b53 100644 --- a/lib/ace/line_widgets.js +++ b/lib/ace/line_widgets.js @@ -45,9 +45,10 @@ function LineWidgets(session) { this.renderWidgets = this.renderWidgets.bind(this); this.measureWidgets = this.measureWidgets.bind(this); this.session._changedWidgets = []; - this.detach = this.detach.bind(this); + this.$onChangeEditor = this.$onChangeEditor.bind(this); this.session.on("change", this.updateOnChange); + this.session.on("changeEditor", this.$onChangeEditor); } (function() { @@ -73,8 +74,12 @@ function LineWidgets(session) { return screenRows; }; + this.$onChangeEditor = function(e) { + this.attach(e.editor); + }; + this.attach = function(editor) { - if (editor.widgetManager && editor.widgetManager != this) + if (editor && editor.widgetManager && editor.widgetManager != this) editor.widgetManager.detach(); if (this.editor == editor) @@ -83,21 +88,16 @@ function LineWidgets(session) { this.detach(); this.editor = editor; - this.editor.on("changeSession", this.detach); - - editor.widgetManager = this; - - editor.renderer.on("beforeRender", this.measureWidgets); - editor.renderer.on("afterRender", this.renderWidgets); + if (editor) { + editor.widgetManager = this; + editor.renderer.on("beforeRender", this.measureWidgets); + editor.renderer.on("afterRender", this.renderWidgets); + } }; this.detach = function(e) { - if (e && e.session == this.session) - return; // sometimes attach can be called before setSession var editor = this.editor; if (!editor) return; - - editor.off("changeSession", this.detach); this.editor = null; editor.widgetManager = null; From c2ce33f368853ca497fe72d15a7eac545f115493 Mon Sep 17 00:00:00 2001 From: nightwing Date: Sun, 5 Oct 2014 22:04:42 +0400 Subject: [PATCH 13/25] fix search.$assembleRegExp not working for multiline strings --- lib/ace/search.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ace/search.js b/lib/ace/search.js index 325ce00f..c5119319 100644 --- a/lib/ace/search.js +++ b/lib/ace/search.js @@ -305,7 +305,7 @@ var Search = function() { if (options.wholeWord) needle = "\\b" + needle + "\\b"; - var modifier = options.caseSensitive ? "g" : "gi"; + var modifier = options.caseSensitive ? "gm" : "gmi"; options.$isMultiLine = !$disableFakeMultiline && /[\n\r]/.test(needle); if (options.$isMultiLine) From 2983c4b19f59f2d5e010764957e622ce22338674 Mon Sep 17 00:00:00 2001 From: nightwing Date: Sun, 5 Oct 2014 22:05:34 +0400 Subject: [PATCH 14/25] do not throw when trying to add intersecting folds --- lib/ace/edit_session/folding.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/ace/edit_session/folding.js b/lib/ace/edit_session/folding.js index a727b15a..0274cd58 100644 --- a/lib/ace/edit_session/folding.js +++ b/lib/ace/edit_session/folding.js @@ -289,13 +289,12 @@ function Folding() { if (startFold && endFold == startFold) return startFold.addSubFold(fold); - if ( - (startFold && !startFold.range.isStart(startRow, startColumn)) - || (endFold && !endFold.range.isEnd(endRow, endColumn)) - ) { - throw new Error("A fold can't intersect already existing fold" + fold.range + startFold.range); - } - + if (startFold && !startFold.range.isStart(startRow, startColumn)) + this.removeFold(startFold); + + if (endFold && !endFold.range.isEnd(endRow, endColumn)) + this.removeFold(endFold); + // Check if there are folds in the range we create the new fold for. var folds = this.getFoldsInRange(fold.range); if (folds.length > 0) { From 6814187c510d43833993d1b23b83de94993d32f3 Mon Sep 17 00:00:00 2001 From: nightwing Date: Sun, 5 Oct 2014 23:07:34 +0400 Subject: [PATCH 15/25] fix name of templates/highlight_rules.js --- tool/templates/{highlight_rules.tmpl.js => highlight_rules.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tool/templates/{highlight_rules.tmpl.js => highlight_rules.js} (100%) diff --git a/tool/templates/highlight_rules.tmpl.js b/tool/templates/highlight_rules.js similarity index 100% rename from tool/templates/highlight_rules.tmpl.js rename to tool/templates/highlight_rules.js From 1a6bb0591b49bf1883e8e141f8884e404a73d428 Mon Sep 17 00:00:00 2001 From: Richo Healey Date: Sun, 5 Oct 2014 19:22:17 -0700 Subject: [PATCH 16/25] Fix @richo's username --- demo/kitchen-sink/docs/sh.sh | 6 +++--- lib/ace/mode/_test/tokens_sh.json | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/demo/kitchen-sink/docs/sh.sh b/demo/kitchen-sink/docs/sh.sh index b1da400d..4c5e6968 100755 --- a/demo/kitchen-sink/docs/sh.sh +++ b/demo/kitchen-sink/docs/sh.sh @@ -2,9 +2,9 @@ # Script to open a browser to current branch # Repo formats: -# ssh git@github.com:richoH/gh_pr.git -# http https://richoH@github.com/richoH/gh_pr.git -# git git://github.com/richoH/gh_pr.git +# ssh git@github.com:richo/gh_pr.git +# http https://richoH@github.com/richo/gh_pr.git +# git git://github.com/richo/gh_pr.git username=`git config --get github.user` diff --git a/lib/ace/mode/_test/tokens_sh.json b/lib/ace/mode/_test/tokens_sh.json index e4ab3412..53411906 100644 --- a/lib/ace/mode/_test/tokens_sh.json +++ b/lib/ace/mode/_test/tokens_sh.json @@ -11,13 +11,13 @@ ["comment","# Repo formats:"] ],[ "start", - ["comment","# ssh git@github.com:richoH/gh_pr.git"] + ["comment","# ssh git@github.com:richo/gh_pr.git"] ],[ "start", - ["comment","# http https://richoH@github.com/richoH/gh_pr.git"] + ["comment","# http https://richoH@github.com/richo/gh_pr.git"] ],[ "start", - ["comment","# git git://github.com/richoH/gh_pr.git"] + ["comment","# git git://github.com/richo/gh_pr.git"] ],[ "start" ],[ @@ -331,4 +331,4 @@ ["keyword","fi"] ],[ "start" -]] \ No newline at end of file +]] From 28b95df12c48f23909f4aa4cc3cd1d3982508183 Mon Sep 17 00:00:00 2001 From: takapa Date: Wed, 8 Oct 2014 22:07:23 +0100 Subject: [PATCH 17/25] Added setCompleters(...) function Added setCompleters() method, so that completers can be explicitly set from a pre-populated array, or can be removed entirely (if null is passed, or setCompleters() is called without an argument or an empty array is passed. Please check this carefully for rookie JavaScript bugs, as I'm not sure if the null/undefined test is best-practise here as I don't typically code in JavaScript in my day-job. --- lib/ace/ext/language_tools.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/ace/ext/language_tools.js b/lib/ace/ext/language_tools.js index cad80605..dac2075c 100644 --- a/lib/ace/ext/language_tools.js +++ b/lib/ace/ext/language_tools.js @@ -67,8 +67,14 @@ var snippetCompleter = { } }; -exports.removeCompleters = function() { - completers = []; +// Allows default completers to be removed or replaced with a explict set of completers +// A null argument here will result in an empty completer array, not a null attribute +exports.setCompleters = function(val) { + if (val == null || val == undefined || (!(val instanceof Array)) ) { + completers = []; + } else { + completers = val; + } }; var completers = [snippetCompleter, textCompleter, keyWordCompleter]; From 38ce8b891907408f2c6cc97e68f567f11b7b50ae Mon Sep 17 00:00:00 2001 From: nightwing Date: Thu, 9 Oct 2014 01:30:05 +0400 Subject: [PATCH 18/25] less java:) --- lib/ace/ext/language_tools.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/ace/ext/language_tools.js b/lib/ace/ext/language_tools.js index dac2075c..4edbf170 100644 --- a/lib/ace/ext/language_tools.js +++ b/lib/ace/ext/language_tools.js @@ -67,17 +67,12 @@ var snippetCompleter = { } }; +var completers = [snippetCompleter, textCompleter, keyWordCompleter]; // Allows default completers to be removed or replaced with a explict set of completers // A null argument here will result in an empty completer array, not a null attribute exports.setCompleters = function(val) { - if (val == null || val == undefined || (!(val instanceof Array)) ) { - completers = []; - } else { - completers = val; - } + completers = val || []; }; - -var completers = [snippetCompleter, textCompleter, keyWordCompleter]; exports.addCompleter = function(completer) { completers.push(completer); }; From 23c64c6ebb2f49644848726e26ee88e947cb8171 Mon Sep 17 00:00:00 2001 From: nightwing Date: Thu, 9 Oct 2014 16:39:14 +0400 Subject: [PATCH 19/25] Add highlighting for raw string literals in rust mode. --- lib/ace/mode/rust_highlight_rules.js | 33 ++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/lib/ace/mode/rust_highlight_rules.js b/lib/ace/mode/rust_highlight_rules.js index acb6c92a..1a7d38a6 100644 --- a/lib/ace/mode/rust_highlight_rules.js +++ b/lib/ace/mode/rust_highlight_rules.js @@ -29,10 +29,6 @@ * ***** END LICENSE BLOCK ***** */ /* This file was autogenerated from https://raw.github.com/dbp/sublime-rust/master/Rust.tmLanguage (uuid: ) */ -/**************************************************************************************** - * IT MIGHT NOT BE PERFECT ...But it's a good start from an existing *.tmlanguage file. * - * fileTypes * - ****************************************************************************************/ define(function(require, exports, module) { "use strict"; @@ -55,6 +51,35 @@ var RustHighlightRules = function() { next: 'pop' }, { include: '#rust_escaped_character' }, { defaultToken: 'string.quoted.single.source.rust' } ] }, + { + stateName: "bracketedComment", + onMatch : function(value, currentState, stack){ + stack.unshift(this.next, value.length - 1, currentState); + return "string.quoted.raw.source.rust"; + }, + regex : /r#*"/, + next : [ + { + onMatch : function(value, currentState, stack) { + var token = "string.quoted.raw.source.rust"; + if (value.length >= stack[1]) { + if (value.length > stack[1]) + token = "invalid"; + stack.shift(); + stack.shift(); + this.next = stack.shift(); + } else { + this.next = ""; + } + return token; + }, + regex : /"#*/, + next : "start" + }, { + defaultToken : "string.quoted.raw.source.rust" + } + ] + }, { token: 'string.quoted.double.source.rust', regex: '"', push: From 52e8bc30829aa543ae7c53d7143c6fb7d621a516 Mon Sep 17 00:00:00 2001 From: nightwing Date: Mon, 8 Sep 2014 23:44:08 +0400 Subject: [PATCH 20/25] add elm mode (fixes #2099) --- demo/kitchen-sink/docs/elm.elm | 12 +++ lib/ace/ext/modelist.js | 1 + lib/ace/mode/elm.js | 58 ++++++++++ lib/ace/mode/elm_highlight_rules.js | 162 ++++++++++++++++++++++++++++ 4 files changed, 233 insertions(+) create mode 100644 demo/kitchen-sink/docs/elm.elm create mode 100644 lib/ace/mode/elm.js create mode 100644 lib/ace/mode/elm_highlight_rules.js diff --git a/demo/kitchen-sink/docs/elm.elm b/demo/kitchen-sink/docs/elm.elm new file mode 100644 index 00000000..eef70b2a --- /dev/null +++ b/demo/kitchen-sink/docs/elm.elm @@ -0,0 +1,12 @@ +{- Ace {- 4 -} Elm -} +main = lift clock (every second) + +clock t = collage 400 400 [ filled lightGrey (ngon 12 110) + , outlined (solid grey) (ngon 12 110) + , hand orange 100 t + , hand charcoal 100 (t/60) + , hand charcoal 60 (t/720) ] + +hand clr len time = + let angle = degrees (90 - 6 * inSeconds time) + in traced (solid clr) <| segment (0,0) (len * cos angle, len * sin angle) \ No newline at end of file diff --git a/lib/ace/ext/modelist.js b/lib/ace/ext/modelist.js index 3ffac26b..7aae9548 100644 --- a/lib/ace/ext/modelist.js +++ b/lib/ace/ext/modelist.js @@ -67,6 +67,7 @@ var supportedModes = { Dot: ["dot"], Eiffel: ["e"], EJS: ["ejs"], + Elm: ["elm"], Erlang: ["erl|hrl"], Forth: ["frt|fs|ldr"], FTL: ["ftl"], diff --git a/lib/ace/mode/elm.js b/lib/ace/mode/elm.js new file mode 100644 index 00000000..169e9b39 --- /dev/null +++ b/lib/ace/mode/elm.js @@ -0,0 +1,58 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Distributed under the BSD license: + * + * Copyright (c) 2012, 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 ***** */ + +/* + THIS FILE WAS AUTOGENERATED BY mode.tmpl.js +*/ + +define(function(require, exports, module) { +"use strict"; + +var oop = require("../lib/oop"); +var TextMode = require("./text").Mode; +var HighlightRules = require("./elm_highlight_rules").ElmHighlightRules; +// TODO: pick appropriate fold mode +var FoldMode = require("./folding/cstyle").FoldMode; + +var Mode = function() { + this.HighlightRules = HighlightRules; + this.foldingRules = new FoldMode(); +}; +oop.inherits(Mode, TextMode); + +(function() { + this.lineCommentStart = "--"; + this.blockComment = {start: "{-", end: "-}"}; + // Extra logic goes here. + this.$id = "ace/mode/elm"; +}).call(Mode.prototype); + +exports.Mode = Mode; +}); \ No newline at end of file diff --git a/lib/ace/mode/elm_highlight_rules.js b/lib/ace/mode/elm_highlight_rules.js new file mode 100644 index 00000000..6b435fdf --- /dev/null +++ b/lib/ace/mode/elm_highlight_rules.js @@ -0,0 +1,162 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Distributed under the BSD license: + * + * Copyright (c) 2012, 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 ***** */ + +// TODO check with https://github.com/deadfoxygrandpa/Elm.tmLanguage + +define(function(require, exports, module) { +"use strict"; + +var oop = require("../lib/oop"); +var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules; + +var ElmHighlightRules = function() { + var keywordMapper = this.createKeywordMapper({ + "keyword": "as|case|class|data|default|deriving|do|else|export|foreign|" + + "hiding|jsevent|if|import|in|infix|infixl|infixr|instance|let|" + + "module|newtype|of|open|then|type|where|_|port|\u03BB" + }, "identifier"); + + var escapeRe = /\\(\d+|['"\\&trnbvf])/; + + var smallRe = /[a-z_]/.source; + var largeRe = /[A-Z]/.source; + var idRe = /[a-z_A-Z0-9\']/.source; + + this.$rules = { + start: [{ + token: "string.start", + regex: '"', + next: "string" + }, { + token: "string.character", + regex: "'(?:" + escapeRe.source + "|.)'?" + }, { + regex: /0(?:[xX][0-9A-Fa-f]+|[oO][0-7]+)|\d+(\.\d+)?([eE][-+]?\d*)?/, + token: "constant.numeric" + }, { + token : "keyword", + regex : /\.\.|\||:|=|\\|\"|->|<-|\u2192/ + }, { + token : "keyword.operator", + regex : /[-!#$%&*+.\/<=>?@\\^|~:\u03BB\u2192]+/ + }, { + token : "operator.punctuation", + regex : /[,;`]/ + }, { + regex : largeRe + idRe + "+\\.?", + token : function(value) { + if (value[value.length - 1] == ".") + return "entity.name.function"; + return "constant.language"; + } + }, { + regex : "^" + smallRe + idRe + "+", + token : function(value) { + return "constant.language"; + } + }, { + token : keywordMapper, + regex : "[\\w\\xff-\\u218e\\u2455-\\uffff]+\\b" + }, { + regex: "{-#?", + token: "comment.start", + onMatch: function(value, currentState, stack) { + this.next = value.length == 2 ? "blockComment" : "docComment"; + return this.token; + } + }, { + token: "variable.language", + regex: /\[markdown\|/, + next: "markdown" + }, { + token: "paren.lparen", + regex: /[\[({]/ + }, { + token: "paren.rparen", + regex: /[\])}]/ + }, ], + markdown: [{ + regex: /\|\]/, + next: "start" + }, { + defaultToken : "string" + }], + blockComment: [{ + regex: "{-", + token: "comment.start", + push: "blockComment" + }, { + regex: "-}", + token: "comment.end", + next: "pop" + }, { + defaultToken: "comment" + }], + docComment: [{ + regex: "{-", + token: "comment.start", + push: "docComment" + }, { + regex: "-}", + token: "comment.end", + next: "pop" + }, { + defaultToken: "doc.comment" + }], + string: [{ + token: "constant.language.escape", + regex: escapeRe, + }, { + token: "text", + regex: /\\(\s|$)/, + next: "stringGap" + }, { + token: "string.end", + regex: '"', + next: "start" + }], + stringGap: [{ + token: "text", + regex: /\\/, + next: "string" + }, { + token: "error", + regex: "", + next: "start" + }], + }; + + this.normalizeRules(); +}; + +oop.inherits(ElmHighlightRules, TextHighlightRules); + +exports.ElmHighlightRules = ElmHighlightRules; +}); From 9683d80f847a3fa15402e070273303ebcd4a37e7 Mon Sep 17 00:00:00 2001 From: nightwing Date: Thu, 9 Oct 2014 20:49:25 +0400 Subject: [PATCH 21/25] highlight todo in javascript comments --- lib/ace/mode/doc_comment_highlight_rules.js | 5 +++-- lib/ace/mode/javascript_highlight_rules.js | 12 ++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/ace/mode/doc_comment_highlight_rules.js b/lib/ace/mode/doc_comment_highlight_rules.js index 5262a8e8..2f10d1bb 100644 --- a/lib/ace/mode/doc_comment_highlight_rules.js +++ b/lib/ace/mode/doc_comment_highlight_rules.js @@ -42,9 +42,10 @@ var DocCommentHighlightRules = function() { regex : "@[\\w\\d_]+" // TODO: fix email addresses }, { token : "comment.doc.tag", - regex : "\\bTODO\\b" + regex : "\\b(?:TODO|FIXME)\\b" }, { - defaultToken : "comment.doc" + defaultToken : "comment.doc", + caseInsensitive: true }] }; }; diff --git a/lib/ace/mode/javascript_highlight_rules.js b/lib/ace/mode/javascript_highlight_rules.js index e81a2dce..151b3168 100644 --- a/lib/ace/mode/javascript_highlight_rules.js +++ b/lib/ace/mode/javascript_highlight_rules.js @@ -302,20 +302,24 @@ var JavaScriptHighlightRules = function(options) { } ], "comment_regex_allowed" : [ + {token : "storage.type", regex : "\\b(?:TODO|FIXME)\\b"}, {token : "comment", regex : "\\*\\/", next : "start"}, - {defaultToken : "comment"} + {defaultToken : "comment", caseInsensitive: true} ], "comment" : [ + {token : "storage.type", regex : "\\b(?:TODO|FIXME)\\b"}, {token : "comment", regex : "\\*\\/", next : "no_regex"}, - {defaultToken : "comment"} + {defaultToken : "comment", caseInsensitive: true} ], "line_comment_regex_allowed" : [ + {token : "storage.type", regex : "\\b(?:TODO|FIXME)\\b"}, {token : "comment", regex : "$|^", next : "start"}, - {defaultToken : "comment"} + {defaultToken : "comment", caseInsensitive: true} ], "line_comment" : [ + {token : "storage.type", regex : "\\b(?:TODO|FIXME)\\b"}, {token : "comment", regex : "$|^", next : "no_regex"}, - {defaultToken : "comment"} + {defaultToken : "comment", caseInsensitive: true} ], "qqstring" : [ { From ed40c1f51f2f10c77694af12004db15d42a18c5f Mon Sep 17 00:00:00 2001 From: nightwing Date: Thu, 9 Oct 2014 23:05:48 +0400 Subject: [PATCH 22/25] improve tmlanguage.js --- Makefile.dryice.js | 2 +- lib/ace/ext/modelist.js | 2 + tool/Readme.md | 26 ++ tool/add_mode.js | 189 ++++---- tool/lib.js | 19 +- tool/mode_creator.js | 6 +- tool/regexp_tokenizer.js | 114 +++++ tool/regexp_tokenizer_test.js | 30 ++ tool/templates/dummy.JSON-tmLanguage | 45 ++ tool/templates/highlight_rules.js | 19 +- tool/templates/mode.js | 4 +- tool/tmlanguage.js | 617 ++++++++++++++++++++++++--- 12 files changed, 898 insertions(+), 175 deletions(-) create mode 100644 tool/Readme.md create mode 100644 tool/regexp_tokenizer.js create mode 100644 tool/regexp_tokenizer_test.js create mode 100644 tool/templates/dummy.JSON-tmLanguage diff --git a/Makefile.dryice.js b/Makefile.dryice.js index 2763da89..a6082558 100755 --- a/Makefile.dryice.js +++ b/Makefile.dryice.js @@ -184,7 +184,7 @@ function jsFileList(path, filter) { filter = /_test/; return fs.readdirSync(path).map(function(x) { - if (x.slice(-3) == ".js" && !filter.test(x) && !/\s/.test(x)) + if (x.slice(-3) == ".js" && !filter.test(x) && !/\s|BASE|(\b|_)dummy(\b|_)/.test(x)) return x.slice(0, -3); }).filter(Boolean); } diff --git a/lib/ace/ext/modelist.js b/lib/ace/ext/modelist.js index 7aae9548..3f85c5b3 100644 --- a/lib/ace/ext/modelist.js +++ b/lib/ace/ext/modelist.js @@ -65,6 +65,8 @@ var supportedModes = { Diff: ["diff|patch"], Dockerfile: ["^Dockerfile"], Dot: ["dot"], + Dummy: ["dummy"], + DummySyntax: ["dummy"], Eiffel: ["e"], EJS: ["ejs"], Elm: ["elm"], diff --git a/tool/Readme.md b/tool/Readme.md new file mode 100644 index 00000000..28dd5166 --- /dev/null +++ b/tool/Readme.md @@ -0,0 +1,26 @@ +Helper Scripts for Ace +====================== + +To use this you need to install node.js. and run `npm install` in this directory. + + +# add_mode.js + + Run +``` +node add_mode.js ModeName "extension1|extension2|^FullName" +``` + to create all the files needed for a new mode named `ModeName` + this adds stubs for: + `ace/mode/mode_name.js` + `ace/mode/mode_name_hightlight_rules.js` + `ace/snippets/mode_name.js` + `ace/demo/kitchen_sink/docs/mode_name.extension1` + and adds entry for the new mode to `ace/ext/modelist.js` + + +# tmlanguage.js + +``` +node tmlanguage.js ./templates/dummy.JSON-tmLanguage +``` \ No newline at end of file diff --git a/tool/add_mode.js b/tool/add_mode.js index 09b4a7e6..2f70a14a 100644 --- a/tool/add_mode.js +++ b/tool/add_mode.js @@ -2,100 +2,105 @@ var fs = require('fs'); var lib = require('./lib'); var path = require('path'); -var args = process.argv.slice(2); +function main(displayName, extRe) { + var name = lib.snakeCase(displayName).replace(/[^\w]/g, ""); + /** demo **/ + var demoFileExt = extRe.split("|")[0] || name; + var demoFileName = demoFileExt[0] == "^" ? demoFileExt.substr(1) : name + "." + demoFileExt; + var demoFilePath = lib.AceRoot + "demo/kitchen-sink/docs/" + demoFileName; + fs.writeFileSync(demoFilePath, "TODO add a nice demo!", "utf8"); + console.log("Created demo file at: " + path.normalize(demoFilePath)); -var displayName = args[0]; -var extRe = args[1]; -if (!displayName || ! extRe) { - console.log("Usage: ModeName ext1|ext2"); - process.exit(1); + /** mode **/ + var template = fs.readFileSync(__dirname + "/templates/mode.js", "utf8"); + var modePath = lib.AceLib + "ace/mode/" + name + ".js"; + var text = lib.fillTemplate(template, { + languageHighlightFilename: name, + languagename: name, + lineCommentStart: "TODO", + blockCommentStart: "TODO", + blockCommentEnd: "TODO" + }); + fs.writeFileSync(modePath, text); + console.log("Created mode file at: " + path.normalize(modePath)); + + /** highlight rules **/ + template = fs.readFileSync(__dirname + "/templates/highlight_rules.js", "utf8"); + var hlPath = lib.AceLib + "ace/mode/" + name + "_highlight_rules.js"; + template = template.replace(/\/\* THIS[\s\S]*?\*{3}\/\s*/, ""); + text = lib.fillTemplate(template, { + language: name, + languageTokens: '{\n\ + start: [{\n\ + token: "string.start",\n\ + regex: \'"\',\n\ + next: "qstring"\n\ + }],\n\ + qstring: [{\n\ + token: "escape",\n\ + regex: /\\\\./,\n\ + }, {\n\ + token: "string.end",\n\ + regex: \'"\',\n\ + next: "start"\n\ + }],\n\ + }' + }); + fs.writeFileSync(hlPath, text); + console.log("Created mode file at: " + path.normalize(hlPath)); + + /** snippets **/ + template = fs.readFileSync(__dirname + "/templates/snippets.js", "utf8"); + var snipetsPath = lib.AceLib + "ace/snippets/" + name + ".js"; + text = lib.fillTemplate(template, { + languagename: name, + snippets: "" + }); + fs.writeFileSync(snipetsPath, text); + console.log("Created snippets file at: " + path.normalize(snipetsPath)); + + /** modelist **/ + var modelistPath = lib.AceLib + "ace/ext/modelist.js"; + var modelist = fs.readFileSync(modelistPath, "utf8").replace(/\r\n?/g, "\n"); + modelist = modelist.replace(/(supportedModes = {\n)([\s\S]*?)(\n^};)/m, function(_, m1, m2, m3) { + var langs = m2.split(/,\n/); + var offset = langs[0].trim().indexOf("["); + var padding = Array(Math.max(offset - displayName.length - 1, 0) + 1).join(" "); + var newLang = " " + displayName + ":" + padding + "[\"" + extRe + "\"]"; + langs = langs.concat(newLang).map(function(x) { + return { + value: x, + id: x.match(/[^"':\s]+/)[0].toLowerCase() + }; + }); + langs[langs.length - 1].isNew = true; + + langs = langs.filter(function(x) { + console.log(x.id, displayName) + return x.id != displayName.toLowerCase() || x.isNew; + }); + langs = langs.sort(function(a, b) { + return a.id.localeCompare(b.id); + }).map(function(x) { + return x.value; + }); + + return m1 + langs.join(",\n") + m3; + }); + fs.writeFileSync(modelistPath, modelist, "utf8"); + console.log("Updated modelist at: " + path.normalize(modelistPath)); } -var name = lib.snakeCase(displayName).replace(/[^\w]/g, ""); - -/** demo **/ -var demoFileExt = extRe.split("|")[0] || name; -var demoFileName = demoFileExt[0] == "^" ? demoFileExt.substr(1) : name + "." + demoFileExt; -var demoFilePath = lib.AceRoot + "demo/kitchen-sink/docs/" + demoFileName; -fs.writeFileSync(demoFilePath, "TODO add a nice demo!", "utf8"); -console.log("Created demo file at: " + path.normalize(demoFilePath)); - -/** mode **/ -var template = fs.readFileSync(__dirname + "/templates/mode.js", "utf8"); -var modePath = lib.AceLib + "ace/mode/" + name + ".js"; -var text = lib.fillTemplate(template, { - languageHighlightFilename: name, - languagename: name, - lineCommentStart: "TODO", - blockCommentStart: "TODO", - blockCommentEnd: "TODO" -}); -fs.writeFileSync(modePath, text); -console.log("Created mode file at: " + path.normalize(modePath)); - -/** highlight rules **/ -template = fs.readFileSync(__dirname + "/templates/highlight_rules.js", "utf8"); -var hlPath = lib.AceLib + "ace/mode/" + name + "_highlight_rules.js"; -template = template.replace(/\/\* THIS[\s\S]*?\*{3}\/\s*/, ""); -text = lib.fillTemplate(template, { - language: name, - languageTokens: '{\n\ - start: [{\n\ - token: "string.start",\n\ - regex: \'"\',\n\ - next: "qstring"\n\ - }],\n\ - qstring: [{\n\ - token: "escape",\n\ - regex: /\\\\./,\n\ - }, {\n\ - token: "string.end",\n\ - regex: \'"\',\n\ - next: "start"\n\ - }],\n\ - }' -}); -fs.writeFileSync(hlPath, text); -console.log("Created mode file at: " + path.normalize(hlPath)); - -/** snippets **/ -template = fs.readFileSync(__dirname + "/templates/snippets.js", "utf8"); -var snipetsPath = lib.AceLib + "ace/snippets/" + name + ".js"; -text = lib.fillTemplate(template, { - languagename: name, - snippets: "" -}); -fs.writeFileSync(snipetsPath, text); -console.log("Created snippets file at: " + path.normalize(snipetsPath)); - -/** modelist **/ -var modelistPath = lib.AceLib + "ace/ext/modelist.js"; -var modelist = fs.readFileSync(modelistPath, "utf8").replace(/\r\n?/g, "\n"); -modelist = modelist.replace(/(supportedModes = {\n)([\s\S]*?)(\n^};)/m, function(_, m1, m2, m3) { - var langs = m2.split(/,\n/); - var offset = langs[0].trim().indexOf("["); - var padding = Array(Math.max(offset - displayName.length - 1, 0) + 1).join(" "); - var newLang = " " + displayName + ":" + padding + "[\"" + extRe + "\"]"; - langs = langs.concat(newLang).map(function(x) { - return { - value: x, - id: x.match(/[^"':\s]+/)[0].toLowerCase() - }; - }); - langs[langs.length - 1].isNew = true; - - langs = langs.filter(function(x) { - return x.id != name || x.isNew; - }); - langs = langs.sort(function(a, b) { - return a.id.localeCompare(b.id); - }).map(function(x) { - return x.value; - }); - - return m1 + langs.join(",\n") + m3; -}); -fs.writeFileSync(modelistPath, modelist, "utf8"); -console.log("Updated modelist at: " + path.normalize(modelistPath)); +if (!module.parent) { + var args = process.argv.slice(2); + var displayName = args[0]; + var extRe = args[1]; + if (!displayName || ! extRe) { + console.log("Usage: ModeName ext1|ext2"); + process.exit(1); + } +} else { + module.exports = main; +} diff --git a/tool/lib.js b/tool/lib.js index d7c0af26..e72194d7 100644 --- a/tool/lib.js +++ b/tool/lib.js @@ -5,13 +5,18 @@ var url = require("url"); var https = require("https"); var http = require("http"); -exports.parsePlist = function(themeXml, callback) { - var result = ""; - plist.parseString(themeXml, function(_, theme) { - result = theme[0]; - callback && callback(theme[0]); - }); - return result; +exports.parsePlist = function(xmlOrJSON, callback) { + var json; + if (xmlOrJSON[0] == "<") { + plist.parseString(xmlOrJSON, function(_, result) { + json = result[0]; + }); + } else { + xmlOrJSON = xmlOrJSON.replace(/^\s*\/\/.*/gm, ""); + json = JSON.parse(xmlOrJSON) + } + callback && callback(json); + return json; }; exports.formatJSON = function(object, initialIndent) { diff --git a/tool/mode_creator.js b/tool/mode_creator.js index 489edaaa..6b1122cd 100644 --- a/tool/mode_creator.js +++ b/tool/mode_creator.js @@ -94,7 +94,7 @@ document.getElementById("perfTest").onclick = function() { } var tk = new Tokenizer(currentRules); - var testPerf = function(lines, tk){ + var testPerf = function(lines, tk) { var state = "start"; for (var i=0, l = lines.length; i |'\w+\b[+-]?\d'))/, merge:false}, + {include: "charTypes", merge:false}, + {token: "charclass", regex: /\[\^?/, push: "charclass", merge:false}, + {token: "alternation", regex: /\|/, merge:false}, + {include: "quantifiers", merge:false}, + {include: "groups", merge:false}, + {include: "xGroup", merge:true} + ], + charTypes: [ + {token: "char", regex: /\\([tvnrbfae]|[0-8]{1,3}|x[\dA-Fa-f]{2}|x7[\dA-Fa-f]{7})/, merge:false}, // todo \cx + {token: "charType", regex: /\.|\\[wWsSdDhH]/, merge:false}, + {token: "charProperty", regex: /\\p{\w+}/, merge:false}, + {token: "char", regex: /\\./, merge:false}, + ], + quantifiers: [ + {token: "quantifier", regex: /([?*+]|{\d+\b,?\d*}|{,\d+})[?+]?/, merge:false} + ], + charclass: [ + {include: "charTypes", merge:false}, + {token: "charclass.start", regex: /\[\^?/, push: "charclass", merge:false}, + {token: "charclass.end", regex: /\]/, next: "pop", merge:false} + ], + groups: [ + {token: "group", regex: /[(]([?](#|[imx\-]+:?|:|=|!|<=||<\w+>|'\w+'|))?|[)]/, + onMatch: function(val, state, stack) { + if (!stack.groupNumber) + stack.groupNumber = 1; + + var isStart = val !== ")"; + var t = {depth:0,type: isStart ? "group.start" : "group.end", value: val}; + t.groupType = val[2]; + + if (val == "(") { + t.number = stack.groupNumber++; + t.isGroup = true + } else if (t.groupType == "'" || (t.groupType == "<" && val.slice(-1) == ">")) { + t.name = val.slice(2, -1) + t.isGroup = true + } else if (t.groupType == ":") { + t.isGroup = true + } + + if (t.groupType && val.indexOf("x") != -1) { + var minus = val.indexOf("-"); + if (minus == -1 || minus > val.indexOf("x")) + stack.xGroup = t; + else + stack.xGroup = null; + } else if (!isStart && stack.xGroup && stack.xGroup == stack[0]) { + if (stack.xGroup.value.slice(-1) == ":") + stack.xGroup = null; + } + + if (isStart) { + if (stack.groupDepth) { + stack[0].hasChildren = true + } + stack.groupDepth = (stack.groupDepth||0)+1; + stack.unshift(t) + } else { + stack.groupDepth --; + t.start = stack.shift(t) + t.start.end = t + } + return [t] + }, merge:false + } + ], + xGroup: [ + {token: "text", regex:/\s+/, onMatch: function(val, state, stack) { + return stack.xGroup ? [] : "text" + }, merge: true}, + {token: "text", regex: /#/, onMatch: function(val, state, stack) { + if (stack.xGroup) { + this.next = "comment"; + stack.unshift(state); + return []; + } + this.next = ""; + return "text"; + }, merge: true} + ], + comment: [{ + regex: "[^\n\r]*|^", token: "", onMatch: function(val, state, stack) { + this.next = stack.shift(); + return []; + } + }] +} +r.normalizeRules() +var tmReTokenizer = new Tokenizer(r.getRules()); + +function tokenize(str) { + return tmReTokenizer.getLineTokens(str).tokens; +} + +function toStr(tokens) { return tokens.map(function(x){return x.value}).join("")} + + +exports.tokenize = tokenize; +exports.toStr = toStr; +exports.tmReTokenizer = tmReTokenizer; \ No newline at end of file diff --git a/tool/regexp_tokenizer_test.js b/tool/regexp_tokenizer_test.js new file mode 100644 index 00000000..aa09ff12 --- /dev/null +++ b/tool/regexp_tokenizer_test.js @@ -0,0 +1,30 @@ +require("amd-loader"); +var assert = require("assert"); + +var tk = require("./regexp_tokenizer"); +var tokenize = tk.tokenize; +var toStr = tk.toStr; + +var logTokens = function(tokens) { + tokens.forEach(function(x) { + delete x.end + delete x.start + }) + console.log(tokens) +} + +assert.equal(toStr( + tokenize("(?x)c + +\n\ + # comment\n\ + (?-x) # (?x: 1 \n\ + (2) [ ] # a \n\ + 3 4) c#" + )), + "(?x)c++(?-x) # (?x:1(2)[ ]34) c#" + ) +assert.equal(toStr( + tokenize("(?x)\n\ + u # comment\n\ + ")), + "(?x)u" + ) diff --git a/tool/templates/dummy.JSON-tmLanguage b/tool/templates/dummy.JSON-tmLanguage new file mode 100644 index 00000000..cb58b29b --- /dev/null +++ b/tool/templates/dummy.JSON-tmLanguage @@ -0,0 +1,45 @@ +// [PackageDev] target_format: plist, ext: tmLanguage +{ + "name": "Dummy", + "scopeName": "source.dummy", + "fileTypes": ["dummy"], + "patterns": [ + { + "include": "#string" + }, { + "include": "#escapes" + } + ], + "repository": { + "escapes": { + "patterns": [ + { + "match": "\\\\[nrt\\\\\\$\\\"']", + "name": "keyword.dummy" + } + ] + }, + "string": { + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.dummy" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.dummy" + } + }, + "contentName": "meta.string-contents.quoted.double.dummy", + "name": "string.quoted.double.dummy", + "end": "'''", + "begin": "'''", + "patterns": [ + { + "include": "#escapes" + } + ], + "comment": "This is a comment" + } + } +} \ No newline at end of file diff --git a/tool/templates/highlight_rules.js b/tool/templates/highlight_rules.js index 82b22c07..db8f5630 100644 --- a/tool/templates/highlight_rules.js +++ b/tool/templates/highlight_rules.js @@ -28,17 +28,11 @@ * * ***** END LICENSE BLOCK ***** */ -/* THIS FILE WAS AUTOGENERATED FROM %name% (UUID: %uuid%) */ -/**************************************************************** - * IT MIGHT NOT BE PERFECT, PARTICULARLY: * - * IN DECIDING STATES TO TRANSITION TO, * - * IGNORING WHITESPACE, * - * IGNORING GROUPS WITH ?:, * - * EXTENDING EXISTING MODES, * - * GATHERING KEYWORDS, OR * - * DECIDING WHEN TO USE PUSH. * - * ...But it's a good start from an existing *.tmlanguage file. * - ****************************************************************/ +/* This file was autogenerated from %name% (uuid: %uuid%) */ +/**************************************************************************************** + * IT MIGHT NOT BE PERFECT ...But it's a good start from an existing *.tmlanguage file. * + * fileTypes * + ****************************************************************************************/ define(function(require, exports, module) { "use strict"; @@ -55,6 +49,9 @@ var %language%HighlightRules = function() { this.normalizeRules(); }; +%language%HighlightRules.metaData = %metaData% + + oop.inherits(%language%HighlightRules, TextHighlightRules); exports.%language%HighlightRules = %language%HighlightRules; diff --git a/tool/templates/mode.js b/tool/templates/mode.js index ff21c885..1cdabf15 100644 --- a/tool/templates/mode.js +++ b/tool/templates/mode.js @@ -48,8 +48,8 @@ var Mode = function() { oop.inherits(Mode, TextMode); (function() { - this.lineCommentStart = "%lineCommentStart%"; - this.blockComment = {start: "%blockCommentStart%", end: "%blockCommentEnd%"}; + // this.lineCommentStart = "%lineCommentStart%"; + // this.blockComment = {start: "%blockCommentStart%", end: "%blockCommentEnd%"}; // Extra logic goes here. this.$id = "ace/mode/%languageHighlightFilename%" }).call(Mode.prototype); diff --git a/tool/tmlanguage.js b/tool/tmlanguage.js index fc575339..d3e42141 100644 --- a/tool/tmlanguage.js +++ b/tool/tmlanguage.js @@ -1,8 +1,330 @@ +require("amd-loader"); + var fs = require("fs"); var util = require("util"); var lib = require("./lib"); +var pathlib = require("path"); var parseLanguage = lib.parsePlist; +var tk = require("./regexp_tokenizer"); +var tokenize = tk.tokenize; +var toStr = tk.toStr; + +function last(array) {return array[array.length - 1]} + +function convertHexEscape(tokens) { + var inChClass = false; + tokens.forEach(function(t) { + if (t.type == "charclass") + inChClass = true; + else if (t.type == "charclass.end") + inChClass = false; + else if (t.type == "charType"){ + if (t.value == "\\h") { + t.type = "text"; + t.value = inChClass ? "\\da-fA-F" : "[\\da-fA-F]"; + } + else if (t.value == "\\H") { + if (inChClass) { + console.warn("can't convert \\H in charclass"); + return; + } + t.type = "text"; + t.value = "[^\\da-fA-F]"; + } + } + }); + return tokens; +} + +function convertNewLinesTo$(str) { + var tokens = tokenize(str); + for (var i = 0; i < tokens.length; i++) { + var t= tokens[i]; + if (t.type == "char" && t.value == "\\n") { + var p = tokens[i + 1] || {}; + if (p.type != "quantifier") { + t.value = "$"; + while (p.value == "\\n" || p.type == "quantifier") { + p.value = ""; + p = tokens[++i + 1] || {}; + } + } else if (/\?|\*|{,|{0,/.test(p.value)) { + t.value = p.value = ""; + } else + p.value = ""; + } + } + return toStr(tokens).replace(/[$]+/g, "$"); +} + +function convertCharacterTypes(str) { + var tokens = tokenize(str); + tokens = convertHexEscape(tokens); + + var warn = false; + tokens.forEach(function(t){ + if (t.type == "quantifier") { + var val = t.value; + if (val.slice(-1) == "+" && val.length > 1) { + t.value = val.slice(0, -1); + warn = val; + } + } + }); + if (warn) + console.log("converted possesive quantifier " + warn + " to *"); + return toStr(tokens); +} + +function removeInlineFlags(str, rule) { + var tokens = tokenize(str); + var caseInsensitive = false; + tokens.forEach(function(t, i) { + if (t.type == "group.start" && /[imsx]/.test(t.value)) { + if (/i/.test(t.value)) + caseInsensitive = true; + t.value = t.value.replace(/[imsx\-]/g, ""); + var next = tokens[i + 1]; + if (next && next.type == "group.end") { + t.value = next.value = ""; + } + } + }); + if (caseInsensitive && rule) + rule.caseInsensitive = true; + return toStr(tokens); +} + +function convertToNonCapturingGroups(str) { + var tokens = tokenize(str); + tokens.forEach(function(t, i) { + if (t.type == "group.start" && t.value == "(") + t.value += "?:"; + }); + return toStr(tokens); +} + +function simplifyNonCapturingGroups(str) { + var tokens = tokenize(str); + var t = tokens[0]; + if (t.type == "group.start" && t.value == "(?:" + && t.end == last(tokens)) { + t.value = t.end.value = ""; + } + var i = 0; + function iter(f) { + for (i = 0; i < tokens.length; i++) + f(tokens[i]); + } + function iterGroup(end, f) { + for (var i1 = i + 1; i1 < tokens.length; i1++) { + var t = tokens[i1]; + if (t == end) + break; + var index = f && f(t); + if (index > i1) + i1 = index; + } + return i1; + } + + iter(function (t) { + if (t.type == "group.start" && t.value == "(?:") { + if (!t.end) + return console.error("malformed regex: " + str); + + var canRemove = true; + var next = tokens[tokens.indexOf(t.end, i) + 1]; + if (next && next.type == "quantifier") + return; + iterGroup(t.end, function(t) { + if (t.type == "alternation") + canRemove = false; + else if (t.type == "group.start" && t.end) + return iterGroup(t.end); + }); + if (canRemove) + t.value = t.end.value = ""; + } + }); + + return toStr(tokens); +} + +function removeLookBehinds(str) { + var tokens = tokenize(str); + var toRemove = null; + tokens.forEach(function(t, i) { + if (!toRemove && t.type == "group.start" && / i) + i = i1; + } + function lst(t) {return t[t.length - 1]} + function iter(f) { + for (i = 0; i < tokens.length; i++) + f(tokens[i]); + } + function iterGroup(end, f) { + for (var i1 = i + 1; i1 < tokens.length; i1++) { + var t = tokens[i1]; + if (t == end) + break; + f(t); + } + } + function peek() { return tokens[i + 1] || {}} + + // groupify + iter(function(t){ + if (t.type == "group.start") { + tryClose(); + isStart = true; + if (!t.hasChildren || t.isSpecial) + skip(t); + } else if (t.type == "group.end") { + isStart = true; + tryClose(); + } else if (t.type == "alternation") { + isStart = true; + tryClose(); + } else if (t.type != "anchor" && t.type != "quantifier"){ + tryOpen(); + } + }); + tryClose(); + + // remove redundand groups + var names = [defaultName]; + iter(function(t){ + if (t.type == "group.start" && !t.isSpecial) { + var captureName = captures[t.number]; + + if (!t.hasChildren) { + t.tokenName = captureName || lst(names); + skip(t); + } else { + var hasCapture = false; + iterGroup(t.end, function(t1) { + if (t1.type == "group.start" && captures[t1.number]) + hasCapture = true; + }); + if (hasCapture) { + t.value = "(?:"; + if (captureName) { + names.push(captureName); + t.isTokenGroup = true; + } + } else { + t.tokenName = captureName || lst(names); + iterGroup(t.end, function(t1) { + if (t1.value == "(") + t1.value = "(?:"; + }); + } + } + } else if (t.type == "group.end") { + if (t.start.isTokenGroup) + names.pop(); + } + }); + + // wrap capturing groups with quantifier + iter(function(t){ + if (t.type == "group.end" && t.start.value == "(" && peek().type == "quantifier") { + peek().value += ")"; + t.start.value += "(?:"; + } + }); + + names = []; + tokens.forEach(function(t) { + if (t.value == "(" || t.value == "((?:" ) + t.tokenName && names.push(t.tokenName); + }); + return { + names: names, + regex: toStr(tokens) + }; +} + +/***** converter */ function logDebug(string, obj) { console.log(string, obj); @@ -13,7 +335,6 @@ function logDebug(string, obj) { // for tracking token states var states = {start: []}; -var stateName = "start"; function processRules(rules){ if (rules.patterns) @@ -40,29 +361,38 @@ function processPatterns(pl) { return pl.map(processPattern); } function processPattern(p) { - if (p.end == "(?!\\G)" && p.patterns && p.patterns.length == 1) { var rule = processPattern(p.patterns[0]); } - else if (p.begin && p.end) { - var rule = simpleRule(p.begin, p.name, p.beginCaptures || p.captures) + else if (p.begin != null && p.end != null) { + convertBeginEndBackrefs(p); + + var rule = simpleRule(p.begin, p.name, p.beginCaptures || p.captures); var next = processPatterns(p.patterns || []); var endRule = simpleRule(p.end, p.name, p.endCaptures || p.captures); endRule.next = "pop"; - next.push(endRule); + if (p.applyEndPatternLast) + next.push(endRule); + else + next.unshift(endRule); if (p.name || p.contentName) next.push({defaultToken: p.name || p.contentName}); rule.push = next; + + rule = removeIncludeSelf(rule); } else if (p.match) { - var rule = simpleRule(p.match, p.name, p.captures) + var rule = simpleRule(p.match, p.name, p.captures); } else if (p.include) { var rule = {include: p.include}; } + else { + var rule = {todo: p}; + } if (p.comment) rule.comment = (rule.comment || "") + p.comment; @@ -73,106 +403,269 @@ function processPattern(p) { } function simpleRule(regex, name, captures) { name = name || "text"; - var rule = {}; + var rule = {token: "", regex: ""}; - var origRegex = regex - regex = transformRegExp(regex, rule); + var origRegex = regex; + regex = transformRegExp(origRegex, rule); if (captures) { var tokenArray = []; Object.keys(captures).forEach(function(x){ tokenArray[x] = captures[x] && captures[x].name; }); + if (tokenArray.length == 1) { name = tokenArray[0]; } else { - for (var i = 0; i < tokenArray.length; i++) - if (!tokenArray[i]) - tokenArray[i] = name; - name = tokenArray; - rule.todo = "fix grouping"; + var fixed = fixGroups(tokenArray, name, regex); + name = fixed.names; + regex = fixed.regex; + if (name.length == 1) + name = name[0]; } } + if (typeof name == "string") + regex = convertToNonCapturingGroups(regex); + + regex = simplifyNonCapturingGroups(regex); + try {new RegExp(regex);} catch(e) { rule.TODO = "FIXME: regexp doesn't have js equivalent"; - rule.originalRegex = origRegex + rule.originalRegex = origRegex; + + // lookbehinds are mostly used to force ordering + // regex = removeLookBehinds(regex); } rule.token = name; rule.regex = regex; return rule; } +function removeIncludeSelf(rule) { + if (!rule.push) + return rule; + var hasSelfInclude = false; + var escapeRule = null; + var complexSelfInclude = false; + rule.push.forEach(function(sub) { + if (sub.include == "$self") { + hasSelfInclude = true; + } else if (sub.defaultToken) { + return; + } else if (sub.next == "pop") { + escapeRule = sub; + } else + complexSelfInclude = true; + }); + + if (hasSelfInclude) { + console.warn("can't convert include $self"); + return {todo: rule}; + + if (complexSelfInclude) { + console.warn("can't convert include $self"); + rule.toDo = "include $self not fully supported"; + return rule; + } + console.warn("include $self not fully supported"); + delete rule.push; + delete escapeRule.next; + rule.includeSelf = true; + escapeRule.includeSelf = true; + return [rule, escapeRule]; + } + return rule; +} // regex transformation function removeXFlag(str) { - if (str && str.slice(0,4) == "(?x)") { - str = str.replace(/\\.|\[([^\]\\]|\\.)*?\]|\s+|(?:#[^\n]*)/g, function(s) { - if (s[0] == "[") - return s; - if (s[0] == "\\") - return /[#\s]/.test(s[1]) ? s[1] : s; - return ""; - }).substr(4); - } - return str; + var tokens = tokenize(str); + return toStr(tokens); } function transformRegExp(str, rule) { - str = removeXFlag(str); - //str = str.replace(/\\n\$|\$\\n/g, '$'); - str = str.replace(/\\n(?!\?).?/g, '$'); // replace newlines by $ except if its postfixed by ? - if (/\(\?[i]\:|\(?\w*i\w*\)/g.test(str)) { - str = str.replace(/\(\?[ims\-]\:/g, "(?:"); // checkForInvariantRegex - str = str.replace(/\(\?[imsx\-]\)/g, ""); - rule && (rule.caseInsensitive = true); - } + str = convertNewLinesTo$(str); + + str = removeInlineFlags(str, rule); + str = str.replace(/(\\[xu]){([a-fA-F\d]+)}/g, '$1$2'); + + str = convertCharacterTypes(str, rule); + + checkForNamedCaptures(str); + return str; } // function extractPatterns(tmRules) { - var patterns = processRules(tmRules); - return lib.restoreJSONComments(lib.formatJSON(patterns, " ")); - + return processRules(tmRules); } +function detectLoops(states) { + var data = {}; + var keys = Object.keys(states); + var flattenedStates = {}; + function addRef(item, name) { + if (item.refs.indexOf(name) == -1) + item.refs.push(name); + } + function anonStateId(name, next) { + var i = 0, old = name; + while (flattenedStates[name] || states[name]) { + name = old + "_" + i++; + } + // console.log(old, name) + return name; + } + function addState(key, rules) { + if (rules && !flattenedStates[key]) + flattenedStates[key] = rules; + return rules || flattenedStates[key]; + } + + + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + var state = addState(key, states[key]); + + var item = data[key] || (data[key] = {/* name: key, */ refs: []}); + state.forEach(function(rule) { + var next = rule.push || rule.next; + if (next == "pop") { + // nothing + } else if (typeof next == "string") { + addRef(item, next); + } else if (next) { + var anonId = anonStateId(key, next); + addState(anonId, next); + if (rule.push) + addRef(item, anonId); + keys.push(anonId); + } else if (rule.include) { + addRef(item, rule.include); + } + }); + } + + + var cycles = []; + function addPath(start, path) { + var node = data[start]; + path.push(start); + if (!node || !node.refs) + console.log(start); + var i = path.indexOf(start); + if (i > -1 && i != path.length - 1 || start == "$self" || start == "$base") { + if (i != -1) + path = path.slice(i); + for (var j = 0; j < cycles.length; j++) { + if (cycles[j] + "" == path + "") + return; + } + return cycles.push(path); + } + + if (!node || !node.refs || !node.refs.length || path.length>30) + return; + node.refs.forEach(function(x) { + addPath(x, path.concat()); + }); + } + addPath("start", []); + + console.error(cycles.join("\n")); +} + + +function test(fileName) { + console.log("testing highlighter"); + try { + var module = require(fileName); + var Mode = module[Object.keys(module)[0]]; + var mode = new Mode(); + mode.getTokenizer().getLineTokens("hello world"); + } catch(e) { + console.log(e); + } +} + +function guessComment(patterns) { + var comment = {}; + for (var i in patterns) { + var state = patterns[i]; + state.forEach(function(r) { + if (typeof r.token == "string") { + if (/\bcomment\b/.test(r.token)) { + comment.line = r.regex; + } + } + }); + } + + return comment; +} // cli stuff var modeTemplate = fs.readFileSync(__dirname + "/templates/mode.js", "utf8"); var modeHighlightTemplate = fs.readFileSync(__dirname + "/templates/highlight_rules.js", "utf8"); -function convertLanguageFile(name) { - var path = /^(\/|\w:)/.test(name) ? name : process.cwd() + "/" + name - var tmLanguage = fs.readFileSync(path, "utf8"); - parseLanguage(tmLanguage, function(language) { - var languageHighlightFilename = language.name.replace(/[-_]/g, "").toLowerCase(); - var languageNameSanitized = language.name.replace(/-/g, ""); +function fetchAndConvert(name) { + console.log("Converting " + name); + if (/^http/.test(name)) { + if (/:\/\/github.com/.test(name)) { + name = name.replace(/\/blob\//, "/").replace("github.com", "raw.github.com"); + } + return lib.download(name, function(data) { + convertTmLanguage(name, data); + }); + } + var path = /^(\/|\w:)/.test(name) ? name : process.cwd() + "/" + name; + var langStr = fs.readFileSync(path, "utf8"); + convertTmLanguage(name, langStr); +} - var languageHighlightFile = __dirname + "/../lib/ace/mode/" + languageHighlightFilename + "_highlight_rules.js"; - var languageModeFile = __dirname + "/../lib/ace/mode/" + languageHighlightFilename + ".js"; - console.log("Converting " + name + " to " + languageHighlightFile); +function convertTmLanguage(name, langStr) { + parseLanguage(langStr, function(language) { + var highlighterFilename = lib.snakeCase(language.name).replace(/[^\w]/g, ""); + var languageNameSanitized = lib.camelCase(language.name).replace(/[^\w]/g, ""); + + require("./add_mode")(languageNameSanitized, (language.fileTypes || []).join("|")); + + var highlighterFile = pathlib.normalize(lib.AceLib + "ace/mode/" + highlighterFilename + "_highlight_rules.js"); + var modeFile = pathlib.normalize(lib.AceLib + "ace/mode/" + highlighterFilename + ".js"); if (devMode) { console.log(util.inspect(language.patterns, false, 4)); console.log(util.inspect(language.repository, false, 4)); } + var patterns = extractPatterns(language); + detectLoops(patterns); + + // var uuid = language.uuid + delete language.uuid; + delete language.patterns; + delete language.repository; + + var comment = guessComment(patterns); var languageMode = lib.fillTemplate(modeTemplate, { language: languageNameSanitized, - languageHighlightFilename: languageHighlightFilename + languageHighlightFilename: highlighterFilename, + lineCommentStart: JSON.stringify(comment.line || "//"), + blockCommentStart: JSON.stringify(comment.start || "/*"), + blockCommentEnd: JSON.stringify(comment.end || "*/") }); - var patterns = extractPatterns(language); - var languageHighlightRules = lib.fillTemplate(modeHighlightTemplate, { language: languageNameSanitized, - languageTokens: patterns.trim(), + languageTokens: lib.formatJSON(patterns, " ").trim(), uuid: language.uuid, - name: name + name: name, + metaData: lib.formatJSON(language, " ").trim() }); if (devMode) { @@ -181,17 +674,23 @@ function convertLanguageFile(name) { console.log("Not writing, 'cause we're in dev mode, baby."); } else { - fs.writeFileSync(languageHighlightFile, languageHighlightRules); - fs.writeFileSync(languageModeFile, languageMode); + fs.writeFileSync(highlighterFile, languageHighlightRules); + fs.writeFileSync(modeFile, languageMode); + console.log("created file " + highlighterFile); + test(modeFile); } }); } -var args = process.argv.splice(2); -var tmLanguageFile = args[0]; -var devMode = args[1]; -if (tmLanguageFile === undefined) { - console.error("Please pass in a language file via the command line."); - process.exit(1); +if (!module.parent) { + var args = process.argv.splice(2); + var tmLanguageFile = args[0]; + var devMode = args[1]; + if (tmLanguageFile === undefined) { + console.error("Usage: node tmlanguage.js path/or/url/to/syntax.file"); + process.exit(1); + } + fetchAndConvert(tmLanguageFile); +} else { + exports.fetchAndConvert = fetchAndConvert; } -convertLanguageFile(tmLanguageFile); \ No newline at end of file From 43cdfd792f2f2475108617f469ea0928c3612d43 Mon Sep 17 00:00:00 2001 From: nightwing Date: Fri, 10 Oct 2014 23:54:19 +0400 Subject: [PATCH 23/25] Add HACK and XXX --- lib/ace/mode/doc_comment_highlight_rules.js | 15 ++++++++++----- lib/ace/mode/javascript_highlight_rules.js | 8 ++++---- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/ace/mode/doc_comment_highlight_rules.js b/lib/ace/mode/doc_comment_highlight_rules.js index 2f10d1bb..11b4e813 100644 --- a/lib/ace/mode/doc_comment_highlight_rules.js +++ b/lib/ace/mode/doc_comment_highlight_rules.js @@ -35,15 +35,13 @@ var oop = require("../lib/oop"); var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules; var DocCommentHighlightRules = function() { - this.$rules = { "start" : [ { token : "comment.doc.tag", regex : "@[\\w\\d_]+" // TODO: fix email addresses - }, { - token : "comment.doc.tag", - regex : "\\b(?:TODO|FIXME)\\b" - }, { + }, + DocCommentHighlightRules.getTagRule(), + { defaultToken : "comment.doc", caseInsensitive: true }] @@ -52,6 +50,13 @@ var DocCommentHighlightRules = function() { oop.inherits(DocCommentHighlightRules, TextHighlightRules); +DocCommentHighlightRules.getTagRule = function(start) { + return { + token : "comment.doc.tag.storage.type", + regex : "\\b(?:TODO|FIXME|XXX|HACK)\\b" + }; +} + DocCommentHighlightRules.getStartRule = function(start) { return { token : "comment.doc", // doc comment diff --git a/lib/ace/mode/javascript_highlight_rules.js b/lib/ace/mode/javascript_highlight_rules.js index 151b3168..767b8fa1 100644 --- a/lib/ace/mode/javascript_highlight_rules.js +++ b/lib/ace/mode/javascript_highlight_rules.js @@ -302,22 +302,22 @@ var JavaScriptHighlightRules = function(options) { } ], "comment_regex_allowed" : [ - {token : "storage.type", regex : "\\b(?:TODO|FIXME)\\b"}, + DocCommentHighlightRules.getTagRule(), {token : "comment", regex : "\\*\\/", next : "start"}, {defaultToken : "comment", caseInsensitive: true} ], "comment" : [ - {token : "storage.type", regex : "\\b(?:TODO|FIXME)\\b"}, + DocCommentHighlightRules.getTagRule(), {token : "comment", regex : "\\*\\/", next : "no_regex"}, {defaultToken : "comment", caseInsensitive: true} ], "line_comment_regex_allowed" : [ - {token : "storage.type", regex : "\\b(?:TODO|FIXME)\\b"}, + DocCommentHighlightRules.getTagRule(), {token : "comment", regex : "$|^", next : "start"}, {defaultToken : "comment", caseInsensitive: true} ], "line_comment" : [ - {token : "storage.type", regex : "\\b(?:TODO|FIXME)\\b"}, + DocCommentHighlightRules.getTagRule(), {token : "comment", regex : "$|^", next : "no_regex"}, {defaultToken : "comment", caseInsensitive: true} ], From f8bf6f253aa934090561524d71410464afdccf06 Mon Sep 17 00:00:00 2001 From: Max Heinritz Date: Mon, 13 Oct 2014 11:54:33 -0700 Subject: [PATCH 24/25] Fix errors in Anchor docs At least, I think they are errors. --- lib/ace/anchor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ace/anchor.js b/lib/ace/anchor.js index 4e330ef6..58a4cb06 100644 --- a/lib/ace/anchor.js +++ b/lib/ace/anchor.js @@ -36,7 +36,7 @@ var EventEmitter = require("./lib/event_emitter").EventEmitter; /** * - * Defines the floating pointer in the document. Whenever text is inserted or deleted before the cursor, the position of the cursor is updated. + * Defines a floating pointer in the document. Whenever text is inserted or deleted before the cursor, the position of the anchor is updated. * * @class Anchor **/ From c97858705db0652eb2e80be92e886730ce8556db Mon Sep 17 00:00:00 2001 From: nightwing Date: Fri, 17 Oct 2014 15:45:42 +0400 Subject: [PATCH 25/25] cleanup --- ChangeLog.txt | 5 +++++ lib/ace/anchor.js | 2 +- lib/ace/commands/command_manager.js | 4 ++-- lib/ace/edit_session.js | 6 +++--- lib/ace/lib/lang.js | 1 - lib/ace/scrollbar.js | 8 ++++---- 6 files changed, 15 insertions(+), 11 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index 8e1d8524..7c3db96d 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,8 @@ + +* API Changes + - `editor.commands.commandKeyBinding` now contains direct map from keys to commands instead of grouping them by hashid + - + 2014.09.21 Version 1.1.7 * Bugfixes diff --git a/lib/ace/anchor.js b/lib/ace/anchor.js index 58a4cb06..9f5e159d 100644 --- a/lib/ace/anchor.js +++ b/lib/ace/anchor.js @@ -201,7 +201,7 @@ var Anchor = exports.Anchor = function(doc, row, column) { }; /** - * When called, the `'change'` event listener is removed. + * When called, the `"change"` event listener is removed. * **/ this.detach = function() { diff --git a/lib/ace/commands/command_manager.js b/lib/ace/commands/command_manager.js index 7b017ed2..df30ef9b 100644 --- a/lib/ace/commands/command_manager.js +++ b/lib/ace/commands/command_manager.js @@ -12,7 +12,7 @@ var EventEmitter = require("../lib/event_emitter").EventEmitter; /** * new CommandManager(platform, commands) - * @param {String} platform Identifier for the platform; must be either `'mac'` or `'win'` + * @param {String} platform Identifier for the platform; must be either `"mac"` or `"win"` * @param {Array} commands A list of commands * **/ @@ -39,7 +39,7 @@ oop.inherits(CommandManager, MultiHashHandler); return false; } - if (typeof command === 'string') + if (typeof command === "string") command = this.commands[command]; if (!command) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 71cda95c..3ef0d64c 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1601,7 +1601,7 @@ var EditSession = function(text, mode) { * @private **/ this.adjustWrapLimit = function(desiredLimit, $printMargin) { - var limits = this.$wrapLimitRange + var limits = this.$wrapLimitRange; if (limits.max < 0) limits = {min: $printMargin, max: $printMargin}; var wrapLimit = this.$constrainWrapLimit(desiredLimit, limits.min, limits.max); @@ -1725,7 +1725,7 @@ var EditSession = function(text, mode) { var foldLine = this.getFoldLine(firstRow); var idx = 0; if (foldLine) { - var cmp = foldLine.range.compareInside(start.row, start.column) + var cmp = foldLine.range.compareInside(start.row, start.column); // Inside of the foldLine range. Need to split stuff up. if (cmp == 0) { foldLine = foldLine.split(start.row, start.column); @@ -2201,7 +2201,7 @@ var EditSession = function(text, mode) { return { row: maxRow, column: this.getLine(maxRow).length - } + }; } else { line = this.getLine(docRow); foldLine = null; diff --git a/lib/ace/lib/lang.js b/lib/ace/lib/lang.js index d6a98149..863bbb55 100644 --- a/lib/ace/lib/lang.js +++ b/lib/ace/lib/lang.js @@ -150,7 +150,6 @@ exports.getMatchOffsets = function(string, regExp) { /* deprecated */ exports.deferredCall = function(fcn) { - var timer = null; var callback = function() { timer = null; diff --git a/lib/ace/scrollbar.js b/lib/ace/scrollbar.js index 28f41fe4..466d1af1 100644 --- a/lib/ace/scrollbar.js +++ b/lib/ace/scrollbar.js @@ -156,9 +156,9 @@ oop.inherits(VScrollBar, ScrollBar); * Sets the scroll top of the scroll bar. * @param {Number} scrollTop The new scroll top **/ - // on chrome 17+ for small zoom levels after calling this function - // this.element.scrollTop != scrollTop which makes page to scroll up. this.setScrollTop = function(scrollTop) { + // on chrome 17+ for small zoom levels after calling this function + // this.element.scrollTop != scrollTop which makes page to scroll up. if (this.scrollTop != scrollTop) { this.skipEvent = true; this.scrollTop = this.element.scrollTop = scrollTop; @@ -249,9 +249,9 @@ oop.inherits(HScrollBar, ScrollBar); * Sets the scroll left of the scroll bar. * @param {Number} scrollTop The new scroll left **/ - // on chrome 17+ for small zoom levels after calling this function - // this.element.scrollTop != scrollTop which makes page to scroll up. this.setScrollLeft = function(scrollLeft) { + // on chrome 17+ for small zoom levels after calling this function + // this.element.scrollTop != scrollTop which makes page to scroll up. if (this.scrollLeft != scrollLeft) { this.skipEvent = true; this.scrollLeft = this.element.scrollLeft = scrollLeft;