5 "github.com/gdamore/tcell"
13 var syntaxFiles map[[2]*regexp.Regexp][2]string
15 // LoadSyntaxFiles loads the syntax files from the default directory ~/.micro
16 func LoadSyntaxFiles() {
17 usr, _ := user.Current()
19 LoadSyntaxFilesFromDir(dir + "/.micro/syntax")
22 // JoinRule takes a syntax rule (which can be multiple regular expressions)
23 // and joins it into one regular expression by ORing everything together
24 func JoinRule(rule string) string {
25 split := strings.Split(rule, `" "`)
26 joined := strings.Join(split, ")|(")
27 joined = "(" + joined + ")"
31 // LoadSyntaxFilesFromDir loads the syntax files from a specified directory
32 // To load the syntax files, we must fill the `syntaxFiles` map
33 // This involves finding the regex for syntax and if it exists, the regex
34 // for the header. Then we must get the text for the file and the filetype.
35 func LoadSyntaxFilesFromDir(dir string) {
36 syntaxFiles = make(map[[2]*regexp.Regexp][2]string)
37 files, _ := ioutil.ReadDir(dir)
38 for _, f := range files {
39 if filepath.Ext(f.Name()) == ".micro" {
40 text, err := ioutil.ReadFile(dir + "/" + f.Name())
43 fmt.Println("Error loading syntax files:", err)
46 lines := strings.Split(string(text), "\n")
48 syntaxParser := regexp.MustCompile(`syntax "(.*?)"\s+"(.*)"+`)
49 headerParser := regexp.MustCompile(`header "(.*)"`)
51 var syntaxRegex *regexp.Regexp
52 var headerRegex *regexp.Regexp
55 for _, line := range lines {
56 if strings.TrimSpace(line) == "" ||
57 strings.TrimSpace(line)[0] == '#' {
62 if strings.HasPrefix(line, "syntax") {
63 syntaxMatches := syntaxParser.FindSubmatch([]byte(line))
64 if len(syntaxMatches) == 3 {
65 if syntaxRegex != nil {
66 regexes := [2]*regexp.Regexp{syntaxRegex, headerRegex}
67 syntaxFiles[regexes] = [2]string{rules, filetype}
70 filetype = string(syntaxMatches[1])
71 extensions := JoinRule(string(syntaxMatches[2]))
73 syntaxRegex, err = regexp.Compile(extensions)
75 fmt.Println("Regex error:", err)
79 fmt.Println("Syntax statement is not valid:", line)
82 } else if strings.HasPrefix(line, "header") {
83 headerMatches := headerParser.FindSubmatch([]byte(line))
84 if len(headerMatches) == 2 {
85 header := JoinRule(string(headerMatches[1]))
87 headerRegex, err = regexp.Compile(header)
89 fmt.Println("Regex error:", err)
93 fmt.Println("Header statement is not valid:", line)
100 if syntaxRegex != nil {
101 regexes := [2]*regexp.Regexp{syntaxRegex, headerRegex}
102 syntaxFiles[regexes] = [2]string{rules, filetype}
108 // GetRules finds the syntax rules that should be used for the buffer
109 // and returns them. It also returns the filetype of the file
110 func GetRules(buf *Buffer) (string, string) {
111 for r := range syntaxFiles {
112 if r[0] != nil && r[0].MatchString(buf.path) {
113 return syntaxFiles[r][0], syntaxFiles[r][1]
114 } else if r[1] != nil && r[1].MatchString(buf.lines[0]) {
115 return syntaxFiles[r][0], syntaxFiles[r][1]
121 // Match takes a buffer and returns a map specifying how it should be syntax highlighted
122 // The map is from character numbers to styles, so map[3] represents the style change
123 // at the third character in the buffer
124 // Note that this map only stores changes in styles, not each character's style
125 func Match(rules string, buf *Buffer, v *View) map[int]tcell.Style {
126 start := v.topline - synLinesUp
127 end := v.topline + v.height + synLinesDown
131 if end > len(buf.lines) {
134 str := strings.Join(buf.lines[start:end], "\n")
135 startNum := v.cursor.loc + v.cursor.Distance(0, start)
136 toplineNum := v.cursor.loc + v.cursor.Distance(0, v.topline)
138 lines := strings.Split(rules, "\n")
139 m := make(map[int]tcell.Style)
140 parser := regexp.MustCompile(`color (.*?)\s+"(.*)"`)
141 for _, line := range lines {
142 if strings.TrimSpace(line) == "" {
146 submatch := parser.FindSubmatch([]byte(line))
147 color := string(submatch[1])
148 regex, err := regexp.Compile(string(submatch[2]))
150 // Error with the regex!
153 st := StringToStyle(color)
155 if regex.MatchString(str) {
156 indicies := regex.FindAllStringIndex(str, -1)
157 for _, value := range indicies {
160 for i := value[0] + 1; i < value[1]; i++ {
161 if _, exists := m[i]; exists {
166 if value[0] < toplineNum && value[1] > toplineNum {
170 if value[0] >= toplineNum {
173 if value[1] >= toplineNum {
174 if _, exists := m[value[1]]; !exists {
175 m[value[1]] = tcell.StyleDefault
185 // StringToStyle returns a style from a string
186 func StringToStyle(str string) tcell.Style {
189 split := strings.Split(str, ",")
191 fg, bg = split[0], split[1]
196 return tcell.StyleDefault.Foreground(StringToColor(fg)).Background(StringToColor(bg))
199 // StringToColor returns a tcell color from a string representation of a color
200 func StringToColor(str string) tcell.Color {
203 return tcell.ColorBlack
205 return tcell.ColorMaroon
207 return tcell.ColorGreen
209 return tcell.ColorOlive
211 return tcell.ColorNavy
213 return tcell.ColorPurple
215 return tcell.ColorTeal
217 return tcell.ColorSilver
219 return tcell.ColorGray
221 return tcell.ColorRed
223 return tcell.ColorLime
225 return tcell.ColorYellow
227 return tcell.ColorBlue
228 case "brightmagenta":
229 return tcell.ColorFuchsia
231 return tcell.ColorAqua
233 return tcell.ColorWhite
235 return tcell.ColorDefault