Skip to content

Instantly share code, notes, and snippets.

@samskivert
Created April 28, 2016 17:56
Show Gist options
  • Save samskivert/9408ddb50f5c26864541ef72af90d8e1 to your computer and use it in GitHub Desktop.
Save samskivert/9408ddb50f5c26864541ef72af90d8e1 to your computer and use it in GitHub Desktop.
//
// 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