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/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/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/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/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/anchor.js b/lib/ace/anchor.js index 3431b3c4..f3a60277 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 **/ 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/commands/command_manager.js b/lib/ace/commands/command_manager.js index 537942ab..df30ef9b 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/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"] + ""); } }; diff --git a/lib/ace/edit_session/folding.js b/lib/ace/edit_session/folding.js index 99983d5f..d5052266 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) { diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 587cb50d..97c4a8e6 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -115,30 +115,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)); @@ -173,18 +151,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); @@ -255,19 +231,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(); } }; @@ -662,13 +638,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 +653,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() { @@ -927,9 +903,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); }; /** @@ -2541,7 +2516,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/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; +}; }); diff --git a/lib/ace/ext/language_tools.js b/lib/ace/ext/language_tools.js index d8b2a1dd..4687efd5 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,15 +60,29 @@ 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(""); + } } }; 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) { + completers = val || []; +}; exports.addCompleter = function(completer) { completers.push(completer); }; @@ -80,9 +95,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/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; diff --git a/lib/ace/ext/modelist.js b/lib/ace/ext/modelist.js index 3ffac26b..3f85c5b3 100644 --- a/lib/ace/ext/modelist.js +++ b/lib/ace/ext/modelist.js @@ -65,8 +65,11 @@ var supportedModes = { Diff: ["diff|patch"], Dockerfile: ["^Dockerfile"], Dot: ["dot"], + Dummy: ["dummy"], + DummySyntax: ["dummy"], Eiffel: ["e"], EJS: ["ejs"], + Elm: ["elm"], Erlang: ["erl|hrl"], Forth: ["frt|fs|ldr"], FTL: ["ftl"], 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; diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index df14b9a1..ab4471b2 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/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(); }; diff --git a/lib/ace/lib/keys.js b/lib/ace/lib/keys.js index e75a6e47..722c544d 100644 --- a/lib/ace/lib/keys.js +++ b/lib/ace/lib/keys.js @@ -145,6 +145,9 @@ var Keys = (function() { } })(); + ret.KEY_MODS[0] = ""; + ret.KEY_MODS[-1] = "input"; + return ret; })(); oop.mixin(exports, Keys); diff --git a/lib/ace/line_widgets.js b/lib/ace/line_widgets.js index 7ee76d48..4e5ae39d 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; 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/_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 +]] 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/doc_comment_highlight_rules.js b/lib/ace/mode/doc_comment_highlight_rules.js index 5262a8e8..11b4e813 100644 --- a/lib/ace/mode/doc_comment_highlight_rules.js +++ b/lib/ace/mode/doc_comment_highlight_rules.js @@ -35,22 +35,28 @@ 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 : "\\bTODO\\b" - }, { - defaultToken : "comment.doc" + }, + DocCommentHighlightRules.getTagRule(), + { + defaultToken : "comment.doc", + caseInsensitive: true }] }; }; 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/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; +}); 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/javascript_highlight_rules.js b/lib/ace/mode/javascript_highlight_rules.js index e81a2dce..767b8fa1 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" : [ + DocCommentHighlightRules.getTagRule(), {token : "comment", regex : "\\*\\/", next : "start"}, - {defaultToken : "comment"} + {defaultToken : "comment", caseInsensitive: true} ], "comment" : [ + DocCommentHighlightRules.getTagRule(), {token : "comment", regex : "\\*\\/", next : "no_regex"}, - {defaultToken : "comment"} + {defaultToken : "comment", caseInsensitive: true} ], "line_comment_regex_allowed" : [ + DocCommentHighlightRules.getTagRule(), {token : "comment", regex : "$|^", next : "start"}, - {defaultToken : "comment"} + {defaultToken : "comment", caseInsensitive: true} ], "line_comment" : [ + DocCommentHighlightRules.getTagRule(), {token : "comment", regex : "$|^", next : "no_regex"}, - {defaultToken : "comment"} + {defaultToken : "comment", caseInsensitive: true} ], "qqstring" : [ { 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/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: 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; 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) 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/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 430699e6..255a2cea 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; }; 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 3eda8c8c..6b1122cd 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 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.tmpl.js b/tool/templates/highlight_rules.js similarity index 79% rename from tool/templates/highlight_rules.tmpl.js rename to tool/templates/highlight_rules.js index 82b22c07..db8f5630 100644 --- a/tool/templates/highlight_rules.tmpl.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/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/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 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 + +