editor.js
来自「在线编辑器」· JavaScript 代码 · 共 1,381 行 · 第 1/4 页
JS
1,381 行
this.toggleCursorFullRepaintCounter = 0; // tracks how many cursor toggles since the last full repaint // these two canvases are used as buffers for the scrollbar images, which are then composited onto the // main code view. we could have saved ourselves some misery by just prerendering slices of the scrollbars and // combining them like sane people, but... meh this.horizontalScrollCanvas = dojo.create("canvas"); this.verticalScrollCanvas = dojo.create("canvas"); this.GUTTER_WIDTH = 54; this.LINE_HEIGHT = 23; this.GUTTER_INSETS = { top: 0, left: 6, right: 0, bottom: 6 } this.LINE_INSETS = { top: 0, left: 5, right: 0, bottom: 6 } this.FALLBACK_CHARACTER_WIDTH = 10; this.NIB_WIDTH = 15; this.NIB_INSETS = { top: Math.floor(this.NIB_WIDTH / 2), left: Math.floor(this.NIB_WIDTH / 2), right: Math.floor(this.NIB_WIDTH / 2), bottom: Math.floor(this.NIB_WIDTH / 2) } this.NIB_ARROW_INSETS = { top: 3, left: 3, right: 3, bottom: 5 } this.lineHeight; // reserved for when line height is calculated dynamically instead of with a constant; set first time a paint occurs this.charWidth; // set first time a paint occurs this.visibleRows; // the number of rows visible in the editor; set each time a paint occurs this.firstVisibleRow; // first row that is visible in the editor; set each time a paint occurs this.nibup; // rect this.nibdown; // rect this.nibleft; // rect this.nibright; // rect this.selectMouseDownPos; // position when the user moused down this.xoffset = 0; // number of pixels to translate the canvas for scrolling this.yoffset = 0; this.showCursor = true; this.overXScrollBar = false; this.overYScrollBar = false; this.hasFocus = false; var source = this.editor.container; dojo.connect(source, "mousemove", this, "handleScrollBars"); dojo.connect(source, "mouseout", this, "handleScrollBars"); dojo.connect(source, "click", this, "handleScrollBars"); dojo.connect(source, "mousedown", this, "handleScrollBars"); dojo.connect(source, "mousedown", this, "mouseDownSelect"); dojo.connect(source, "mousemove", this, "mouseMoveSelect"); dojo.connect(source, "mouseup", this, "mouseUpSelect"); // painting optimization state this.lastLineCount = 0; this.lastCursorPos = null; this.lastxoffset = 0; this.lastyoffset = 0; this.xscrollbar = new bespin.editor.Scrollbar(this, "horizontal"); this.xscrollbar.valueChanged = dojo.hitch(this, function() { this.xoffset = -this.xscrollbar.value; this.editor.paint(); }); dojo.connect(window, "mousemove", this.xscrollbar, "onmousemove"); dojo.connect(window, "mouseup", this.xscrollbar, "onmouseup"); dojo.connect(window, (!dojo.isMozilla ? "onmousewheel" : "DOMMouseScroll"), this.xscrollbar, "onmousewheel"); this.yscrollbar = new bespin.editor.Scrollbar(this, "vertical"); this.yscrollbar.valueChanged = dojo.hitch(this, function() { this.yoffset = -this.yscrollbar.value; this.editor.paint(); }); dojo.connect(window, "mousemove", this.yscrollbar, "onmousemove"); dojo.connect(window, "mouseup", this.yscrollbar, "onmouseup"); dojo.connect(window, (!dojo.isMozilla ? "onmousewheel" : "DOMMouseScroll"), this.yscrollbar, "onmousewheel"); setTimeout(dojo.hitch(this, function() { this.toggleCursor(this); }), 250); }, // col is -1 if user clicked in gutter; clicking below last line maps to last line convertClientPointToCursorPoint: function(pos) { var x, y; if (y > (this.lineHeight * this.editor.model.getRowCount())) { y = this.editor.model.getRowCount() - 1; } else { var ty = pos.y; y = Math.floor(ty / this.lineHeight); } if (pos.x <= (this.GUTTER_WIDTH + this.LINE_INSETS.left)) { x = -1; } else { var tx = pos.x - this.GUTTER_WIDTH - this.LINE_INSETS.left; x = Math.floor(tx / this.charWidth); // With striclines turned on, don't select past the end of the line if (_settings.isOn(_settings.get('strictlines'))) { var maxcol = this.editor.model.getRowLength(y); if (x >= maxcol) { x = this.editor.model.getRowLength(y); } } } return { col: x, row: y }; }, mouseDownSelect: function(e) { var clientY = e.clientY - this.getTopOffset(); var clientX = e.clientX - this.getLeftOffset(); if (this.overXScrollBar || this.overYScrollBar) return; if (e.shiftKey) { this.selectMouseDownPos = (this.editor.selection) ? this.editor.selection.startPos : this.editor.cursorPosition; this.setSelection(e); } else { var point = { x: clientX, y: clientY }; point.x += Math.abs(this.xoffset); point.y += Math.abs(this.yoffset); if ((this.xscrollbar.rect.contains(point)) || (this.yscrollbar.rect.contains(point))) return; this.selectMouseDownPos = this.convertClientPointToCursorPoint(point); } }, mouseMoveSelect: function(e) { this.setSelection(e); }, mouseUpSelect: function(e) { this.setSelection(e); this.selectMouseDownPos = undefined; }, setSelection: function(e) { var clientY = e.clientY - this.getTopOffset(); var clientX = e.clientX - this.getLeftOffset(); if (!this.selectMouseDownPos) return; var down = bespin.editor.utils.copyPos(this.selectMouseDownPos); var point = { x: clientX, y: clientY }; point.x += Math.abs(this.xoffset); point.y += Math.abs(this.yoffset); var up = this.convertClientPointToCursorPoint(point); if (down.col == -1) down.col = 0; if (up.col == -1) up.col = 0; if (!bespin.editor.utils.posEquals(down, up)) { this.editor.setSelection({ startPos: down, endPos: up }); } else { if (e.detail == 1) { this.editor.setSelection(undefined); } else if (e.detail == 2) { var row = this.editor.model.rows[down.row]; var cursorAt = row[down.col]; if (!cursorAt || cursorAt.charAt(0) == ' ') { // empty space // For now, don't select anything, but think about copying Textmate and grabbing around it } else { var startPos = up = this.editor.model.findBefore(down.row, down.col); var endPos = this.editor.model.findAfter(down.row, down.col); this.editor.setSelection({ startPos: startPos, endPos: endPos }); } } else if (e.detail > 2) { // select the line this.editor.setSelection({ startPos: { row: down.row, col: 0 }, endPos: { row: down.row + 1, col: 0 } }); } } this.editor.moveCursor(up); this.editor.paint(); }, toggleCursor: function(ui) { ui.showCursor = !ui.showCursor; if (++this.toggleCursorFullRepaintCounter > 0) { this.toggleCursorFullRepaintCounter = 0; ui.editor.paint(true); } else { ui.editor.paint(); } setTimeout(function() { ui.toggleCursor(ui) }, 250); }, ensureCursorVisible: function() { if ((!this.lineHeight) || (!this.charWidth)) return; // can't do much without these var y = this.lineHeight * this.editor.cursorPosition.row; var x = this.charWidth * this.editor.cursorPosition.col; var cheight = this.getHeight(); var cwidth = this.getWidth() - this.GUTTER_WIDTH; if (Math.abs(this.yoffset) > y) { // current row before top-most visible row this.yoffset = -y; } else if ((Math.abs(this.yoffset) + cheight) < (y + this.lineHeight)) { // current row after bottom-most visible row this.yoffset = -((y + this.lineHeight) - cheight); } if (Math.abs(this.xoffset) > x) { // current col before left-most visible col this.xoffset = -x; } else if ((Math.abs(this.xoffset) + cwidth) < (x + (this.charWidth * 2))) { // current col after right-most visible col this.xoffset = -((x + (this.charWidth * 2)) - cwidth); } }, handleFocus: function(e) { this.editor.model.clear(); this.editor.model.insertCharacters({ row: 0, col: 0}, e.type); }, handleScrollBars: function(e) { var clientY = e.clientY - this.getTopOffset(); var clientX = e.clientX - this.getLeftOffset(); var oldX = this.overXScrollBar; var oldY = this.overYScrollBar; var scrolled = false; var w = this.editor.container.clientWidth; var h = this.editor.container.clientHeight; var sx = w - this.NIB_WIDTH - this.NIB_INSETS.right; // x start of the vert. scroll bar var sy = h - this.NIB_WIDTH - this.NIB_INSETS.bottom; // y start of the hor. scroll bar var p = { x: clientX, y:clientY }; if (e.type == "mousedown") { // dispatch to the scrollbars if ((this.xscrollbar) && (this.xscrollbar.rect.contains(p))) { this.xscrollbar.onmousedown(e); } else if ((this.yscrollbar) && (this.yscrollbar.rect.contains(p))) { this.yscrollbar.onmousedown(e); } } if (e.type == "mouseout") { this.overXScrollBar = false; this.overYScrollBar = false; } if ((e.type == "mousemove") || (e.type == "click")) { this.overYScrollBar = p.x > sx; this.overXScrollBar = p.y > sy; } if (e.type == "click") { if ((typeof e.button != "undefined") && (e.button == 0)) { var button; if (this.nibup.contains(p)) { button = "up"; } else if (this.nibdown.contains(p)) { button = "down"; } else if (this.nibleft.contains(p)) { button = "left"; } else if (this.nibright.contains(p)) { button = "right"; } if (button == "up") { this.yoffset += this.lineHeight; scrolled = true; } else if (button == "down") { this.yoffset -= this.lineHeight; scrolled = true; } else if (button == "left") { this.xoffset += this.charWidth * 2; scrolled = true; } else if (button == "right") { this.xoffset -= this.charWidth * 2; scrolled = true; } } } if ((oldX != this.overXScrollBar) || (oldY != this.overYScrollBar) || scrolled) this.editor.paint(); }, installKeyListener: function(listener) { var Key = bespin.util.keys.Key; // alias if (this.oldkeydown) dojo.disconnect(this.oldkeydown); if (this.oldkeypress) dojo.disconnect(this.oldkeypress); this.oldkeydown = dojo.hitch(listener, "onkeydown"); this.oldkeypress = dojo.hitch(listener, "onkeypress"); dojo.connect(document, "keydown", this, "oldkeydown"); dojo.connect(document, "keypress", this, "oldkeypress"); // Modifiers, Key, Action listener.bindKeyStringSelectable("", Key.ARROW_LEFT, this.actions.moveCursorLeft); listener.bindKeyStringSelectable("", Key.ARROW_RIGHT, this.actions.moveCursorRight); listener.bindKeyStringSelectable("", Key.ARROW_UP, this.actions.moveCursorUp); listener.bindKeyStringSelectable("", Key.ARROW_DOWN, this.actions.moveCursorDown); listener.bindKeyStringSelectable("ALT", Key.ARROW_LEFT, this.actions.moveWordLeft); listener.bindKeyStringSelectable("ALT", Key.ARROW_RIGHT, this.actions.moveWordRight); listener.bindKeyStringSelectable("", Key.HOME, this.actions.moveToLineStart); listener.bindKeyStringSelectable("APPLE", Key.ARROW_LEFT, this.actions.moveToLineStart); listener.bindKeyStringSelectable("", Key.END, this.actions.moveToLineEnd); listener.bindKeyStringSelectable("APPLE", Key.ARROW_RIGHT, this.actions.moveToLineEnd); listener.bindKeyString("CTRL", Key.K, this.actions.killLine); listener.bindKeyString("CTRL", Key.L, this.actions.moveCursorRowToCenter); listener.bindKeyString("", Key.BACKSPACE, this.actions.backspace); listener.bindKeyString("", Key.DELETE, this.actions.deleteKey); listener.bindKeyString("", Key.ENTER, this.actions.newline); listener.bindKeyString("", Key.TAB, this.actions.insertTab); listener.bindKeyString("SHIFT", Key.TAB, this.actions.unindent); listener.bindKeyString("APPLE", Key.A, this.actions.selectAll); listener.bindKeyString("CTRL", Key.A, this.actions.selectAll); listener.bindKeyString("APPLE", Key.Z, this.actions.undoRedo); listener.bindKeyString("CTRL", Key.Z, this.actions.undoRedo); listener.bindKeyStringSelectable("APPLE", Key.ARROW_UP, this.actions.moveToFileTop); listener.bindKeyStringSelectable("APPLE", Key.ARROW_DOWN, this.actions.moveToFileBottom); listener.bindKeyStringSelectable("", Key.PAGE_UP, this.actions.movePageUp); listener.bindKeyStringSelectable("", Key.PAGE_DOWN, this.actions.movePageDown); // Other key bindings can be found in commands themselves. // For example, this: // listener.bindKeyString("CTRL SHIFT", Key.N, "bespin:editor:newfile"); // has been moved to the 'newfile' command withKey // Also, the clipboard.js handles C, V, and X }, getWidth: function() { return parseInt(dojo.attr(this.editor.canvas.parentNode, "width")); }, getHeight: function() { return parseInt(dojo.attr(this.editor.canvas.parentNode, "height"));
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?