builtin-programs/editor/editor-utils.folk
Claim the editor utils library is [library create editorUtilsLib {
proc applyTextViewport {originalText x y width height} {
set lines [split $originalText \n]
set lines [lrange $lines $y [expr {($height - 1) + $y}]]
set lines [lmap line $lines {
set line [string range $line $x [expr {($width - 1) + $x}]]
}]
return [join $lines \n]
}
proc cursorToXy {code cursor} {
set codeBeforeCursor [string range $code 0 [- $cursor 1]]
set linesBeforeCursor [split $codeBeforeCursor "\n"]
set lineCountBeforeCursor [llength $linesBeforeCursor]
set cursorX [string length [lindex $linesBeforeCursor end]]
set cursorY [max [- $lineCountBeforeCursor 1] 0]
return [list $cursorX $cursorY]
}
proc xyToCursor {code cursorX cursorY} {
if { $cursorX < 0 } { set cursorX 0 }
if { $cursorY < 0 } { set cursorY 0 }
set lines [split $code "\n"]
set maxCursorY [max 0 [- [llength $lines] 1]]
set cursorY [min $cursorY $maxCursorY]
set relevantLines [lrange $lines 0 [- $cursorY 1]]
set relevantLineLen [string length [lindex $lines $cursorY]]
set joined [join $relevantLines "\n"]
# make sure cursorX < line length
set cursorX [min $cursorX $relevantLineLen]
set cursor [+ [string length $joined] $cursorX]
# don't forget to add the length of \n at the beginning
if {$cursorY > 0} { incr cursor }
return $cursor
}
proc insertText {code cursor newText} {
set before [string range $code 0 [- $cursor 1]]
set after [string range $code $cursor end]
set joined [join [list $before $newText $after] ""]
incr cursor
lassign [cursorToXy $code $cursor] cursorX cursorY
set maxCursorX $cursorX
return [list $joined $cursor $maxCursorX]
}
proc deleteText {code cursor count} {
set before [string range $code 0 [- $cursor 1]]
set after [string range $code [+ $cursor $count] end]
set joined [join [list $before $after] ""]
return $joined
}
proc deleteToBeginning {code cursor} {
lassign [cursorToXy $code $cursor] x y
set lines [split $code "\n"]
set line [lindex $lines $y]
set newLine [string range $line $x end]
lset lines $y $newLine
return [join $lines "\n"]
}
proc getLine {code cursor} {
lassign [cursorToXy $code $cursor] x y
set lines [split $code "\n"]
set line [lindex $lines $y]
}
proc getLineLength {code cursor} {
set line [getLine $code $cursor]
set ll [string length $line]
return $ll
}
# returns {newCursor newMaxCursorX}
proc handleNavigation {key code cursor maxCursorX} {
switch $key {
Left {
set cursor [- $cursor 1]
set cursor [max $cursor 0]
lassign [cursorToXy $code $cursor] cursorX cursorY
set maxCursorX $cursorX
}
Right {
set cursor [+ $cursor 1]
set codeLength [string length $code]
set cursor [min $cursor $codeLength]
lassign [cursorToXy $code $cursor] cursorX cursorY
set maxCursorX $cursorX
}
Up {
lassign [cursorToXy $code $cursor] cursorX cursorY
set cursorX $maxCursorX
set cursorY [- $cursorY 1]
set cursor [xyToCursor $code $cursorX $cursorY]
}
Down {
lassign [cursorToXy $code $cursor] cursorX cursorY
set cursorX $maxCursorX
set cursorY [+ $cursorY 1]
set cursor [xyToCursor $code $cursorX $cursorY]
}
Control_a {
lassign [cursorToXy $code $cursor] cursorX cursorY
set newX 0
set maxCursorX $newX
set cursor [xyToCursor $code $newX $cursorY]
}
Control_e {
lassign [cursorToXy $code $cursor] cursorX cursorY
set newX [getLineLength $code $cursor]
set maxCursorX $newX
set cursor [xyToCursor $code $newX $cursorY]
}
}
return [list $cursor $maxCursorX]
}
# returns {newCode newCursor newMaxCursorX}
proc handleRemovalAndReturn {key code cursor maxCursorX} {
switch $key {
Delete {
if { $cursor != 0 } {
set cursor [- $cursor 1]
set code [deleteText $code $cursor 1]
set maxCursorX [lindex [cursorToXy $code $cursor] 0]
}
}
Remove {
set code [deleteText $code $cursor 1]
}
Control_u {
# delete from cursor back to 0 and move cursor to 0
lassign [cursorToXy $code $cursor] cursorX cursorY
set code [deleteToBeginning $code $cursor]
set newX 0
set cursor [xyToCursor $code $newX $cursorY]
}
Return {
# figure out how many spaces there are before the current line
regexp {^(\s*)} [getLine $code $cursor] -> spacing
set spacingLen [string length $spacing]
lassign [insertText $code $cursor "\n$spacing"] code
set maxCursorX $spacingLen
set cursor [+ $cursor [+ 1 $spacingLen]]
}
}
return [list $code $cursor $maxCursorX]
}
proc getSelectedText {code selAnchor cursor} {
set start [min $selAnchor $cursor]
set end [max $selAnchor $cursor]
return [string range $code $start [- $end 1]]
}
proc replaceRange {code rangeStart rangeEnd newText} {
if {$rangeStart > 0} {
set before [string range $code 0 [- $rangeStart 1]]
} else {
set before ""
}
set after [string range $code $rangeEnd end]
set code "${before}${newText}${after}"
set cursor [+ $rangeStart [string length $newText]]
lassign [cursorToXy $code $cursor] cursorX cursorY
set maxCursorX $cursorX
return [list $code $cursor $maxCursorX]
}
proc lineNumberView {ystart linecount} {
set yend [expr {$ystart + $linecount}]
set numbers [list]
for {set i [expr {$ystart + 1}]} {$i <= $yend} {incr i} {
lappend numbers $i
}
join $numbers "\n"
}
# For rendering:
proc getAdvance {em} {
# From NeomatrixCode.csv
return $(0.5859375 * $em)
}
proc widthAndHeight {resolvedGeom} {
set tagSize [dict get $resolvedGeom tagSize]
set left [dict get $resolvedGeom left]
set right [dict get $resolvedGeom right]
set top [dict get $resolvedGeom top]
set bottom [dict get $resolvedGeom bottom]
set width $($left + $tagSize + $right)
set height $($top + $tagSize + $bottom)
return [list $width $height]
}
# given program and the editor options, figure out how many characters can
# fit in this editor
proc editorSizeInCharacters {margin resolvedGeom options} {
set textScale [dict get $options scale]
set advance [getAdvance $textScale]
lassign [widthAndHeight $resolvedGeom] width height
set width $($width - [lindex $margin 3] - $advance*2.5 - [lindex $margin 1])
set height $($height - [lindex $margin 0] - [lindex $margin 2])
set widthInCharacters $(int($width / $advance))
set heightInCharacters $(int($height / $textScale))
return [list $widthInCharacters $heightInCharacters]
}
}]