]> git.lizzy.rs Git - micro.git/blobdiff - src/highlighter.go
Add options
[micro.git] / src / highlighter.go
index 8ef9ad4a6e36693150f6278012196abc62589226..86d9a0bcc53171267da855213e085ce43bcd1d3f 100644 (file)
@@ -1,12 +1,12 @@
 package main
 
 import (
-       "fmt"
        "github.com/gdamore/tcell"
+       "github.com/mitchellh/go-homedir"
        "io/ioutil"
-       "os/user"
        "path/filepath"
        "regexp"
+       "strconv"
        "strings"
 )
 
@@ -20,6 +20,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
 }
@@ -28,8 +32,11 @@ 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")
 }
 
@@ -54,16 +61,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 +100,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 +114,65 @@ 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
+                                               }
+
+                                               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
                                                }
 
@@ -133,7 +182,7 @@ func LoadSyntaxFilesFromDir(dir string) {
                                                } else {
                                                        st = StringToStyle(color)
                                                }
-                                               rules = append(rules, SyntaxRule{regex, st})
+                                               rules = append(rules, SyntaxRule{regex, flags, true, st})
                                        }
                                }
                        }
@@ -160,37 +209,92 @@ func GetRules(buf *Buffer) ([]SyntaxRule, string) {
 
 // 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
 }