Action
Move Lines Down
Moves the selected line(s) down by one line.
Steps
-
script
// Direction to move the lines. var moveUp = false; String.prototype.splitLines = function () { // Splits string into lines, each ending with a newline (\n). // Note: the last fragment might not end with a newline. return this.match(/[^\n]*\n/g); }; function minRange(theRange) { // Returns index of start of theRange. return theRange[0]; } function maxRange(theRange) { // Returns index immediately AFTER end of theRange. return theRange[0] + theRange[1]; } String.prototype.lastChar = function () { return this.substring(this.length - 1, this.length); }; String.prototype.inRange = function (theRange) { return this.substring(theRange[0], theRange[0] + theRange[1]); } function show(title, content) { alert(title + "\n#" + content + "#"); } function showInRange(title, range, text) { show(title, text.substring(range[0], range[0] + range[1])); } /* We need to distinguish between a user-selection which spans a newLine onto the following line, and a getSelectedLineRange() result which has automatically expanded to include a line's trailing newline. This is for the case where getSelectedLineRange() is ambiguous: - This occurs only when a user selection exists. If no selection exists, the insertion point is always on a single line. - This occurs only when the user selection ends immediately after a newLine. Otherwise, there's no ambiguity in the range returned from getSelectedLineRange(). - It doesn't matter whether the following line is blank or not. - This doesn't occur with a _preceding_ selected blank line, because getSelectedLineRange() will be different. The ambiguity in getSelectedLineRange() arises because: A. The getSelectedLineRange() might span the trailing linebreak because **the user selected past that point**. B. The getSelectedLineRange() might span the trailing linebreak because **it auto-expands to include it**. In situation A, the user wants to **also act on the following line**; they selected it explicitly. In situation B, the user wants **not to act on the following line**; its preceding newline was just part of the line-range. We can disambiguate by checking getSelectedLineRange() against getSelectedRange(). If both ranges end at the same maxRange, it's a user-selection, and we **should** include the following line (by expanding the candidate line-range up to and including the next newline). Otherwise, the range was auto-expanded to include the trailing newline, and we should **not** include the following line. */ // Do some sanity checking. var newLine = "\n"; var lineRange = editor.getSelectedLineRange(); var selRange = editor.getSelectedRange(); var draftText = editor.getText(); var draftLength = draftText.length; var proceed = true; if (moveUp && minRange(lineRange) == 0) { // Can't move lines up. proceed = false; } else if (!moveUp && maxRange(lineRange) >= draftLength) { // Can't move lines down. proceed = false; } if (proceed) { // Normalise, to ensure all lines end with a newLine. var addedNewLine = false; if (draftText.lastChar() != newLine) { draftText = draftText + newLine; addedNewLine = true; if (moveUp) { // Check to see if we need to adjust lineRange accordingly. // Doesn't apply when moving down, because we can't be on the last line. if (maxRange(lineRange) == draftLength) { // Last line of draft is selected. Expand length of range for added newLine. lineRange[1] = lineRange[1] + 1; } } draftLength = draftLength + 1; } // Handle ambiguous line-range case of user-selection spanning a trailing newLine. // (See full explanation above.) if (selRange[1] > 0) { // There's a selection. var selText = draftText.inRange(selRange); if (selText.lastChar() == newLine) { // Last char of user selection is a newline; expand lineRange to include next line. // (Because visually in terms of the iOS selection, that's the user's intention.) // Note: also include the newLine itself at the end of the next line. var nextNewLinePosn = draftText.indexOf(newLine, maxRange(selRange)); lineRange[1] = lineRange[1] + (nextNewLinePosn - maxRange(lineRange)) + 1; } } // Expand lineRange to give us an extra line to move. if (moveUp) { // Moving up. Get extra line before the selected lines, to move down. // We offset by -2 to go back past the preceding lineBreak. var prevLineStart = draftText.lastIndexOf(newLine, minRange(lineRange) - 2); // Adjust prevLineStart so we don't capture the found newline itself. if (prevLineStart == -1) { // In case we hit start of draft. prevLineStart = 0; } else { // Exclude the newLine itself which preceeds the previous line. if ((minRange(lineRange) - prevLineStart) > 1) { prevLineStart = prevLineStart + 1; } } lineRange[1] = lineRange[1] + (lineRange[0] - prevLineStart); lineRange[0] = prevLineStart; } else { // Moving down. Get extra line after the selected lines, to move up. var nextLineEnd = draftText.indexOf(newLine, maxRange(lineRange)); if (nextLineEnd == -1) { // We hit end of draft. nextLineEnd = draftLength; } lineRange[1] = (nextLineEnd + 1) - lineRange[0]; } // Grab the relevant full lines of text. var selectedLines = draftText.substring(minRange(lineRange), maxRange(lineRange)); // Rearrange lines. var lines = selectedLines.splitLines(); var extraLineArray = []; if (moveUp) { // Shift first (extra) line to end, thus moving all others up. extraLineArray = lines.splice(0, 1); extraLineArray = lines.splice(lines.length, 0, extraLineArray[0]); } else { // Shift last (extra) line to start, thus moving all others down. extraLineArray = lines.splice(lines.length - 1, 1); extraLineArray = lines.splice(0, 0, extraLineArray[0]); } // Replace selected lines with reordered ones. var newLinesChunk = lines.join(""); // If we added a newLine to the end of draftText originally, // and we're replacing in that portion, remove it again. if (addedNewLine && maxRange(lineRange) >= (draftLength - 1)) { newLinesChunk = newLinesChunk.substring(0, newLinesChunk.length - 1); } // Work out new selection range, to preserve selected lines. var newSelectionRange = [ lineRange[0], lineRange[1] ]; newSelectionRange[1] = newLinesChunk.length; var delta = 0; if (moveUp) { // Should select all but the last (extra; moved down) line. delta = lines[lines.length - 1].length; newSelectionRange[1] = newSelectionRange[1] - delta; } else { // Should select all but the first (extra; moved up) line. delta = lines[0].length; newSelectionRange[0] = newSelectionRange[0] + delta; newSelectionRange[1] = newSelectionRange[1] - delta; } // Replace text in editor. editor.setTextInRange(lineRange[0], lineRange[1], newLinesChunk); // Maintain selection, ensuring it doesn't end in a spurious newLine. if (editor.getTextInRange(newSelectionRange[0], newSelectionRange[1]).lastChar() == newLine) { newSelectionRange[1] = newSelectionRange[1] - 1; } editor.setSelectedRange(newSelectionRange[0], newSelectionRange[1]); } /* Test data below. one two three four */
Options
-
After Success Nothing Notification Error Log Level Error
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.