diff --git a/kittens/resize_window/cli_generated.go b/kittens/resize_window/cli_generated.go new file mode 100644 index 000000000..411c6e9a3 --- /dev/null +++ b/kittens/resize_window/cli_generated.go @@ -0,0 +1,51 @@ +// Code generated by go_code.py; DO NOT EDIT. + +package resize_window +import "fmt" +import "github.com/kovidgoyal/kitty/tools/cli" +var _ = fmt.Sprintf +func create_cmd(root *cli.Command, run_func func(*cli.Command, *Options, []string)(int, error)) { +ans := root.AddSubCommand(&cli.Command{ +Name: "resize_window", +ShortDescription: "Resize the current window", +HelpText: "Resize the current window", +Run: func(cmd *cli.Command, args []string) (int, error) { +opts := Options{} +err := cmd.GetOptionValues(&opts) +if err != nil { return 1, err } +return run_func(cmd, &opts, args)}, +Hidden: true, +}) +ans.Add(cli.OptionSpec{ + Name: "--horizontal-increment", + Type: "int", + Dest: "HorizontalIncrement", + Help: "The base horizontal increment.", + + Default: "2", +}) +ans.Add(cli.OptionSpec{ + Name: "--vertical-increment", + Type: "int", + Dest: "VerticalIncrement", + Help: "The base vertical increment.", + + Default: "2", +}) +clone := root.AddClone(ans.Group, ans) +clone.Hidden = false +clone.Name = "resize-window" +} +type Options struct { +HorizontalIncrement int +VerticalIncrement int +} +func (opts Options) AsCommandLine() (ans []string) { + sval := "" + _ = sval + sval = fmt.Sprintf(`%d`, opts.HorizontalIncrement) + if (sval != `2`) { ans = append(ans, `--horizontal-increment=` + sval)} + sval = fmt.Sprintf(`%d`, opts.VerticalIncrement) + if (sval != `2`) { ans = append(ans, `--vertical-increment=` + sval)} +return +} diff --git a/kittens/resize_window/main.go b/kittens/resize_window/main.go new file mode 100644 index 000000000..2f11ac1d2 --- /dev/null +++ b/kittens/resize_window/main.go @@ -0,0 +1,200 @@ +// License: GPLv3 Copyright: 2026, Kovid Goyal, + +package resize_window + +import ( + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/kovidgoyal/kitty/tools/cli" + "github.com/kovidgoyal/kitty/tools/cli/markup" + "github.com/kovidgoyal/kitty/tools/tui" + "github.com/kovidgoyal/kitty/tools/tui/loop" +) + +var _ = fmt.Print + +type handler struct { + lp *loop.Loop + opts *Options + ctx *markup.Context + original_size loop.ScreenSize + print_on_fail string +} + +type rc_response struct { + Ok bool `json:"ok"` + Error string `json:"error"` + Tb string `json:"tb"` + Data json.RawMessage `json:"data"` +} + +func (h *handler) do_window_resize(is_decrease, is_horizontal, reset bool, multiplier int) error { + increment := h.opts.HorizontalIncrement + if !is_horizontal { + increment = h.opts.VerticalIncrement + } + increment *= multiplier + if is_decrease { + increment = -increment + } + axis := "reset" + if !reset { + if is_horizontal { + axis = "horizontal" + } else { + axis = "vertical" + } + } + ec, err := resize_command_escape_code(increment, axis) + if err != nil { + return err + } + h.lp.QueueWriteString(ec) + return nil +} + +// json_is_truthy mirrors Python's truthiness test (`if value:`) for a decoded +// JSON value, so on_rc_response's "beep if data is truthy" check matches the +// behavior of kittens/resize_window/main.py's on_kitty_cmd_response. The +// resize-window RC command returns bool | None | str for "data" (see +// kitty/rc/resize_window.py), so False/None/"" must all be treated as falsy. +func json_is_truthy(raw json.RawMessage) bool { + if len(raw) == 0 { + return false + } + var v any + if err := json.Unmarshal(raw, &v); err != nil { + return false + } + switch val := v.(type) { + case nil: + return false + case bool: + return val + case float64: + return val != 0 + case string: + return val != "" + case []any: + return len(val) != 0 + case map[string]any: + return len(val) != 0 + default: + return true + } +} + +func (h *handler) on_rc_response(raw []byte) error { + var response rc_response + if err := json.Unmarshal(raw, &response); err != nil { + return err + } + if !response.Ok { + emsg := response.Error + if response.Tb != "" { + emsg += "\n" + response.Tb + } + h.print_on_fail = emsg + h.lp.Quit(1) + return nil + } + if json_is_truthy(response.Data) { + h.lp.Beep() + } + return nil +} + +func (h *handler) on_text(text string) error { + text = strings.ToUpper(text) + switch text { + case "W", "N", "T", "S", "R": + return h.do_window_resize(text == "N" || text == "S", text == "W" || text == "N", text == "R", 1) + case "Q": + h.lp.Quit(0) + } + return nil +} + +func (h *handler) on_key(e *loop.KeyEvent) error { + if e.MatchesPressOrRepeat("esc") { + e.Handled = true + h.lp.Quit(0) + return nil + } + for _, k := range []string{"w", "n", "t", "s"} { + if e.MatchesPressOrRepeat("ctrl+" + k) { + e.Handled = true + return h.do_window_resize(k == "n" || k == "s", k == "w" || k == "n", false, 2) + } + } + return nil +} + +func (h *handler) draw_screen() { + lp, ctx := h.lp, h.ctx + lp.StartAtomicUpdate() + defer lp.EndAtomicUpdate() + lp.ClearScreen() + lp.Println(lp.SprintStyled("bold fg=white", "Resize this window")) + lp.Println() + lp.Println("Press one of the following keys:") + lp.Println(" " + ctx.Green("W") + "ider") + lp.Println(" " + ctx.Green("N") + "arrower") + lp.Println(" " + ctx.Green("T") + "aller") + lp.Println(" " + ctx.Green("S") + "horter") + lp.Println(" " + ctx.Red("R") + "eset") + lp.Println() + lp.Println("Press " + lp.SprintStyled("italic", "Esc") + " to quit resize mode") + lp.Println("Hold down " + lp.SprintStyled("italic", "Ctrl") + " to double step size") + lp.Println() + lp.Println(lp.SprintStyled("bold fg=white", "Sizes")) + lp.Printf("Original: %d rows %d cols\r\n", h.original_size.HeightCells, h.original_size.WidthCells) + if sz, err := lp.ScreenSize(); err == nil { + lp.Printf("Current: %s rows %s cols\r\n", + ctx.Magenta(fmt.Sprint(sz.HeightCells)), ctx.Magenta(fmt.Sprint(sz.WidthCells))) + } +} + +func run_loop(opts *Options) (rc int, err error) { + lp, err := loop.New(loop.FullKeyboardProtocol) + if err != nil { + return 1, err + } + h := &handler{lp: lp, opts: opts, ctx: markup.New(true)} + lp.OnInitialize = func() (string, error) { + sz, err := lp.ScreenSize() + if err != nil { + return "", err + } + h.original_size = sz + lp.SetCursorVisible(false) + lp.AllowLineWrapping(false) + h.draw_screen() + return "", nil + } + lp.OnResize = func(old, new loop.ScreenSize) error { h.draw_screen(); return nil } + lp.OnText = func(text string, from_key_event, in_bracketed_paste bool) error { return h.on_text(text) } + lp.OnKeyEvent = h.on_key + lp.OnRCResponse = h.on_rc_response + err = lp.Run() + if err != nil { + return 1, err + } + lp.KillIfSignalled() + if h.print_on_fail != "" { + fmt.Fprintln(os.Stderr, h.print_on_fail) + tui.HoldTillEnter(false) + } + return lp.ExitCode(), nil +} + +func main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) { + return run_loop(opts) +} + +func EntryPoint(parent *cli.Command) { + create_cmd(parent, main) +} diff --git a/kittens/resize_window/main.py b/kittens/resize_window/main.py index 02769255a..0f9dfeff8 100644 --- a/kittens/resize_window/main.py +++ b/kittens/resize_window/main.py @@ -112,10 +112,12 @@ default=2 type=int The base vertical increment. '''.format +help_text = 'Resize the current window' +usage = '' def main(args: list[str]) -> None: - msg = 'Resize the current window' + msg = help_text try: cli_opts, items = parse_args(args[1:], OPTIONS, '', msg, 'resize_window', result_class=ResizeCLIOptions) except SystemExit as e: @@ -131,3 +133,13 @@ def main(args: list[str]) -> None: print(handler.print_on_fail, file=sys.stderr) input('Press Enter to quit') raise SystemExit(loop.return_code) + + +if __name__ == '__main__': + main(sys.argv) +elif __name__ == '__doc__': + cd = sys.cli_docs # type: ignore + cd['usage'] = usage + cd['options'] = OPTIONS + cd['help_text'] = help_text + cd['short_desc'] = help_text diff --git a/tools/cmd/tool/main.go b/tools/cmd/tool/main.go index ca458b029..3af3db0b9 100644 --- a/tools/cmd/tool/main.go +++ b/tools/cmd/tool/main.go @@ -20,6 +20,7 @@ import ( "github.com/kovidgoyal/kitty/kittens/panel" "github.com/kovidgoyal/kitty/kittens/query_terminal" "github.com/kovidgoyal/kitty/kittens/quick_access_terminal" + "github.com/kovidgoyal/kitty/kittens/resize_window" "github.com/kovidgoyal/kitty/kittens/show_key" "github.com/kovidgoyal/kitty/kittens/ssh" "github.com/kovidgoyal/kitty/kittens/themes" @@ -66,6 +67,8 @@ func KittyToolEntryPoints(root *cli.Command) { panel.EntryPoint(root) // quick_access_terminal quick_access_terminal.EntryPoint(root) + // resize_window + resize_window.EntryPoint(root) // unicode_input unicode_input.EntryPoint(root) // show_key