editor.js
来自「在线编辑器」· JavaScript 代码 · 共 1,381 行 · 第 1/4 页
JS
1,381 行
/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. * See the License for the specific language governing rights and * limitations under the License. * * The Original Code is Bespin. * * The Initial Developer of the Original Code is Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Bespin Team (bespin@mozilla.com) * * ***** END LICENSE BLOCK ***** */dojo.provide("bespin.editor.editor");dojo.require("bespin.util.clipboard");// = Editor =//// This is the guts. The metal. The core editor has most of its classes living in here://// * {{{bespin.editor.API}}} : The editor API itself// * {{{bespin.editor.UI}}} : Knowledge of the UI pieces of the editor is here, bulk of the code. paints!// * {{{bespin.editor.Scrollbar}}} : The custom scrollbar (to be factored out and use TH scrollbar instead)// * {{{bespin.editor.SelectionHelper}}} : Handle text selection// * {{{bespin.editor.DefaultEditorKeyListener}}} : Key listener operations// * {{{bespin.editor.Rect}}} : Helper to hold a rectangle// * {{{bespin.editor.Events}}} : Helper to hold a rectangle// * {{{bespin.editor.Utils}}} : Blobby utility object to do common things//// * {{{bespin.editor.Actions}}} : The actions that the editor can do (can be added too) are are in actions.js// ** {{{ bespin.editor.Scrollbar }}} **//// some state mgmt. for scrollbars; not a true componentdojo.declare("bespin.editor.Scrollbar", null, { HORIZONTAL: "horizontal", VERTICAL: "vertical", MINIMUM_HANDLE_SIZE: 20, constructor: function(ui, orientation, rect, value, min, max, extent) { this.ui = ui; this.orientation = orientation; // "horizontal" or "vertical" this.rect = rect; // position/size of the scrollbar track this.value = value; // current offset value this.min = min; // minimum offset value this.max = max; // maximum offset value this.extent = extent; // size of the current visible subset this.mousedownScreenPoint; // used for scroll bar dragging tracking; point at which the mousedown first occurred this.mousedownValue; // value at time of scroll drag start }, // return a Rect for the scrollbar handle getHandleBounds: function() { var sx = (this.isH()) ? this.rect.x : this.rect.y; var sw = (this.isH()) ? this.rect.w : this.rect.h; var smultiple = this.extent / (this.max + this.extent); var asw = smultiple * sw; if (asw < this.MINIMUM_HANDLE_SIZE) asw = this.MINIMUM_HANDLE_SIZE; sx += (sw - asw) * (this.value / (this.max - this.min)); return (this.isH()) ? new bespin.editor.Rect(Math.floor(sx), this.rect.y, asw, this.rect.h) : new bespin.editor.Rect(this.rect.x, sx, this.rect.w, asw); }, isH: function() { return (!(this.orientation == this.VERTICAL)); }, fixValue: function(value) { if (value < this.min) value = this.min; if (value > this.max) value = this.max; return value; }, onmousewheel: function(e) { var wheel = bespin.util.mousewheelevent.wheel(e); var axis = bespin.util.mousewheelevent.axis(e); if (this.orientation == this.VERTICAL && axis == this.VERTICAL) { this.setValue(this.value + (wheel * this.ui.lineHeight)); } else if (this.orientation == this.HORIZONTAL && axis == this.HORIZONTAL) { this.setValue(this.value + (wheel * this.ui.charWidth)); } }, onmousedown: function(e) { var clientY = e.clientY - this.ui.getTopOffset(); var clientX = e.clientX - this.ui.getLeftOffset(); var bar = this.getHandleBounds(); if (bar.contains({ x: clientX, y: clientY })) { this.mousedownScreenPoint = (this.isH()) ? e.screenX : e.screenY; this.mousedownValue = this.value; } else { var p = (this.isH()) ? clientX : clientY; var b1 = (this.isH()) ? bar.x : bar.y; var b2 = (this.isH()) ? bar.x2 : bar.y2; if (p < b1) { this.setValue(this.value -= this.extent); } else if (p > b2) { this.setValue(this.value += this.extent); } } }, onmouseup: function(e) { this.mousedownScreenPoint = null; this.mousedownValue = null; if (this.valueChanged) this.valueChanged(); // make the UI responsive when the user releases the mouse button (in case arrow no longer hovers over scrollbar) }, onmousemove: function(e) { if (this.mousedownScreenPoint) { var diff = ((this.isH()) ? e.screenX : e.screenY) - this.mousedownScreenPoint; var multiplier = diff / (this.isH() ? this.rect.w : this.rect.h); this.setValue(this.mousedownValue + Math.floor(((this.max + this.extent) - this.min) * multiplier)); } }, setValue: function(value) { this.value = this.fixValue(value); if (this.valueChanged) this.valueChanged(); }});// ** {{{ bespin.editor.Rect }}} **//// treat as immutable (pretty please)dojo.declare("bespin.editor.Rect", null, { constructor: function(x, y, w, h) { this.x = x; this.y = y; this.w = w; this.h = h; this.x2 = x + w; this.y2 = y + h; }, // inclusive of bounding lines contains: function(point) { if (!this.x) return false; return ((this.x <= point.x) && ((this.x + this.w) >= point.x) && (this.y <= point.y) && ((this.y + this.h) >= point.y)); }});// ** {{{ bespin.editor.SelectionHelper }}} **dojo.declare("bespin.editor.SelectionHelper", null, { constructor: function(editor) { this.editor = editor; }, // returns an object with the startCol and endCol of the selection. If the col is -1 on the endPos, the selection goes for the entire line // returns undefined if the row has no selection getRowSelectionPositions: function(rowIndex) { var startCol; var endCol; var selection = this.editor.getSelection(); if (!selection) return undefined; if ((selection.endPos.row < rowIndex) || (selection.startPos.row > rowIndex)) return undefined; startCol = (selection.startPos.row < rowIndex) ? 0 : selection.startPos.col; endCol = (selection.endPos.row > rowIndex) ? -1 : selection.endPos.col; return { startCol: startCol, endCol: endCol } }}); // ** {{{ bespin.editor.utils }}} **//// Mess with positions mainly dojo.mixin(bespin.editor, { utils: { buildArgs: function(oldPos) { return { pos: bespin.editor.utils.copyPos(oldPos || _editor.cursorPosition) }; }, changePos: function(args, pos) { ar return { pos: bespin.editor.utils.copyPos(oldPos || _editor.cursorPosition) }; }, copyPos: function(oldPos) { return { row: oldPos.row, col: oldPos.col }; }, posEquals: function(pos1, pos2) { if (pos1 == pos2) return true; if (!pos1 || !pos2) return false; return (pos1.col == pos2.col) && (pos1.row == pos2.row); }, diffObjects: function(o1, o2) { var diffs = {}; if (!o1 || !o2) return undefined; for (var key in o1) { if (o2[key]) { if (o1[key] != o2[key]) { diffs[key] = o1[key] + " => " + o2[key]; } } else { diffs[key] = "o1: " + key + " = " + o1[key]; } } for (var key2 in o2) { if (!o1[key2]) { diffs[key2] = "o2: " + key2 + " = " + o2[key2]; } } return diffs; }}});// ** {{{ bespin.editor.DefaultEditorKeyListener }}} **// // Core key listener to decide which actions to rundojo.declare("bespin.editor.DefaultEditorKeyListener", null, { constructor: function(editor) { this.editor = editor; this.actions = editor.ui.actions; this.skipKeypress = false; this.defaultKeyMap = {}; // Allow for multiple key maps to be defined this.keyMap = this.defaultKeyMap; }, bindKey: function(keyCode, metaKey, ctrlKey, altKey, shiftKey, action) { this.defaultKeyMap[[keyCode, metaKey, ctrlKey, altKey, shiftKey]] = (typeof action == "string") ? function() { var toFire = bespin.events.toFire(action); bespin.publish(toFire.name, toFire.args); } : dojo.hitch(this.actions, action); }, bindKeyString: function(modifiers, keyCode, action) { var ctrlKey = (modifiers.toUpperCase().indexOf("CTRL") != -1); var altKey = (modifiers.toUpperCase().indexOf("ALT") != -1); var metaKey = (modifiers.toUpperCase().indexOf("META") != -1) || (modifiers.toUpperCase().indexOf("APPLE") != -1) || (modifiers.toUpperCase().indexOf("CMD") != -1); var shiftKey = (modifiers.toUpperCase().indexOf("SHIFT") != -1); return this.bindKey(keyCode, metaKey, ctrlKey, altKey, shiftKey, action); }, bindKeyStringSelectable: function(modifiers, keyCode, action) { this.bindKeyString(modifiers, keyCode, action); this.bindKeyString("SHIFT " + modifiers, keyCode, action); }, onkeydown: function(e) { var handled = _commandLine.handleCommandLineFocus(e); if (handled) return false; var args = { event: e, pos: bespin.editor.utils.copyPos(this.editor.cursorPosition) } this.skipKeypress = false; this.returnValue = false; var action = this.keyMap[[e.keyCode, e.metaKey, e.ctrlKey, e.altKey, e.shiftKey]]; var hasAction = false; if (dojo.isFunction(action)) { hasAction = true; action(args); this.lastAction = action; } // If a special key is pressed OR if an action is assigned to a given key (e.g. TAB or BACKSPACE) if (e.metaKey || e.ctrlKey || e.altKey) { this.skipKeypress = true; this.returnValue = true; } // stop going, but allow special strokes to get to the browser if (hasAction || !bespin.util.keys.passThroughToBrowser(e)) dojo.stopEvent(e); }, onkeypress: function(e) { var handled = _commandLine.handleCommandLineFocus(e); if (handled) return false; // This is to get around the Firefox bug that happens the first time of jumping between command line and editor // Bug https://bugzilla.mozilla.org/show_bug.cgi?id=478686 if (e.charCode == 'j'.charCodeAt() && e.ctrlKey) { dojo.stopEvent(e); return false; } // If key should be skipped, BUT there are some chars like "@|{}[]\" that NEED the ALT- or CTRL-key to be accessable // on some platforms and keyboardlayouts (german?). This is not working for "^" if ([64 /*@*/, 91/*[*/, 92/*\*/, 93/*]*/, 94/*^*/, 123/*{*/, 124/*|*/, 125/*}*/, 126/*~*/ ].indexOf(e.charCode) != -1) { this.skipKeypress = false; } else if (this.skipKeypress) { if (!bespin.util.keys.passThroughToBrowser(e)) dojo.stopEvent(e); return this.returnValue; } var args = { event: e, pos: bespin.editor.utils.copyPos(this.editor.cursorPosition) }; var actions = this.editor.ui.actions; // Only allow ascii through if ((e.charCode >= 32) && (e.charCode <= 126) || e.charCode >= 160) { args.newchar = String.fromCharCode(e.charCode); actions.insertCharacter(args); } else { // Allow user to move with the arrow continuously var action = this.keyMap[[e.keyCode, e.metaKey, e.ctrlKey, e.altKey, e.shiftKey]]; if (this.lastAction == action) { delete this.lastAction; } else if (typeof action == "function") { action(args); } } dojo.stopEvent(e); }});// ** {{{ bespin.editor.UI }}} **//// Holds the UI. The editor itself, the syntax highlighter, the actions, and moredojo.declare("bespin.editor.UI", null, { constructor: function(editor) { this.editor = editor; this.syntaxModel = new bespin.syntax.Model(editor); this.selectionHelper = new bespin.editor.SelectionHelper(editor); this.actions = new bespin.editor.Actions(editor);
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?