diff --git a/kittens/show_error/__init__.py b/kittens/show_error/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/kittens/show_error/main.py b/kittens/show_error/main.py deleted file mode 100644 index 1f3abc215..000000000 --- a/kittens/show_error/main.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python3 -# License: GPL v3 Copyright: 2018, Kovid Goyal - -import json -import os -import sys -import termios -from contextlib import suppress -from typing import List - -from kitty.cli import parse_args -from kitty.cli_stub import ErrorCLIOptions -from kitty.fast_data_types import open_tty -from kitty.utils import hold_till_enter, no_echo, write_all - -from ..tui.operations import styled - -OPTIONS = '''\ ---title -default=ERROR -The title for the error message. -'''.format - - -def real_main(args: List[str]) -> None: - msg = 'Show an error message. For internal use by kitty.' - cli_opts, items = parse_args(args[1:], OPTIONS, '', msg, 'kitty +kitten show_error', result_class=ErrorCLIOptions) - if sys.stdin.isatty(): - raise SystemExit('Input data for this kitten must be piped as JSON to STDIN') - data = json.loads(sys.stdin.buffer.read()) - error_message = data['msg'] - if cli_opts.title: - print(styled(cli_opts.title, fg_intense=True, fg='red', bold=True)) - print() - print(error_message, flush=True) - if data.get('tb'): - import select - - from kittens.tui.operations import init_state, set_cursor_visible - fd, original_termios = open_tty() - msg = '\n\r\x1b[1;32mPress e to see detailed traceback or any other key to exit\x1b[m' - write_all(fd, msg) - write_all(fd, init_state(alternate_screen=False, kitty_keyboard_mode=False) + set_cursor_visible(False)) - with no_echo(fd): - termios.tcdrain(fd) - while True: - rd = select.select([fd], [], [])[0] - if not rd: - break - q = os.read(fd, 1) - if q in b'eE': - break - return - if data.get('tb'): - tb = data['tb'] - for ln in tb.splitlines(): - print('\r\n', ln, sep='', end='') - print(end='\r\n', flush=True) - hold_till_enter() - - -def main(args: List[str]) -> None: - try: - with suppress(KeyboardInterrupt, EOFError): - real_main(args) - except Exception: - import traceback - traceback.print_exc() - input('Press Enter to close') - - -if __name__ == '__main__': - main(sys.argv) diff --git a/kitty/boss.py b/kitty/boss.py index 29fb24be8..1c423acef 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -1859,7 +1859,23 @@ class Boss: if ec != (None, None, None): import traceback tb = traceback.format_exc() - self.run_kitten_with_metadata('show_error', args=['--title', title], input_data=json.dumps({'msg': msg, 'tb': tb})) + cmd = [kitten_exe(), '__show_error__', kitten_exe(), '__show_error__', '--title', title] + env = {} + env['KITTEN_RUNNING_AS_UI'] = '1' + env['KITTY_CONFIG_DIRECTORY'] = config_dir + tab = self.active_tab + w = self.active_window + if w is not None and tab is not None: + tab.new_special_window( + SpecialWindow( + cmd, + stdin=json.dumps({'msg': msg, 'tb': tb}).encode(), + env=env, + overlay_for=w.id, + ), + copy_colors_from=w + ) + @ac('mk', 'Create a new marker') def create_marker(self) -> None: diff --git a/kitty/cli_stub.py b/kitty/cli_stub.py index bc1d01f13..80fb24a4f 100644 --- a/kitty/cli_stub.py +++ b/kitty/cli_stub.py @@ -66,9 +66,6 @@ def generate_stub() -> None: from kittens.resize_window.main import OPTIONS do(OPTIONS(), 'ResizeCLIOptions') - from kittens.show_error.main import OPTIONS - do(OPTIONS(), 'ErrorCLIOptions') - from kittens.unicode_input.main import OPTIONS do(OPTIONS(), 'UnicodeCLIOptions') diff --git a/tools/cmd/show_error/main.go b/tools/cmd/show_error/main.go new file mode 100644 index 000000000..d6c0fefbf --- /dev/null +++ b/tools/cmd/show_error/main.go @@ -0,0 +1,114 @@ +// License: GPLv3 Copyright: 2023, Kovid Goyal, + +package show_error + +import ( + "encoding/json" + "fmt" + "io" + "os" + + "kitty/tools/cli" + "kitty/tools/cli/markup" + "kitty/tools/tty" + "kitty/tools/tui" + "kitty/tools/tui/loop" +) + +var _ = fmt.Print + +type Options struct { + Title string +} + +type Message struct { + Msg string `json:"msg"` + Traceback string `json:"tb"` +} + +func main(args []string, opts *Options) (rc int, err error) { + if tty.IsTerminal(os.Stdin.Fd()) { + return 1, fmt.Errorf("Input data for this kitten must be piped as JSON to STDIN") + } + data, err := io.ReadAll(os.Stdin) + if err != nil { + return 1, err + } + m := Message{} + err = json.Unmarshal(data, &m) + if err != nil { + return 1, err + } + f := markup.New(true) + if opts.Title != "" { + fmt.Println(f.Err(opts.Title)) + fmt.Println() + } + fmt.Println(m.Msg) + show_traceback := false + if m.Traceback != "" { + lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking) + if err != nil { + return 1, err + } + lp.OnInitialize = func() (string, error) { + lp.SetCursorVisible(false) + lp.QueueWriteString("\n\r\x1b[1;32mPress e to see detailed traceback or any other key to exit\x1b[m\r\n") + return "", nil + } + lp.OnFinalize = func() string { + lp.SetCursorVisible(true) + return "" + } + + lp.OnKeyEvent = func(event *loop.KeyEvent) error { + if event.Type == loop.PRESS || event.Type == loop.REPEAT { + if event.MatchesPressOrRepeat("e") || event.MatchesPressOrRepeat("shift+e") || event.MatchesPressOrRepeat("E") { + show_traceback = true + lp.Quit(0) + } else { + lp.Quit(1) + } + } + if event.MatchesPressOrRepeat("enter") || event.MatchesPressOrRepeat("kp_enter") || event.MatchesPressOrRepeat("esc") || event.MatchesPressOrRepeat("ctrl+c") || event.MatchesPressOrRepeat("ctrl+d") { + event.Handled = true + lp.Quit(0) + } + return nil + } + lp.Run() + if lp.ExitCode() == 1 { + return 0, nil + } + } + if show_traceback { + fmt.Println(m.Traceback) + fmt.Println() + } + tui.HoldTillEnter(true) + return +} + +func EntryPoint(root *cli.Command) *cli.Command { + sc := root.AddSubCommand(&cli.Command{ + Name: "__show_error__", + Hidden: true, + Usage: "[options]", + ShortDescription: "Show an error message. Internal use.", + HelpText: "Show an error message. Used internally by kitty.", + Run: func(cmd *cli.Command, args []string) (ret int, err error) { + opts := &Options{} + err = cmd.GetOptionValues(opts) + if err != nil { + return 1, err + } + return main(args, opts) + }, + }) + sc.Add(cli.OptionSpec{ + Name: "--title", + Default: "ERROR", + Help: "The title for the error message", + }) + return sc +} diff --git a/tools/cmd/tool/main.go b/tools/cmd/tool/main.go index ebdcc5149..7e8f191c8 100644 --- a/tools/cmd/tool/main.go +++ b/tools/cmd/tool/main.go @@ -20,6 +20,7 @@ import ( "kitty/tools/cmd/edit_in_kitty" "kitty/tools/cmd/pytest" "kitty/tools/cmd/run_shell" + "kitty/tools/cmd/show_error" "kitty/tools/cmd/update_self" "kitty/tools/tui" ) @@ -58,6 +59,8 @@ func KittyToolEntryPoints(root *cli.Command) { themes.ParseEntryPoint(root) // run-shell run_shell.EntryPoint(root) + // show_error + show_error.EntryPoint(root) // __pytest__ pytest.EntryPoint(root) // __hold_till_enter__