// Package highlight provides an Elvish syntax highlighter.
package highlight import ( ) // Config keeps configuration for highlighting code. type Config struct { Check func(n parse.Tree) error HasCommand func(name string) bool } // Information collected about a command region, used for asynchronous // highlighting. type cmdRegion struct { seg int cmd string } // MaxBlockForLate specifies the maximum wait time to block for late results. // It can be changed for test cases. var MaxBlockForLate = 10 * time.Millisecond // Highlights a piece of Elvish code. func ( string, Config, func(ui.Text)) (ui.Text, []error) { var []error var []region , := parse.Parse(parse.Source{Name: "[tty]", Code: }, parse.Config{}) if != nil { for , := range .(*parse.Error).Entries { if .Context.From != len() { = append(, ) = append(, region{ .Context.From, .Context.To, semanticRegion, errorRegion}) } } } if .Check != nil { := .Check() if , := .(diag.Ranger); && .Range().From != len() { = append(, ) = append(, region{ .Range().From, .Range().To, semanticRegion, errorRegion}) } } var ui.Text := getRegionsInner(.Root) = append(, ...) = fixRegions() := 0 var []cmdRegion for , := range { if .begin > { // Add inter-region text. = append(, &ui.Segment{Text: [:.begin]}) } := [.begin:.end] var ui.Styling if .typ == commandRegion { if .HasCommand != nil { // Do not highlight now, but collect the index of the region and the // segment. = append(, cmdRegion{len(), }) } else { // Treat all commands as good commands. = stylingForGoodCommand } } else { = stylingFor[.typ] } := &ui.Segment{Text: } if != nil { = ui.StyleSegment(, ) } = append(, ) = .end } if len() > { // Add text after the last region as unstyled. = append(, &ui.Segment{Text: [:]}) } if .HasCommand != nil && len() > 0 { // Launch a goroutine to style command regions asynchronously. := make(chan ui.Text) go func() { := .Clone() for , := range { var ui.Styling if .HasCommand(.cmd) { = stylingForGoodCommand } else { = stylingForBadCommand } := &[.seg] * = ui.StyleSegment(*, ) } <- }() // Block a short while for the late text to arrive, in order to reduce // flickering. Otherwise, return the text already computed, and pass the // late result to lateCb in another goroutine. select { case := <-: return , case <-time.After(MaxBlockForLate): go func() { (<-) }() return , } } return , }