package main
import (
- "fmt"
"github.com/gdamore/tcell"
+ "github.com/mitchellh/go-homedir"
"io/ioutil"
- "os/user"
"path/filepath"
"regexp"
+ "strconv"
"strings"
)
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
}
// LoadSyntaxFiles loads the syntax files from the default directory ~/.micro
func LoadSyntaxFiles() {
- usr, _ := user.Current()
- dir := usr.HomeDir
+ dir, err := homedir.Dir()
+ if err != nil {
+ TermMessage("Error finding your home directory\nCan't load runtime files")
+ return
+ }
LoadSyntaxFilesFromDir(dir + "/.micro/syntax")
}
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
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") {
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
+ }
+
+ st := tcell.StyleDefault
+ if _, ok := colorscheme[color]; ok {
+ st = colorscheme[color]
+ } else {
+ st = StringToStyle(color)
+ }
+ 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
}
} else {
st = StringToStyle(color)
}
- rules = append(rules, SyntaxRule{regex, st})
+ rules = append(rules, SyntaxRule{regex, flags, true, st})
}
}
}
// SyntaxMatches is an alias to a map from character numbers to styles,
// so map[3] represents the style of the third character
-type SyntaxMatches map[int]tcell.Style
+type SyntaxMatches [][]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
+func Match(v *View) SyntaxMatches {
+ buf := v.buf
+ rules := v.buf.rules
+
+ viewStart := v.topline
+ viewEnd := v.topline + v.height
+ if viewEnd > len(buf.lines) {
+ viewEnd = len(buf.lines)
+ }
+
+ lines := buf.lines[viewStart:viewEnd]
+ matches := make(SyntaxMatches, len(lines))
+
+ for i, line := range lines {
+ matches[i] = make([]tcell.Style, len(line))
}
- if end > len(buf.lines) {
- end = len(buf.lines)
+
+ totalStart := v.topline - synLinesUp
+ totalEnd := v.topline + v.height + synLinesDown
+ if totalStart < 0 {
+ totalStart = 0
}
- 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)
+ if totalEnd > len(buf.lines) {
+ totalEnd = len(buf.lines)
+ }
+
+ str := strings.Join(buf.lines[totalStart:totalEnd], "\n")
+ startNum := v.cursor.loc + v.cursor.Distance(0, totalStart)
- m := make(map[int]tcell.Style)
for _, rule := range rules {
- if rule.regex.MatchString(str) {
- indicies := rule.regex.FindAllStringIndex(str, -1)
- for _, value := range indicies {
- value[0] += startNum
- value[1] += startNum
- for i := value[0]; i < value[1]; i++ {
- if i >= toplineNum {
- m[i] = rule.style
+ if rule.startend {
+ if rule.regex.MatchString(str) {
+ indicies := rule.regex.FindAllStringIndex(str, -1)
+ for _, value := range indicies {
+ value[0] += startNum
+ value[1] += startNum
+ for i := value[0]; i < value[1]; i++ {
+ colNum, lineNum := GetPos(startNum, totalStart, i, buf)
+ if lineNum == -1 || colNum == -1 {
+ continue
+ }
+ lineNum -= viewStart
+ if lineNum >= 0 && lineNum < v.height {
+ if lineNum >= len(matches) {
+ messenger.Error("Line " + strconv.Itoa(lineNum))
+ } else if colNum >= len(matches[lineNum]) {
+ messenger.Error("Line " + strconv.Itoa(lineNum) + " Col " + strconv.Itoa(colNum) + " " + strconv.Itoa(len(matches[lineNum])))
+ }
+ matches[lineNum][colNum] = rule.style
+ }
+ }
+ }
+ }
+ } else {
+ for lineN, line := range lines {
+ if rule.regex.MatchString(line) {
+ indicies := rule.regex.FindAllStringIndex(line, -1)
+ for _, value := range indicies {
+ for i := value[0]; i < value[1]; i++ {
+ matches[lineN][i] = rule.style
+ }
}
}
}
}
}
- return m
+ return matches
+}
+
+// GetPos returns an x, y position given a character location in the buffer
+func GetPos(startLoc, startLine, loc int, buf *Buffer) (int, int) {
+ charNum := startLoc
+ x, y := 0, startLine
+
+ for i, line := range buf.lines {
+ if charNum+Count(line) > loc {
+ y = i
+ x = loc - charNum
+ return x, y
+ }
+ charNum += Count(line) + 1
+ }
+
+ return -1, -1
}