mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-11 07:02:44 +09:00
Much improved highlighting (turned off for now)
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
60
src/view.go
60
src/view.go
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user