newedit/highlighter: Highlighter now relays late updates.

This commit is contained in:
Qi Xiao 2018-12-07 20:30:49 +00:00
parent 25bdb6ecd7
commit 28cbb3112e
4 changed files with 70 additions and 24 deletions

View File

@ -57,7 +57,8 @@ func (ed *Editor) State() *types.State {
}
// A special event type signalling something has seen a late update and a
// refresh is needed. This is currently only used for refreshing prompts.
// refresh is needed. This is currently used for refreshing prompts and
// highlighting.
type lateUpdate struct{}
func (ed *Editor) handle(e event) handleResult {
@ -197,9 +198,10 @@ func (ed *Editor) ReadCode() (string, error) {
}()
}
// Relay late prompt/rprompt updates.
stopRelayLateUpdates := make(chan struct{})
defer close(stopRelayLateUpdates)
// Relay late prompt/rprompt updates.
relayLateUpdates := func(p Prompt) {
if p == nil {
return
@ -220,7 +222,21 @@ func (ed *Editor) ReadCode() (string, error) {
relayLateUpdates(ed.Prompt)
relayLateUpdates(ed.RPrompt)
// TODO: Relay highlighter late updates.
// Relay highlighter late updates.
if ed.Highlighter != nil {
wg.Add(1)
go func() {
defer wg.Done()
for {
select {
case <-ed.Highlighter.LateUpdates():
ed.loop.Input(lateUpdate{})
case <-stopRelayLateUpdates:
return
}
}
}()
}
// Trigger an initial prompt update.
ed.triggerPrompts(true)

View File

@ -14,7 +14,7 @@ type Dep struct {
}
// Highlights a piece of Elvish code.
func highlight(code string, dep Dep) (styled.Text, []error) {
func highlight(code string, dep Dep, lateCb func(styled.Text)) (styled.Text, []error) {
var errors []error
n, errParse := parse.AsChunk("[interactive]", code)

View File

@ -43,22 +43,24 @@ func (anyMatcher) Match(tt.RetValue) bool { return true }
func TestHighlight(t *testing.T) {
any := anyMatcher{}
dep := Dep{}
hl := func(code string) (styled.Text, []error) {
return highlight(code, Dep{}, nopLateCb)
}
tt.Test(t, tt.Fn("highlight", highlight), tt.Table{
Args("ls", dep).Rets(styled.Text{
tt.Test(t, tt.Fn("highlight", hl), tt.Table{
Args("ls").Rets(styled.Text{
&styled.Segment{styled.Style{Foreground: "green"}, "ls"},
}, noErrors),
Args(" ls\n", dep).Rets(styled.Text{
Args(" ls\n").Rets(styled.Text{
styled.UnstyledSegment(" "),
&styled.Segment{styled.Style{Foreground: "green"}, "ls"},
styled.UnstyledSegment("\n"),
}, noErrors),
// Parse error
Args("ls ]", dep).Rets(any, matchErrors(parseErrorMatcher{3, 4})),
Args("ls ]").Rets(any, matchErrors(parseErrorMatcher{3, 4})),
// Errors at the end are elided
Args("ls $", dep).Rets(any, noErrors),
Args("ls [", dep).Rets(any, noErrors),
Args("ls $").Rets(any, noErrors),
Args("ls [").Rets(any, noErrors),
// TODO: Test for multiple parse errors
})
@ -85,31 +87,33 @@ func TestHighlight_Check(t *testing.T) {
}
checkError = fakeCheckError{0, 2}
_, errors := highlight("code", dep)
_, errors := highlight("code", dep, nopLateCb)
if !reflect.DeepEqual(errors, []error{checkError}) {
t.Errorf("Got errors %v, want %v", errors, []error{checkError})
}
// Errors at the end
checkError = fakeCheckError{4, 4}
_, errors = highlight("code", dep)
_, errors = highlight("code", dep, nopLateCb)
if len(errors) != 0 {
t.Errorf("Got errors %v, want 0 error", errors)
}
}
func TestHighlight_HasCommand(t *testing.T) {
dep := Dep{
HasCommand: func(cmd string) bool {
return cmd == "ls"
},
hasCommand := func(cmd string) bool { return cmd == "ls" }
hl := func(code string) (styled.Text, []error) {
return highlight(code, Dep{HasCommand: hasCommand}, nopLateCb)
}
tt.Test(t, tt.Fn("highlight", highlight), tt.Table{
Args("ls", dep).Rets(styled.Text{
tt.Test(t, tt.Fn("highlight", hl), tt.Table{
Args("ls").Rets(styled.Text{
&styled.Segment{styled.Style{Foreground: "green"}, "ls"},
}, noErrors),
Args("echo", dep).Rets(styled.Text{
Args("echo").Rets(styled.Text{
&styled.Segment{styled.Style{Foreground: "red"}, "echo"},
}, noErrors),
})
}
func nopLateCb(styled.Text) {}

View File

@ -1,24 +1,50 @@
package highlight
import (
"sync"
"github.com/elves/elvish/styled"
)
const latesBufferSize = 128
// Highlighter is a code highlighter that can deliver results asynchronously.
type Highlighter struct {
dep Dep
dep Dep
state state
lates chan struct{}
}
type state struct {
sync.RWMutex
code string
styledCode styled.Text
errors []error
}
func NewHighlighter(dep Dep) *Highlighter {
return &Highlighter{dep}
return &Highlighter{dep, state{}, make(chan struct{}, latesBufferSize)}
}
// Get returns the highlighted code and static errors found in the code.
func (hl *Highlighter) Get(code string) (styled.Text, []error) {
return highlight(code, hl.dep)
hl.state.RLock()
if code == hl.state.code {
hl.state.RUnlock()
return hl.state.styledCode, hl.state.errors
}
hl.state.RUnlock()
lateCb := func(styledCode styled.Text) {
hl.state.Lock()
hl.state.styledCode = styledCode
hl.state.Unlock()
hl.lates <- struct{}{}
}
return highlight(code, hl.dep, lateCb)
}
// LateUpdates returns a channel for notifying late updates.
func (hl *Highlighter) LateUpdates() <-chan struct{} {
return nil
return hl.lates
}