Editor line navigation
Find the nearest line with content and the next change of indentation.
module editor.lines
import nanyx/text.regex
-- Navigating the lines of a text document: finding the nearest line with
-- content, and the next line where the indentation changes.
type TextLine = (number: int, text: string, firstNonWhitespace: int)
type TextDocument = (lineCount: int, lineAt: int -> TextLine)
export type Direction = #backwards | #forwards
export type Change = #greaterThan | #lessThan
export type RelativeIndentation =
| #noIndentation
| #lessIndentation
| #moreIndentation
| #sameIndentation
def TextLine.isBlank = { line -> line.text \trim == "" }
-- A "stop line" is one that is blank or contains only punctuation
export def TextLine.isStopLine = { line -> not (line.text \Regex.matches('[a-zA-Z0-9]')) }
export def TextLine.isSignificant = { line -> not line.isStopLine }
def changeToDiff: Change -> ((int, int) -> bool) = {
| #greaterThan -> { > }
| #lessThan -> { < }
}
def directionToDelta: Direction -> (int -> int) = {
| #forwards -> { + 1 }
| #backwards -> { - 1 }
}
-- Every line from `start` in the given direction, until the document ends
export def iterLines: (TextDocument, start: int, direction: Direction) -> seq(TextLine)
= { document, start, direction ->
def advance = directionToDelta(direction)
seq {
memory {
def current = mut start
while current >= 0 and current < document.lineCount ->
yield document.lineAt(current)
current := advance(current)
}
}
}
type Neighbours = (#some(TextLine) | #none, #some(TextLine) | #none)
-- Walk outwards from a line, yielding the lines either side of it
export def iterLinesOutwards: (TextDocument, int) -> seq(Neighbours) = { document, lineNumber ->
seq {
memory {
def back = mut lineNumber - 1
def forward = mut lineNumber + 1
while back >= 0 or forward < document.lineCount ->
def backLine = if back >= 0 then #some(document.lineAt(back)) else #none
def forwardLine = if forward < document.lineCount then #some(document.lineAt(forward)) else #none
yield (backLine, forwardLine)
back := back - 1
forward := forward + 1
}
}
}
export def nearestSignificantLine: (TextDocument, int) -> TextLine = { document, lineNumber ->
def current = document.lineAt(lineNumber)
if current.isSignificant then current
else
def #some(line) =
iterLinesOutwards(document, lineNumber)
\flatMap { back, forward -> [back, forward] \Option.values }
\find { .isSignificant }
except #notFound -> #some(current)
line
}
export def nextLineOfChangeOfIndentation
: (TextDocument, TextLine, Change, Direction) -> #some(TextLine) | #none
= { document, currentLine, change, direction ->
def diff = changeToDiff(change)
def start = directionToDelta(direction)(currentLine.number)
iterLines(document, start = start, direction = direction)
\filter { .isSignificant }
\find { line -> diff(line.firstNonWhitespace, currentLine.firstNonWhitespace) }
except #notFound -> #none
}
export def relativeIndentation: (TextLine, TextLine) -> RelativeIndentation = { from, to ->
if | to.isBlank -> #noIndentation
| from.firstNonWhitespace > to.firstNonWhitespace -> #lessIndentation
| from.firstNonWhitespace < to.firstNonWhitespace -> #moreIndentation
| else -> #sameIndentation
}
This program lives in the repository as examples/editor.nyx.