From 090b5a8e5af34ea21f5fc8ece93ee9cb15ab8252 Mon Sep 17 00:00:00 2001 From: Caleb Foust Date: Thu, 25 Jul 2024 16:44:46 +0800 Subject: [PATCH] fix: clearer execution context --- docs/src/configuration.md | 13 ++++++++++++- pkg/cy/api/input.go | 17 ++++++++--------- pkg/cy/api/key.go | 10 +++++----- pkg/cy/api/module.go | 13 +++++++++++++ pkg/cy/api/msg.go | 6 +++--- pkg/cy/api/pane.go | 20 +++++++++----------- pkg/cy/api/params.go | 16 +++++++--------- pkg/cy/api/replay.go | 6 +++--- pkg/cy/api/viewport.go | 33 +++++++++++++++++++-------------- 9 files changed, 79 insertions(+), 55 deletions(-) diff --git a/docs/src/configuration.md b/docs/src/configuration.md index 3c76a5ac..a90deead 100644 --- a/docs/src/configuration.md +++ b/docs/src/configuration.md @@ -2,7 +2,7 @@ `cy` was designed to be as configurable as possible. Users of `cy` provide a configuration written in [Janet](https://janet-lang.org/) that uses `cy`'s [API](/api.md) to define key bindings, set parameters, and set up `cy` in any way they like. -Janet is a fun, embeddable [Lisp-like](https://en.wikipedia.org/wiki/Lisp_(programming_language) language that is easy to learn. If you are new to Janet, I recommend starting out with its [documentation](https://janet-lang.org/docs/syntax.html) and Ian Henry's fantastic [_Janet for Mortals_](https://janet.guide/). +Janet is a fun, embeddable [Lisp-like](https://en.wikipedia.org/wiki/Lisp_(programming_language)) language that is easy to learn. If you are new to Janet, I recommend starting out with its [documentation](https://janet-lang.org/docs/syntax.html) and Ian Henry's fantastic [_Janet for Mortals_](https://janet.guide/). Janet looks like this: @@ -49,3 +49,14 @@ An example configuration that uses functionality from this API is shown below. Y # Bind a key sequence to this function (key/bind :root ["ctrl+a" "g"] toast-pane-path) ``` + +### Execution context + +The Janet code executed in `cy` can be executed in two different contexts: + +- **The context of the server:** This is the context in which your configuration file is run, because when the `cy` server starts up, there are no clients. +- **The context of a client:** When a client invokes an [action](/keybindings.md#actions) or types a keybinding sequence, the executed Janet code can see what client ran the action and respond appropriately. + +Because of this, _some API functionality can only be used in a keybinding or action._ This is because some kinds of state, such as the state that `(viewport/*)` family of functions uses to work, only makes sense in the context of a user. + +This is a confusing aspect of `cy` that I plan to improve over time, such as by letting you execute custom code in the context of a client whenever one connects. diff --git a/pkg/cy/api/input.go b/pkg/cy/api/input.go index d0d239b0..52da4647 100644 --- a/pkg/cy/api/input.go +++ b/pkg/cy/api/input.go @@ -2,7 +2,6 @@ package api import ( "context" - "fmt" "math/rand" "github.com/cfoust/cy/pkg/anim" @@ -32,7 +31,7 @@ type FuzzyParams struct { func (i *InputModule) Find( ctx context.Context, - user interface{}, + context interface{}, choices *janet.Value, named *janet.Named[FuzzyParams], ) (interface{}, error) { @@ -40,9 +39,9 @@ func (i *InputModule) Find( params := named.Values() - client, ok := user.(Client) - if !ok { - return nil, fmt.Errorf("missing client context") + client, err := getClient(context) + if err != nil { + return nil, err } options, err := fuzzy.UnmarshalOptions(choices) @@ -154,15 +153,15 @@ type TextParams struct { func (i *InputModule) Text( ctx context.Context, - user interface{}, + context interface{}, prompt string, named *janet.Named[TextParams], ) (interface{}, error) { params := named.Values() - client, ok := user.(Client) - if !ok { - return nil, fmt.Errorf("missing client context") + client, err := getClient(context) + if err != nil { + return nil, err } outerLayers := client.OuterLayers() diff --git a/pkg/cy/api/key.go b/pkg/cy/api/key.go index 7f9de30f..b72fe4bc 100644 --- a/pkg/cy/api/key.go +++ b/pkg/cy/api/key.go @@ -187,11 +187,11 @@ func (k *KeyModule) Get(target *janet.Value) ([]Binding, error) { return binds, nil } -func (k *KeyModule) Current(user interface{}) []Binding { - client, ok := user.(Client) - if !ok { - return nil +func (k *KeyModule) Current(context interface{}) ([]Binding, error) { + client, err := getClient(context) + if err != nil { + return nil, err } - return client.Binds() + return client.Binds(), nil } diff --git a/pkg/cy/api/module.go b/pkg/cy/api/module.go index 2108f4b7..7ac37783 100644 --- a/pkg/cy/api/module.go +++ b/pkg/cy/api/module.go @@ -1,6 +1,8 @@ package api import ( + "fmt" + "github.com/cfoust/cy/pkg/frames" "github.com/cfoust/cy/pkg/mux/screen" "github.com/cfoust/cy/pkg/mux/screen/toasts" @@ -28,3 +30,14 @@ type Server interface { ExecuteJanet(path string) error Log(level zerolog.Level, message string) } + +func getClient(context interface{}) (Client, error) { + client, ok := context.(Client) + if !ok { + return nil, fmt.Errorf( + "this functionality can only be accessed in a keybinding or action, see https://cfoust.github.io/cy/configuration.html#execution-context for more information", + ) + } + + return client, nil +} diff --git a/pkg/cy/api/msg.go b/pkg/cy/api/msg.go index 15b4202e..29d5865e 100644 --- a/pkg/cy/api/msg.go +++ b/pkg/cy/api/msg.go @@ -46,9 +46,9 @@ func (m *MsgModule) Toast(context interface{}, level *janet.Value, message strin return err } - client, ok := context.(Client) - if !ok { - return fmt.Errorf("unable to detect client") + client, err := getClient(context) + if err != nil { + return err } client.Toast(toasts.Toast{ diff --git a/pkg/cy/api/pane.go b/pkg/cy/api/pane.go index 114062b8..fdee9d8c 100644 --- a/pkg/cy/api/pane.go +++ b/pkg/cy/api/pane.go @@ -1,8 +1,6 @@ package api import ( - "fmt" - "github.com/cfoust/cy/pkg/janet" "github.com/cfoust/cy/pkg/mux/screen/tree" ) @@ -14,9 +12,9 @@ type PaneModule struct { func (p *PaneModule) Attach(context interface{}, id *janet.Value) error { defer id.Free() - client, ok := context.(Client) - if !ok { - return fmt.Errorf("missing client context") + client, err := getClient(context) + if err != nil { + return err } pane, err := resolvePane(p.Tree, id) @@ -28,18 +26,18 @@ func (p *PaneModule) Attach(context interface{}, id *janet.Value) error { } func (p *PaneModule) HistoryForward(context interface{}) error { - client, ok := context.(Client) - if !ok { - return fmt.Errorf("missing client context") + client, err := getClient(context) + if err != nil { + return err } return client.HistoryForward() } func (p *PaneModule) HistoryBackward(context interface{}) error { - client, ok := context.(Client) - if !ok { - return fmt.Errorf("missing client context") + client, err := getClient(context) + if err != nil { + return err } return client.HistoryBackward() diff --git a/pkg/cy/api/params.go b/pkg/cy/api/params.go index f040336c..6bf38bbb 100644 --- a/pkg/cy/api/params.go +++ b/pkg/cy/api/params.go @@ -1,8 +1,6 @@ package api import ( - "fmt" - "github.com/cfoust/cy/pkg/janet" "github.com/cfoust/cy/pkg/mux/screen/tree" "github.com/cfoust/cy/pkg/params" @@ -43,9 +41,9 @@ func (p *ParamModule) Get( } if params.Target == nil || isClientTarget(params.Target) { - client, ok := context.(Client) - if !ok { - return nil, fmt.Errorf("missing client context") + client, err := getClient(context) + if err != nil { + return nil, err } value, _ := client.Get(string(keyword)) @@ -78,11 +76,11 @@ func (p *ParamModule) Set( var params *params.Parameters if isClientTarget(target) { - if client, ok := context.(Client); ok { - params = client.Params() - } else { - return fmt.Errorf("missing client context") + client, err := getClient(context) + if err != nil { + return err } + params = client.Params() } else { node, err := resolveNode(p.Tree, target) if err != nil { diff --git a/pkg/cy/api/replay.go b/pkg/cy/api/replay.go index bba69391..bad2f5e5 100644 --- a/pkg/cy/api/replay.go +++ b/pkg/cy/api/replay.go @@ -23,9 +23,9 @@ type ReplayModule struct { } func (m *ReplayModule) send(context interface{}, msg taro.Msg) error { - client, ok := context.(Client) - if !ok { - return fmt.Errorf("no client could be inferred") + client, err := getClient(context) + if err != nil { + return err } node := client.Node() diff --git a/pkg/cy/api/viewport.go b/pkg/cy/api/viewport.go index 3de7aa8d..60fcf2a3 100644 --- a/pkg/cy/api/viewport.go +++ b/pkg/cy/api/viewport.go @@ -1,6 +1,8 @@ package api import ( + "fmt" + "github.com/cfoust/cy/pkg/anim" "github.com/cfoust/cy/pkg/frames" "github.com/cfoust/cy/pkg/geom" @@ -9,34 +11,37 @@ import ( type ViewportModule struct { } -func (c *ViewportModule) Size(context interface{}) *geom.Vec2 { - client, ok := context.(Client) - if !ok { - return nil +func (c *ViewportModule) Size(context interface{}) (*geom.Vec2, error) { + client, err := getClient(context) + if err != nil { + return nil, err } + size := client.Margins().Size() - return &size + return &size, nil } -func (c *ViewportModule) SetSize(context interface{}, size geom.Size) { - client, ok := context.(Client) - if !ok { - return +func (c *ViewportModule) SetSize(context interface{}, size geom.Size) error { + client, err := getClient(context) + if err != nil { + return err } client.Margins().SetSize(size) + return nil } -func (c *ViewportModule) SetFrame(context interface{}, name string) { - client, ok := context.(Client) - if !ok { - return +func (c *ViewportModule) SetFrame(context interface{}, name string) error { + client, err := getClient(context) + if err != nil { + return err } frame, ok := frames.Frames[name] if !ok { - return + return fmt.Errorf("could not find frame '%s'", name) } client.Frame().Set(frame) + return nil } func (c *ViewportModule) GetFrames(context interface{}) []string {