mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-13 09:57:51 +08:00
4b4726b9a6
The main benefits of this change are: 1) It uses a hermetic "home" directory with a known command and location history. Which means it no longer depends on the interactive history and directory layout of the person creating the ttyshot. Which also means it no longer leaks the private history of anyone creating a ttyshot. This produces reproducible results when updating ttyshots. 2) The user no longer has to augment the ttyshot by manually adding the output of the commands to the generated HTML file. A process that is error prone. The output of the commands that generate the ttyshot is now captured and automatically included in the resulting HTML. 3) It makes it trivial to recreate every ttyshot. Simply execute these commands: ``` make ttyshot for f [website/ttyshot/**.spec] { put $f; ./ttyshot $f } ``` 4) It makes it easy to introduce new "ttyshot" images by creating a shell session "spec" file. This makes it easy to replace the existing "```elvish-transcript...```" examples with ttyshots in order to ensure a consistent representation and visual consistency with the transcripts that are currently generated as ttyshots. The downside of this change is the introduction of a dependency on the Tmux application. But that seems reasonable since Tmux is a mature application available on Linux, macOS, BSD, and probably every other UNIX like OS we care about. Note that generating the Elvish documentation already depends on similar apps such as Pandoc. Related #1459
117 lines
2.3 KiB
Go
117 lines
2.3 KiB
Go
package edit
|
|
|
|
import (
|
|
"bufio"
|
|
"io"
|
|
"os"
|
|
"sync"
|
|
|
|
"src.elv.sh/pkg/cli/term"
|
|
"src.elv.sh/pkg/cli/tk"
|
|
"src.elv.sh/pkg/eval"
|
|
"src.elv.sh/pkg/eval/vals"
|
|
"src.elv.sh/pkg/eval/vars"
|
|
"src.elv.sh/pkg/parse"
|
|
"src.elv.sh/pkg/ui"
|
|
)
|
|
|
|
type mapBindings struct {
|
|
nt notifier
|
|
ev *eval.Evaler
|
|
mapVars []vars.PtrVar
|
|
}
|
|
|
|
func newMapBindings(nt notifier, ev *eval.Evaler, mapVars ...vars.PtrVar) tk.Bindings {
|
|
return mapBindings{nt, ev, mapVars}
|
|
}
|
|
|
|
func (b mapBindings) Handle(w tk.Widget, e term.Event) bool {
|
|
k, ok := e.(term.KeyEvent)
|
|
if !ok {
|
|
return false
|
|
}
|
|
maps := make([]bindingsMap, len(b.mapVars))
|
|
for i, v := range b.mapVars {
|
|
maps[i] = v.GetRaw().(bindingsMap)
|
|
}
|
|
f := indexLayeredBindings(ui.Key(k), maps...)
|
|
if f == nil {
|
|
return false
|
|
}
|
|
callWithNotifyPorts(b.nt, b.ev, f)
|
|
return true
|
|
}
|
|
|
|
// Indexes a series of layered bindings. Returns nil if none of the bindings
|
|
// have the required key or a default.
|
|
func indexLayeredBindings(k ui.Key, maps ...bindingsMap) eval.Callable {
|
|
for _, m := range maps {
|
|
if m.HasKey(k) {
|
|
return m.GetKey(k)
|
|
}
|
|
}
|
|
for _, m := range maps {
|
|
if m.HasKey(ui.DefaultKey) {
|
|
return m.GetKey(ui.DefaultKey)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var bindingSource = parse.Source{Name: "[editor binding]"}
|
|
|
|
func callWithNotifyPorts(nt notifier, ev *eval.Evaler, f eval.Callable, args ...any) {
|
|
notifyPort, cleanup := makeNotifyPort(nt)
|
|
defer cleanup()
|
|
|
|
err := ev.Call(f,
|
|
eval.CallCfg{Args: args, From: "[editor binding]"},
|
|
eval.EvalCfg{Ports: []*eval.Port{nil, notifyPort, notifyPort}})
|
|
if err != nil {
|
|
nt.notifyError("binding", err)
|
|
}
|
|
}
|
|
|
|
func makeNotifyPort(nt notifier) (*eval.Port, func()) {
|
|
ch := make(chan any)
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
var wg sync.WaitGroup
|
|
wg.Add(2)
|
|
go func() {
|
|
// Relay value outputs
|
|
for v := range ch {
|
|
nt.notifyf("[value out] %s", vals.ReprPlain(v))
|
|
}
|
|
wg.Done()
|
|
}()
|
|
go func() {
|
|
// Relay byte outputs
|
|
reader := bufio.NewReader(r)
|
|
for {
|
|
line, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
if line != "" {
|
|
nt.notifyf("[bytes out] %s", line)
|
|
}
|
|
if err != io.EOF {
|
|
nt.notifyf("[bytes error] %s", err)
|
|
}
|
|
break
|
|
}
|
|
nt.notifyf("[bytes out] %s", line[:len(line)-1])
|
|
}
|
|
r.Close()
|
|
wg.Done()
|
|
}()
|
|
port := &eval.Port{Chan: ch, File: w}
|
|
cleanup := func() {
|
|
close(ch)
|
|
w.Close()
|
|
wg.Wait()
|
|
}
|
|
return port, cleanup
|
|
}
|