diff --git a/lib/ace/css/editor.css b/lib/ace/css/editor.css index 5a544076..11831f03 100644 --- a/lib/ace/css/editor.css +++ b/lib/ace/css/editor.css @@ -36,6 +36,12 @@ z-index: 1000; } +.ace_gutter_active_line { + position: absolute; + right: 0; + width: 100%; +} + .ace_gutter.horscroll { box-shadow: 0px 0px 20px rgba(0,0,0,0.4); } @@ -94,8 +100,8 @@ .ace_editor textarea { position: fixed; z-index: 0; - width: 10px; - height: 30px; + width: 0.5em; + height: 1em; opacity: 0; background: transparent; appearance: none; diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 851c314a..85d7076b 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -80,6 +80,7 @@ var Editor = function(renderer, session) { this.commands = new CommandManager(useragent.isMac ? "mac" : "win", defaultCommands); this.textInput = new TextInput(renderer.getTextAreaContainer(), this); + this.renderer.textarea = this.textInput.getElement(); this.keyBinding = new KeyBinding(this); // TODO detect touch event support @@ -385,11 +386,7 @@ var Editor = function(renderer, session) { this.$cursorChange = function() { this.renderer.updateCursor(); - - // move text input over the cursor - // this is required for iOS and IME - this.renderer.moveTextAreaToCursor(this.textInput.getElement()); - } + }; /** * Editor@onDocumentChange(e) @@ -458,7 +455,6 @@ var Editor = function(renderer, session) { this.$highlightBrackets(); this.$updateHighlightActiveLine(); - this.$updateHighlightGutterLine(); }; /** internal, hide @@ -490,21 +486,6 @@ var Editor = function(renderer, session) { } }; - /** 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) @@ -526,7 +507,6 @@ var Editor = function(renderer, session) { session.$selectionMarker = session.addMarker(range, "ace_selection", style); } else { this.$updateHighlightActiveLine(); - this.$updateHighlightGutterLine(); } if (this.$highlightSelectedWord) @@ -605,7 +585,6 @@ 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(); }; @@ -891,12 +870,11 @@ var Editor = function(renderer, session) { }; this.$highlightGutterLine = true; - this.setHighlightGutterLine = function(shouldHighlightGutterLine) { - if (this.$highlightGutterLine == shouldHighlightGutterLine) + this.setHighlightGutterLine = function(shouldHighlight) { + if (this.$highlightGutterLine == shouldHighlight) return; - this.$highlightGutterLine = shouldHighlightGutterLine; - this.$updateHighlightGutterLine(); + this.renderer.setHighlightGutterLine(shouldHighlight); }; this.getHighlightGutterLine = function() { diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index 10f12865..fa570e66 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -49,7 +49,9 @@ var TextInput = function(parentNode, host) { var text = dom.createElement("textarea"); if (useragent.isTouchPad) text.setAttribute("x-palm-disable-auto-cap", true); - + + text.setAttribute("wrap", "off"); + text.style.left = "-10000px"; text.style.position = "fixed"; parentNode.insertBefore(text, parentNode.firstChild); diff --git a/lib/ace/layer/cursor.js b/lib/ace/layer/cursor.js index 1c55d7a8..f43b36b7 100644 --- a/lib/ace/layer/cursor.js +++ b/lib/ace/layer/cursor.js @@ -175,6 +175,9 @@ var Cursor = function(parentEl) { if (overwrite != this.overwrite) this.$setOverite(overwrite); + // cache for textarea and gutter highlight + this.$pixelPos = pixelPos; + this.restartTimer(); }; diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index e4069659..bb8d733d 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -395,9 +395,9 @@ var Text = function(parentEl) { if (a) { return new Array(c.length+1).join(" "); } else if (c == "&") { - return useragent.isOldGecko ? "&" : "&"; + return "&"; } else if (c == "<") { - return "<"; + return "<"; } else if (c == "\t") { var tabSize = self.session.getScreenTabSize(screenColumn + tabIdx); screenColumn += tabSize - 1; @@ -411,10 +411,7 @@ var Text = function(parentEl) { (self.config.characterWidth * 2) + "px'>" + space + ""; } else if (b) { - if (self.showInvisibles) - return "" + self.SPACE_CHAR + ""; - else - return " "; + return "" + self.SPACE_CHAR + ""; } else { screenColumn += 1; return " lead.row || (anchor.row == lead.row && anchor.column > lead.column)); }; @@ -203,8 +203,8 @@ var Selection = function(session) { * [Returns the [[Range `Range`]] for the selected text.]{: #Selection.getRange} **/ this.getRange = function() { - var anchor = this.selectionAnchor; - var lead = this.selectionLead; + var anchor = this.anchor; + var lead = this.lead; if (this.isEmpty()) return Range.fromPoints(lead, lead); @@ -249,18 +249,23 @@ var Selection = function(session) { * **/ this.setSelectionRange = function(range, reverse) { - if (reverse) { - this.setSelectionAnchor(range.end.row, range.end.column); - this.selectTo(range.start.row, range.start.column); + if (range.isEmpty()) { + this.lead.setPosition(range.start.row, range.start.column); + this.clearSelection(); + } else if (reverse) { + this.$isEmpty = false; + this.anchor.setPosition(range.end.row, range.end.column); + this.lead.setPosition(range.start.row, range.start.column); } else { - this.setSelectionAnchor(range.start.row, range.start.column); - this.selectTo(range.end.row, range.end.column); + this.$isEmpty = false; + this.anchor.setPosition(range.start.row, range.start.column); + this.lead.setPosition(range.end.row, range.end.column); } this.$desiredColumn = null; }; this.$moveSelection = function(mover) { - var lead = this.selectionLead; + var lead = this.lead; if (this.$isEmpty) this.setSelectionAnchor(lead.row, lead.column); @@ -391,7 +396,7 @@ var Selection = function(session) { **/ this.getWordRange = function(row, column) { if (typeof column == "undefined") { - var cursor = row || this.selectionLead; + var cursor = row || this.lead; row = cursor.row; column = cursor.column; } @@ -414,7 +419,7 @@ var Selection = function(session) { }; this.getLineRange = function(row, excludeLastChar) { - var rowStart = typeof row == "number" ? row : this.selectionLead.row; + var rowStart = typeof row == "number" ? row : this.lead.row; var rowEnd; var foldLine = this.session.getFoldLine(rowStart); @@ -463,7 +468,7 @@ var Selection = function(session) { * Moves the cursor left one column. **/ this.moveCursorLeft = function() { - var cursor = this.selectionLead.getPosition(), + var cursor = this.lead.getPosition(), fold; if (fold = this.session.getFoldAt(cursor.row, cursor.column, -1)) { @@ -489,19 +494,19 @@ var Selection = function(session) { * Moves the cursor right one column. **/ this.moveCursorRight = function() { - var cursor = this.selectionLead.getPosition(), + var cursor = this.lead.getPosition(), fold; if (fold = this.session.getFoldAt(cursor.row, cursor.column, 1)) { this.moveCursorTo(fold.end.row, fold.end.column); } - else if (this.selectionLead.column == this.doc.getLine(this.selectionLead.row).length) { - if (this.selectionLead.row < this.doc.getLength() - 1) { - this.moveCursorTo(this.selectionLead.row + 1, 0); + else if (this.lead.column == this.doc.getLine(this.lead.row).length) { + if (this.lead.row < this.doc.getLength() - 1) { + this.moveCursorTo(this.lead.row + 1, 0); } } else { var tabSize = this.session.getTabSize(); - var cursor = this.selectionLead; + var cursor = this.lead; if (this.session.isTabStop(cursor) && this.doc.getLine(cursor.row).slice(cursor.column, cursor.column+tabSize).split(" ").length-1 == tabSize) this.moveCursorBy(0, tabSize); else @@ -515,8 +520,8 @@ var Selection = function(session) { * Moves the cursor to the start of the line. **/ this.moveCursorLineStart = function() { - var row = this.selectionLead.row; - var column = this.selectionLead.column; + var row = this.lead.row; + var column = this.lead.column; var screenRow = this.session.documentToScreenRow(row, column); // Determ the doc-position of the first character at the screen line. @@ -548,7 +553,7 @@ var Selection = function(session) { * Moves the cursor to the end of the line. **/ this.moveCursorLineEnd = function() { - var lead = this.selectionLead; + var lead = this.lead; var lastRowColumnPosition = this.session.getDocumentLastRowColumnPosition(lead.row, lead.column); this.moveCursorTo( @@ -583,8 +588,8 @@ var Selection = function(session) { * Moves the cursor to the word on the right. **/ this.moveCursorLongWordRight = function() { - var row = this.selectionLead.row; - var column = this.selectionLead.column; + var row = this.lead.row; + var column = this.lead.column; var line = this.doc.getLine(row); var rightOfCursor = line.substring(column); @@ -630,8 +635,8 @@ var Selection = function(session) { * Moves the cursor to the word on the left. **/ this.moveCursorLongWordLeft = function() { - var row = this.selectionLead.row; - var column = this.selectionLead.column; + var row = this.lead.row; + var column = this.lead.column; // skip folds var fold; @@ -712,8 +717,8 @@ var Selection = function(session) { }; this.moveCursorShortWordRight = function() { - var row = this.selectionLead.row; - var column = this.selectionLead.column; + var row = this.lead.row; + var column = this.lead.column; var line = this.doc.getLine(row); var rightOfCursor = line.substring(column); @@ -730,8 +735,8 @@ var Selection = function(session) { }; this.moveCursorShortWordLeft = function() { - var row = this.selectionLead.row; - var column = this.selectionLead.column; + var row = this.lead.row; + var column = this.lead.column; var fold; if (fold = this.session.getFoldAt(row, column, -1)) @@ -770,8 +775,8 @@ var Selection = function(session) { **/ this.moveCursorBy = function(rows, chars) { var screenPos = this.session.documentToScreenPosition( - this.selectionLead.row, - this.selectionLead.column + this.lead.row, + this.lead.column ); if (chars === 0) { @@ -814,7 +819,7 @@ var Selection = function(session) { } this.$keepDesiredColumnOnChange = true; - this.selectionLead.setPosition(row, column); + this.lead.setPosition(row, column); this.$keepDesiredColumnOnChange = false; if (!keepDesiredColumn) @@ -836,8 +841,8 @@ var Selection = function(session) { // remove listeners from document this.detach = function() { - this.selectionLead.detach(); - this.selectionAnchor.detach(); + this.lead.detach(); + this.anchor.detach(); this.session = this.doc = null; } diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index 69909c6a..fdff0160 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -82,6 +82,9 @@ var VirtualRenderer = function(container, theme) { // TODO: this breaks rendering in Cloud9 with multiple ace instances // // Imports CSS once per DOM document ('ace_editor' serves as an identifier). // dom.importCssString(editorCss, "ace_editor", container.ownerDocument); + + // in IE <= 9 the native cursor always shines through + this.$keepTextAreaAtCursor = !useragent.isIE; dom.addCssClass(container, "ace_editor"); @@ -99,6 +102,7 @@ var VirtualRenderer = function(container, theme) { this.content.className = "ace_content"; this.scroller.appendChild(this.content); + this.setHighlightGutterLine(true); this.$gutterLayer = new GutterLayer(this.$gutter); this.$gutterLayer.on("changeGutterWidth", this.onResize.bind(this, true)); this.setFadeFoldWidgets(true); @@ -449,6 +453,33 @@ var VirtualRenderer = function(container, theme) { dom.removeCssClass(this.$gutter, "ace_fade-fold-widgets"); }; + this.$highlightGutterLine = false; + this.setHighlightGutterLine = function(shouldHighlight) { + if (this.$highlightGutterLine == shouldHighlight) + return; + this.$highlightGutterLine = shouldHighlight; + + + if (!this.$gutterLineHighlight) { + this.$gutterLineHighlight = dom.createElement("div"); + this.$gutterLineHighlight.className = "ace_gutter_active_line"; + this.$gutter.appendChild(this.$gutterLineHighlight); + return; + } + + this.$gutterLineHighlight.style.display = shouldHighlight ? "" : "none"; + this.$updateGutterLineHighlight(); + }; + + this.getHighlightGutterLine = function() { + return this.$highlightGutterLine; + }; + + this.$updateGutterLineHighlight = function() { + this.$gutterLineHighlight.style.top = this.$cursorLayer.$pixelPos.top + "px"; + this.$gutterLineHighlight.style.height = this.layerConfig.lineHeight + "px"; + }; + this.$updatePrintMargin = function() { var containerEl; @@ -496,30 +527,23 @@ var VirtualRenderer = function(container, theme) { return this.container; }; - /** - * VirtualRenderer.moveTextAreaToCursor(textarea) -> Void - * - textarea (DOMElement): A text area to work with - * - * Changes the position of `textarea` to where the cursor is pointing. - **/ - this.moveTextAreaToCursor = function(textarea) { - // in IE the native cursor always shines through - // this persists in IE9 - if (useragent.isIE) + // move text input over the cursor + // this is required for iOS and IME + this.$moveTextAreaToCursor = function() { + if (!this.$keepTextAreaAtCursor) return; - if (this.layerConfig.lastRow === 0) + var posTop = this.$cursorLayer.$pixelPos.top; + var posLeft = this.$cursorLayer.$pixelPos.left; + posTop -= this.layerConfig.offset; + + if (posTop < 0 || posTop > this.layerConfig.height) return; - var pos = this.$cursorLayer.getPixelPosition(); - if (!pos) - return; - - var bounds = this.content.getBoundingClientRect(); - var offset = this.layerConfig.offset; - - textarea.style.left = (bounds.left + pos.left) + "px"; - textarea.style.top = (bounds.top + pos.top - this.scrollTop + offset) + "px"; + posLeft += (this.showGutter ? this.$gutterLayer.gutterWidth : 0) - this.scrollLeft; + var bounds = this.container.getBoundingClientRect(); + this.textarea.style.left = (bounds.left + posLeft) + "px"; + this.textarea.style.top = (bounds.top + posTop) + "px"; }; /** @@ -640,6 +664,8 @@ var VirtualRenderer = function(container, theme) { this.$markerBack.update(this.layerConfig); this.$markerFront.update(this.layerConfig); this.$cursorLayer.update(this.layerConfig); + this.$moveTextAreaToCursor(); + this.$highlightGutterLine && this.$updateGutterLineHighlight(); return; } @@ -656,6 +682,8 @@ var VirtualRenderer = function(container, theme) { this.$markerBack.update(this.layerConfig); this.$markerFront.update(this.layerConfig); this.$cursorLayer.update(this.layerConfig); + this.$moveTextAreaToCursor(); + this.$highlightGutterLine && this.$updateGutterLineHighlight(); return; } @@ -675,8 +703,11 @@ var VirtualRenderer = function(container, theme) { this.$gutterLayer.update(this.layerConfig); } - if (changes & this.CHANGE_CURSOR) + if (changes & this.CHANGE_CURSOR) { this.$cursorLayer.update(this.layerConfig); + this.$moveTextAreaToCursor(); + this.$highlightGutterLine && this.$updateGutterLineHighlight(); + } if (changes & (this.CHANGE_MARKER | this.CHANGE_MARKER_FRONT)) { this.$markerFront.update(this.layerConfig); @@ -753,7 +784,7 @@ var VirtualRenderer = function(container, theme) { // For debugging. // console.log(JSON.stringify(this.layerConfig)); - this.$gutterLayer.element.style.marginTop = (-offset) + "px"; + this.$gutter.style.marginTop = (-offset) + "px"; this.content.style.marginTop = (-offset) + "px"; this.content.style.width = longestLine + 2 * this.$padding + "px"; this.content.style.height = minHeight + "px"; @@ -1103,14 +1134,24 @@ var VirtualRenderer = function(container, theme) { // todo: handle horizontal scrolling }; - this.screenToTextCoordinates = function(pageX, pageY) { + this.pixelToScreenCoordinates = function(x, y) { + var canvasPos = this.scroller.getBoundingClientRect(); + + var offset = (x + this.scrollLeft - canvasPos.left - this.$padding) / this.characterWidth; + var row = Math.floor((y + this.scrollTop - canvasPos.top) / this.lineHeight); + var col = Math.round(offset); + + return {row: row, column: col, side: offset - col > 0 ? 1 : -1}; + }; + + this.screenToTextCoordinates = function(x, y) { var canvasPos = this.scroller.getBoundingClientRect(); var col = Math.round( - (pageX + this.scrollLeft - canvasPos.left - this.$padding - dom.getPageScrollLeft()) / this.characterWidth + (x + this.scrollLeft - canvasPos.left - this.$padding) / this.characterWidth ); var row = Math.floor( - (pageY + this.scrollTop - canvasPos.top - dom.getPageScrollTop()) / this.lineHeight + (y + this.scrollTop - canvasPos.top) / this.lineHeight ); return this.session.screenToDocumentPosition(row, Math.max(col, 0));