diff --git a/.gitignore b/.gitignore index 813d6c10..c6a7ecc9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ # Project files that should not be in the repo .* +\#* !/.gitignore .*.gz *.tmTheme.js diff --git a/demo/kitchen-sink/demo.js b/demo/kitchen-sink/demo.js index 1b12175e..3a1c6d63 100644 --- a/demo/kitchen-sink/demo.js +++ b/demo/kitchen-sink/demo.js @@ -34,8 +34,11 @@ define(function(require, exports, module) { require("ace/lib/fixoldbrowsers"); -require("ace/multi_select") +require("ace/multi_select"); require("ace/ext/spellcheck"); +require("./inline_editor"); +require("./dev_util"); +require("./file_drop"); var config = require("ace/config"); config.init(); @@ -58,6 +61,8 @@ var Editor = require("ace/editor").Editor; var whitespace = require("ace/ext/whitespace"); + + var doclist = require("./doclist"); var modelist = require("ace/ext/modelist"); var layout = require("./layout"); @@ -160,11 +165,11 @@ env.editor.commands.addCommands([{ bindKey: "ctrl+enter", exec: function(editor) { try { - var r = window.eval(editor.getCopyText()||editor.getValue()); + var r = window.eval(editor.getCopyText() || editor.getValue()); } catch(e) { r = e; } - editor.cmdLine.setValue(r + "") + editor.cmdLine.setValue(r + ""); }, readOnly: true }, { @@ -173,8 +178,8 @@ env.editor.commands.addCommands([{ exec: function(editor) { config.loadModule("ace/ext/keybinding_menu", function(module) { module.init(editor); - editor.showKeyboardShortcuts() - }) + editor.showKeyboardShortcuts(); + }); } }, { name: "increaseFontSize", @@ -220,7 +225,7 @@ commands.addCommand({ bindKey: {win: "Ctrl-S", mac: "Command-S"}, exec: function(arg) { var session = env.editor.session; - name = session.name.match(/[^\/]+$/) + var name = session.name.match(/[^\/]+$/); localStorage.setItem( "saved_file:" + name, session.getValue() @@ -234,7 +239,7 @@ commands.addCommand({ bindKey: {win: "Ctrl-O", mac: "Command-O"}, exec: function(arg) { var session = env.editor.session; - name = session.name.match(/[^\/]+$/) + var name = session.name.match(/[^\/]+$/); var value = localStorage.getItem("saved_file:" + name); if (typeof value == "string") { session.setValue(value); @@ -309,7 +314,7 @@ doclist.history = doclist.docs.map(function(doc) { }); doclist.history.index = 0; doclist.cycleOpen = function(editor, dir) { - var h = this.history + var h = this.history; h.index += dir; if (h.index >= h.length) h.index = 0; @@ -318,17 +323,16 @@ doclist.cycleOpen = function(editor, dir) { var s = h[h.index]; docEl.value = s; docEl.onchange(); - h.index -} +}; doclist.addToHistory = function(name) { - var h = this.history + var h = this.history; var i = h.indexOf(name); if (i != h.index) { if (i != -1) h.splice(i, 1); h.index = h.push(name); } -} +}; bindDropdown("doc", function(name) { doclist.loadDoc(name, function(session) { @@ -367,15 +371,15 @@ function updateUIEditorOptions() { } event.addListener(themeEl, "mouseover", function(e){ - this.desiredValue = e.target.value; - if (!this.$timer) - this.$timer = setTimeout(this.updateTheme); + themeEl.desiredValue = e.target.value; + if (!themeEl.$timer) + themeEl.$timer = setTimeout(themeEl.updateTheme); }); event.addListener(themeEl, "mouseout", function(e){ - this.desiredValue = null; - if (!this.$timer) - this.$timer = setTimeout(this.updateTheme, 20); + themeEl.desiredValue = null; + if (!themeEl.$timer) + themeEl.$timer = setTimeout(themeEl.updateTheme, 20); }); themeEl.updateTheme = function(){ @@ -532,44 +536,6 @@ bindCheckbox("highlight_token", function(checked) { } }); - -/************** dragover ***************************/ -event.addListener(container, "dragover", function(e) { - var types = e.dataTransfer.types; - if (types && Array.prototype.indexOf.call(types, 'Files') !== -1) - return event.preventDefault(e); -}); - -event.addListener(container, "drop", function(e) { - var file; - try { - file = e.dataTransfer.files[0]; - if (window.FileReader) { - var reader = new FileReader(); - reader.onload = function() { - var mode = modelist.getModeForPath(file.name); - - env.editor.session.doc.setValue(reader.result); - modeEl.value = mode.name; - env.editor.session.setMode(mode.mode); - env.editor.session.modeName = mode.name; - }; - reader.readAsText(file); - } - return event.preventDefault(e); - } catch(err) { - return event.stopEvent(e); - } -}); - - - - - - - - - var StatusBar = require("ace/ext/statusbar").StatusBar; new StatusBar(env.editor, cmdLine.container); @@ -601,68 +567,25 @@ env.editSnippets = function() { var text = m.snippetText; var s = doclist.initDoc(text, "", {}); s.setMode("ace/mode/snippets"); - doclist["snippets/" + id] = s + doclist["snippets/" + id] = s; } editor.on("blur", function() { m.snippetText = editor.getValue(); snippetManager.unregister(m.snippets); m.snippets = snippetManager.parseSnippetFile(m.snippetText, m.scope); snippetManager.register(m.snippets); - }) + }); sp.$editors[0].once("changeMode", function() { sp.setSplits(1); - }) + }); editor.setSession(doclist["snippets/" + id], 1); editor.focus(); -} +}; require("ace/ext/language_tools"); env.editor.setOptions({ enableBasicAutocompletion: true, enableSnippets: true -}) -/* for textinput debuggging -dom.importCssString("\ - .ace_text-input {\ - position: absolute;\ - z-index: 10!important;\ - width: 6em!important;\ - height: 1em;\ - opacity: 1!important;\ - background: rgba(0, 92, 255, 0.11);\ - border: none;\ - font: inherit;\ - padding: 0 1px;\ - margin: 0 -1px;\ - text-indent: 0em;\ -}\ -")*/ }); -// allow easy access to ace in console, but not in ace code which uses strict -void function() { -function isStrict() { - try { return !arguments.callee.caller.caller.caller} - catch(e){ return true } -} -function warn() { - if (isStrict()) { - console.error("trying to access to global variable"); - } -} -function def(o, key, get) { - Object.defineProperty(o, key, { - configurable: true, - get: get, - set: function(val) { - delete o[key]; - o[key] = val; - } - }); -} -def(window, "ace", function(){ warn(); return env.editor }); -def(window, "editor", function(){ warn(); return env.editor }); -def(window, "session", function(){ warn(); return env.editor.session }); -def(window, "split", function(){ warn(); return env.split }); - -}(); \ No newline at end of file +}); \ No newline at end of file diff --git a/demo/kitchen-sink/dev_util.js b/demo/kitchen-sink/dev_util.js new file mode 100644 index 00000000..dfe3d942 --- /dev/null +++ b/demo/kitchen-sink/dev_util.js @@ -0,0 +1,75 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Distributed under the BSD license: + * + * Copyright (c) 2010, Ajax.org B.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Ajax.org B.V. nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ***** END LICENSE BLOCK ***** */ + +define(function(require, exports, module) { +// allow easy access to ace in console, but not in ace code which uses strict +function isStrict() { + try { return !arguments.callee.caller.caller.caller} + catch(e){ return true } +} +function warn() { + if (isStrict()) { + console.error("trying to access to global variable"); + } +} +function def(o, key, get) { + Object.defineProperty(o, key, { + configurable: true, + get: get, + set: function(val) { + delete o[key]; + o[key] = val; + } + }); +} +def(window, "ace", function(){ warn(); return window.env.editor }); +def(window, "editor", function(){ warn(); return window.env.editor }); +def(window, "session", function(){ warn(); return window.env.editor.session }); +def(window, "split", function(){ warn(); return window.env.split }); + + +/* for textinput debuggging +dom.importCssString("\ + .ace_text-input {\ + position: absolute;\ + z-index: 10!important;\ + width: 6em!important;\ + height: 1em;\ + opacity: 1!important;\ + background: rgba(0, 92, 255, 0.11);\ + border: none;\ + font: inherit;\ + padding: 0 1px;\ + margin: 0 -1px;\ + text-indent: 0em;\ +}\ +")*/ + +}); diff --git a/demo/kitchen-sink/doclist.js b/demo/kitchen-sink/doclist.js index 3abdf664..2aeafb55 100644 --- a/demo/kitchen-sink/doclist.js +++ b/demo/kitchen-sink/doclist.js @@ -95,7 +95,7 @@ modelist.modes.forEach(function(m) { if (ext[0] === "^") { path = ext.substr(1); } else { - var path = m.name + "." + ext + var path = m.name + "." + ext; } path = "docs/" + path; if (!docs[path]) { @@ -103,7 +103,7 @@ modelist.modes.forEach(function(m) { } else if (typeof docs[path] == "object" && !docs[path].name) { docs[path].name = m.caption; } -}) +}); @@ -121,7 +121,7 @@ function sort(list) { return list.sort(function(a, b) { var cmp = (b.order || 0) - (a.order || 0); return cmp || a.name && a.name.localeCompare(b.name); - }) + }); } function prepareDocList(docs) { diff --git a/demo/kitchen-sink/file_drop.js b/demo/kitchen-sink/file_drop.js new file mode 100644 index 00000000..8b89d5f1 --- /dev/null +++ b/demo/kitchen-sink/file_drop.js @@ -0,0 +1,73 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Distributed under the BSD license: + * + * Copyright (c) 2010, Ajax.org B.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Ajax.org B.V. nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ***** END LICENSE BLOCK ***** */ + +define(function(require, exports, module) { + +var config = require("ace/config"); +var event = require("ace/lib/event"); +var modelist = require("ace/ext/modelist"); + +module.exports = function(editor) { + event.addListener(editor.container, "dragover", function(e) { + var types = e.dataTransfer.types; + if (types && Array.prototype.indexOf.call(types, 'Files') !== -1) + return event.preventDefault(e); + }); + + event.addListener(editor.container, "drop", function(e) { + var file; + try { + file = e.dataTransfer.files[0]; + if (window.FileReader) { + var reader = new FileReader(); + reader.onload = function() { + var mode = modelist.getModeForPath(file.name); + editor.session.doc.setValue(reader.result); + editor.session.setMode(mode.mode); + editor.session.modeName = mode.name; + }; + reader.readAsText(file); + } + return event.preventDefault(e); + } catch(err) { + return event.stopEvent(e); + } + }); +}; + +var Editor = require("ace/editor").Editor; +config.defineOptions(Editor.prototype, "editor", { + loadDroppedFile: { + set: function() { module.exports(this); }, + value: true + } +}); + +}); \ No newline at end of file diff --git a/demo/kitchen-sink/inline_editor.js b/demo/kitchen-sink/inline_editor.js new file mode 100644 index 00000000..0c050aed --- /dev/null +++ b/demo/kitchen-sink/inline_editor.js @@ -0,0 +1,105 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Distributed under the BSD license: + * + * Copyright (c) 2010, Ajax.org B.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Ajax.org B.V. nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ***** END LICENSE BLOCK ***** */ + + +define(function(require, exports, module) { +"use strict"; + +var LineWidgets = require("ace/line_widgets").LineWidgets; +var Editor = require("ace/editor").Editor; +var Renderer = require("ace/virtual_renderer").VirtualRenderer; +var dom = require("ace/lib/dom"); + + +require("ace/commands/default_commands").commands.push({ + name: "openInlineEditor", + bindKey: "F3", + exec: function(editor) { + var split = window.env.split; + var s = editor.session; + var inlineEditor = new Editor(new Renderer()); + var splitSession = split.$cloneSession(s); + + var row = editor.getCursorPosition().row; + if (editor.session.lineWidgets && editor.session.lineWidgets[row]) { + editor.session.lineWidgets[row].destroy(); + return; + } + + var rowCount = 10; + var w = { + row: row, + // rowCount: rowCount, + fixedWidth: true, + el: dom.createElement("div"), + editor: editor + }; + var el = w.el; + el.appendChild(inlineEditor.container); + + if (!editor.session.widgetManager) { + editor.session.widgetManager = new LineWidgets(editor.session); + editor.session.widgetManager.attach(editor); + } + + var h = rowCount*editor.renderer.layerConfig.lineHeight; + inlineEditor.container.style.height = h + "px"; + + el.style.position = "absolute"; + el.style.zIndex = "4"; + el.style.borderTop = "solid blue 2px"; + el.style.borderBottom = "solid blue 2px"; + + inlineEditor.setSession(splitSession); + editor.session.widgetManager.addLineWidget(w); + + var kb = { + handleKeyboard:function(_,hashId, keyString) { + if (hashId === 0 && keyString === "esc") { + w.destroy(); + return true; + } + } + }; + + w.destroy = function() { + editor.keyBinding.removeKeyboardHandler(kb); + s.widgetManager.removeLineWidget(w); + }; + + 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/token_tooltip.js b/demo/kitchen-sink/token_tooltip.js index b16966c2..f182824a 100644 --- a/demo/kitchen-sink/token_tooltip.js +++ b/demo/kitchen-sink/token_tooltip.js @@ -63,8 +63,8 @@ var TokenTooltip = function(editor) { if (this.lastT - (r.timeStamp || 0) > 1000) { r.rect = null; r.timeStamp = this.lastT; - this.maxHeight = innerHeight; - this.maxWidth = innerWidth; + this.maxHeight = window.innerHeight; + this.maxWidth = window.innerWidth; } var canvasPos = r.rect || (r.rect = r.scroller.getBoundingClientRect()); @@ -145,8 +145,8 @@ var TokenTooltip = function(editor) { this.updateTooltipPosition = function(x, y) { var st = tooltipNode.style; if (x + 10 + this.tooltipWidth > this.maxWidth) - x = innerWidth - this.tooltipWidth - 10; - if (y > innerHeight * 0.75 || y + 20 + this.tooltipHeight > this.maxHeight) + x = window.innerWidth - this.tooltipWidth - 10; + if (y > window.innerHeight * 0.75 || y + 20 + this.tooltipHeight > this.maxHeight) y = y - this.tooltipHeight - 30; st.left = x + 10 + "px"; diff --git a/demo/kitchen-sink/util.js b/demo/kitchen-sink/util.js index d2e32524..3b00c1ce 100644 --- a/demo/kitchen-sink/util.js +++ b/demo/kitchen-sink/util.js @@ -42,7 +42,7 @@ var MultiSelect = require("ace/multi_select").MultiSelect; exports.createEditor = function(el) { return new Editor(new Renderer(el)); -} +}; exports.createSplitEditor = function(el) { if (typeof(el) == "string") @@ -62,9 +62,6 @@ exports.createSplitEditor = function(el) { split.editor1 = split[1] = new Editor(new Renderer(e1)); split.splitter = s; - MultiSelect(split.editor0); - MultiSelect(split.editor1); - s.ratio = 0.5; split.resize = function resize(){ @@ -108,8 +105,8 @@ exports.createSplitEditor = function(el) { }; var onResizeInterval = function() { - s.ratio = (x - rect.left) / rect.width - split.resize() + s.ratio = (x - rect.left) / rect.width; + split.resize(); }; event.capture(s, onMouseMove, onResizeEnd); diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 5f77bfd8..ab6f8d71 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1037,8 +1037,20 @@ var EditSession = function(text, mode) { **/ this.getScreenWidth = function() { this.$computeWidth(); + if (this.lineWidgets) + return Math.max(this.getLineWidgetMaxWidth(), this.screenWidth); return this.screenWidth; }; + + this.getLineWidgetMaxWidth = function() { + if (this.lineWidgetsWidth != null) return this.lineWidgetsWidth + var width = 0; + this.lineWidgets.forEach(function(w) { + if (w && w.screenWidth > width) + width = w.screenWidth; + }); + return this.lineWidgetWidth = width; + } this.$computeWidth = function(force) { if (this.$modified || force) { @@ -2043,6 +2055,7 @@ var EditSession = function(text, mode) { return [screenColumn, column]; }; + this.lineWidgets = null; /** * Returns number of screenrows in a wrapped line. * @param {Number} row The row number to check @@ -2050,6 +2063,17 @@ var EditSession = function(text, mode) { * @returns {Number} **/ this.getRowLength = function(row) { + if (this.lineWidgets) + var h = this.lineWidgets[row] && this.lineWidgets[row].rowCount || 0; + else + h = 0 + if (!this.$useWrapMode || !this.$wrapData[row]) { + return 1 + h; + } else { + return this.$wrapData[row].length + 1 + h; + } + }; + this.getRowLineCount = function(row) { if (!this.$useWrapMode || !this.$wrapData[row]) { return 1; } else { @@ -2163,7 +2187,7 @@ var EditSession = function(text, mode) { while (row <= screenRow) { rowLength = this.getRowLength(docRow); - if (row + rowLength - 1 >= screenRow || docRow >= maxRow) { + if (row + rowLength > screenRow || docRow >= maxRow) { break; } else { row += rowLength; @@ -2198,9 +2222,10 @@ var EditSession = function(text, mode) { if (this.$useWrapMode) { var splits = this.$wrapData[docRow]; if (splits) { - column = splits[screenRow - row]; - if(screenRow > row && splits.length) { - docColumn = splits[screenRow - row - 1] || splits[splits.length - 1]; + var splitIndex = Math.floor(screenRow - row); + column = splits[splitIndex]; + if(splitIndex > 0 && splits.length) { + docColumn = splits[splitIndex - 1] || splits[splits.length - 1]; line = line.substring(docColumn); } } @@ -2372,6 +2397,10 @@ var EditSession = function(text, mode) { } } + // todo + if (this.lineWidgets) + screenRows += this.$getWidgetScreenLength(); + return screenRows; }; diff --git a/lib/ace/edit_session/folding.js b/lib/ace/edit_session/folding.js index e6725a51..9ebc2869 100644 --- a/lib/ace/edit_session/folding.js +++ b/lib/ace/edit_session/folding.js @@ -639,6 +639,8 @@ function Folding() { if (depth == undefined) depth = 100000; // JSON.stringify doesn't hanle Infinity var foldWidgets = this.foldWidgets; + if (!foldWidgets) + return; // mode doesn't support folding endRow = endRow || this.getLength(); startRow = startRow || 0; for (var row = startRow; row < endRow; row++) { diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index 54f0ef46..52940dcf 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -294,6 +294,7 @@ var Text = function(parentEl) { this.$renderLine( html, row, !this.$useLineGroups(), row == foldStart ? foldLine : false ); + lineElement.style.height = config.lineHeight * this.session.getRowLength(row) + "px"; dom.setInnerHtml(lineElement, html.join("")); } row++; @@ -360,6 +361,8 @@ var Text = function(parentEl) { if (this.$useLineGroups()) { container.className = 'ace_line_group'; fragment.appendChild(container); + container.style.height = config.lineHeight * this.session.getRowLength(row) + "px"; + } else { var lines = container.childNodes while(lines.length) @@ -391,7 +394,7 @@ var Text = function(parentEl) { break; if (this.$useLineGroups()) - html.push("
") + html.push("
") this.$renderLine(html, row, false, row == foldStart ? foldLine : false); @@ -551,7 +554,10 @@ var Text = function(parentEl) { if (!onlyContents) { stringBuilder.push( - "
" + "
" ); } diff --git a/lib/ace/line_widgets.js b/lib/ace/line_widgets.js new file mode 100644 index 00000000..597b3ca2 --- /dev/null +++ b/lib/ace/line_widgets.js @@ -0,0 +1,293 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Distributed under the BSD license: + * + * Copyright (c) 2010, Ajax.org B.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Ajax.org B.V. nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ***** END LICENSE BLOCK ***** */ + +define(function(require, exports, module) { +"use strict"; + +var oop = require("./lib/oop"); +var dom = require("./lib/dom"); +var Range = require("./range").Range; + + +function LineWidgets(session) { + this.session = session; + this.session.widgetManager = this; + this.session.getRowLength = this.getRowLength; + this.session.$getWidgetScreenLength = this.$getWidgetScreenLength; + this.updateOnChange = this.updateOnChange.bind(this); + this.renderWidgets = this.renderWidgets.bind(this); + this.measureWidgets = this.measureWidgets.bind(this); + this.session._changedWidgets = []; + this.detach = this.detach.bind(this); + + this.session.on("change", this.updateOnChange); +}; + +(function() { + this.getRowLength = function(row) { + if (this.lineWidgets) + var h = this.lineWidgets[row] && this.lineWidgets[row].rowCount || 0; + else + h = 0; + if (!this.$useWrapMode || !this.$wrapData[row]) { + return 1 + h; + } else { + return this.$wrapData[row].length + 1 + h; + } + }; + + this.$getWidgetScreenLength = function() { + var screenRows = 0; + this.lineWidgets.forEach(function(w){ + if (w && w.rowCount) + screenRows +=w.rowCount; + }); + return screenRows; + }; + + this.attach = function(editor) { + if (editor.widgetManager && editor.widgetManager != this) + editor.widgetManager.detach(); + + if (this.editor == editor) + return; + + this.detach(); + this.editor = editor; + + this.editor.on("changeSession", this.detach); + + editor.widgetManager = this; + + editor.setOption("enableLineWidgets", true); + 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; + + editor.renderer.off("beforeRender", this.measureWidgets); + editor.renderer.off("afterRender", this.renderWidgets); + this.session.lineWidgets.forEach(function(w) { + if (w && w.el && w.el.parentNode) { + w._inDocument = false; + w.el.parentNode.removeChild(w.el); + } + }); + }; + + this.updateOnChange = function(e) { + var cells = this.session.lineWidgets; + if (!cells) return; + + var delta = e.data; + var range = delta.range; + var startRow = range.start.row; + var len = range.end.row - startRow; + + if (len === 0) { + // return + } else if (delta.action == "removeText" || delta.action == "removeLines") { + var removed = cells.splice(startRow + 1, len); + removed.forEach(function(w) { + w && this.removeLineWidget(w); + }, this); + this.$updateRows(); + } else { + var args = Array(len); + args.unshift(startRow, 0); + cells.splice.apply(cells, args); + this.$updateRows(); + } + }; + + this.$updateRows = function() { + var lw = this.session.lineWidgets; + if (!lw) return; + var noWidgets = true; + lw.forEach(function(w, i) { + if (w) { + noWidgets = false; + w.row = i; + } + }); + if (noWidgets) + this.session.lineWidgets = null; + } + + this.addLineWidget = function(w) { + if (!this.session.lineWidgets) + this.session.lineWidgets = Array(this.session.getLength()) + + this.session.lineWidgets[w.row] = w; + + var renderer = this.editor.renderer; + if (w.html && !w.el) { + w.el = dom.createElement("div"); + w.el.innerHTML = w.html; + } + if (w.el) { + dom.addCssClass(w.el, "ace_lineWidgetContainer"); + renderer.container.appendChild(w.el); + w._inDocument = true; + } + + if (!w.coverGutter) { + w.el.style.zIndex = 3; + } + if (!w.pixelHeight) { + w.pixelHeight = w.el.offsetHeight; + } + if (w.rowCount == null) + w.rowCount = w.pixelHeight / renderer.layerConfig.lineHeight; + + this.session._emit("changeFold", {data:{start:{row: w.row}}}); + + this.$updateRows(); + this.renderWidgets(null, renderer); + return w; + }; + + this.removeLineWidget = function(w) { + w._inDocument = false; + if (w.el && w.el.parentNode) + w.el.parentNode.removeChild(w.el); + if (w.editor && w.editor.destroy) try { + w.editor.destroy(); + } catch(e){} + this.session.lineWidgets[w.row] = undefined; + this.session._emit("changeFold", {data:{start:{row: w.row}}}); + this.$updateRows(); + }; + + this.onWidgetChanged = function(w) { + this.session._changedWidgets.push(w); + this.editor && this.editor.renderer.updateFull(); + }; + + this.measureWidgets = function(e, renderer) { + var ws = this.session._changedWidgets; + var config = renderer.layerConfig; + + if (!ws || !ws.length) return; + var min = Infinity; + for (var i = 0; i < ws.length; i++) { + var w = ws[i].lineWidget; + if (!w._inDocument) { + w._inDocument = true; + renderer.container.appendChild(w.el); + } + + w.h = w.el.offsetHeight; + + if (!w.fixedWidth) { + w.w = w.el.offsetWidth; + w.screenWidth = Math.ceil(w.w / config.characterWidth); + } + + var rowCount = w.h / config.lineHeight; + if (w.coverLine) { + rowCount -= this.session.getRowLineCount(w.row); + if (rowCount < 0) + rowCount = 0; + } + if (w.rowCount != rowCount) { + w.rowCount = rowCount; + if (w.row < min) + min = w.row; + } + } + if (min != Infinity) { + this.session._emit("changeFold", {data:{start:{row: min}}}); + this.session.lineWidgetWidth = null; + } + this.session._changedWidgets = []; + }; + + this.renderWidgets = function(e, renderer) { + var config = renderer.layerConfig; + var ws = this.session.lineWidgets; + if (!ws) + return; + var first = Math.min(this.firstRow, config.firstRow); + var last = Math.max(this.lastRow, config.lastRow, ws.length); + + while (first > 0 && !ws[first]) + first--; + + this.firstRow = config.firstRow; + this.lastRow = config.lastRow; + + renderer.$cursorLayer.config = config; + for (var i = first; i <= last; i++) { + var w = ws[i]; + if (!w || !w.el) continue; + + if (!w._inDocument) { + w._inDocument = true; + renderer.container.appendChild(w.el); + } + var top = renderer.$cursorLayer.getPixelPosition({row: i, column:0}, true).top; + if (!w.coverLine) + top += config.lineHeight * this.session.getRowLineCount(w.row); + w.el.style.top = top - config.offset + "px"; + + var left = w.coverGutter ? 0 : renderer.gutterWidth; + if (!w.fixedWidth) + left -= renderer.scrollLeft; + w.el.style.left = left + "px"; + + if (w.fixedWidth) { + w.el.style.right = renderer.scrollBar.getWidth() + "px"; + } else { + w.el.style.right = ""; + } + } + }; + +}).call(LineWidgets.prototype); + + +exports.LineWidgets = LineWidgets; + +}); + + + + diff --git a/lib/ace/selection.js b/lib/ace/selection.js index d3dab831..adfb6518 100644 --- a/lib/ace/selection.js +++ b/lib/ace/selection.js @@ -783,6 +783,11 @@ var Selection = function(session) { } var docPos = this.session.screenToDocumentPosition(screenPos.row + rows, screenPos.column); + + if (rows !== 0 && chars === 0 && docPos.row === this.lead.row && docPos.column === this.lead.column) { + if (this.session.lineWidgets && this.session.lineWidgets[docPos.row]) + docPos.row++; + } // move the cursor and update the desired column this.moveCursorTo(docPos.row, docPos.column + chars, chars === 0);