package cli
import (
)
type App interface {
ReadCode() (string, error)
MutateState(f func(*State))
CopyState() State
CodeArea() tk.CodeArea
SetAddon(w tk.Widget, accept bool)
CommitEOF()
CommitCode()
Redraw()
RedrawFull()
Notify(note string)
}
type app struct {
loop *loop
reqRead chan struct{}
TTY TTY
MaxHeight func() int
RPromptPersistent func() bool
BeforeReadline []func()
AfterReadline []func(string)
Highlighter Highlighter
Prompt Prompt
RPrompt Prompt
GlobalBindings tk.Bindings
StateMutex sync.RWMutex
State State
codeArea tk.CodeArea
}
type State struct {
Notes []string
Addon tk.Widget
}
func ( AppSpec) App {
:= newLoop()
:= app{
loop: ,
TTY: .TTY,
MaxHeight: .MaxHeight,
RPromptPersistent: .RPromptPersistent,
BeforeReadline: .BeforeReadline,
AfterReadline: .AfterReadline,
Highlighter: .Highlighter,
Prompt: .Prompt,
RPrompt: .RPrompt,
GlobalBindings: .GlobalBindings,
State: .State,
}
if .TTY == nil {
.TTY = NewTTY(os.Stdin, os.Stderr)
}
if .MaxHeight == nil {
.MaxHeight = func() int { return -1 }
}
if .RPromptPersistent == nil {
.RPromptPersistent = func() bool { return false }
}
if .Highlighter == nil {
.Highlighter = dummyHighlighter{}
}
if .Prompt == nil {
.Prompt = NewConstPrompt(nil)
}
if .RPrompt == nil {
.RPrompt = NewConstPrompt(nil)
}
if .GlobalBindings == nil {
.GlobalBindings = tk.DummyBindings{}
}
.HandleCb(.handle)
.RedrawCb(.redraw)
.codeArea = tk.NewCodeArea(tk.CodeAreaSpec{
Bindings: .CodeAreaBindings,
Highlighter: .Highlighter.Get,
Prompt: .Prompt.Get,
RPrompt: .RPrompt.Get,
Abbreviations: .Abbreviations,
QuotePaste: .QuotePaste,
OnSubmit: .CommitCode,
State: .CodeAreaState,
SmallWordAbbreviations: .SmallWordAbbreviations,
})
return &
}
func ( *app) ( func(*State)) {
.StateMutex.Lock()
defer .StateMutex.Unlock()
(&.State)
}
func ( *app) () State {
.StateMutex.RLock()
defer .StateMutex.RUnlock()
return .State
}
func ( *app) () tk.CodeArea {
return .codeArea
}
type closer interface {
Close(bool)
}
func ( *app) ( tk.Widget, bool) {
.StateMutex.Lock()
defer .StateMutex.Unlock()
if , := .State.Addon.(closer); {
.Close()
}
.State.Addon =
}
func ( *app) () {
.MutateState(func( *State) { * = State{} })
.codeArea.MutateState(
func( *tk.CodeAreaState) { * = tk.CodeAreaState{} })
}
func ( *app) ( event) {
switch e := .(type) {
case os.Signal:
switch {
case syscall.SIGHUP:
.loop.Return("", io.EOF)
case syscall.SIGINT:
.resetAllStates()
.triggerPrompts(true)
case sys.SIGWINCH:
.RedrawFull()
}
case term.Event:
var tk.Widget
if := .CopyState().Addon; != nil {
=
} else {
= .codeArea
}
:= .Handle()
if ! {
.GlobalBindings.Handle(, )
}
if !.loop.HasReturned() {
.triggerPrompts(false)
.reqRead <- struct{}{}
}
}
}
func ( *app) ( bool) {
.Prompt.Trigger()
.RPrompt.Trigger()
}
func ( *app) ( redrawFlag) {
, := .TTY.Size()
if := .MaxHeight(); > 0 && < {
=
}
var []string
var tk.Renderer
.MutateState(func( *State) {
, = .Notes, .Addon
.Notes = nil
})
:= renderNotes(, )
:= &finalRedraw != 0
if {
:= !.RPromptPersistent()
if {
.codeArea.MutateState(func( *tk.CodeAreaState) { .HideRPrompt = true })
}
:= renderApp(.codeArea, nil , , )
if {
.codeArea.MutateState(func( *tk.CodeAreaState) { .HideRPrompt = false })
}
.Extend(term.NewBuffer(), true)
.TTY.UpdateBuffer(, , &fullRedraw != 0)
.TTY.ResetBuffer()
} else {
:= renderApp(.codeArea, , , )
.TTY.UpdateBuffer(, , &fullRedraw != 0)
}
}
func ( []string, int) *term.Buffer {
if len() == 0 {
return nil
}
:= term.NewBufferBuilder()
for , := range {
if > 0 {
.Newline()
}
.Write()
}
return .Buffer()
}
type focuser interface {
Focus() bool
}
func (, tk.Renderer, , int) *term.Buffer {
:= .Render(, )
if != nil && len(.Lines) < {
:= .Render(, -len(.Lines))
:= true
if , := .(focuser); {
= .Focus()
}
.Extend(, )
}
return
}
func ( *app) () (string, error) {
for , := range .BeforeReadline {
()
}
defer func() {
:= .codeArea.CopyState().Buffer.Content
for , := range .AfterReadline {
()
}
.resetAllStates()
}()
, := .TTY.Setup()
if != nil {
return "",
}
defer ()
var sync.WaitGroup
defer .Wait()
.reqRead = make(chan struct{}, 1)
.reqRead <- struct{}{}
defer close(.reqRead)
defer .TTY.CloseReader()
.Add(1)
go func() {
defer .Done()
for range .reqRead {
, := .TTY.ReadEvent()
if == nil {
.loop.Input()
} else if == term.ErrStopped {
return
} else if term.IsReadErrorRecoverable() {
.loop.Input(term.NonfatalErrorEvent{Err: })
} else {
.loop.Input(term.FatalErrorEvent{Err: })
return
}
}
}()
:= .TTY.NotifySignals()
defer .TTY.StopSignals()
.Add(1)
go func() {
for := range {
.loop.Input()
}
.Done()
}()
:= make(chan struct{})
defer close()
:= func( <-chan struct{}) {
if == nil {
return
}
.Add(1)
go func() {
defer .Done()
for {
select {
case <-:
.Redraw()
case <-:
return
}
}
}()
}
(.Prompt.LateUpdates())
(.RPrompt.LateUpdates())
(.Highlighter.LateUpdates())
.triggerPrompts(true)
return .loop.Run()
}
func ( *app) () {
.loop.Redraw(false)
}
func ( *app) () {
.loop.Redraw(true)
}
func ( *app) () {
.loop.Return("", io.EOF)
}
func ( *app) () {
:= .codeArea.CopyState().Buffer.Content
.loop.Return(, nil)
}
func ( *app) ( string) {
.MutateState(func( *State) { .Notes = append(.Notes, ) })
.Redraw()
}