mirror of
https://github.com/felixangell/phi.git
synced 2024-10-26 17:18:31 +03:00
819 lines
18 KiB
Go
819 lines
18 KiB
Go
package gui
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"log"
|
|
"path"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
"unicode"
|
|
|
|
"github.com/felixangell/go-rope"
|
|
"github.com/felixangell/phi-editor/cfg"
|
|
"github.com/felixangell/strife"
|
|
"github.com/veandco/go-sdl2/sdl"
|
|
)
|
|
|
|
var (
|
|
timer int64 = 0
|
|
reset_timer int64 = 0
|
|
should_draw bool = true
|
|
should_flash bool
|
|
)
|
|
|
|
// TODO: allow font setting or whatever
|
|
|
|
type camera struct {
|
|
x int
|
|
y int
|
|
}
|
|
|
|
type Buffer struct {
|
|
BaseComponent
|
|
HasFocus bool
|
|
index int
|
|
parent *View
|
|
font *strife.Font
|
|
contents []*rope.Rope
|
|
curs *Cursor
|
|
cfg *cfg.TomlConfig
|
|
cam *camera
|
|
filePath string
|
|
|
|
languageInfo string
|
|
}
|
|
|
|
func NewBuffer(conf *cfg.TomlConfig, parent *View, index int) *Buffer {
|
|
config := conf
|
|
if config == nil {
|
|
config = cfg.NewDefaultConfig()
|
|
}
|
|
|
|
buffContents := []*rope.Rope{}
|
|
buff := &Buffer{
|
|
index: index,
|
|
parent: parent,
|
|
contents: buffContents,
|
|
curs: &Cursor{},
|
|
cfg: config,
|
|
filePath: "/tmp/phi_file_" + time.Now().String(), // TODO make this a randomly chosen temp file
|
|
cam: &camera{0, 0},
|
|
}
|
|
|
|
return buff
|
|
}
|
|
|
|
func (b *Buffer) OpenFile(filePath string) {
|
|
b.filePath = filePath
|
|
|
|
log.Println("Opening file ", filePath)
|
|
|
|
ext := path.Ext(filePath)
|
|
lang, err := b.cfg.GetLanguageFromExt(ext)
|
|
if err != nil {
|
|
log.Println(err.Error())
|
|
} else {
|
|
log.Println("- this file is a ", lang, " language program")
|
|
b.languageInfo = lang
|
|
}
|
|
|
|
contents, err := ioutil.ReadFile(filePath)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
lines := strings.Split(string(contents), "\n")
|
|
for _, line := range lines {
|
|
b.appendLine(line)
|
|
}
|
|
}
|
|
|
|
func (b *Buffer) OnDispose() {
|
|
// hm!
|
|
// os.Remove(b.fileHandle)
|
|
}
|
|
|
|
func (b *Buffer) OnInit() {}
|
|
|
|
func (b *Buffer) appendLine(val string) {
|
|
b.contents = append(b.contents, rope.New(val))
|
|
|
|
// because we've added a new line
|
|
// we have to set the x to the start
|
|
b.curs.x = 0
|
|
}
|
|
|
|
func (b *Buffer) insertRune(r rune) {
|
|
log.Println("Inserting rune ", r, " into current line at ", b.curs.x, ":", b.curs.y)
|
|
log.Println("Line before insert> ", b.contents[b.curs.y])
|
|
|
|
b.contents[b.curs.y] = b.contents[b.curs.y].Insert(b.curs.x, string(r))
|
|
b.curs.move(1, 0)
|
|
}
|
|
|
|
// TODO handle EVERYTHING but for now im handling
|
|
// my UK macbook key layout.
|
|
var shiftAlternative = map[rune]rune{
|
|
'1': '!',
|
|
'2': '@',
|
|
'3': '£',
|
|
'4': '$',
|
|
'5': '%',
|
|
'6': '^',
|
|
'7': '&',
|
|
'8': '*',
|
|
'9': '(',
|
|
'0': ')',
|
|
'-': '_',
|
|
'=': '+',
|
|
'`': '~',
|
|
'/': '?',
|
|
'.': '>',
|
|
',': '<',
|
|
'[': '{',
|
|
']': '}',
|
|
';': ':',
|
|
'\'': '"',
|
|
'\\': '|',
|
|
'§': '±',
|
|
}
|
|
|
|
func (b *Buffer) processTextInput(r rune) bool {
|
|
if ALT_DOWN && r == '\t' {
|
|
// nop, we dont want to
|
|
// insert tabs when we
|
|
// alt tab out of view of this app
|
|
return true
|
|
}
|
|
|
|
if CAPS_LOCK {
|
|
if unicode.IsLetter(r) {
|
|
r = unicode.ToUpper(r)
|
|
}
|
|
}
|
|
|
|
if CONTROL_DOWN {
|
|
actionName, actionExists := cfg.Shortcuts.Controls[string(unicode.ToLower(r))]
|
|
if actionExists {
|
|
if proc, ok := actions[actionName]; ok {
|
|
return proc(b)
|
|
}
|
|
} else {
|
|
log.Println("warning, unimplemented shortcut ctrl+", unicode.ToLower(r), actionName)
|
|
}
|
|
}
|
|
|
|
if SUPER_DOWN {
|
|
actionName, actionExists := cfg.Shortcuts.Supers[string(unicode.ToLower(r))]
|
|
if actionExists {
|
|
if proc, ok := actions[actionName]; ok {
|
|
return proc(b)
|
|
}
|
|
} else {
|
|
log.Println("warning, unimplemented shortcut ctrl+", unicode.ToLower(r), actionName)
|
|
}
|
|
}
|
|
|
|
if SHIFT_DOWN {
|
|
// if it's a letter convert to uppercase
|
|
if unicode.IsLetter(r) {
|
|
r = unicode.ToUpper(r)
|
|
} else {
|
|
|
|
// otherwise we have to look in our trusy
|
|
// shift mapping thing.
|
|
if val, ok := shiftAlternative[r]; ok {
|
|
r = val
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// NOTE: we have to do this AFTER we map the
|
|
// shift combo for the value!
|
|
// this will not insert a ), }, or ] if there
|
|
// is one to the right of us... basically
|
|
// this escapes out of a closing bracket
|
|
// rather than inserting a new one IF we are inside
|
|
// brackets.
|
|
if b.cfg.Editor.Match_Braces {
|
|
if r == ')' || r == '}' || r == ']' {
|
|
currLine := b.contents[b.curs.y]
|
|
if b.curs.x < currLine.Len() {
|
|
curr := currLine.Index(b.curs.x + 1)
|
|
if curr == r {
|
|
b.curs.move(1, 0)
|
|
return true
|
|
} else {
|
|
log.Print("no it's ", curr)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
b.contents[b.curs.y] = b.contents[b.curs.y].Insert(b.curs.x, string(r))
|
|
b.curs.move(1, 0)
|
|
|
|
// we don't need to match braces
|
|
// let's not continue any further
|
|
if !b.cfg.Editor.Match_Braces {
|
|
return true
|
|
}
|
|
|
|
// TODO: shall we match single quotes and double quotes too?
|
|
|
|
matchingPair := int(r)
|
|
|
|
// the offset in the ASCII Table is +2 for { and for [
|
|
// but its +1 for parenthesis (
|
|
offset := 2
|
|
|
|
switch r {
|
|
case '(':
|
|
offset = 1
|
|
fallthrough
|
|
case '{':
|
|
fallthrough
|
|
case '[':
|
|
matchingPair += offset
|
|
b.contents[b.curs.y] = b.contents[b.curs.y].Insert(b.curs.x, string(rune(matchingPair)))
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func remove(slice []*rope.Rope, s int) []*rope.Rope {
|
|
return append(slice[:s], slice[s+1:]...)
|
|
}
|
|
|
|
func (b *Buffer) deleteNext() {
|
|
b.moveRight()
|
|
b.deletePrev()
|
|
}
|
|
|
|
func (b *Buffer) deletePrev() {
|
|
if b.curs.x > 0 {
|
|
offs := -1
|
|
if !b.cfg.Editor.Tabs_Are_Spaces {
|
|
if b.contents[b.curs.y].Index(b.curs.x) == '\t' {
|
|
offs = int(-b.cfg.Editor.Tab_Size)
|
|
}
|
|
} else if b.cfg.Editor.Hungry_Backspace && b.curs.x >= int(b.cfg.Editor.Tab_Size) {
|
|
// cut out the last {TAB_SIZE} amount of characters
|
|
// and check em
|
|
tabSize := int(b.cfg.Editor.Tab_Size)
|
|
lastTabSizeChars := b.contents[b.curs.y].Substr(b.curs.x+1-tabSize, tabSize).String()
|
|
if strings.Compare(lastTabSizeChars, b.makeTab()) == 0 {
|
|
// delete {TAB_SIZE} amount of characters
|
|
// from the cursors x pos
|
|
for i := 0; i < int(b.cfg.Editor.Tab_Size); i++ {
|
|
b.contents[b.curs.y] = b.contents[b.curs.y].Delete(b.curs.x, 1)
|
|
b.curs.move(-1, 0)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
b.contents[b.curs.y] = b.contents[b.curs.y].Delete(b.curs.x, 1)
|
|
b.curs.moveRender(-1, 0, offs, 0)
|
|
} else if b.curs.x == 0 && b.curs.y > 0 {
|
|
// start of line, wrap to previous
|
|
prevLineLen := b.contents[b.curs.y-1].Len()
|
|
b.contents[b.curs.y-1] = b.contents[b.curs.y-1].Concat(b.contents[b.curs.y])
|
|
b.contents = append(b.contents[:b.curs.y], b.contents[b.curs.y+1:]...)
|
|
b.curs.move(prevLineLen, -1)
|
|
}
|
|
}
|
|
|
|
func (b *Buffer) deleteBeforeCursor() {
|
|
// delete so we're at the end
|
|
// of the previous line
|
|
if b.curs.x == 0 {
|
|
b.deletePrev()
|
|
return
|
|
}
|
|
|
|
for b.curs.x > 0 {
|
|
b.deletePrev()
|
|
}
|
|
}
|
|
|
|
func (b *Buffer) moveLeft() {
|
|
if b.curs.x == 0 && b.curs.y > 0 {
|
|
b.curs.move(b.contents[b.curs.y-1].Len(), -1)
|
|
|
|
} else if b.curs.x > 0 {
|
|
b.curs.move(-1, 0)
|
|
}
|
|
}
|
|
|
|
func (b *Buffer) moveRight() {
|
|
currLineLength := b.contents[b.curs.y].Len()
|
|
|
|
if b.curs.x >= currLineLength && b.curs.y < len(b.contents)-1 {
|
|
// we're at the end of the line and we have
|
|
// some lines after, let's wrap around
|
|
b.curs.move(0, 1)
|
|
b.curs.move(-currLineLength, 0)
|
|
} else if b.curs.x < b.contents[b.curs.y].Len() {
|
|
// we have characters to the right, let's move along
|
|
b.curs.move(1, 0)
|
|
}
|
|
}
|
|
|
|
func (b *Buffer) moveUp() {
|
|
if b.curs.y > 0 {
|
|
b.curs.move(0, -1)
|
|
}
|
|
}
|
|
|
|
func (b *Buffer) moveDown() {
|
|
if b.curs.y < len(b.contents) {
|
|
b.curs.move(0, 1)
|
|
}
|
|
}
|
|
|
|
func (b *Buffer) swapLineUp() bool {
|
|
if b.curs.y > 0 {
|
|
currLine := b.contents[b.curs.y]
|
|
prevLine := b.contents[b.curs.y-1]
|
|
b.contents[b.curs.y-1] = currLine
|
|
b.contents[b.curs.y] = prevLine
|
|
b.moveUp()
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (b *Buffer) swapLineDown() bool {
|
|
if b.curs.y < len(b.contents) {
|
|
currLine := b.contents[b.curs.y]
|
|
nextLine := b.contents[b.curs.y+1]
|
|
b.contents[b.curs.y+1] = currLine
|
|
b.contents[b.curs.y] = nextLine
|
|
b.moveDown()
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (b *Buffer) scrollUp() {
|
|
// TODO move the cursor down 45 lines
|
|
// IF the buffer exceeds the window size.
|
|
lineScrollAmount := 10
|
|
b.cam.y -= lineScrollAmount
|
|
for i := 0; i < lineScrollAmount; i++ {
|
|
b.moveUp()
|
|
}
|
|
}
|
|
|
|
func (b *Buffer) scrollDown() {
|
|
// TODO move the cursor down 45 lines
|
|
// IF the buffer exceeds the window size.
|
|
lineScrollAmount := 10
|
|
|
|
b.cam.y += lineScrollAmount
|
|
for i := 0; i < lineScrollAmount; i++ {
|
|
b.moveDown()
|
|
}
|
|
}
|
|
|
|
// processes a key press. returns if there
|
|
// was a key that MODIFIED the buffer.
|
|
func (b *Buffer) processActionKey(key int) bool {
|
|
switch key {
|
|
case sdl.K_CAPSLOCK:
|
|
CAPS_LOCK = !CAPS_LOCK
|
|
return true
|
|
case sdl.K_RETURN:
|
|
if SUPER_DOWN {
|
|
// in sublime this goes
|
|
// into the next block
|
|
// nicely indented!
|
|
}
|
|
|
|
initial_x := b.curs.x
|
|
prevLineLen := b.contents[b.curs.y].Len()
|
|
|
|
var newRope *rope.Rope
|
|
if initial_x < prevLineLen && initial_x > 0 {
|
|
// we're not at the end of the line, but we're not at
|
|
// the start, i.e. we're SPLITTING the line
|
|
left, right := b.contents[b.curs.y].Split(initial_x)
|
|
newRope = right
|
|
b.contents[b.curs.y] = left
|
|
} else if initial_x == 0 {
|
|
// we're at the start of a line, so we want to
|
|
// shift the line down and insert an empty line
|
|
// above it!
|
|
b.contents = append(b.contents, new(rope.Rope)) // grow
|
|
copy(b.contents[b.curs.y+1:], b.contents[b.curs.y:]) // shift
|
|
b.contents[b.curs.y] = new(rope.Rope) // set
|
|
b.curs.move(0, 1)
|
|
return true
|
|
} else {
|
|
// we're at the end of a line
|
|
newRope = new(rope.Rope)
|
|
}
|
|
|
|
b.curs.move(0, 1)
|
|
for x := 0; x < initial_x; x++ {
|
|
// TODO(Felix): there's a bug here where
|
|
// this doesn't account for the rendered x
|
|
// position when we use tabs as tabs and not spaces
|
|
b.curs.move(-1, 0)
|
|
}
|
|
|
|
b.contents = append(b.contents, nil)
|
|
copy(b.contents[b.curs.y+1:], b.contents[b.curs.y:])
|
|
b.contents[b.curs.y] = newRope
|
|
return true
|
|
case sdl.K_BACKSPACE:
|
|
if SUPER_DOWN {
|
|
b.deleteBeforeCursor()
|
|
} else {
|
|
b.deletePrev()
|
|
}
|
|
return true
|
|
case sdl.K_RIGHT:
|
|
currLineLength := b.contents[b.curs.y].Len()
|
|
|
|
if CONTROL_DOWN && b.parent != nil {
|
|
b.parent.ChangeFocus(1)
|
|
return true
|
|
}
|
|
|
|
if SUPER_DOWN {
|
|
for b.curs.x < currLineLength {
|
|
b.curs.move(1, 0)
|
|
}
|
|
return true
|
|
}
|
|
|
|
// FIXME this is weird!
|
|
if ALT_DOWN {
|
|
currLine := b.contents[b.curs.y]
|
|
|
|
var i int
|
|
for i = b.curs.x + 1; i < currLine.Len(); i++ {
|
|
curr := currLine.Index(i)
|
|
if curr <= ' ' || curr == '_' {
|
|
break
|
|
}
|
|
}
|
|
|
|
for j := 0; j < i; j++ {
|
|
b.moveRight()
|
|
}
|
|
return true
|
|
}
|
|
|
|
b.moveRight()
|
|
return true
|
|
case sdl.K_LEFT:
|
|
if CONTROL_DOWN && b.parent != nil {
|
|
b.parent.ChangeFocus(-1)
|
|
return true
|
|
}
|
|
|
|
if SUPER_DOWN {
|
|
// TODO go to the nearest \t
|
|
// if no \t (i.e. start of line) go to
|
|
// the start of the line!
|
|
b.curs.gotoStart()
|
|
}
|
|
|
|
if ALT_DOWN {
|
|
currLine := b.contents[b.curs.y]
|
|
|
|
i := b.curs.x
|
|
for i > 0 {
|
|
currChar := currLine.Index(i)
|
|
// TODO is a seperator thing?
|
|
if currChar <= ' ' || currChar == '_' {
|
|
// move over one more?
|
|
i = i - 1
|
|
break
|
|
}
|
|
i = i - 1
|
|
}
|
|
|
|
start := b.curs.x
|
|
for j := 0; j < start-i; j++ {
|
|
b.moveLeft()
|
|
}
|
|
return true
|
|
}
|
|
|
|
b.moveLeft()
|
|
return true
|
|
case sdl.K_UP:
|
|
if ALT_DOWN {
|
|
return b.swapLineUp()
|
|
}
|
|
|
|
if SUPER_DOWN {
|
|
// go to the start of the file
|
|
}
|
|
|
|
if b.curs.y > 0 {
|
|
offs := 0
|
|
prevLineLen := b.contents[b.curs.y-1].Len()
|
|
if b.curs.x > prevLineLen {
|
|
offs = prevLineLen - b.curs.x
|
|
}
|
|
// TODO: offset should account for tabs
|
|
b.curs.move(offs, -1)
|
|
}
|
|
return true
|
|
case sdl.K_DOWN:
|
|
if ALT_DOWN {
|
|
return b.swapLineDown()
|
|
}
|
|
|
|
if SUPER_DOWN {
|
|
// go to the end of the file
|
|
}
|
|
|
|
if b.curs.y < len(b.contents)-1 {
|
|
offs := 0
|
|
nextLineLen := b.contents[b.curs.y+1].Len()
|
|
if b.curs.x > nextLineLen {
|
|
offs = nextLineLen - b.curs.x
|
|
}
|
|
// TODO: offset should account for tabs
|
|
b.curs.move(offs, 1)
|
|
}
|
|
return true
|
|
case sdl.K_TAB:
|
|
if b.cfg.Editor.Tabs_Are_Spaces {
|
|
// make an empty rune array of TAB_SIZE, cast to string
|
|
// and insert it.
|
|
b.contents[b.curs.y] = b.contents[b.curs.y].Insert(b.curs.x, b.makeTab())
|
|
b.curs.move(int(b.cfg.Editor.Tab_Size), 0)
|
|
} else {
|
|
b.contents[b.curs.y] = b.contents[b.curs.y].Insert(b.curs.x, string('\t'))
|
|
// the actual position is + 1, but we make it
|
|
// move by TAB_SIZE characters on the view.
|
|
b.curs.moveRender(1, 0, int(b.cfg.Editor.Tab_Size), 0)
|
|
}
|
|
return true
|
|
|
|
case sdl.K_END:
|
|
currLine := b.contents[b.curs.y]
|
|
if b.curs.x < currLine.Len() {
|
|
distToMove := currLine.Len() - b.curs.x
|
|
b.curs.move(distToMove, 0)
|
|
}
|
|
return true
|
|
|
|
case sdl.K_HOME:
|
|
if b.curs.x > 0 {
|
|
b.curs.move(-b.curs.x, 0)
|
|
}
|
|
return true
|
|
|
|
case sdl.K_PAGEUP:
|
|
b.scrollUp()
|
|
return true
|
|
|
|
case sdl.K_PAGEDOWN:
|
|
b.scrollDown()
|
|
return true
|
|
|
|
case sdl.K_DELETE:
|
|
b.deleteNext()
|
|
return true
|
|
|
|
case sdl.K_LGUI:
|
|
fallthrough
|
|
case sdl.K_RGUI:
|
|
fallthrough
|
|
|
|
case sdl.K_LALT:
|
|
fallthrough
|
|
case sdl.K_RALT:
|
|
fallthrough
|
|
|
|
case sdl.K_LCTRL:
|
|
fallthrough
|
|
case sdl.K_RCTRL:
|
|
fallthrough
|
|
|
|
case sdl.K_LSHIFT:
|
|
fallthrough
|
|
case sdl.K_RSHIFT:
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
var (
|
|
SHIFT_DOWN bool = false
|
|
SUPER_DOWN = false // cmd on mac, ctrl on windows
|
|
CONTROL_DOWN = false // what is this on windows?
|
|
ALT_DOWN = false // option on mac
|
|
CAPS_LOCK = false
|
|
)
|
|
|
|
// TODO(Felix) this is really stupid
|
|
func (b *Buffer) makeTab() string {
|
|
blah := []rune{}
|
|
for i := 0; i < int(b.cfg.Editor.Tab_Size); i++ {
|
|
blah = append(blah, ' ')
|
|
}
|
|
return string(blah)
|
|
}
|
|
|
|
func (b *Buffer) OnUpdate() bool {
|
|
b.HasFocus = true
|
|
|
|
prev_x := b.curs.x
|
|
prev_y := b.curs.y
|
|
|
|
SHIFT_DOWN = strife.KeyPressed(sdl.K_LSHIFT) || strife.KeyPressed(sdl.K_RSHIFT)
|
|
SUPER_DOWN = strife.KeyPressed(sdl.K_LGUI) || strife.KeyPressed(sdl.K_RGUI)
|
|
ALT_DOWN = strife.KeyPressed(sdl.K_LALT) || strife.KeyPressed(sdl.K_RALT)
|
|
CONTROL_DOWN = strife.KeyPressed(sdl.K_LCTRL) || strife.KeyPressed(sdl.K_RCTRL)
|
|
|
|
if strife.PollKeys() {
|
|
keyCode := strife.PopKey()
|
|
|
|
// try process this key input as an
|
|
// action first
|
|
actionPerformed := b.processActionKey(keyCode)
|
|
if actionPerformed {
|
|
return true
|
|
}
|
|
|
|
textEntered := b.processTextInput(rune(keyCode))
|
|
if textEntered {
|
|
return true
|
|
}
|
|
}
|
|
|
|
// FIXME handle focus properly
|
|
if b.inputHandler == nil {
|
|
return false
|
|
}
|
|
|
|
if b.curs.x != prev_x || b.curs.y != prev_y {
|
|
should_draw = true
|
|
should_flash = false
|
|
reset_timer = strife.CurrentTimeMillis()
|
|
}
|
|
|
|
// fixme to not use CurrentTimeMillis
|
|
if !should_flash && strife.CurrentTimeMillis()-reset_timer > b.cfg.Cursor.Reset_Delay {
|
|
should_flash = true
|
|
}
|
|
|
|
if strife.CurrentTimeMillis()-timer > b.cfg.Cursor.Flash_Rate && (should_flash && b.cfg.Cursor.Flash) {
|
|
timer = strife.CurrentTimeMillis()
|
|
should_draw = !should_draw
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// dimensions of the last character we rendered
|
|
var last_w, last_h int
|
|
|
|
func (b *Buffer) renderAt(ctx *strife.Renderer, rx int, ry int) {
|
|
// BACKGROUND
|
|
ctx.SetColor(strife.HexRGB(b.cfg.Theme.Background))
|
|
ctx.Rect(b.x, b.y, b.w, b.h, strife.Fill)
|
|
|
|
if b.cfg.Editor.Highlight_Line && b.HasFocus {
|
|
ctx.SetColor(strife.Black) // highlight_line_col?
|
|
ctx.Rect(rx, (ry+b.curs.ry*last_h)-(b.cam.y*last_h), b.w, last_h, strife.Fill)
|
|
}
|
|
|
|
// render the ol' cursor
|
|
if should_draw && b.cfg.Cursor.Draw && b.HasFocus {
|
|
cursorWidth := b.cfg.Cursor.GetCaretWidth()
|
|
if cursorWidth == -1 {
|
|
cursorWidth = last_w
|
|
}
|
|
|
|
ctx.SetColor(strife.HexRGB(b.cfg.Theme.Cursor)) // caret colour
|
|
ctx.Rect((rx+b.curs.rx*last_w)-(b.cam.x*last_w), (ry+b.curs.ry*last_h)-(b.cam.y*last_h), cursorWidth, last_h, strife.Fill)
|
|
}
|
|
|
|
var visibleLines int = 50
|
|
|
|
// last_h > 0 means we have done
|
|
// a render.
|
|
if int(last_h) > 0 && int(b.h) != 0 {
|
|
// render an extra three lines just
|
|
// so we dont cut anything off if its
|
|
// not evenly divisible
|
|
visibleLines = (int(b.h) / int(last_h)) + 3
|
|
}
|
|
|
|
start := b.cam.y
|
|
upper := b.cam.y + visibleLines
|
|
if upper > len(b.contents) {
|
|
upper = len(b.contents)
|
|
}
|
|
|
|
var y_col int
|
|
for _, rope := range b.contents[start:upper] {
|
|
// this is because if we had the following
|
|
// text input:
|
|
//
|
|
// Foo
|
|
// _ <-- underscore is a space!
|
|
// Blah
|
|
// and we delete that underscore... it causes
|
|
// a panic because there are no characters in
|
|
// the empty string!
|
|
if rope.Len() == 0 {
|
|
// even though the string is empty
|
|
// we still need to offset it by a line
|
|
y_col += 1
|
|
continue
|
|
}
|
|
|
|
currLine := []rune(rope.String())
|
|
|
|
// char index => colour
|
|
matches := map[int]int{}
|
|
|
|
subjects := []cfg.SyntaxCriteria{}
|
|
colours := []int{}
|
|
|
|
stuff := b.cfg.Syntax[b.languageInfo]
|
|
for _, criteria := range stuff {
|
|
colours = append(colours, criteria.Colour)
|
|
subjects = append(subjects, criteria)
|
|
}
|
|
|
|
// HOLY SLOW BATMAN
|
|
for idx, _ := range currLine {
|
|
for syntaxIndex, syntax := range subjects {
|
|
if syntax.Pattern != "" {
|
|
a := currLine[idx:]
|
|
match, _ := regexp.MatchString(syntax.Pattern, string(a))
|
|
if match {
|
|
for i := 0; i < len(string(a)); i++ {
|
|
matches[i] = colours[syntaxIndex]
|
|
}
|
|
}
|
|
} else {
|
|
for _, subject := range syntax.Match {
|
|
if idx+len(subject) > len(currLine) {
|
|
continue
|
|
}
|
|
a := currLine[idx : idx+len(subject)]
|
|
if strings.Compare(string(a), subject) == 0 {
|
|
for i := 0; i < len(subject); i++ {
|
|
if _, ok := matches[i+idx]; ok {
|
|
continue
|
|
}
|
|
matches[i+idx] = colours[syntaxIndex]
|
|
}
|
|
idx += len(subject)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var x_col int
|
|
for idx, char := range currLine {
|
|
switch char {
|
|
case '\n':
|
|
x_col = 0
|
|
y_col += 1
|
|
continue
|
|
case '\t':
|
|
x_col += b.cfg.Editor.Tab_Size
|
|
continue
|
|
}
|
|
|
|
x_col += 1
|
|
|
|
ctx.SetColor(strife.HexRGB(b.cfg.Theme.Foreground))
|
|
|
|
// if we're currently over a character then set
|
|
// the font colour to something else
|
|
// ONLY SET THE COLOUR IF WE HAVE FOCUS ALSO!
|
|
if b.HasFocus && b.curs.x+1 == x_col && b.curs.y == y_col && should_draw {
|
|
ctx.SetColor(strife.HexRGB(b.cfg.Theme.Cursor_Invert))
|
|
}
|
|
if col, ok := matches[idx]; ok {
|
|
ctx.SetColor(strife.HexRGB(int32(col)))
|
|
}
|
|
|
|
last_w, last_h = ctx.String(string(char), (rx + ((x_col - 1) * last_w)), (ry + (y_col * last_h)))
|
|
}
|
|
|
|
y_col += 1
|
|
}
|
|
}
|
|
|
|
func (b *Buffer) OnRender(ctx *strife.Renderer) {
|
|
b.renderAt(ctx, b.x, b.y)
|
|
}
|