mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-13 01:47:51 +08:00
cli/clicore: Access all config via the Config interface.
The cli package has been changed accordingly.
This commit is contained in:
parent
2584bd2510
commit
123aeae493
31
cli/app.go
31
cli/app.go
|
@ -2,6 +2,7 @@ package cli
|
|||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/elves/elvish/cli/clicore"
|
||||
"github.com/elves/elvish/cli/clitypes"
|
||||
|
@ -22,8 +23,11 @@ type App struct {
|
|||
Lastcmd *lastcmd.Mode
|
||||
}
|
||||
|
||||
// AppConfig is a struct containing configurations for initializing an App.
|
||||
// AppConfig is a struct containing configurations for initializing an App. It
|
||||
// must not be copied once used.
|
||||
type AppConfig struct {
|
||||
Mutex sync.Mutex
|
||||
|
||||
MaxHeight int
|
||||
|
||||
BeforeReadline []func()
|
||||
|
@ -63,30 +67,9 @@ func NewApp(cfg *AppConfig, t clicore.TTY, sigs clicore.SignalSource) *App {
|
|||
core: coreApp,
|
||||
cfg: cfg,
|
||||
}
|
||||
coreApp.Config = &coreConfig{
|
||||
maxHeight: cfg.MaxHeight,
|
||||
rpromptPersistent: cfg.RPromptPersistent,
|
||||
}
|
||||
coreApp.BeforeReadline = cfg.BeforeReadline
|
||||
recordCmd := func(code string) {
|
||||
if cfg.HistoryStore == nil {
|
||||
return
|
||||
}
|
||||
_, err := cfg.HistoryStore.AddCmd(histutil.Entry{Text: code})
|
||||
if err != nil {
|
||||
coreApp.Notify("db error: " + err.Error())
|
||||
}
|
||||
}
|
||||
afterReadline := append([]func(string){recordCmd}, cfg.AfterReadline...)
|
||||
coreApp.AfterReadline = afterReadline
|
||||
coreApp.Highlighter = cfg.Highlighter
|
||||
coreApp.Prompt = cfg.Prompt
|
||||
coreApp.RPrompt = cfg.RPrompt
|
||||
|
||||
insertMode := newInsertMode(&cfg.InsertModeConfig, app)
|
||||
app.Insert = insertMode
|
||||
coreApp.InitMode = insertMode
|
||||
coreApp.Config = coreConfig{app}
|
||||
|
||||
app.Insert = newInsertMode(&cfg.InsertModeConfig, app)
|
||||
lsMode := &listing.Mode{}
|
||||
app.Listing = lsMode
|
||||
app.Histlist = &histlist.Mode{
|
||||
|
|
|
@ -22,29 +22,8 @@ type App struct {
|
|||
|
||||
state clitypes.State
|
||||
|
||||
// Configuration that can be modified concurrently.
|
||||
// Configuration.
|
||||
Config Config
|
||||
|
||||
// Functions called when ReadCode starts.
|
||||
BeforeReadline []func()
|
||||
// Functions called when ReadCode ends; the argument is the code that has
|
||||
// just been read.
|
||||
AfterReadline []func(string)
|
||||
|
||||
// Code highlighter.
|
||||
Highlighter Highlighter
|
||||
|
||||
// Left-hand and right-hand prompt.
|
||||
Prompt, RPrompt Prompt
|
||||
|
||||
// Initial mode.
|
||||
InitMode clitypes.Mode
|
||||
}
|
||||
|
||||
// Config is the configuration for an App.
|
||||
type Config interface {
|
||||
MaxHeight() int
|
||||
RPromptPersistent() bool
|
||||
}
|
||||
|
||||
// NewApp creates a new App from two abstract dependencies. The creation does
|
||||
|
@ -52,7 +31,7 @@ type Config interface {
|
|||
// active. This is the most general way to create an App.
|
||||
func NewApp(t TTY, sigs SignalSource) *App {
|
||||
lp := newLoop()
|
||||
app := &App{loop: lp, tty: t, sigs: sigs}
|
||||
app := &App{loop: lp, tty: t, sigs: sigs, Config: DefaultConfig{}}
|
||||
lp.HandleCb(app.handle)
|
||||
lp.RedrawCb(app.redraw)
|
||||
return app
|
||||
|
@ -98,7 +77,7 @@ func (app *App) handle(e event) handleResult {
|
|||
}
|
||||
return handleResult{}
|
||||
case term.Event:
|
||||
action := getMode(app.state.Mode(), app.InitMode).HandleEvent(e, &app.state)
|
||||
action := getMode(app.state.Mode(), app.Config.InitMode()).HandleEvent(e, &app.state)
|
||||
|
||||
switch action {
|
||||
case clitypes.CommitCode:
|
||||
|
@ -114,11 +93,13 @@ func (app *App) handle(e event) handleResult {
|
|||
}
|
||||
|
||||
func (app *App) triggerPrompts(force bool) {
|
||||
if app.Prompt != nil {
|
||||
app.Prompt.Trigger(force)
|
||||
prompt := app.Config.Prompt()
|
||||
rprompt := app.Config.RPrompt()
|
||||
if prompt != nil {
|
||||
prompt.Trigger(force)
|
||||
}
|
||||
if app.RPrompt != nil {
|
||||
app.RPrompt.Trigger(force)
|
||||
if rprompt != nil {
|
||||
rprompt.Trigger(force)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,16 +126,16 @@ func (app *App) redraw(flag redrawFlag) {
|
|||
|
||||
// Prepare the code: applying pending, and highlight.
|
||||
code, dot := applyPending(rawState)
|
||||
styledCode, errors := highlighterGet(app.Highlighter, code)
|
||||
styledCode, errors := highlighterGet(app.Config.Highlighter(), code)
|
||||
// TODO: Apply transformerForPending to pending code.
|
||||
|
||||
// Render onto buffers.
|
||||
setup := &renderSetup{
|
||||
height, width,
|
||||
promptGet(app.Prompt), promptGet(app.RPrompt),
|
||||
promptGet(app.Config.Prompt()), promptGet(app.Config.RPrompt()),
|
||||
styledCode, dot, errors,
|
||||
rawState.Notes,
|
||||
getMode(rawState.Mode, app.InitMode)}
|
||||
getMode(rawState.Mode, app.Config.InitMode())}
|
||||
|
||||
bufNotes, bufMain := render(setup)
|
||||
|
||||
|
@ -238,14 +219,17 @@ func (app *App) ReadCode() (string, error) {
|
|||
}
|
||||
}()
|
||||
}
|
||||
if app.Prompt != nil {
|
||||
relayLateUpdates(app.Prompt.LateUpdates())
|
||||
prompt := app.Config.Prompt()
|
||||
if prompt != nil {
|
||||
relayLateUpdates(prompt.LateUpdates())
|
||||
}
|
||||
if app.RPrompt != nil {
|
||||
relayLateUpdates(app.RPrompt.LateUpdates())
|
||||
rprompt := app.Config.RPrompt()
|
||||
if rprompt != nil {
|
||||
relayLateUpdates(rprompt.LateUpdates())
|
||||
}
|
||||
if app.Highlighter != nil {
|
||||
relayLateUpdates(app.Highlighter.LateUpdates())
|
||||
highlighter := app.Config.Highlighter()
|
||||
if highlighter != nil {
|
||||
relayLateUpdates(highlighter.LateUpdates())
|
||||
}
|
||||
|
||||
// Trigger an initial prompt update.
|
||||
|
@ -255,16 +239,10 @@ func (app *App) ReadCode() (string, error) {
|
|||
defer app.state.Reset()
|
||||
|
||||
// BeforeReadline and AfterReadline hooks.
|
||||
for _, f := range app.BeforeReadline {
|
||||
f()
|
||||
}
|
||||
if len(app.AfterReadline) > 0 {
|
||||
defer func() {
|
||||
for _, f := range app.AfterReadline {
|
||||
f(app.state.Code())
|
||||
}
|
||||
}()
|
||||
}
|
||||
app.Config.BeforeReadline()
|
||||
defer func() {
|
||||
app.Config.AfterReadline(app.state.Code())
|
||||
}()
|
||||
|
||||
return app.loop.Run()
|
||||
}
|
||||
|
@ -293,13 +271,3 @@ func (app *App) Notify(note string) {
|
|||
app.state.AddNote(note)
|
||||
app.Redraw(false)
|
||||
}
|
||||
|
||||
// AddBeforeReadline adds a new before-readline hook function.
|
||||
func (app *App) AddBeforeReadline(f func()) {
|
||||
app.BeforeReadline = append(app.BeforeReadline, f)
|
||||
}
|
||||
|
||||
// AddAfterReadline adds a new after-readline hook function.
|
||||
func (app *App) AddAfterReadline(f func(string)) {
|
||||
app.AfterReadline = append(app.AfterReadline, f)
|
||||
}
|
||||
|
|
|
@ -9,12 +9,42 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
clitypes "github.com/elves/elvish/cli/clitypes"
|
||||
"github.com/elves/elvish/cli/term"
|
||||
"github.com/elves/elvish/edit/ui"
|
||||
"github.com/elves/elvish/styled"
|
||||
"github.com/elves/elvish/sys"
|
||||
)
|
||||
|
||||
type testConfig struct {
|
||||
maxHeight int
|
||||
rpromptPersistent bool
|
||||
beforeReadline func()
|
||||
afterReadline func(string)
|
||||
highlighter Highlighter
|
||||
prompt Prompt
|
||||
rprompt Prompt
|
||||
}
|
||||
|
||||
func (tc testConfig) MaxHeight() int { return tc.maxHeight }
|
||||
func (tc testConfig) RPromptPersistent() bool { return tc.rpromptPersistent }
|
||||
func (tc testConfig) Highlighter() Highlighter { return tc.highlighter }
|
||||
func (tc testConfig) Prompt() Prompt { return tc.prompt }
|
||||
func (tc testConfig) RPrompt() Prompt { return tc.rprompt }
|
||||
func (tc testConfig) InitMode() clitypes.Mode { return nil }
|
||||
|
||||
func (tc testConfig) BeforeReadline() {
|
||||
if tc.beforeReadline != nil {
|
||||
tc.beforeReadline()
|
||||
}
|
||||
}
|
||||
|
||||
func (tc testConfig) AfterReadline(s string) {
|
||||
if tc.afterReadline != nil {
|
||||
tc.afterReadline(s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadCode_AbortsOnSetupError(t *testing.T) {
|
||||
ed, tty, _ := setup()
|
||||
|
||||
|
@ -78,7 +108,7 @@ func TestReadCode_CallsBeforeReadlineOnce(t *testing.T) {
|
|||
ed, tty, _ := setup()
|
||||
|
||||
called := 0
|
||||
ed.AddBeforeReadline(func() { called++ })
|
||||
ed.Config = testConfig{beforeReadline: func() { called++ }}
|
||||
// Causes BasicMode to quit
|
||||
tty.Inject(term.KeyEvent{Rune: '\n'})
|
||||
|
||||
|
@ -94,10 +124,10 @@ func TestReadCode_CallsAfterReadlineOnceWithCode(t *testing.T) {
|
|||
|
||||
called := 0
|
||||
code := ""
|
||||
ed.AddAfterReadline(func(s string) {
|
||||
ed.Config = testConfig{afterReadline: func(s string) {
|
||||
called++
|
||||
code = s
|
||||
})
|
||||
}}
|
||||
// Causes BasicMode to write state.Code and then quit
|
||||
tty.Inject(term.KeyEvent{Rune: 'a'})
|
||||
tty.Inject(term.KeyEvent{Rune: 'b'})
|
||||
|
@ -114,18 +144,10 @@ func TestReadCode_CallsAfterReadlineOnceWithCode(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
type testConfig struct {
|
||||
maxHeight int
|
||||
rpromptPersistent bool
|
||||
}
|
||||
|
||||
func (tc testConfig) MaxHeight() int { return tc.maxHeight }
|
||||
func (tc testConfig) RPromptPersistent() bool { return tc.rpromptPersistent }
|
||||
|
||||
func TestReadCode_RespectsMaxHeight(t *testing.T) {
|
||||
ed, tty, _ := setup()
|
||||
|
||||
ed.Config = &testConfig{maxHeight: 2}
|
||||
ed.Config = testConfig{maxHeight: 2}
|
||||
tty.SetSize(10, 5) // Width = 5 to make it easy to test
|
||||
|
||||
// The code needs 3 lines to completely show.
|
||||
|
@ -147,12 +169,12 @@ var bufChTimeout = 1 * time.Second
|
|||
func TestReadCode_RendersHighlightedCode(t *testing.T) {
|
||||
ed, tty, _ := setup()
|
||||
|
||||
ed.Highlighter = fakeHighlighter{
|
||||
ed.Config = testConfig{highlighter: fakeHighlighter{
|
||||
get: func(code string) (styled.Text, []error) {
|
||||
return styled.Text{
|
||||
&styled.Segment{styled.Style{Foreground: "red"}, code}}, nil
|
||||
},
|
||||
}
|
||||
}}
|
||||
tty.Inject(term.KeyEvent{Rune: 'a'})
|
||||
tty.Inject(term.KeyEvent{Rune: 'b'})
|
||||
tty.Inject(term.KeyEvent{Rune: 'c'})
|
||||
|
@ -180,7 +202,7 @@ func TestReadCode_RedrawsOnHighlighterLateUpdate(t *testing.T) {
|
|||
func TestReadCode_RendersPrompt(t *testing.T) {
|
||||
ed, tty, _ := setup()
|
||||
|
||||
ed.Prompt = constPrompt{styled.Plain("> ")}
|
||||
ed.Config = testConfig{prompt: constPrompt{styled.Plain("> ")}}
|
||||
tty.Inject(term.KeyEvent{Rune: 'a'})
|
||||
|
||||
codeCh, _ := ed.readCodeAsync()
|
||||
|
@ -199,7 +221,7 @@ func TestReadCode_RendersRPrompt(t *testing.T) {
|
|||
ed, tty, _ := setup()
|
||||
tty.SetSize(80, 4) // Set a width of 4 for easier testing.
|
||||
|
||||
ed.RPrompt = constPrompt{styled.Plain("R")}
|
||||
ed.Config = testConfig{rprompt: constPrompt{styled.Plain("R")}}
|
||||
tty.Inject(term.KeyEvent{Rune: 'a'})
|
||||
|
||||
codeCh, _ := ed.readCodeAsync()
|
||||
|
@ -217,7 +239,7 @@ func TestReadCode_TriggersPrompt(t *testing.T) {
|
|||
ed, tty, _ := setup()
|
||||
|
||||
called := 0
|
||||
ed.Prompt = fakePrompt{trigger: func(bool) { called++ }}
|
||||
ed.Config = testConfig{prompt: fakePrompt{trigger: func(bool) { called++ }}}
|
||||
|
||||
codeCh, _ := ed.readCodeAsync()
|
||||
cleanup(tty, codeCh)
|
||||
|
@ -235,7 +257,7 @@ func TestReadCode_RedrawsOnPromptLateUpdate(t *testing.T) {
|
|||
get: func() styled.Text { return styled.Plain(promptContent) },
|
||||
lateUpdates: make(chan styled.Text),
|
||||
}
|
||||
ed.Prompt = prompt
|
||||
ed.Config = testConfig{prompt: prompt}
|
||||
|
||||
codeCh, _ := ed.readCodeAsync()
|
||||
bufOldPrompt := ui.NewBufferBuilder(80).
|
||||
|
|
44
cli/clicore/config.go
Normal file
44
cli/clicore/config.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package clicore
|
||||
|
||||
import clitypes "github.com/elves/elvish/cli/clitypes"
|
||||
|
||||
// Config is the configuration for an App.
|
||||
type Config interface {
|
||||
MaxHeight() int
|
||||
RPromptPersistent() bool
|
||||
BeforeReadline()
|
||||
AfterReadline(string)
|
||||
Highlighter() Highlighter
|
||||
Prompt() Prompt
|
||||
RPrompt() Prompt
|
||||
InitMode() clitypes.Mode
|
||||
}
|
||||
|
||||
// DefaultConfig implements the Config interface, providing sensible default
|
||||
// behavior. Other implementations of Config cam embed this struct and only
|
||||
// implement the methods that it needs.
|
||||
type DefaultConfig struct{}
|
||||
|
||||
// MaxHeight returns -1.
|
||||
func (DefaultConfig) MaxHeight() int { return -1 }
|
||||
|
||||
// RPromptPersistent returns false.
|
||||
func (DefaultConfig) RPromptPersistent() bool { return false }
|
||||
|
||||
// BeforeReadline does nothing.
|
||||
func (DefaultConfig) BeforeReadline() {}
|
||||
|
||||
// AfterReadline does nothing.
|
||||
func (DefaultConfig) AfterReadline(string) {}
|
||||
|
||||
// Highlighter returns nil.
|
||||
func (DefaultConfig) Highlighter() Highlighter { return nil }
|
||||
|
||||
// Prompt returns nil.
|
||||
func (DefaultConfig) Prompt() Prompt { return nil }
|
||||
|
||||
// RPrompt returns nil.
|
||||
func (DefaultConfig) RPrompt() Prompt { return nil }
|
||||
|
||||
// InitMode returns nil.
|
||||
func (DefaultConfig) InitMode() clitypes.Mode { return nil }
|
|
@ -1,22 +1,69 @@
|
|||
package cli
|
||||
|
||||
import "sync"
|
||||
import (
|
||||
"github.com/elves/elvish/cli/clitypes"
|
||||
"github.com/elves/elvish/cli/histutil"
|
||||
)
|
||||
|
||||
// Adaps App to implement the clicore.Config interface.
|
||||
type coreConfig struct {
|
||||
mu sync.Mutex
|
||||
|
||||
maxHeight int
|
||||
rpromptPersistent bool
|
||||
*App
|
||||
}
|
||||
|
||||
func (cc *coreConfig) MaxHeight() int {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
return cc.maxHeight
|
||||
func (cc coreConfig) MaxHeight() (ret int) {
|
||||
cc.cfg.Mutex.Lock()
|
||||
defer cc.cfg.Mutex.Unlock()
|
||||
return cc.cfg.MaxHeight
|
||||
}
|
||||
|
||||
func (cc *coreConfig) RPromptPersistent() bool {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
return cc.rpromptPersistent
|
||||
func (cc coreConfig) RPromptPersistent() bool {
|
||||
cc.cfg.Mutex.Lock()
|
||||
defer cc.cfg.Mutex.Unlock()
|
||||
return cc.cfg.RPromptPersistent
|
||||
}
|
||||
|
||||
func (cc coreConfig) BeforeReadline() {
|
||||
cc.cfg.Mutex.Lock()
|
||||
defer cc.cfg.Mutex.Unlock()
|
||||
for _, f := range cc.cfg.BeforeReadline {
|
||||
f()
|
||||
}
|
||||
}
|
||||
|
||||
func (cc coreConfig) AfterReadline(code string) {
|
||||
cc.cfg.Mutex.Lock()
|
||||
defer cc.cfg.Mutex.Unlock()
|
||||
for _, f := range cc.cfg.AfterReadline {
|
||||
f(code)
|
||||
}
|
||||
if cc.cfg.HistoryStore != nil {
|
||||
_, err := cc.cfg.HistoryStore.AddCmd(histutil.Entry{Text: code})
|
||||
if err != nil {
|
||||
cc.core.Notify("db error: " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cc coreConfig) Highlighter() Highlighter {
|
||||
cc.cfg.Mutex.Lock()
|
||||
defer cc.cfg.Mutex.Unlock()
|
||||
return cc.cfg.Highlighter
|
||||
}
|
||||
|
||||
func (cc coreConfig) Prompt() Prompt {
|
||||
cc.cfg.Mutex.Lock()
|
||||
defer cc.cfg.Mutex.Unlock()
|
||||
return cc.cfg.Prompt
|
||||
}
|
||||
|
||||
func (cc coreConfig) RPrompt() Prompt {
|
||||
cc.cfg.Mutex.Lock()
|
||||
defer cc.cfg.Mutex.Unlock()
|
||||
return cc.cfg.RPrompt
|
||||
}
|
||||
|
||||
func (cc coreConfig) InitMode() clitypes.Mode {
|
||||
cc.cfg.Mutex.Lock()
|
||||
defer cc.cfg.Mutex.Unlock()
|
||||
return cc.Insert
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user