Much improved highlighting (turned off for now)

This commit is contained in:
Zachary Yedidia
2016-03-25 14:32:35 -04:00
parent 770ad7f444
commit e1506e300d
5 changed files with 160 additions and 32 deletions

View File

@@ -1,7 +1,6 @@
package main
import (
"fmt"
"github.com/gdamore/tcell"
"io/ioutil"
"os/user"
@@ -20,6 +19,10 @@ type FileTypeRules struct {
type SyntaxRule struct {
// What to highlight
regex *regexp.Regexp
// Any flags
flags string
// Whether this regex is a start=... end=... regex
startend bool
// How to highlight it
style tcell.Style
}
@@ -54,16 +57,19 @@ func LoadSyntaxFilesFromDir(dir string) {
for _, f := range files {
if filepath.Ext(f.Name()) == ".micro" {
text, err := ioutil.ReadFile(dir + "/" + f.Name())
filename := dir + "/" + f.Name()
if err != nil {
fmt.Println("Error loading syntax files:", err)
TermMessage("Error loading syntax files: " + err.Error())
continue
}
lines := strings.Split(string(text), "\n")
syntaxParser := regexp.MustCompile(`syntax "(.*?)"\s+"(.*)"+`)
headerParser := regexp.MustCompile(`header "(.*)"`)
ruleParser := regexp.MustCompile(`color (.*?)\s+(?:\((.*?)\)\s+)?"(.*)"`)
ruleStartEndParser := regexp.MustCompile(`color (.*?)\s+(?:\((.*?)\)\s+)?start="(.*?)"\s+end="(.*?)"`)
var syntaxRegex *regexp.Regexp
var headerRegex *regexp.Regexp
@@ -90,11 +96,11 @@ func LoadSyntaxFilesFromDir(dir string) {
syntaxRegex, err = regexp.Compile(extensions)
if err != nil {
fmt.Println("Regex error:", err)
TermError(filename, lineNum, err.Error())
continue
}
} else {
fmt.Println("Syntax statement is not valid:", line)
TermError(filename, lineNum, "Syntax statement is not valid: "+line)
continue
}
} else if strings.HasPrefix(line, "header") {
@@ -104,26 +110,32 @@ func LoadSyntaxFilesFromDir(dir string) {
headerRegex, err = regexp.Compile(header)
if err != nil {
fmt.Println("Regex error:", err)
TermError(filename, lineNum, "Regex error: "+err.Error())
continue
}
} else {
fmt.Println("Header statement is not valid:", line)
TermError(filename, lineNum, "Header statement is not valid: "+line)
continue
}
} else {
if ruleParser.MatchString(line) {
submatch := ruleParser.FindSubmatch([]byte(line))
color := string(submatch[1])
var color string
var regexStr string
var flags string
if len(submatch) == 4 {
regexStr = "(?m" + string(submatch[2]) + ")" + JoinRule(string(submatch[3]))
color = string(submatch[1])
flags = string(submatch[2])
regexStr = "(?" + flags + ")" + JoinRule(string(submatch[3]))
} else if len(submatch) == 3 {
regexStr = "(?m)" + JoinRule(string(submatch[2]))
color = string(submatch[1])
regexStr = JoinRule(string(submatch[2]))
} else {
TermError(filename, lineNum, "Invalid statement: "+line)
}
regex, err := regexp.Compile(regexStr)
if err != nil {
fmt.Println(f.Name(), lineNum, err)
TermError(filename, lineNum, err.Error())
continue
}
@@ -133,7 +145,40 @@ func LoadSyntaxFilesFromDir(dir string) {
} else {
st = StringToStyle(color)
}
rules = append(rules, SyntaxRule{regex, st})
rules = append(rules, SyntaxRule{regex, flags, false, st})
} else if ruleStartEndParser.MatchString(line) {
submatch := ruleStartEndParser.FindSubmatch([]byte(line))
var color string
var start string
var end string
// Use m and s flags by default
flags := "ms"
if len(submatch) == 5 {
color = string(submatch[1])
flags = string(submatch[2])
start = string(submatch[3])
end = string(submatch[4])
} else if len(submatch) == 4 {
color = string(submatch[1])
start = string(submatch[2])
end = string(submatch[3])
} else {
TermError(filename, lineNum, "Invalid statement: "+line)
}
regex, err := regexp.Compile("(?" + flags + ")" + "(" + start + ").*?(" + end + ")")
if err != nil {
TermError(filename, lineNum, err.Error())
continue
}
st := tcell.StyleDefault
if _, ok := colorscheme[color]; ok {
st = colorscheme[color]
} else {
st = StringToStyle(color)
}
rules = append(rules, SyntaxRule{regex, flags, true, st})
}
}
}
@@ -164,21 +209,34 @@ type SyntaxMatches map[int]tcell.Style
// Match takes a buffer and returns the syntax matches a map specifying how it should be syntax highlighted
func Match(rules []SyntaxRule, buf *Buffer, v *View) SyntaxMatches {
start := v.topline - synLinesUp
end := v.topline + v.height + synLinesDown
if start < 0 {
start = 0
}
if end > len(buf.lines) {
end = len(buf.lines)
}
str := strings.Join(buf.lines[start:end], "\n")
startNum := v.cursor.loc + v.cursor.Distance(0, start)
toplineNum := v.cursor.loc + v.cursor.Distance(0, v.topline)
m := make(SyntaxMatches)
m := make(map[int]tcell.Style)
lineStart := v.updateLines[0]
lineEnd := v.updateLines[1] + 1
if lineStart < 0 {
// Don't need to update syntax highlighting
return m
}
totalStart := v.topline - synLinesUp
totalEnd := v.topline + v.height + synLinesDown
if totalStart < 0 {
totalStart = 0
}
if totalEnd > len(buf.lines) {
totalEnd = len(buf.lines)
}
if lineEnd > len(buf.lines) {
lineEnd = len(buf.lines)
}
lines := buf.lines[lineStart:lineEnd]
str := strings.Join(buf.lines[totalStart:totalEnd], "\n")
startNum := v.cursor.loc + v.cursor.Distance(0, totalStart)
toplineNum := v.cursor.loc + v.cursor.Distance(0, v.topline)
for _, rule := range rules {
if rule.regex.MatchString(str) {
if rule.startend && rule.regex.MatchString(str) {
indicies := rule.regex.FindAllStringIndex(str, -1)
for _, value := range indicies {
value[0] += startNum
@@ -189,6 +247,19 @@ func Match(rules []SyntaxRule, buf *Buffer, v *View) SyntaxMatches {
}
}
}
} else {
for _, line := range lines {
if rule.regex.MatchString(line) {
indicies := rule.regex.FindAllStringIndex(str, -1)
for _, value := range indicies {
value[0] += toplineNum
value[1] += toplineNum
for i := value[0]; i < value[1]; i++ {
m[i] = rule.style
}
}
}
}
}
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"github.com/gdamore/tcell"
"os"
"strconv"
)
// TermMessage sends a message to the user in the terminal. This usually occurs before
@@ -21,6 +22,12 @@ func TermMessage(msg string) {
reader.ReadString('\n')
}
// TermError sends an error to the user in the terminal. Like TermMessage except formatted
// as an error
func TermError(filename string, lineNum int, err string) {
TermMessage(filename + ", " + strconv.Itoa(lineNum) + ": " + err)
}
// Messenger is an object that makes it easy to send messages to the user
// and get input from the user
type Messenger struct {

View File

@@ -71,6 +71,9 @@ func main() {
os.Setenv("TERM", "xterm-truecolor")
}
// Load the syntax files, including the colorscheme
LoadSyntaxFiles()
// Initilize tcell
screen, err = tcell.NewScreen()
if err != nil {

View File

@@ -42,6 +42,9 @@ func (sline *Statusline) Display() {
file += " " + sline.view.buf.filetype
statusLineStyle := tcell.StyleDefault.Reverse(true)
if style, ok := colorscheme["statusline"]; ok {
statusLineStyle = style
}
// Maybe there is a unicode filename?
fileRunes := []rune(file)

View File

@@ -46,6 +46,11 @@ type View struct {
// Syntax higlighting matches
matches SyntaxMatches
// The matches from the last frame
lastMatches SyntaxMatches
// This is the range of lines that should have their syntax highlighting updated
updateLines [2]int
// The messenger so we can send messages to the user and get input from them
m *Messenger
@@ -84,6 +89,10 @@ func NewViewWidthHeight(buf *Buffer, m *Messenger, w, h int) *View {
view: v,
}
// Update the syntax highlighting for the entire buffer at the start
v.UpdateLines(v.topline, v.topline+v.height)
// v.matches = Match(v.buf.rules, v.buf, v)
// Set mouseReleased to true because we assume the mouse is not being pressed when
// the editor is opened
v.mouseReleased = true
@@ -91,6 +100,12 @@ func NewViewWidthHeight(buf *Buffer, m *Messenger, w, h int) *View {
return v
}
// UpdateLines sets the values for v.updateLines
func (v *View) UpdateLines(start, end int) {
v.updateLines[0] = start
v.updateLines[1] = end
}
// Resize recalculates the actual width and height of the view from the width and height
// percentages
// This is usually called when the window is resized, or when a split has been added and
@@ -307,6 +322,8 @@ func (v *View) HandleEvent(event tcell.Event) {
// This bool determines whether the view is relocated at the end of the function
// By default it's true because most events should cause a relocate
relocate := true
// By default we don't update and syntax highlighting
v.UpdateLines(-2, 0)
switch e := event.(type) {
case *tcell.EventResize:
// Window resized
@@ -329,15 +346,19 @@ func (v *View) HandleEvent(event tcell.Event) {
// Insert a newline
v.eh.Insert(v.cursor.loc, "\n")
v.cursor.Right()
v.UpdateLines(v.cursor.y-1, v.cursor.y)
case tcell.KeySpace:
// Insert a space
v.eh.Insert(v.cursor.loc, " ")
v.cursor.Right()
v.UpdateLines(v.cursor.y, v.cursor.y)
case tcell.KeyBackspace2:
// Delete a character
if v.cursor.HasSelection() {
v.cursor.DeleteSelection()
v.cursor.ResetSelection()
// Rehighlight the entire buffer
v.UpdateLines(v.topline, v.topline+v.height)
} else if v.cursor.loc > 0 {
// We have to do something a bit hacky here because we want to
// delete the line by first moving left and then deleting backwards
@@ -349,27 +370,41 @@ func (v *View) HandleEvent(event tcell.Event) {
v.cursor.Right()
v.eh.Remove(v.cursor.loc-1, v.cursor.loc)
v.cursor.x, v.cursor.y, v.cursor.loc = cx, cy, cloc
v.UpdateLines(v.cursor.y, v.cursor.y+1)
}
case tcell.KeyTab:
// Insert a tab
v.eh.Insert(v.cursor.loc, "\t")
v.cursor.Right()
v.UpdateLines(v.cursor.y, v.cursor.y)
case tcell.KeyCtrlS:
v.Save()
case tcell.KeyCtrlZ:
v.eh.Undo()
// Rehighlight the entire buffer
v.UpdateLines(v.topline, v.topline+v.height)
case tcell.KeyCtrlY:
v.eh.Redo()
// Rehighlight the entire buffer
v.UpdateLines(v.topline, v.topline+v.height)
case tcell.KeyCtrlC:
v.Copy()
// Rehighlight the entire buffer
v.UpdateLines(v.topline, v.topline+v.height)
case tcell.KeyCtrlX:
v.Cut()
// Rehighlight the entire buffer
v.UpdateLines(v.topline, v.topline+v.height)
case tcell.KeyCtrlV:
v.Paste()
// Rehighlight the entire buffer
v.UpdateLines(v.topline, v.topline+v.height)
case tcell.KeyCtrlA:
v.SelectAll()
case tcell.KeyCtrlO:
v.OpenFile()
// Rehighlight the entire buffer
v.UpdateLines(v.topline, v.topline+v.height)
case tcell.KeyPgUp:
v.PageUp()
case tcell.KeyPgDn:
@@ -386,6 +421,7 @@ func (v *View) HandleEvent(event tcell.Event) {
}
v.eh.Insert(v.cursor.loc, string(e.Rune()))
v.cursor.Right()
v.UpdateLines(v.cursor.y, v.cursor.y)
}
case *tcell.EventMouse:
x, y := e.Position()
@@ -442,10 +478,14 @@ func (v *View) HandleEvent(event tcell.Event) {
if relocate {
v.Relocate()
}
// v.matches = Match(v.buf.rules, v.buf, v)
}
// DisplayView renders the view to the screen
func (v *View) DisplayView() {
matches := make(SyntaxMatches)
// The character number of the character in the top left of the screen
charNum := v.cursor.loc + v.cursor.Distance(0, v.topline)
@@ -468,8 +508,8 @@ func (v *View) DisplayView() {
// Write the line number
lineNumStyle := tcell.StyleDefault
if _, ok := colorscheme["line-number"]; ok {
lineNumStyle = colorscheme["line-number"]
if style, ok := colorscheme["line-number"]; ok {
lineNumStyle = style
}
// Write the spaces before the line number if necessary
lineNum := strconv.Itoa(lineN + v.topline + 1)
@@ -491,12 +531,14 @@ func (v *View) DisplayView() {
for _, ch := range line {
var lineStyle tcell.Style
// Does the current character need to be syntax highlighted?
st, ok := v.matches[charNum]
if ok {
if st, ok := v.matches[charNum]; ok {
highlightStyle = st
} else if st, ok := v.lastMatches[charNum]; ok {
highlightStyle = st
} else {
highlightStyle = tcell.StyleDefault
}
matches[charNum] = highlightStyle
if v.cursor.HasSelection() &&
(charNum >= v.cursor.selectionStart && charNum <= v.cursor.selectionEnd ||
@@ -504,8 +546,8 @@ func (v *View) DisplayView() {
lineStyle = tcell.StyleDefault.Reverse(true)
if _, ok := colorscheme["selection"]; ok {
lineStyle = colorscheme["selection"]
if style, ok := colorscheme["selection"]; ok {
lineStyle = style
}
} else {
lineStyle = highlightStyle
@@ -533,8 +575,8 @@ func (v *View) DisplayView() {
selectStyle := tcell.StyleDefault.Reverse(true)
if _, ok := colorscheme["selection"]; ok {
selectStyle = colorscheme["selection"]
if style, ok := colorscheme["selection"]; ok {
selectStyle = style
}
screen.SetContent(x+tabchars, lineN, ' ', nil, selectStyle)
}
@@ -542,6 +584,8 @@ func (v *View) DisplayView() {
x = 0
charNum++
}
v.lastMatches = matches
}
// Display renders the view, the cursor, and statusline