diff --git a/build_support/style.css b/build_support/style.css index b452add7..43f46d0e 100644 --- a/build_support/style.css +++ b/build_support/style.css @@ -1,3 +1,5 @@ +@import url(//fonts.googleapis.com/css?family=Droid+Sans+Mono); + body { margin:0; padding:0; diff --git a/demo/kitchen-sink/demo.js b/demo/kitchen-sink/demo.js index 9d272bd6..bcd240ff 100644 --- a/demo/kitchen-sink/demo.js +++ b/demo/kitchen-sink/demo.js @@ -301,7 +301,8 @@ split.on("focus", function(editor) { }); env.split = split; window.env = env; -window.ace = env.editor; +window.editor = window.ace = env.editor; +env.editor.setAnimatedScroll(true); var docEl = document.getElementById("doc"); var modeEl = document.getElementById("mode"); @@ -395,10 +396,28 @@ function saveOption(el, val) { } } +event.addListener(themeEl, "mouseover", function(e){ + this.desiredValue = e.target.value; + if (!this.$timer) + this.$timer = setTimeout(this.updateTheme); +}) + +event.addListener(themeEl, "mouseout", function(e){ + this.desiredValue = null; + if (!this.$timer) + this.$timer = setTimeout(this.updateTheme, 20); +}) + +themeEl.updateTheme = function(){ + env.split.setTheme(themeEl.desiredValue || themeEl.selectedValue); + themeEl.$timer = null; +} + bindDropdown("theme", function(value) { if (!value) return; env.editor.setTheme(value); + themeEl.selectedValue = value; }); bindDropdown("keybinding", function(value) { @@ -480,6 +499,9 @@ bindCheckbox("enable_behaviours", function(checked) { env.editor.setBehavioursEnabled(checked); }); +bindCheckbox("fade_fold_widgets", function(checked) { + env.editor.setFadeFoldWidgets(checked); +}); var secondSession = null; bindDropdown("split", function(value) { diff --git a/demo/kitchen-sink/styles.css b/demo/kitchen-sink/styles.css index 74c53b90..e71e7e53 100644 --- a/demo/kitchen-sink/styles.css +++ b/demo/kitchen-sink/styles.css @@ -1,3 +1,5 @@ +@import url(//fonts.googleapis.com/css?family=Droid+Sans+Mono); + html { height: 100%; width: 100%; diff --git a/doc/wiki b/doc/wiki index d2a65d0a..cc5ccde4 160000 --- a/doc/wiki +++ b/doc/wiki @@ -1 +1 @@ -Subproject commit d2a65d0addc2e5ab922bbff9cb6022a4652b4f13 +Subproject commit cc5ccde4565f7b21c81345f47e941e011e94b810 diff --git a/kitchen-sink.html b/kitchen-sink.html index bf37f827..5fc01e17 100644 --- a/kitchen-sink.html +++ b/kitchen-sink.html @@ -200,6 +200,14 @@ + + + + + + + +
diff --git a/lib/ace/commands/command_manager.js b/lib/ace/commands/command_manager.js index a6c800f5..b919dbc1 100644 --- a/lib/ace/commands/command_manager.js +++ b/lib/ace/commands/command_manager.js @@ -31,7 +31,7 @@ var CommandManager = function(platform, commands) { this.addCommands(commands); this.setDefaultHandler("exec", function(e) { - e.command.exec(e.editor, e.args || {}); + return e.command.exec(e.editor, e.args || {}); }); }; @@ -51,8 +51,18 @@ oop.inherits(CommandManager, HashHandler); if (editor && editor.$readOnly && !command.readOnly) return false; - this._emit("exec", {editor: editor, command: command, args: args}); - return true; + try { + var retvalue = this._emit("exec", { + editor: editor, + command: command, + args: args + }); + } catch (e) { + window.console && window.console.log(e); + return true; + } + + return retvalue === false ? false : true; }; this.toggleRecording = function() { diff --git a/lib/ace/commands/default_commands.js b/lib/ace/commands/default_commands.js index f109b336..d12478a3 100644 --- a/lib/ace/commands/default_commands.js +++ b/lib/ace/commands/default_commands.js @@ -311,7 +311,7 @@ exports.commands = [{ multiSelectAction: "forEach" }, { name: "togglecomment", - bindKey: bindKey("Ctrl-7", "Command-7"), + bindKey: bindKey("Ctrl-/", "Command-/"), exec: function(editor) { editor.toggleCommentLines(); }, multiSelectAction: "forEach" }, { diff --git a/lib/ace/commands/multi_select_commands.js b/lib/ace/commands/multi_select_commands.js index 71f0a3e5..cce05705 100644 --- a/lib/ace/commands/multi_select_commands.js +++ b/lib/ace/commands/multi_select_commands.js @@ -84,16 +84,17 @@ exports.defaultCommands = [{ exec: function(editor) { editor.multiSelect.splitIntoLines(); }, bindKey: {win: "Ctrl-Shift-L", mac: "Ctrl-Shift-L"}, readonly: true -}]; - -// commands active in multiselect mode -exports.multiEditCommands = [{ +}, { name: "singleSelection", bindKey: "esc", exec: function(editor) { editor.exitMultiSelectMode(); }, - readonly: true + readonly: true, + isAvailable: function(editor) {return editor.inMultiSelectMode} }]; +// commands active in multiselect mode +exports.multiEditCommands = {"singleSelection": "esc"}; + var HashHandler = require("../keyboard/hash_handler").HashHandler; exports.keyboardHandler = new HashHandler(exports.multiEditCommands); diff --git a/lib/ace/css/editor.css b/lib/ace/css/editor.css index 83c21130..5a544076 100644 --- a/lib/ace/css/editor.css +++ b/lib/ace/css/editor.css @@ -1,4 +1,3 @@ -@import url(//fonts.googleapis.com/css?family=Droid+Sans+Mono); .ace_editor { position: absolute; @@ -131,6 +130,7 @@ .ace_text-layer { color: black; + font: inherit !important; } .ace_cjk { @@ -179,10 +179,6 @@ z-index: 2; } -.ace_gutter .ace_gutter_active_line{ - background-color : #dcdcdc; -} - .ace_marker-layer .ace_selected_word { position: absolute; z-index: 4; @@ -289,3 +285,25 @@ background-color: #FFB4B4; border-color: #DE5555; } + +.ace_fade-fold-widgets .ace_fold-widget { + -moz-transition: 0.5s opacity; + -webkit-transition: 0.5s opacity; + -o-transition: 0.5s opacity; + -ms-transition: 0.5s opacity; + transition: 0.5s opacity; + opacity: 0; +} +.ace_fade-fold-widgets:hover .ace_fold-widget { + -moz-transition-duration: 0.05s; + -webkit-transition-duration: 0.05s; + -o-transition-duration: 0.05s; + -ms-transition-duration: 0.05s; + transition-duration: 0.05s; + -moz-transition-delay: 0.2s; + -webkit-transition-delay: 0.2s; + -o-transition-delay: 0.2s; + -ms-transition-delay: 0.2s; + transition-delay: 0.2s; + opacity:1; +} diff --git a/lib/ace/document.js b/lib/ace/document.js index f984ee81..bc6ac32e 100644 --- a/lib/ace/document.js +++ b/lib/ace/document.js @@ -257,10 +257,9 @@ var Document = function(text) { range.end.column); } else { - var lines = []; - lines.push(this.$lines[range.start.row].substring(range.start.column)); - lines.push.apply(lines, this.getLines(range.start.row+1, range.end.row-1)); - lines.push(this.$lines[range.end.row].substring(0, range.end.column)); + var lines = this.getLines(range.start.row+1, range.end.row-1); + lines.unshift((this.$lines[range.start.row] || "").substring(range.start.column)); + lines.push((this.$lines[range.end.row] || "").substring(0, range.end.column)); return lines.join(this.getNewLineCharacter()); } }; diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 12fa89ce..8a57bcda 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -93,10 +93,7 @@ var EditSession = function(text, mode) { } this.selection = new Selection(this); - if (mode) - this.setMode(mode); - else - this.setMode(new TextMode()); + this.setMode(mode); }; @@ -766,7 +763,7 @@ var EditSession = function(text, mode) { this._loadMode = function(mode, callback) { if (this.$modes[mode]) return callback(this.$modes[mode]); - + var _self = this; var module; try { @@ -774,16 +771,17 @@ var EditSession = function(text, mode) { } catch (e) {}; if (module) return done(module); - + fetch(function() { require([mode], done); }); - + function done(module) { if (_self.$modes[mode]) return callback(_self.$modes[mode]); - + _self.$modes[mode] = new module.Mode(); + _self.$modes[mode].$id = mode; _self._emit("loadmode", { name: mode, mode: _self.$modes[mode] @@ -794,40 +792,48 @@ var EditSession = function(text, mode) { function fetch(callback) { if (!config.get("packaged")) return callback(); - + var base = mode.split("/").pop(); var filename = config.get("modePath") + "/mode-" + base + config.get("suffix"); net.loadScript(filename, callback); } }; + /** + * EditSession.setMode(mode) + * - mode (TextMode): Set a new text mode + * + * Sets a new text mode for the `EditSession`. This method also emits the `'changeMode'` event. If a [[BackgroundTokenizer `BackgroundTokenizer`]] is set, the `'tokenizerUpdate'` event is also emitted. + * + **/ this.$mode = null; - this.$origMode = null; - /** - * EditSession.setMode(mode) - * - mode (TextMode): Set a new text mode - * - * Sets a new text mode for the `EditSession`. This method also emits the `'changeMode'` event. If a [[BackgroundTokenizer `BackgroundTokenizer`]] is set, the `'tokenizerUpdate'` event is also emitted. - * - **/ + this.$modeId = null; this.setMode = function(mode) { - this.$origMode = mode; - // load on demand if (typeof mode === "string") { + if (this.$modeId == mode) + return; + + this.$modeId = mode; var _self = this; this._loadMode(mode, function(module) { - if (_self.$origMode !== mode) + if (_self.$modeId !== mode) return; - + _self.setMode(module); }); return; + } else if (mode == null) { + mode = "ace/mode/text" + this.$modeId = mode; + this.$modes[mode] = this.$modes[mode] || (new TextMode()); + this.setMode(this.$modes[mode]); + return; } - + if (this.$mode === mode) return; this.$mode = mode; - + this.$modeId = mode.$id; this.$stopWorker(); @@ -901,7 +907,7 @@ var EditSession = function(text, mode) { this.getMode = function() { return this.$mode; }; - + this.$scrollTop = 0; /** * EditSession.setScrollTop(scrollTop) @@ -926,7 +932,7 @@ var EditSession = function(text, mode) { this.getScrollTop = function() { return this.$scrollTop; }; - + this.$scrollLeft = 0; /** * EditSession.setScrollLeft(scrollLeft) @@ -1419,7 +1425,7 @@ var EditSession = function(text, mode) { range.start.column ); } - + var len = this.doc.getLength() - 1; if (range.end.row > len) { range.end.row = len; diff --git a/lib/ace/edit_session/fold_line.js b/lib/ace/edit_session/fold_line.js index 7676c4a1..291fc80f 100644 --- a/lib/ace/edit_session/fold_line.js +++ b/lib/ace/edit_session/fold_line.js @@ -177,7 +177,9 @@ function FoldLine(foldData, folds) { && fold.start.column != column && fold.start.row != row) { - throw "Moving characters inside of a fold should never be reached"; + //throwing here breaks whole editor + //@todo properly handle this + window.console && window.console.log(row, column, fold); } else if (fold.start.row == row) { folds = this.folds; var i = folds.indexOf(fold); diff --git a/lib/ace/editor.js b/lib/ace/editor.js index b80a232b..b0342377 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -78,6 +78,7 @@ var Editor = function(renderer, session) { this.container = container; this.renderer = renderer; + this.commands = new CommandManager(useragent.isMac ? "mac" : "win", defaultCommands); this.textInput = new TextInput(renderer.getTextAreaContainer(), this); this.keyBinding = new KeyBinding(this); @@ -94,7 +95,6 @@ var Editor = function(renderer, session) { wrap: true }); - this.commands = new CommandManager(useragent.isMac ? "mac" : "win", defaultCommands); this.setSession(session || new EditSession("")); }; @@ -458,6 +458,7 @@ var Editor = function(renderer, session) { this.$highlightBrackets(); this.$updateHighlightActiveLine(); + this.$updateHighlightGutterLine(); }; /** internal, hide @@ -470,13 +471,10 @@ var Editor = function(renderer, session) { if (session.$highlightLineMarker) session.removeMarker(session.$highlightLineMarker); - if (typeof this.$lastrow == "number") - this.renderer.removeGutterDecoration(this.$lastrow, "ace_gutter_active_line"); session.$highlightLineMarker = null; - this.$lastrow = null; - if (this.getHighlightActiveLine()) { + if (this.$highlightActiveLine) { var cursor = this.getCursorPosition(), foldLine = this.session.getFoldLine(cursor.row); @@ -489,11 +487,24 @@ var Editor = function(renderer, session) { } session.$highlightLineMarker = session.addMarker(range, "ace_active_line", "background"); } - - this.renderer.addGutterDecoration(this.$lastrow = cursor.row, "ace_gutter_active_line"); - } }; + /** internal, hide + * Editor.$updateHighlightGutterLine() + * + * + **/ + this.$updateHighlightGutterLine = function(){ + if (typeof this.$lastrow == "number") + this.renderer.removeGutterDecoration(this.$lastrow, "ace_gutter_active_line"); + + this.$lastrow = null; + + if (this.$highlightGutterLine) + this.renderer.addGutterDecoration( + this.$lastrow = this.getCursorPosition().row, "ace_gutter_active_line"); + } + /** * Editor@onSelectionChange(e) * - e (Object): Contains a single property, `data`, which has the delta of changes @@ -514,6 +525,7 @@ var Editor = function(renderer, session) { session.$selectionMarker = session.addMarker(range, "ace_selection", style); } else { this.$updateHighlightActiveLine(); + this.$updateHighlightGutterLine(); } if (this.$highlightSelectedWord) @@ -592,6 +604,7 @@ var Editor = function(renderer, session) { // Update the active line marker as due to folding changes the current // line range on the screen might have changed. this.$updateHighlightActiveLine(); + this.$updateHighlightGutterLine(); // TODO: This might be too much updating. Okay for now. this.renderer.updateFull(); }; @@ -610,15 +623,34 @@ var Editor = function(renderer, session) { return text; }; + /** + * Editor.onCopy() + * + * Called whenever a text "copy" happens. + **/ + this.onCopy = function() { + this.commands.exec("copy", this); + }; + /** * Editor.onCut() * - * Emitted whenever a text "cut" happens. + * called whenever a text "cut" happens. **/ this.onCut = function() { this.commands.exec("cut", this); }; + /** + * Editor.onPaste() + * + * called whenever a text "paste" happens. + **/ + this.onPaste = function(text) { + this._emit("paste", text); + this.insert(text); + }; + /** * Editor.insert(text) * - text (String): The new text to add @@ -724,11 +756,8 @@ var Editor = function(renderer, session) { * * Emitted when text is entered. **/ - this.onTextInput = function(text, pasted) { - if (pasted) - this._emit("paste", text); - - this.keyBinding.onTextInput(text, pasted); + this.onTextInput = function(text) { + this.keyBinding.onTextInput(text); }; /** @@ -844,7 +873,8 @@ var Editor = function(renderer, session) { * **/ this.setHighlightActiveLine = function(shouldHighlight) { - if (this.$highlightActiveLine == shouldHighlight) return; + if (this.$highlightActiveLine == shouldHighlight) + return; this.$highlightActiveLine = shouldHighlight; this.$updateHighlightActiveLine(); @@ -859,6 +889,19 @@ var Editor = function(renderer, session) { return this.$highlightActiveLine; }; + this.$highlightGutterLine = true; + this.setHighlightGutterLine = function(shouldHighlightGutterLine) { + if (this.$highlightGutterLine == shouldHighlightGutterLine) + return; + + this.$highlightGutterLine = shouldHighlightGutterLine; + this.$updateHighlightGutterLine(); + }; + + this.getHighlightGutterLine = function() { + return this.$highlightGutterLine; + }; + this.$highlightSelectedWord = true; /** * Editor.setHighlightSelectedWord(shouldHighlight) @@ -1013,6 +1056,14 @@ var Editor = function(renderer, session) { return this.renderer.$gutterLayer.getShowFoldWidgets(); }; + this.setFadeFoldWidgets = function(show) { + this.renderer.setFadeFoldWidgets(show); + }; + + this.getFadeFoldWidgets = function() { + return this.renderer.getFadeFoldWidgets(); + }; + /** * Editor.remove(dir) * - dir (String): The direction of the deletion to occur, either "left" or "right" @@ -1022,7 +1073,7 @@ var Editor = function(renderer, session) { **/ this.remove = function(dir) { if (this.selection.isEmpty()){ - if(dir == "left") + if (dir == "left") this.selection.selectLeft(); else this.selection.selectRight(); @@ -1447,15 +1498,29 @@ var Editor = function(renderer, session) { return this.renderer.getScrollBottomRow() - this.renderer.getScrollTopRow() + 1; }; - this.$getPageDownRow = function() { - return this.renderer.getScrollBottomRow(); - }; + this.$moveByPage = function(dir, select) { + var renderer = this.renderer; + var config = this.renderer.layerConfig; + var rows = dir * Math.floor(config.height / config.lineHeight); - this.$getPageUpRow = function() { - var firstRow = this.renderer.getScrollTopRow(); - var lastRow = this.renderer.getScrollBottomRow(); + this.$blockScrolling++; + if (select == true) { + this.selection.$moveSelection(function(){ + this.moveCursorBy(rows, 0); + }); + } else if (select == false) { + this.selection.moveCursorBy(rows, 0); + this.selection.clearSelection(); + } + this.$blockScrolling--; - return firstRow - (lastRow - firstRow); + var scrollTop = renderer.scrollTop; + + renderer.scrollBy(0, rows * config.lineHeight); + if (select != null) + renderer.scrollCursorIntoView(null, 0.5); + + renderer.animateScrolling(scrollTop); }; /** @@ -1464,14 +1529,7 @@ var Editor = function(renderer, session) { * Selects the text from the current position of the document until where a "page down" finishes. **/ this.selectPageDown = function() { - var row = this.$getPageDownRow() + Math.floor(this.$getVisibleRowCount() / 2); - - this.scrollPageDown(); - - var selection = this.getSelection(); - var leadScreenPos = this.session.documentToScreenPosition(selection.getSelectionLead()); - var dest = this.session.screenToDocumentPosition(row, leadScreenPos.column); - selection.selectTo(dest.row, dest.column); + this.$moveByPage(1, true); }; /** @@ -1480,15 +1538,7 @@ var Editor = function(renderer, session) { * Selects the text from the current position of the document until where a "page up" finishes. **/ this.selectPageUp = function() { - var visibleRows = this.renderer.getScrollTopRow() - this.renderer.getScrollBottomRow(); - var row = this.$getPageUpRow() + Math.round(visibleRows / 2); - - this.scrollPageUp(); - - var selection = this.getSelection(); - var leadScreenPos = this.session.documentToScreenPosition(selection.getSelectionLead()); - var dest = this.session.screenToDocumentPosition(row, leadScreenPos.column); - selection.selectTo(dest.row, dest.column); + this.$moveByPage(-1, true); }; /** @@ -1497,11 +1547,7 @@ var Editor = function(renderer, session) { * Shifts the document to wherever "page down" is, as well as moving the cursor position. **/ this.gotoPageDown = function() { - var row = this.$getPageDownRow(); - var column = this.getCursorPositionScreen().column; - - this.scrollToRow(row); - this.getSelection().moveCursorToScreen(row, column); + this.$moveByPage(1, false); }; /** @@ -1510,11 +1556,7 @@ var Editor = function(renderer, session) { * Shifts the document to wherever "page up" is, as well as moving the cursor position. **/ this.gotoPageUp = function() { - var row = this.$getPageUpRow(); - var column = this.getCursorPositionScreen().column; - - this.scrollToRow(row); - this.getSelection().moveCursorToScreen(row, column); + this.$moveByPage(-1, false); }; /** @@ -1523,7 +1565,7 @@ var Editor = function(renderer, session) { * Scrolls the document to wherever "page down" is, without changing the cursor position. **/ this.scrollPageDown = function() { - this.scrollToRow(this.$getPageDownRow()); + this.$moveByPage(1); }; /** @@ -1532,7 +1574,7 @@ var Editor = function(renderer, session) { * Scrolls the document to wherever "page up" is, without changing the cursor position. **/ this.scrollPageUp = function() { - this.renderer.scrollToRow(this.$getPageUpRow()); + this.$moveByPage(-1); }; /** related to: VirtualRenderer.scrollToRow @@ -1550,11 +1592,13 @@ var Editor = function(renderer, session) { * Editor.scrollToLine(line, center) * - line (Number): The line to scroll to * - center (Boolean): If `true` + * - animate (Boolean): If `true` animates scrolling + * - callback (Function): Function to be called when the animation has finished * - * TODO scrollsa to line, if center == true, puts line in middle of screen or attempts to) + * TODO scrolls a to line, if center == true, puts line in middle of screen or attempts to) **/ - this.scrollToLine = function(line, center) { - this.renderer.scrollToLine(line, center); + this.scrollToLine = function(line, center, animate, callback) { + this.renderer.scrollToLine(line, center, animate, callback); }; /** @@ -1672,19 +1716,21 @@ var Editor = function(renderer, session) { * Editor.gotoLine(lineNumber, column) * - lineNumber (Number): The line number to go to * - column (Number): A column number to go to + * - animate (Boolean): If `true` animates scolling * * Moves the cursor to the specified line number, and also into the indiciated column. * **/ - this.gotoLine = function(lineNumber, column) { + this.gotoLine = function(lineNumber, column, animate) { this.selection.clearSelection(); this.session.unfold({row: lineNumber - 1, column: column || 0}); this.$blockScrolling += 1; - this.moveCursorTo(lineNumber-1, column || 0); + this.moveCursorTo(lineNumber - 1, column || 0); this.$blockScrolling -= 1; - if (!this.isRowFullyVisible(this.getCursorPosition().row)) - this.scrollToLine(lineNumber, true); + + if (!this.isRowFullyVisible(lineNumber - 1)) + this.scrollToLine(lineNumber - 1, true, animate); }; /** related to: Editor.moveCursorTo @@ -1790,8 +1836,10 @@ var Editor = function(renderer, session) { * Moves the cursor to the end of the current file. Note that this does de-select the current selection. **/ this.navigateFileEnd = function() { + var scrollTop = this.renderer.scrollTop; this.selection.moveCursorFileEnd(); this.clearSelection(); + this.renderer.animateScrolling(scrollTop); }; /** @@ -1800,8 +1848,10 @@ var Editor = function(renderer, session) { * Moves the cursor to the start of the current file. Note that this does de-select the current selection. **/ this.navigateFileStart = function() { + var scrollTop = this.renderer.scrollTop; this.selection.moveCursorFileStart(); this.clearSelection(); + this.renderer.animateScrolling(scrollTop); }; /** @@ -1910,44 +1960,45 @@ var Editor = function(renderer, session) { * Editor.find(needle, options) * - needle (String): The text to search for * - options (Object): An object defining various search properties + * - animate (Boolean): If `true` animate scrolling * * Attempts to find `needle` within the document. For more information on `options`, see [[Search `Search`]]. **/ - this.find = function(needle, options) { + this.find = function(needle, options, animate) { this.clearSelection(); options = options || {}; options.needle = needle; this.$search.set(options); - this.$find(); + this.$find(false, animate); }; /** related to: Editor.find * Editor.findNext(options) + * - options (Object): search options + * - animate (Boolean): If `true` animate scrolling * * Performs another search for `needle` in the document. For more information on `options`, see [[Search `Search`]]. **/ - this.findNext = function(options) { + this.findNext = function(options, animate) { options = options || {}; - if (typeof options.backwards == "undefined") - options.backwards = false; this.$search.set(options); - this.$find(); + this.$find(false, animate); }; /** related to: Editor.find * Editor.findPrevious(options) + * - options (Object): search options + * - animate (Boolean): If `true` animate scrolling * * Performs a search for `needle` backwards. For more information on `options`, see [[Search `Search`]]. **/ - this.findPrevious = function(options) { + this.findPrevious = function(options, animate) { options = options || {}; - if (typeof options.backwards == "undefined") - options.backwards = true; this.$search.set(options); - this.$find(); + this.$find(true, animate); }; - this.$find = function(backwards) { + this.$find = function(backwards, animate) { if (!this.selection.isEmpty()) this.$search.set({needle: this.session.getTextRange(this.getSelectionRange())}); @@ -1956,24 +2007,14 @@ var Editor = function(renderer, session) { var range = this.$search.find(this.session); if (range) { - this.session.unfold(range); - this.$blockScrolling += 1; + this.session.unfold(range); this.selection.setSelectionRange(range); this.$blockScrolling -= 1; - if (this.getAnimatedScroll()) { - var cursor = this.getCursorPosition(); - if (!this.isRowFullyVisible(cursor.row)) - this.scrollToLine(cursor.row, true); - - //@todo scroll X - //if (!this.isColumnFullyVisible(cursor.column)) - //this.scrollToRow(cursor.column); - } - else { - this.renderer.scrollSelectionIntoView(range.start, range.end); - } + var scrollTop = this.renderer.scrollTop; + this.renderer.scrollSelectionIntoView(range.start, range.end, 0.5); + this.renderer.animateScrolling(scrollTop); } }; @@ -1983,7 +2024,10 @@ var Editor = function(renderer, session) { * {:UndoManager.undo} **/ this.undo = function() { + this.$blockScrolling++; this.session.getUndoManager().undo(); + this.$blockScrolling--; + this.renderer.scrollCursorIntoView(null, 0.5); }; /** related to: UndoManager.redo @@ -1992,7 +2036,10 @@ var Editor = function(renderer, session) { * {:UndoManager.redo} **/ this.redo = function() { + this.$blockScrolling++; this.session.getUndoManager().redo(); + this.$blockScrolling--; + this.renderer.scrollCursorIntoView(null, 0.5); }; /** diff --git a/lib/ace/editor_navigation_test.js b/lib/ace/editor_navigation_test.js index 93eaa2c2..9a715445 100644 --- a/lib/ace/editor_navigation_test.js +++ b/lib/ace/editor_navigation_test.js @@ -82,7 +82,7 @@ module.exports = { editor.navigateTo(0, 0); editor.gotoLine(101); assert.position(editor.getCursorPosition(), 100, 0); - assert.equal(editor.getFirstVisibleRow(), 90); + assert.equal(editor.getFirstVisibleRow(), 89); editor.navigateTo(100, 0); editor.gotoLine(11); @@ -102,7 +102,7 @@ module.exports = { editor.navigateTo(0, 0); editor.gotoLine(191); assert.position(editor.getCursorPosition(), 190, 0); - assert.equal(editor.getFirstVisibleRow(), 180); + assert.equal(editor.getFirstVisibleRow(), 179); editor.navigateTo(0, 0); editor.gotoLine(196); diff --git a/lib/ace/keyboard/hash_handler.js b/lib/ace/keyboard/hash_handler.js index c4f14181..53a9c9c4 100644 --- a/lib/ace/keyboard/hash_handler.js +++ b/lib/ace/keyboard/hash_handler.js @@ -78,6 +78,18 @@ function HashHandler(config, platform) { } }; + this.bindKey = function(key, command) { + if(!key) + return; + + var ckb = this.commmandKeyBinding; + key.split("|").forEach(function(keyPart) { + var binding = this.parseKeys(keyPart, command); + var hashId = binding.hashId; + (ckb[hashId] || (ckb[hashId] = {}))[binding.key] = command; + }, this); + }; + this.addCommands = function(commands) { commands && Object.keys(commands).forEach(function(name) { var command = commands[name]; @@ -100,18 +112,6 @@ function HashHandler(config, platform) { }, this); }; - this.bindKey = function(key, command) { - if(!key) - return; - - var ckb = this.commmandKeyBinding; - key.split("|").forEach(function(keyPart) { - var binding = parseKeys(keyPart, command); - var hashId = binding.hashId; - (ckb[hashId] || (ckb[hashId] = {}))[binding.key] = command; - }); - }; - this.bindKeys = function(keyList) { Object.keys(keyList).forEach(function(key) { this.bindKey(key, keyList[key]); @@ -127,10 +127,10 @@ function HashHandler(config, platform) { this.bindKey(key, command); }; - function parseKeys(keys, val, ret) { + this.parseKeys = function(keys, val) { var key; var hashId = 0; - var parts = splitSafe(keys.toLowerCase()); + var parts = keys.toLowerCase().trim().split(/\s*\-\s*/); for (var i = 0, l = parts.length; i < l; i++) { if (keyUtil.KEY_MODS[parts[i]]) @@ -143,17 +143,12 @@ function HashHandler(config, platform) { key: key, hashId: hashId }; - } - - function splitSafe(s) { - return (s.trim() - .split(new RegExp("[\\s ]*\\-[\\s ]*", "g"), 999)); - } + }; this.findKeyCommand = function findKeyCommand(hashId, keyString) { var ckbr = this.commmandKeyBinding; return ckbr[hashId] && ckbr[hashId][keyString.toLowerCase()]; - } + }; this.handleKeyboard = function(data, hashId, keyString, keyCode) { return { @@ -164,4 +159,4 @@ function HashHandler(config, platform) { }).call(HashHandler.prototype) exports.HashHandler = HashHandler; -}); \ No newline at end of file +}); diff --git a/lib/ace/keyboard/keybinding.js b/lib/ace/keyboard/keybinding.js index 6e6656d5..79fb7aa9 100644 --- a/lib/ace/keyboard/keybinding.js +++ b/lib/ace/keyboard/keybinding.js @@ -47,15 +47,27 @@ require("../commands/default_commands"); var KeyBinding = function(editor) { this.$editor = editor; this.$data = { }; - this.$handlers = [this]; + this.$handlers = []; + this.setDefaultHandler(editor.commands); }; (function() { + this.setDefaultHandler = function(keyboardHandler) { + this.removeKeyboardHandler(this.$defaultHandler); + this.$defaultHandler = keyboardHandler; + if (keyboardHandler) + this.$handlers.unshift(keyboardHandler); + this.$data = { }; + }; + this.setKeyboardHandler = function(keyboardHandler) { if (this.$handlers[this.$handlers.length - 1] == keyboardHandler) return; this.$data = { }; - this.$handlers = keyboardHandler ? [this, keyboardHandler] : [this]; + this.$handlers = []; + this.setDefaultHandler(this.$defaultHandler); + if (keyboardHandler) + this.$handlers.push(keyboardHandler); }; this.addKeyboardHandler = function(keyboardHandler) { @@ -93,7 +105,7 @@ var KeyBinding = function(editor) { // allow keyboardHandler to consume keys if (toExecute.command != "null") - success = commands.exec(toExecute.command, this.$editor, toExecute.args); + success = commands.exec(toExecute.command, this.$editor, toExecute.args, e); else success = true; @@ -103,20 +115,14 @@ var KeyBinding = function(editor) { return success; }; - this.handleKeyboard = function(data, hashId, keyString) { - return { - command: this.$editor.commands.findKeyCommand(hashId, keyString) - }; - }; - this.onCommandKey = function(e, hashId, keyCode) { var keyString = keyUtil.keyCodeToString(keyCode); this.$callKeyboardHandlers(hashId, keyString, keyCode, e); }; - this.onTextInput = function(text, pasted) { + this.onTextInput = function(text) { var success = false; - if (!pasted && text.length == 1) + if (text.length == 1) success = this.$callKeyboardHandlers(0, text); if (!success) this.$editor.commands.exec("insertstring", this.$editor, text); diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index bb917f5b..10f12865 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -72,13 +72,18 @@ var TextInput = function(parentNode, host) { if (!copied) { var value = valueToSend || text.value; if (value) { - if (value.charCodeAt(value.length-1) == PLACEHOLDER.charCodeAt(0)) { - value = value.slice(0, -1); - if (value) - host.onTextInput(value, pasted); + if (value.length > 1) { + if (value.charAt(0) == PLACEHOLDER) + value = value.substr(1); + else if (value.charAt(value.length - 1) == PLACEHOLDER) + value = value.slice(0, -1); } - else { - host.onTextInput(value, pasted); + + if (value && value != PLACEHOLDER) { + if (pasted) + host.onPaste(value); + else + host.onTextInput(value); } // If editor is no longer focused we quit immediately, since @@ -99,7 +104,7 @@ var TextInput = function(parentNode, host) { var onTextInput = function(e) { setTimeout(function () { if (!inCompostion) - sendText(e.data); + sendText(e.data); }, 0); }; @@ -155,6 +160,7 @@ var TextInput = function(parentNode, host) { }; event.addCommandKeyListener(text, host.onCommandKey.bind(host)); + if (useragent.isOldIE) { var keytable = { 13:1, 27:1 }; event.addListener(text, "keyup", function (e) { diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index d4d39bc0..e4069659 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -390,21 +390,18 @@ var Text = function(parentEl) { this.$renderToken = function(stringBuilder, screenColumn, token, value) { var self = this; - var replaceReg = /\t|&|<|( +)|([\v\f \u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000])|[\u1100-\u115F]|[\u11A3-\u11A7]|[\u11FA-\u11FF]|[\u2329-\u232A]|[\u2E80-\u2E99]|[\u2E9B-\u2EF3]|[\u2F00-\u2FD5]|[\u2FF0-\u2FFB]|[\u3000-\u303E]|[\u3041-\u3096]|[\u3099-\u30FF]|[\u3105-\u312D]|[\u3131-\u318E]|[\u3190-\u31BA]|[\u31C0-\u31E3]|[\u31F0-\u321E]|[\u3220-\u3247]|[\u3250-\u32FE]|[\u3300-\u4DBF]|[\u4E00-\uA48C]|[\uA490-\uA4C6]|[\uA960-\uA97C]|[\uAC00-\uD7A3]|[\uD7B0-\uD7C6]|[\uD7CB-\uD7FB]|[\uF900-\uFAFF]|[\uFE10-\uFE19]|[\uFE30-\uFE52]|[\uFE54-\uFE66]|[\uFE68-\uFE6B]|[\uFF01-\uFF60]|[\uFFE0-\uFFE6]/g; + var replaceReg = /\t|&|<|( +)|([\u0000-\u0019\u00a0\u2000-\u200b\u2028\u2029\u3000])|[\u1100-\u115F\u11A3-\u11A7\u11FA-\u11FF\u2329-\u232A\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3000-\u303E\u3041-\u3096\u3099-\u30FF\u3105-\u312D\u3131-\u318E\u3190-\u31BA\u31C0-\u31E3\u31F0-\u321E\u3220-\u3247\u3250-\u32FE\u3300-\u4DBF\u4E00-\uA48C\uA490-\uA4C6\uA960-\uA97C\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFAFF\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFF01-\uFF60\uFFE0-\uFFE6]/g; var replaceFunc = function(c, a, b, tabIdx, idx4) { - if (c.charCodeAt(0) == 32) { + if (a) { return new Array(c.length+1).join(" "); + } else if (c == "&") { + return useragent.isOldGecko ? "&" : "&"; + } else if (c == "<") { + return "<"; } else if (c == "\t") { var tabSize = self.session.getScreenTabSize(screenColumn + tabIdx); screenColumn += tabSize - 1; return self.$tabStrings[tabSize]; - } else if (c == "&") { - if (useragent.isOldGecko) - return "&"; - else - return "&"; - } else if (c == "<") { - return "<"; } else if (c == "\u3000") { // U+3000 is both invisible AND full-width, so must be handled uniquely var classToUse = self.showInvisibles ? "ace_cjk ace_invisible" : "ace_cjk"; @@ -413,13 +410,11 @@ var Text = function(parentEl) { return "" + space + ""; - } else if (c.match(/[\v\f \u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000]/)) { - if (self.showInvisibles) { - var space = new Array(c.length+1).join(self.SPACE_CHAR); - return "" + space + ""; - } else { + } else if (b) { + if (self.showInvisibles) + return "" + self.SPACE_CHAR + ""; + else return " "; - } } else { screenColumn += 1; return " Void + * VirtualRenderer.scrollCursorIntoView(cursor, offset) -> Void * * Scrolls the cursor into the first visibile area of the editor **/ - this.scrollCursorIntoView = function(cursor) { + this.scrollCursorIntoView = function(cursor, offset) { // the editor is not visible if (this.$size.scrollerHeight === 0) return; @@ -900,10 +914,12 @@ var VirtualRenderer = function(container, theme) { var top = pos.top; if (this.scrollTop > top) { + if (offset) + top -= offset * this.$size.scrollerHeight; this.session.setScrollTop(top); - } - - if (this.scrollTop + this.$size.scrollerHeight < top + this.lineHeight) { + } else if (this.scrollTop + this.$size.scrollerHeight < top + this.lineHeight) { + if (offset) + top += offset * this.$size.scrollerHeight; this.session.setScrollTop(top + this.lineHeight - this.$size.scrollerHeight); } @@ -913,9 +929,7 @@ var VirtualRenderer = function(container, theme) { if (left < this.$padding + 2 * this.layerConfig.characterWidth) left = 0; this.session.setScrollLeft(left); - } - - if (scrollLeft + this.$size.scrollerWidth < left + this.characterWidth) { + } else if (scrollLeft + this.$size.scrollerWidth < left + this.characterWidth) { this.session.setScrollLeft(Math.round(left + this.characterWidth - this.$size.scrollerWidth)); } }; @@ -966,36 +980,70 @@ var VirtualRenderer = function(container, theme) { this.session.setScrollTop(row * this.lineHeight); }; + this.STEPS = 8; + this.$calcSteps = function(fromValue, toValue){ + var i = 0; + var l = this.STEPS; + var steps = []; + + var func = function(t, x_min, dx) { + return dx * (Math.pow(t - 1, 3) + 1) + x_min; + }; + + for (i = 0; i < l; ++i) + steps.push(func(i / this.STEPS, fromValue, toValue - fromValue)); + + return steps; + }; + /** - * VirtualRenderer.scrollToLine(line, center) -> Void + * VirtualRenderer.scrollToLine(line, center, animate, callback) -> Void * - line (Number): A line number * - center (Boolean): If `true`, centers the editor the to indicated line - * + * - animate (Boolean): If `true` animates scrolling + * - callback (Function): Function to be called after the animation has finished + * * Gracefully scrolls the editor to the row indicated. **/ - this.scrollToLine = function(line, center) { + this.scrollToLine = function(line, center, animate, callback) { var pos = this.$cursorLayer.getPixelPosition({row: line, column: 0}); var offset = pos.top; if (center) offset -= this.$size.scrollerHeight / 2; - if (this.$animatedScroll && Math.abs(offset - this.scrollTop) < 10000) { - var _self = this; - var steps = _self.$calcSteps(this.scrollTop, offset); - - clearInterval(this.$timer); - this.$timer = setInterval(function() { - _self.session.setScrollTop(steps.shift()); - - if (!steps.length) - clearInterval(_self.$timer); - }, 10); - } - else { + var initialScroll = this.scrollTop; this.session.setScrollTop(offset); - } + if (animate !== false) + this.animateScrolling(initialScroll, callback); }; + this.animateScrolling = function(fromValue, callback) { + var toValue = this.scrollTop; + if (this.$animatedScroll && Math.abs(fromValue - toValue) < 100000) { + var _self = this; + var steps = _self.$calcSteps(fromValue, toValue); + this.$inScrollAnimation = true; + + clearInterval(this.$timer); + + _self.session.setScrollTop(steps.shift()); + this.$timer = setInterval(function() { + if (steps.length) { + _self.session.setScrollTop(steps.shift()); + // trick session to think it's already scrolled to not loose toValue + _self.session.$scrollTop = toValue; + } else { + this.$inScrollAnimation = false; + clearInterval(_self.$timer); + + _self.session.$scrollTop = -1; + _self.session.setScrollTop(toValue); + callback && callback(); + } + }, 10); + } + }; + /** * VirtualRenderer.scrollToY(scrollTop) -> Number * - scrollTop (Number): The position to scroll to