mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-13 09:57:51 +08:00
cli: Provide a Go-friendly API.
Eventually, the newedit package will become just an Elvish binding of this package.
This commit is contained in:
parent
6c4f100b83
commit
726ce887a0
|
@ -4,12 +4,37 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"unicode"
|
||||
|
||||
"github.com/elves/elvish/cli/clicore"
|
||||
"github.com/elves/elvish/cli"
|
||||
"github.com/elves/elvish/edit/ui"
|
||||
"github.com/elves/elvish/styled"
|
||||
)
|
||||
|
||||
func highlight(code string) styled.Text {
|
||||
t := styled.Text{}
|
||||
for _, r := range code {
|
||||
style := ""
|
||||
if unicode.IsDigit(r) {
|
||||
style = "green"
|
||||
}
|
||||
t = append(t, styled.MakeText(string(r), style)...)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := clicore.NewAppFromStdIO()
|
||||
app := cli.NewApp(&cli.AppConfig{
|
||||
Prompt: cli.ConstPlainPrompt("> "),
|
||||
Highlighter: cli.FuncHighlighterNoError(highlight),
|
||||
InsertConfig: cli.InsertModeConfig{
|
||||
Binding: cli.MapBinding(map[ui.Key]cli.KeyHandler{
|
||||
ui.K('D', ui.Ctrl): cli.CommitEOF,
|
||||
ui.Default: cli.DefaultInsert,
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
for {
|
||||
code, err := app.ReadCode()
|
||||
if err != nil {
|
||||
|
@ -19,9 +44,5 @@ func main() {
|
|||
break
|
||||
}
|
||||
fmt.Println("got:", code)
|
||||
if code == "exit" {
|
||||
fmt.Println("bye")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
40
cli/app.go
Normal file
40
cli/app.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"github.com/elves/elvish/cli/clicore"
|
||||
)
|
||||
|
||||
// App represents a CLI app.
|
||||
type App = clicore.App
|
||||
|
||||
// AppConfig is a struct containing configurations for initializing an App.
|
||||
type AppConfig struct {
|
||||
MaxHeight int
|
||||
|
||||
BeforeReadline []func()
|
||||
AfterReadline []func(string)
|
||||
|
||||
Highlighter Highlighter
|
||||
Prompt, RPrompt Prompt
|
||||
RPromptPersistent bool
|
||||
|
||||
InsertConfig InsertModeConfig
|
||||
}
|
||||
|
||||
// NewApp creates a new App.
|
||||
func NewApp(cfg *AppConfig) *App {
|
||||
app := clicore.NewAppFromStdIO()
|
||||
app.Config.Raw = clicore.RawConfig{
|
||||
MaxHeight: cfg.MaxHeight,
|
||||
RPromptPersistent: cfg.RPromptPersistent,
|
||||
}
|
||||
app.BeforeReadline = cfg.BeforeReadline
|
||||
app.AfterReadline = cfg.AfterReadline
|
||||
app.Highlighter = cfg.Highlighter
|
||||
app.Prompt = cfg.Prompt
|
||||
app.RPrompt = cfg.RPrompt
|
||||
|
||||
insertMode := newInsertMode(&cfg.InsertConfig, app.State())
|
||||
app.InitMode = insertMode
|
||||
return app
|
||||
}
|
82
cli/binding.go
Normal file
82
cli/binding.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"github.com/elves/elvish/cli/clitypes"
|
||||
"github.com/elves/elvish/edit/ui"
|
||||
)
|
||||
|
||||
// Binding represents key binding.
|
||||
type Binding interface {
|
||||
// KeyHandler returns a KeyHandler for the given key.
|
||||
KeyHandler(ui.Key) KeyHandler
|
||||
}
|
||||
|
||||
// KeyHandler is a function that can handle a key event.
|
||||
type KeyHandler func(KeyEvent)
|
||||
|
||||
// KeyEvent is passed to a KeyHandler, containing information about the event
|
||||
// and can be used for specifying actions.
|
||||
type KeyEvent interface {
|
||||
// Key returns the key that triggered the KeyEvent.
|
||||
Key() ui.Key
|
||||
// State returns the State of the app.
|
||||
State() *clitypes.State
|
||||
|
||||
// CommitEOF specifies that the app should return from ReadCode with io.EOF
|
||||
// after the key handler returns.
|
||||
CommitEOF()
|
||||
// CommitCode specifies that the app should return from ReadCode after the
|
||||
// key handler returns.
|
||||
CommitCode()
|
||||
}
|
||||
|
||||
// Internal implementation of KeyHandler interface.
|
||||
type keyEvent struct {
|
||||
key ui.Key
|
||||
state *clitypes.State
|
||||
commitEOF bool
|
||||
commitLine bool
|
||||
}
|
||||
|
||||
func (ev *keyEvent) Key() ui.Key { return ev.key }
|
||||
func (ev *keyEvent) State() *clitypes.State { return ev.state }
|
||||
func (ev *keyEvent) CommitEOF() { ev.commitEOF = true }
|
||||
func (ev *keyEvent) CommitCode() { ev.commitLine = true }
|
||||
|
||||
// MapBinding builds a Binding from a map. The map may contain the special
|
||||
// key ui.Default for a default KeyHandler.
|
||||
func MapBinding(m map[ui.Key]KeyHandler) Binding {
|
||||
return mapBinding(m)
|
||||
}
|
||||
|
||||
type mapBinding map[ui.Key]KeyHandler
|
||||
|
||||
func (b mapBinding) KeyHandler(k ui.Key) KeyHandler {
|
||||
handler, ok := b[k]
|
||||
if ok {
|
||||
return handler
|
||||
}
|
||||
return b[ui.Default]
|
||||
}
|
||||
|
||||
func adaptBinding(b Binding, st *clitypes.State) func(ui.Key) clitypes.HandlerAction {
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
return func(k ui.Key) clitypes.HandlerAction {
|
||||
ev := &keyEvent{k, st, false, false}
|
||||
handler := b.KeyHandler(k)
|
||||
if handler == nil {
|
||||
return clitypes.NoAction
|
||||
}
|
||||
handler(ev)
|
||||
switch {
|
||||
case ev.commitEOF:
|
||||
return clitypes.CommitEOF
|
||||
case ev.commitLine:
|
||||
return clitypes.CommitCode
|
||||
default:
|
||||
return clitypes.NoAction
|
||||
}
|
||||
}
|
||||
}
|
25
cli/builtin_handlers.go
Normal file
25
cli/builtin_handlers.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"github.com/elves/elvish/cli/clitypes"
|
||||
"github.com/elves/elvish/cli/cliutil"
|
||||
"github.com/elves/elvish/edit/tty"
|
||||
)
|
||||
|
||||
// CommitEOF is an EventHandler that calls CommitEOF.
|
||||
var CommitEOF = KeyEvent.CommitEOF
|
||||
|
||||
// CommitCode is an EventHandler that calls CommitCode.
|
||||
var CommitCode = KeyEvent.CommitCode
|
||||
|
||||
// DefaultInsert is an EventHandler that is suitable as the default EventHandler
|
||||
// of insert mode.
|
||||
func DefaultInsert(ev KeyEvent) {
|
||||
action := cliutil.BasicHandler(tty.KeyEvent(ev.Key()), ev.State())
|
||||
switch action {
|
||||
case clitypes.CommitCode:
|
||||
ev.CommitCode()
|
||||
case clitypes.CommitEOF:
|
||||
ev.CommitEOF()
|
||||
}
|
||||
}
|
8
cli/doc.go
Normal file
8
cli/doc.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
// Package cli provides the facility for building a readline-like CLI with very
|
||||
// rich features.
|
||||
//
|
||||
// The current implementation involves a lot of wrappers and additional
|
||||
// utilities, to avoid breaking the newedit package. Eventually, those changes
|
||||
// will be moved upstream and the newedit package will be an Elvish binding for
|
||||
// this package.
|
||||
package cli
|
35
cli/highlighter.go
Normal file
35
cli/highlighter.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"github.com/elves/elvish/cli/clicore"
|
||||
"github.com/elves/elvish/styled"
|
||||
)
|
||||
|
||||
// Highlighter represents a highlighter.
|
||||
type Highlighter = clicore.Highlighter
|
||||
|
||||
// FuncHighlighter builds a Highlighter from a function that takes the code and
|
||||
// returns styled text and a slice of errors.
|
||||
func FuncHighlighter(f func(string) (styled.Text, []error)) Highlighter {
|
||||
return funcHighlighter{f}
|
||||
}
|
||||
|
||||
// FuncHighlighterNoError builds a Highlighter from a function that takes the
|
||||
// code and returns styled text.
|
||||
func FuncHighlighterNoError(f func(string) styled.Text) Highlighter {
|
||||
return funcHighlighter{func(code string) (styled.Text, []error) {
|
||||
return f(code), nil
|
||||
}}
|
||||
}
|
||||
|
||||
type funcHighlighter struct {
|
||||
f func(string) (styled.Text, []error)
|
||||
}
|
||||
|
||||
func (hl funcHighlighter) Get(code string) (styled.Text, []error) {
|
||||
return hl.f(code)
|
||||
}
|
||||
|
||||
func (hl funcHighlighter) LateUpdates() <-chan styled.Text {
|
||||
return nil
|
||||
}
|
51
cli/insert_mode.go
Normal file
51
cli/insert_mode.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"github.com/elves/elvish/cli/clitypes"
|
||||
"github.com/elves/elvish/newedit/insert"
|
||||
)
|
||||
|
||||
// InsertModeConfig is a struct containing configuration for the insert mode.
|
||||
type InsertModeConfig struct {
|
||||
Binding Binding
|
||||
Abbrs StringPairs
|
||||
QuotePaste bool
|
||||
}
|
||||
|
||||
// StringPairs is a general interface for accessing pairs of strings.
|
||||
type StringPairs interface {
|
||||
IterateStringPairs(func(a, b string))
|
||||
}
|
||||
|
||||
// StringsPairsFromSlice builds a StringPairs from a slice.
|
||||
func StringsPairsFromSlice(s [][2]string) StringPairs {
|
||||
return sliceStringPairs(s)
|
||||
}
|
||||
|
||||
type sliceStringPairs [][2]string
|
||||
|
||||
func (s sliceStringPairs) IterateStringPairs(f func(abbr, full string)) {
|
||||
for _, a := range s {
|
||||
f(a[0], a[1])
|
||||
}
|
||||
}
|
||||
|
||||
func makeAbbrIterate(sp StringPairs) func(func(abbr, full string)) {
|
||||
if sp == nil {
|
||||
return nil
|
||||
}
|
||||
return sp.IterateStringPairs
|
||||
}
|
||||
|
||||
// Initializes an insert mode.
|
||||
func newInsertMode(cfg *InsertModeConfig, st *clitypes.State) clitypes.Mode {
|
||||
return &insert.Mode{
|
||||
KeyHandler: adaptBinding(cfg.Binding, st),
|
||||
AbbrIterate: makeAbbrIterate(cfg.Abbrs),
|
||||
Config: insert.Config{
|
||||
Raw: insert.RawConfig{
|
||||
QuotePaste: cfg.QuotePaste,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
42
cli/prompt.go
Normal file
42
cli/prompt.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"github.com/elves/elvish/cli/clicore"
|
||||
"github.com/elves/elvish/styled"
|
||||
)
|
||||
|
||||
// Prompt represents a prompt.
|
||||
type Prompt = clicore.Prompt
|
||||
|
||||
// ConstPrompt builds a styled Prompt that does not change.
|
||||
func ConstPrompt(t styled.Text) Prompt {
|
||||
return constPrompt{t}
|
||||
}
|
||||
|
||||
// ConstPlainPrompt builds a plain Prompt that does not change.
|
||||
func ConstPlainPrompt(s string) Prompt {
|
||||
return constPrompt{styled.Plain(s)}
|
||||
}
|
||||
|
||||
// FuncPrompt builds a styled Prompt from a function.
|
||||
func FuncPrompt(f func() styled.Text) Prompt {
|
||||
return funcPrompt{f}
|
||||
}
|
||||
|
||||
// FuncPlainPrompt builds a plain Prompt from a function.
|
||||
func FuncPlainPrompt(f func() string) Prompt {
|
||||
return funcPrompt{func() styled.Text { return styled.Plain(f()) }}
|
||||
}
|
||||
|
||||
// A Prompt implementation that always return the same styled.Text.
|
||||
type constPrompt struct{ t styled.Text }
|
||||
|
||||
func (constPrompt) Trigger(force bool) {}
|
||||
func (p constPrompt) Get() styled.Text { return p.t }
|
||||
func (constPrompt) LateUpdates() <-chan styled.Text { return nil }
|
||||
|
||||
type funcPrompt struct{ f func() styled.Text }
|
||||
|
||||
func (funcPrompt) Trigger(force bool) {}
|
||||
func (p funcPrompt) Get() styled.Text { return p.f() }
|
||||
func (funcPrompt) LateUpdates() <-chan styled.Text { return nil }
|
Loading…
Reference in New Issue
Block a user