Created
April 28, 2016 17:56
-
-
Save samskivert/9408ddb50f5c26864541ef72af90d8e1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// RECTANGLE FNS | |
/** Creates a rect region based on the supplied `start` and `end` locations. A rect region is | |
* represented as a `Loc` for the start of each line and a number of characters. Note: this | |
* function does not validate that `start` and `end` are in the buffer or otherwise valid. */ | |
def rectRegion (start :Loc, end :Loc) :Seq[(Loc, Int)] = { | |
val width = end.col - start.col | |
val lines = Seq.builder[(Loc, Int)]() | |
var cur = start ; while (cur.row <= end.row) { | |
lines += (cur, width) | |
cur = cur.nextL | |
} | |
lines.build() | |
} | |
/** Adjusts the current region boundaries to be 'upper left' and 'lower right' of the rectangular | |
* region demarcated by 'start' and 'end'. This is necessary because sometimes start and end are | |
* at the 'upper right' and 'lower left', which complicates rect code. */ | |
def withRectRegion (fn :(Loc, Loc) => Unit) :Unit = withRegion { (start, end) => | |
if (start.col > end.col) fn(start.atCol(end.col), end.atCol(start.col)) | |
else fn(start, end) | |
} | |
/** Extracts the rectangular region from the current region and applies `fn` to it. */ | |
def withRectRegion (fn :Seq[(Loc, Int)] => Unit) :Unit = withRectRegion { (start, end) => | |
fn(rectRegion(start, end)) | |
} | |
@Fn("Deletes the region-rectangle and saves it to the kill ring.") | |
def killRectangle () :Unit = withRectRegion { lines => | |
val killed = lines.map { case (start, length) => | |
val line = buffer.line(start) | |
val end = math.min(line.length, start.col+length) | |
pad(buffer.delete(start, end-start.col), length-end) | |
} | |
editor.rectKillRing add killed | |
} | |
@Fn("Yanks the last killed rectangle with upper left corner at the point.") | |
def yankRectangle () :Unit = editor.rectKillRing.entry(0) match { | |
case None => window.popStatus("Rectangle kill ring is empty.") | |
case Some(region) => insertRectAt(view.point(), region) | |
} | |
@Fn("Blanks out the region-rectangle, shifting text right.") | |
def openRectangle () :Unit = withRectRegion { (start, end) => | |
var blank = whitespace(end.col-start.col) | |
var lines = Seq.builder[LineV]() | |
for (_ <- 1 to (end.row-start.row+1)) { lines += blank } | |
insertRectAt(start, lines.build()) | |
// move the point to the bottom right of the opened rect | |
view.point() = end | |
} | |
@Fn("Replaces the contents of a rectangle with a supplied string on each line.") | |
def replaceRectangle () { | |
window.mini.read("Replacement:", "", replaceRectHistory, Completer.none) onSuccess { str => | |
val repline = Line(str) | |
withRectRegion { lines => | |
val killed = lines.map { case (start, length) => | |
val line = buffer.line(start) | |
val count = math.min(line.length-start.col, length) | |
buffer.replace(start, count, repline) | |
} | |
} | |
} | |
} | |
@Fn("Copies, but does not delete, the region-rectangle and saves it to the kill ring.") | |
def copyRectangleAsKill () :Unit = withRectRegion { lines => | |
val killed = lines.map { case (start, length) => | |
val line = buffer.line(start) | |
val end = math.min(line.length, start.col+length) | |
pad(buffer.line(start).slice(start.col, end-start.col), length-end) | |
} | |
editor.rectKillRing add killed | |
} | |
private def insertRectAt (loc :Loc, region :Seq[LineV]) { | |
var pos = loc | |
for (rline <- region) { | |
// if we're past the end of the buffer, add a line | |
if (pos.row >= buffer.lines.size) buffer.split(buffer.end) | |
// pad the line out with spaces if it's not long enough | |
val bline = buffer.line(pos) | |
if (bline.length < pos.col) buffer.insert( | |
pos.atCol(bline.length), whitespace(pos.col-bline.length)) | |
buffer.insert(pos, rline) | |
pos = pos.nextL | |
} | |
} | |
private def whitespace (length :Int) = Line(" " * length) | |
private def pad (line :LineV, spaces :Int) :LineV = | |
if (spaces <= 0) line else line.merge(whitespace(spaces)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment