From 4e12d1c9b931f37ed8ecadababa2529bfebdc14c Mon Sep 17 00:00:00 2001 From: nightwing Date: Sun, 19 Aug 2012 18:21:33 +0400 Subject: [PATCH] add alignCursors command (ctrl+alt+a) --- lib/ace/commands/multi_select_commands.js | 8 +- lib/ace/multi_select.js | 150 +++++++++++++++++++--- 2 files changed, 139 insertions(+), 19 deletions(-) diff --git a/lib/ace/commands/multi_select_commands.js b/lib/ace/commands/multi_select_commands.js index 735c930a..ff59f045 100644 --- a/lib/ace/commands/multi_select_commands.js +++ b/lib/ace/commands/multi_select_commands.js @@ -74,11 +74,15 @@ exports.defaultCommands = [{ }, { name: "splitIntoLines", exec: function(editor) { editor.multiSelect.splitIntoLines(); }, - bindKey: {win: "Ctrl-Shift-L", mac: "Ctrl-Shift-L"}, + bindKey: {win: "Ctrl-Alt-L", mac: "Ctrl-Alt-L"}, readonly: true +}, { + name: "alignCursors", + exec: function(editor) { editor.alignCursors(); }, + bindKey: {win: "Ctrl-Alt-A", mac: "Ctrl-Alt-A"} }]; -// commands active in multiselect mode +// commands active only in multiselect mode exports.multiSelectCommands = [{ name: "singleSelection", bindKey: "esc", diff --git a/lib/ace/multi_select.js b/lib/ace/multi_select.js index 7b90ebe6..7332a9a6 100644 --- a/lib/ace/multi_select.js +++ b/lib/ace/multi_select.js @@ -3,7 +3,7 @@ * * Copyright (c) 2010, Ajax.org B.V. * All rights reserved. - * + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright @@ -14,7 +14,7 @@ * * Neither the name of Ajax.org B.V. nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. - * + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -35,6 +35,7 @@ var Range = require("./range").Range; var Selection = require("./selection").Selection; var onMouseDown = require("./mouse/multi_select_handler").onMouseDown; var event = require("./lib/event"); +var lang = require("./lib/lang"); var commands = require("./commands/multi_select_commands"); exports.commands = commands.defaultCommands.concat(commands.multiSelectCommands); @@ -66,7 +67,7 @@ var EditSession = require("./edit_session").EditSession; this.rangeList = null; /** extension - * Selection.addRange(range, $blockChangeEvents) + * Selection.addRange(range, $blockChangeEvents) * - range (Range): The new range to add * - $blockChangeEvents (Boolean): Whether or not to block changing events * @@ -113,7 +114,7 @@ var EditSession = require("./edit_session").EditSession; range && this.fromOrientedRange(range); }; - + /** extension * Selection.substractPoint(pos) -> Range * - pos (Range): The position to remove, as a `{row, column}` object @@ -129,7 +130,7 @@ var EditSession = require("./edit_session").EditSession; }; /** extension - * Selection.mergeOverlappingRanges() + * Selection.mergeOverlappingRanges() * * Merges overlapping ranges ensuring consistency after changes **/ @@ -241,7 +242,7 @@ var EditSession = require("./edit_session").EditSession; * - screenCursor (Cursor): The cursor to use * - screenAnchor (Anchor): The anchor to use * - includeEmptyLines (Boolean): If true, this includes ranges inside the block which are empty due to clipping - * + * * Gets list of ranges composing rectangular block on the screen * **/ @@ -313,9 +314,9 @@ var EditSession = require("./edit_session").EditSession; // extend Editor var Editor = require("./editor").Editor; (function() { - + /** extension - * Editor.updateSelectionMarkers() + * Editor.updateSelectionMarkers() * * Updates the cursor and marker layers. **/ @@ -343,7 +344,7 @@ var Editor = require("./editor").Editor; }; /** extension - * Editor.removeSelectionMarker(range) + * Editor.removeSelectionMarker(range) * - range (Range): The selection range added with [[Editor.addSelectionMarker `addSelectionMarker()`]]. * * Removes the selection marker. @@ -431,7 +432,7 @@ var Editor = require("./editor").Editor; }; /** extension - * Editor.forEachSelection(cmd, args) + * Editor.forEachSelection(cmd, args) * - cmd (String): The command to execute * - args (String): Any arguments for the command * @@ -496,7 +497,7 @@ var Editor = require("./editor").Editor; // todo this should change when paste becomes a command this.onPaste = function(text) { - if (this.$readOnly) + if (this.$readOnly) return; this._emit("paste", text); @@ -551,7 +552,7 @@ var Editor = require("./editor").Editor; // commands /** extension - * Editor.selectMoreLines(dir, skip) + * Editor.selectMoreLines(dir, skip) * - dir (Number): The direction of lines to select: -1 for up, 1 for down * - skip (Boolean): If `true`, removes the active selection range * @@ -596,10 +597,10 @@ var Editor = require("./editor").Editor; }; /** extension - * Editor.transposeSelections(dir) + * Editor.transposeSelections(dir) * - dir (Number): The direction to rotate selections * - * Transposes the selected ranges. + * Transposes the selected ranges. **/ this.transposeSelections = function(dir) { var session = this.session; @@ -639,13 +640,13 @@ var Editor = require("./editor").Editor; } /** extension - * Editor.selectMore(dir, skip) + * Editor.selectMore(dir, skip) * - dir (Number): The direction of lines to select: -1 for up, 1 for down * - skip (Boolean): If `true`, removes the active selection range * * Finds the next occurence of text in an active selection and adds it to the selections. **/ - this.selectMore = function (dir, skip) { + this.selectMore = function(dir, skip) { var session = this.session; var sel = session.multiSelect; @@ -665,6 +666,121 @@ var Editor = require("./editor").Editor; if (skip) this.multiSelect.substractPoint(range.cursor); }; + + /** extension + * Editor.alignCursors() + * + * aligns cursors or selected text + **/ + this.alignCursors = function() { + var session = this.session; + var sel = session.multiSelect; + var ranges = sel.ranges; + + if (!ranges.length) { + var range = this.selection.getRange(); + var fr = range.start.row, lr = range.end.row; + var lines = this.session.doc.removeLines(fr, lr); + lines = this.$reAlignText(lines); + this.session.doc.insertLines(fr, lines); + range.start.column = 0; + range.end.column = lines[lines.length - 1].length; + this.selection.setRange(range); + } else { + // filter out ranges on same row + var row = -1; + var sameRowRanges = ranges.filter(function(r) { + if (r.cursor.row == row) + return true; + row = r.cursor.row; + }); + sel.$onRemoveRange(sameRowRanges); + + var maxCol = 0; + var minSpace = Infinity; + var spaceOffsets = ranges.map(function(r) { + var p = r.cursor; + var line = session.getLine(p.row); + var spaceOffset = line.substr(p.column).search(/\S/g); + if (spaceOffset == -1) + spaceOffset = 0; + + if (p.column > maxCol) + maxCol = p.column; + if (spaceOffset < minSpace) + minSpace = spaceOffset; + return spaceOffset; + }); + ranges.forEach(function(r, i) { + var p = r.cursor; + var l = maxCol - p.column; + var d = spaceOffsets[i] - minSpace; + if (l > d) + session.insert(p, lang.stringRepeat(" ", l - d)); + else + session.remove(new Range(p.row, p.column, p.row, p.column - l + d)); + + r.start.column = r.end.column = maxCol; + r.start.row = r.end.row = p.row; + r.cursor = r.end; + }); + sel.fromOrientedRange(ranges[0]); + this.renderer.updateCursor(); + this.renderer.updateBackMarkers(); + } + }; + + this.$reAlignText = function(lines) { + var isLeftAligned = true, isRightAligned = true; + var startW, textW, endW; + + return lines.map(function(line) { + var m = line.match(/(\s*)(.*?)(\s*)([=:].*)/); + if (!m) + return [line]; + + if (startW == null) { + startW = m[1].length; + textW = m[2].length; + endW = m[3].length; + return m; + } + + if (startW + textW + endW != m[1].length + m[2].length + m[3].length) + isRightAligned = false; + if (startW != m[1].length) + isLeftAligned = false; + + if (startW > m[1].length) + startW = m[1].length; + if (textW < m[2].length) + textW = m[2].length; + if (endW > m[3].length) + endW = m[3].length; + + return m; + }).map(isLeftAligned ? isRightAligned ? alignRight : alignLeft : unAlign); + + function strRepeat(n, ch) { + return Array(n + 1).join(ch) + } + + function alignLeft(m) { + return !m[2] ? m[0] : strRepeat(startW, " ") + m[2] + + strRepeat(textW - m[2].length + endW, " ") + + m[4].replace(/^([=:])\s+/, "$1 ") + } + function alignRight(m) { + return !m[2] ? m[0] : strRepeat(startW + textW - m[2].length, " ") + m[2] + + strRepeat(endW, " ") + + m[4].replace(/^([=:])\s+/, "$1 ") + } + function unAlign(m) { + return !m[2] ? m[0] : strRepeat(startW, " ") + m[2] + + strRepeat(endW, " ") + + m[4].replace(/^([=:])\s+/, "$1 ") + } + } }).call(Editor.prototype); @@ -710,7 +826,7 @@ exports.onSessionChange = function(e) { } }; -// MultiSelect(editor) +// MultiSelect(editor) // adds multiple selection support to the editor // (note: should be called only once for each editor instance) function MultiSelect(editor) {