UNPKG

ares-ide

Version:

A browser-based code editor and UI designer for Enyo 2 projects

568 lines (556 loc) 16.8 kB
/*global require, ace, enyo, ComponentsRegistry */ enyo.kind({ name: "enyo.AceWrapper", kind: enyo.Control, published: { value: "", theme: "clouds", fontSize: "", wordWrap: false, readonly: false, highlightActiveLine: false, showPrintMargin: false, useSoftTabs: false, persistentHScroll: false, //** Modes: editingMode: "javascript", translateX: "" }, requiresDomMousedown: true, events: { onChange: "", onScroll: "", onCursorChange: "", onAutoCompletion: "", onFind: "", onWordwrap: "", onFkey: "", onChildRequest: "", onAceGotFocus: "", /// FIXME just add these for now onSetBreakpoint: "", onClearBreakpoint: "" }, // NOTE: ace doesn't like being flexed, so we wrap the editor node in a positioned element style: "font-size: 0.8em;", components: [ {name: "aceEditor", classes: "enyo-view enyo-fit"} ], rendered: function() { this.inherited(arguments); var n = this.hasNode(); if (n) { this.editor = ace.edit(this.$.aceEditor.id); this.valueChanged(); this.updateSessionSettings(this.getSession()); this.readonlyChanged(); this.showPrintMarginChanged(); this.highlightActiveLineChanged(); this.persistentHScrollChanged(); this.gotoLine(0); //this.removeMeasureNode(); this.addListeners(); this.addSessionListeners(); this.addKeyBindings(); } }, /** * Apply settings from editor settings popup * @public */ applyAceSettings: function(settings) { this.setTheme(settings.theme); this.setFontSize(settings.fontsize); this.setHighlightActiveLine(settings.highlight); this.setWordWrap(settings.wordwrap); }, /** * Register some specific commands for save, ... * @protected */ addKeyBindings: function() { var commands = this.editor.commands; // Add keybinding for save commands.addCommand({ name: "save", bindKey: {win: "Ctrl-S", mac: "Command-S"}, exec: this.doChildRequest.bind(this,{task: "saveCurrentDoc"}) }); // Add keybinding for auto completion commands.addCommand({ name: "autocompletion", bindKey: {win: "Ctrl-SPACE", mac: "Ctrl-SPACE"}, exec: enyo.bind(this, "doAutoCompletion") }); // Add keybinding for find commands.addCommand({ name: "find", bindKey: {win: "Ctrl-F", mac: "Command-F"}, exec: enyo.bind(this, "doFind") }); // Add keybinding for Word wrap commands.addCommand({ name: "Wordwrap", bindKey: {win: "Alt-W", mac: "Alt-W"}, exec: enyo.bind(this, "doWordwrap") }); // Add keybinding for F1 t F12 var i,key; for (i=1; i<13; i++) { key = 'F' + i; //console.log(key); commands.addCommand({ name: key, bindKey: {win: "Ctrl-SHIFT-"+key}, exec: enyo.bind(this, 'doFkey' , [key]) }); } }, /** * Add a new command with key kinding * @param command: the new command with key binding. * @public */ addCommand: function(command) { this.editor.commands.addCommand(command); }, addListeners: function() { this.editor.renderer.on("gutterclick", enyo.bind(this, "gutterclick")); this.editor.renderer.scrollBar.on("scroll", enyo.bind(this, "doScroll")); //intercept event showing the ace error tooltip var gutterEl = this.editor.renderer.$gutter; enyo.dispatcher.listen(gutterEl, "mousemove", enyo.bind(this, "guttermousemove")); this.editor.on("focus", this.bubble.bind(this, "onAceGotFocus") ); }, addSessionListeners: function() { this.getSession().on("change", enyo.bind(this, "doChange")); this.getSession().selection.on("changeCursor", enyo.bind(this, "doCursorChange")); this.getSession().on("changeScrollLeft", enyo.bind(this, "handleScroll")); this.getSession().on("changeScrollTop", enyo.bind(this, "handleScroll")); }, handleScroll: function(/* Dont handle parameters as we dont need them yet */) { this.doScroll(); }, gutterclick: function(inEventInfo) { this.toggleBreakpoint(inEventInfo.row); }, // FIXME: ace should have this method toggleBreakpoint: function(inRow) { var s = this.getSession(); var bp = s.getBreakpoints(); this.log(inRow); if (!bp[inRow]) { s.setBreakpoint(inRow); this.doSetBreakpoint(inRow); } else { s.clearBreakpoint(inRow); this.doClearBreakpoint(inRow); } }, // remove the stupid ace measuring node removeMeasureNode: function() { // measure nodes are at -40000px left, directly under the body var measureNode = document.body.querySelector("body > div[style ~= '-40000px;']"); if (measureNode) { measureNode.parentNode.removeChild(measureNode); } }, themeChanged: function() { var t = this.theme || ""; t = (!t || t.indexOf("/") >= 0) ? t : "ace/theme/" + t; this.editor.setTheme(t); }, highlightActiveLineChanged: function() { this.editor.setHighlightActiveLine(this.highlightActiveLine); }, readonlyChanged: function() { this.editor.setReadOnly(this.readonly); }, setSessionMode: function(inSession, inMode) { try { inSession.setMode("ace/mode/" + inMode); } catch(e) { this.warn(e); } }, updateSessionSettings: function(inSession) { this.setSessionMode(inSession, this.editingMode); inSession.setUseWrapMode(this.wordWrap); inSession.setUseSoftTabs(this.useSoftTabs); }, editingModeChanged: function() { this.updateSessionSettings(this.getSession()); }, wordWrapChanged: function() { this.updateSessionSettings(this.getSession()); }, useSoftTabsChanged: function() { this.updateSessionSettings(this.getSession()); }, getValue: function() { this.value = this.getSession().getValue(); return this.value; }, valueChanged: function() { var s = this.getSession(); if (s) { s.setValue(this.value); } }, persistentHScrollChanged: function() { this.editor.renderer.setHScrollBarAlwaysVisible(this.persistentHScroll); }, updateValue: function(inValue) { this.editor.selectAll(); this.insertAtCursor(inValue); }, focus: function() { if (this.editor !== undefined){ this.editor.focus(); } }, blur: function() { this.editor.blur(); }, showPrintMarginChanged: function () { this.editor.renderer.setShowPrintMargin(this.showPrintMargin); }, getSelection: function() { return this.getSession().doc.getTextRange(this.editor.getSelectionRange()); }, insertAt: function(inPos, inText) { this.editor.moveCursorToPosition(inPos); this.editor.insert(inText); }, insertAtCursor: function(inText) { this.editor.insert(inText); }, /*insertAt: function(inPosition, inText) { var cursorPos; if (inPosition) { cursorPos = this.getCursorPositionInDocument(); this.editor.moveCursorTo(inPosition.row, inPosition.column); } this.insertAtCursor(inText); if (cursorPos) { this.editor.moveCursorTo(inPosition.row, inPosition.column); } },*/ gotoLine: function(inLine, inRow) { this.editor.gotoLine(inLine, inRow); }, navigateUp: function(times) { this.editor.navigateUp(times); }, navigateDown: function(times) { this.editor.navigateDown(times); }, /** inOptions {Object} backwards: false, wrap: false, caseSensitive: false, wholeWord: false, regExp: false */ find: function(inFind, inOptions) { this.editor.find(inFind, inOptions); }, findNext: function() { this.editor.findNext(); }, findPrevious: function() { this.editor.findPrevious(); }, replace: function(inFind, inReplace, inOptions) { this.editor.replace(inReplace); }, replacefind: function(inFind, inReplace, inOptions) { this.editor.replace(inReplace); this.find(inFind, inOptions); }, replaceAll: function(inFind, inReplace, inOptions) { this.find(inFind, inOptions); return this.editor.replaceAll(inReplace); }, getUndoManager: function() { return this.editor.getSession().getUndoManager(); }, undo: function() { if (this.canUndo()) { this.getUndoManager().undo(); } }, redo: function() { if (this.canRedo()) { this.getUndoManager().redo(); } }, canUndo: function() { return this.getUndoManager().hasUndo(); }, canRedo: function() { return this.getUndoManager().hasRedo(); }, isDirty: function(inCompare) { if (inCompare) { return inCompare !== this.getValue(); } return true; }, scrollToY: function(inY) { this.editor.renderer.scrollToY(inY); }, getLineCount: function() { return this.getSession().getScreenLength(); }, getLineHeight: function() { return this.editor.renderer.lineHeight; }, getContentHeight: function() { return this.getLineCount() * this.getLineHeight(); }, getLines: function(inStartLine, inEndLine) { return this.getSession().getLines(inStartLine, inEndLine); }, getLine: function(inLine) { return this.getLines(inLine, inLine)[0]; }, getLength: function() { return this.getSession().getLength(); }, findMatchingBracketAtCursor: function() { return this.getSession().findMatchingBracket(this.getCursorPositionInDocument()); }, getCursorPositionInDocument: function() { return this.editor.getCursorPosition(); }, getCursorPositionOnScreen: function() { // find cursor position in screen pixels var rect = this.editor.renderer.$cursorLayer.cursor.getBoundingClientRect(); // clone out properties we want (clientrect is not modifiable) return enyo.clone(rect); }, textToScreenCoordinates: function(row, column) { return this.editor.renderer.textToScreenCoordinates(row, column); }, /** * Insert the content of the input parameter at the end of the file * if cursorMarker is defined, the cursor will be positioned * at the first character of the pattern specified by cursorMarker * NB: The pattern cursorMarker is removed from input before * the append a the end of the file * @param input data to append to the file * @param cursorMarker for cursor positioning */ insertAtEndOfFile: function(input, cursorMarker) { var cursorPos = {row: this.getLineCount(), column: 0}; // goto last line this.gotoLine(cursorPos.row); // And the end of that line this.editor.navigateLineEnd(); if (cursorMarker) { // Find the wanted cursor position var lines = input.split('\n'); for(var i = 0; i < lines.length ; i++) { var column = lines[i].indexOf(cursorMarker); if (column !== -1) { cursorPos.row += (i + 1); cursorPos.column = column; input = input.replace(cursorMarker, ""); } } } // auto indent is dumb, so we must add a line at a time this.insertAtCursor("\n"); this.insertAtCursor(input); if (cursorMarker) { // Move the cursor to the expected position this.gotoLine(cursorPos.row, cursorPos.column); } this.focus(); }, insertMethod: function(inMethodName, inArgumentNames, inLine, firstProperty) { // goto referenced line this.gotoLine(inLine + 1); // And the end of that line this.editor.navigateLineEnd(); // insert a comma between properties in the kind bag if (!firstProperty) { this.insertAtCursor(","); } // auto indent is dumb, so we must add a line at a time this.insertAtCursor("\n"); this.insertAtCursor(inMethodName + ": function("); if (enyo.isArray(inArgumentNames)) { this.insertAtCursor(inArgumentNames.join(", ")); } this.insertAtCursor(") {"); this.insertAtCursor("\n"); this.insertAtCursor("\t"); this.insertAtCursor("\n"); this.insertAtCursor("}"); // place cursor on the blank line, at the end this.editor.navigateUp(1); this.editor.navigateLineEnd(); }, createSession: function(inText, inMode) { var EditSession = require("ace/edit_session").EditSession; var UndoManager = require("ace/undomanager").UndoManager; var session = new EditSession(inText || ""); session.setUndoManager(new UndoManager()); this.updateSessionSettings(session); if (inMode) { this.setSessionMode(session, inMode); } return session; }, destroySession: function(inSession) { // TODO: Not sure what should be done exactly. }, addFold: function(inStart, inLeft, inEnd, inRight, inComment) { var r = this.createRange(inStart, inLeft, inEnd, inRight); this.getSession().addFold(inComment, r); }, addMarker: function(inClass, inType, inStart, inLeft, inEnd, inRight) { this.log(inStart, inLeft, inEnd, inRight); var r = this.createRange(inStart, inLeft, inEnd, inRight); return this.getSession().addMarker(r, inClass, inType); }, removeMarker: function(inMarkerId) { this.getSession().removeMarker(inMarkerId); }, createRange: function(inStart, inLeft, inEnd, inRight) { var Range = require("ace/range").Range; return (new Range(inStart, inLeft, inEnd, inRight)); }, getSession: function() { return this.editor.getSession(); }, setSession: function(inSession) { this.editor.setSession(inSession); this.addSessionListeners(); }, /** * Maps a list of stream positions in the current document * to the corresponding lines and columns in the same documemt * @param streamPositions: an array of ordered positions in the document stream * @return an array of corresponding positions in row and column * @public */ mapToLineColumns: function(streamPositions) { var session = this.getSession(); var document = session.getDocument(); var nbNewLineCharacters = document.getNewLineCharacter().length; var lineCount = session.getLength(); var lineIdx = 0; var line; var bytesCount = 0; var nbBytes; var streamIdx = 0; var streamPos = streamPositions[streamIdx]; var positions = []; while (lineIdx < lineCount) { line = session.getLine(lineIdx); nbBytes = line.length; if (bytesCount + nbBytes >= streamPos) { // this.log(streamPos + " --> line: " + (lineIdx + 1) + " column: " + (streamPos - bytesCount)); positions.push({row: lineIdx, column: (streamPos - bytesCount)}); streamIdx++; if (streamIdx < streamPositions.length) { // Find the next one streamPos = streamPositions[streamIdx]; } else { // Were are done return positions; } } else { bytesCount += (nbBytes + nbNewLineCharacters); lineIdx++; } } // We don't return anything here as this is an error condition: bad order, out of range, ... }, /** * Maps a 'start' and 'end' stream positions in the current document * to the corresponding lines and columns in the same documemt * as an ACE Range * @param start: starting stream position in the current document * @param end: ending stream position in the current document * @return an ACE Range matching the stream position. * @public */ mapToLineColumnRange: function(start, end) { var pos = this.mapToLineColumns([start, end]); return this.createRange(pos[0].row, pos[0].column, pos[1].row, pos[1].column); }, /** * Replaces the specified range of text with the new text passed * in the parameters * @param range: the range to replace with the new text * @param text: the new text * @public */ replaceRange: function(range, text) { this.getSession().replace(range, text); }, /** * Inserts at the specified position the new text passed * in the parameters * @param position: the position to insert the new text * @param text: the new text * @public */ insertPosition: function(position, text) { this.getSession().insert(position, text); }, /** * Inserts at the specified position a new line * according to the line termination mode * @param position: the position to insert the new line * @public */ insertNewLine: function(position) { this.getSession().getDocument().insertNewLine(position); }, /** * Get the line termination used according to the related mode * @public */ getNewLineCharacter: function() { return this.getSession().getDocument().getNewLineCharacter(); }, setFontSize: function(size){ var s = size; this.editor.setFontSize(s); }, resize: function(){ if (this.editor !== undefined) { this.editor.resize(); } }, /** @public */ requestSelectedText: function() { return this.editor.session.getTextRange(this.editor.getSelectionRange()); }, navigateToPosition: function(start, end){ var pos = this.mapToLineColumns([start, end]); this.editor.navigateTo(pos[0].row, pos[0].column); }, guttermousemove: function(inSender, inEvent) { if(enyo.dom.canTransform() && enyo.platform.ie !== 10){ var enyoEditorTranslateX = ComponentsRegistry.getComponent("enyoEditor").domTransforms.translateX; var aceTooltip = document.getElementsByClassName("ace_gutter-tooltip"); if(aceTooltip[0]){ if(this.translateX !== enyoEditorTranslateX){ this.setTranslateX(enyoEditorTranslateX); this.applyInverseTransform(aceTooltip[0]); } } } }, applyInverseTransform: function(htmlDiv){ //apply inverse transform to ace error tooltip var transformProp = enyo.dom.getStyleTransformProp(); htmlDiv.className +=" ace_gutter-margin-tooltip"; if(this.translateX){ htmlDiv.style[transformProp] = "translateX(-"+this.translateX+")"; }else{ htmlDiv.style[transformProp] = "none"; } } });