]> git.lizzy.rs Git - micro.git/blobdiff - src/highlighter.go
Add options
[micro.git] / src / highlighter.go
index 0613f52fff1749961e651c1bee26a1a950a65e74..86d9a0bcc53171267da855213e085ce43bcd1d3f 100644 (file)
@@ -1,21 +1,42 @@
 package main
 
 import (
-       "fmt"
-       "github.com/zyedidia/tcell"
+       "github.com/gdamore/tcell"
+       "github.com/mitchellh/go-homedir"
        "io/ioutil"
-       "os/user"
        "path/filepath"
        "regexp"
+       "strconv"
        "strings"
 )
 
-var syntaxFiles map[[2]*regexp.Regexp][2]string
+// FileTypeRules represents a complete set of syntax rules for a filetype
+type FileTypeRules struct {
+       filetype string
+       rules    []SyntaxRule
+}
+
+// SyntaxRule represents a regex to highlight in a certain style
+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
+}
+
+var syntaxFiles map[[2]*regexp.Regexp]FileTypeRules
 
 // 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")
 }
 
@@ -35,14 +56,15 @@ func JoinRule(rule string) string {
 func LoadSyntaxFilesFromDir(dir string) {
        InitColorscheme()
 
-       syntaxFiles = make(map[[2]*regexp.Regexp][2]string)
+       syntaxFiles = make(map[[2]*regexp.Regexp]FileTypeRules)
        files, _ := ioutil.ReadDir(dir)
        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")
@@ -50,11 +72,14 @@ func LoadSyntaxFilesFromDir(dir string) {
                        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
                        var filetype string
-                       var rules string
-                       for _, line := range lines {
+                       var rules []SyntaxRule
+                       for lineNum, line := range lines {
                                if strings.TrimSpace(line) == "" ||
                                        strings.TrimSpace(line)[0] == '#' {
                                        // Ignore this line
@@ -66,19 +91,20 @@ func LoadSyntaxFilesFromDir(dir string) {
                                        if len(syntaxMatches) == 3 {
                                                if syntaxRegex != nil {
                                                        regexes := [2]*regexp.Regexp{syntaxRegex, headerRegex}
-                                                       syntaxFiles[regexes] = [2]string{rules, filetype}
+                                                       syntaxFiles[regexes] = FileTypeRules{filetype, rules}
                                                }
+                                               rules = rules[:0]
 
                                                filetype = string(syntaxMatches[1])
                                                extensions := JoinRule(string(syntaxMatches[2]))
 
                                                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") {
@@ -88,20 +114,81 @@ 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 {
-                                       rules += line + "\n"
+                                       if ruleParser.MatchString(line) {
+                                               submatch := ruleParser.FindSubmatch([]byte(line))
+                                               var color string
+                                               var regexStr string
+                                               var flags string
+                                               if len(submatch) == 4 {
+                                                       color = string(submatch[1])
+                                                       flags = string(submatch[2])
+                                                       regexStr = "(?" + flags + ")" + JoinRule(string(submatch[3]))
+                                               } else if len(submatch) == 3 {
+                                                       color = string(submatch[1])
+                                                       regexStr = JoinRule(string(submatch[2]))
+                                               } else {
+                                                       TermError(filename, lineNum, "Invalid statement: "+line)
+                                               }
+                                               regex, err := regexp.Compile(regexStr)
+                                               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, 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})
+                                       }
                                }
                        }
                        if syntaxRegex != nil {
                                regexes := [2]*regexp.Regexp{syntaxRegex, headerRegex}
-                               syntaxFiles[regexes] = [2]string{rules, filetype}
+                               syntaxFiles[regexes] = FileTypeRules{filetype, rules}
                        }
                }
        }
@@ -109,75 +196,105 @@ func LoadSyntaxFilesFromDir(dir string) {
 
 // GetRules finds the syntax rules that should be used for the buffer
 // and returns them. It also returns the filetype of the file
-func GetRules(buf *Buffer) (string, string) {
+func GetRules(buf *Buffer) ([]SyntaxRule, string) {
        for r := range syntaxFiles {
                if r[0] != nil && r[0].MatchString(buf.path) {
-                       return syntaxFiles[r][0], syntaxFiles[r][1]
+                       return syntaxFiles[r].rules, syntaxFiles[r].filetype
                } else if r[1] != nil && r[1].MatchString(buf.lines[0]) {
-                       return syntaxFiles[r][0], syntaxFiles[r][1]
+                       return syntaxFiles[r].rules, syntaxFiles[r].filetype
                }
        }
-       return "", "Unknown"
+       return nil, "Unknown"
 }
 
-// Match takes a buffer and returns a map specifying how it should be syntax highlighted
-// The map is from character numbers to styles, so map[3] represents the style change
-// at the third character in the buffer
-// Note that this map only stores changes in styles, not each character's style
-func Match(rules string, buf *Buffer, v *View) map[int]tcell.Style {
-       start := v.topline - synLinesUp
-       end := v.topline + v.height + synLinesDown
-       if start < 0 {
-               start = 0
+// SyntaxMatches is an alias to a map from character numbers to styles,
+// so map[3] represents the style of the third character
+type SyntaxMatches [][]tcell.Style
+
+// Match takes a buffer and returns the syntax matches a map specifying how it should be syntax highlighted
+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)
        }
-       if end > len(buf.lines) {
-               end = 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))
        }
-       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)
-
-       lines := strings.Split(rules, "\n")
-       m := make(map[int]tcell.Style)
-       parser := regexp.MustCompile(`color (.*?)\s+(?:\((.*?)\)\s+)?"(.*)"`)
-       for _, line := range lines {
-               if strings.TrimSpace(line) == "" {
-                       // Ignore this line
-                       continue
-               }
-               submatch := parser.FindSubmatch([]byte(line))
-               color := string(submatch[1])
-               var regexStr string
-               if len(submatch) == 4 {
-                       regexStr = "(?m" + string(submatch[2]) + ")" + string(submatch[3])
-               } else if len(submatch) == 3 {
-                       regexStr = "(?m)" + string(submatch[2])
-               }
-               regex, err := regexp.Compile(regexStr)
-               if err != nil {
-                       // Error with the regex!
-                       continue
-               }
-               st := tcell.StyleDefault
-               if _, ok := colorscheme[color]; ok {
-                       st = colorscheme[color]
-               } else {
-                       st = StringToStyle(color)
-               }
 
-               if regex.MatchString(str) {
-                       indicies := 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] = st
+       totalStart := v.topline - synLinesUp
+       totalEnd := v.topline + v.height + synLinesDown
+       if totalStart < 0 {
+               totalStart = 0
+       }
+       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)
+
+       for _, rule := range rules {
+               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 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 m
+       return -1, -1
 }