Move the query_terminal implementation to Go

This commit is contained in:
Kovid Goyal 2024-05-08 13:00:40 +05:30
parent f0cac79143
commit 2be91d73dd
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
9 changed files with 98 additions and 60 deletions

View file

@ -17,8 +17,9 @@ slow, since it requires a roundtrip to the terminal emulator and back.
If you want to do some of the same querying in your terminal program without
depending on the kitten, you can do so, by processing the same escape codes.
Search `this page <https://invisible-island.net/xterm/ctlseqs/ctlseqs.html>`__
for *XTGETTCAP* to see the syntax for the escape code and read the source of
this kitten to find the values of the keys for the various queries.
for *XTGETTCAP* to see the syntax for the escape code. The kitty specific keys
are all documented below, when sent via escape code they must be prefixed with
``kitty-query-``.
.. include:: ../generated/cli-kitten-query_terminal.rst

View file

@ -570,10 +570,12 @@ def load_ref_map() -> Dict[str, Dict[str, str]]:
def generate_constants() -> str:
from kittens.hints.main import DEFAULT_REGEX
from kittens.query_terminal.main import all_queries
from kitty.config import option_names_for_completion
from kitty.fast_data_types import FILE_TRANSFER_CODE
from kitty.options.utils import allowed_shell_integration_values, url_style_map
del sys.modules['kittens.hints.main']
del sys.modules['kittens.query_terminal.main']
ref_map = load_ref_map()
with open('kitty/data-types.h') as dt:
m = re.search(r'^#define IMAGE_PLACEHOLDER_CHAR (\S+)', dt.read(), flags=re.M)
@ -583,6 +585,7 @@ def generate_constants() -> str:
url_prefixes = ','.join(f'"{x}"' for x in Options.url_prefixes)
option_names = '`' + '\n'.join(option_names_for_completion()) + '`'
url_style = {v:k for k, v in url_style_map.items()}[Options.url_style]
query_names = ', '.join(f'"{name}"' for name in all_queries)
return f'''\
package kitty
@ -611,6 +614,7 @@ var ConfigModMap = map[string]uint16{serialize_go_dict(config_mod_map)}
var RefMap = map[string]string{serialize_go_dict(ref_map['ref'])}
var DocTitleMap = map[string]string{serialize_go_dict(ref_map['doc'])}
var AllowedShellIntegrationValues = []string{{ {str(sorted(allowed_shell_integration_values))[1:-1].replace("'", '"')} }}
var QueryNames = []string{{ {query_names} }}
var KittyConfigDefaults = struct {{
Term, Shell_integration, Select_by_word_characters, Url_excluded_characters, Shell string
Wheel_scroll_multiplier int

View file

@ -0,0 +1,82 @@
package query_terminal
import (
"bytes"
"fmt"
"kitty"
"os"
"slices"
"strings"
"time"
"kitty/tools/cli"
"kitty/tools/tui/loop"
)
var _ = fmt.Print
func main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) {
queries := kitty.QueryNames
if len(args) > 0 && !slices.Contains(args, "all") {
queries = make([]string, len(args))
for i, x := range args {
if !slices.Contains(kitty.QueryNames, x) {
return 1, fmt.Errorf("Unknown query: %s", x)
}
queries[i] = x
}
}
lp, err := loop.New(loop.NoAlternateScreen, loop.NoKeyboardStateChange, loop.NoMouseTracking, loop.NoRestoreColors)
if err != nil {
return 1, err
}
timed_out := false
lp.OnInitialize = func() (string, error) {
lp.QueryTerminal(queries...)
lp.QueueWriteString("\x1b[c")
_, err := lp.AddTimer(time.Duration(opts.WaitFor*float64(time.Second)), false, func(timer_id loop.IdType) error {
timed_out = true
lp.Quit(1)
return nil
})
return "", err
}
buf := strings.Builder{}
lp.OnQueryResponse = func(key, val string, found bool) error {
if found {
fmt.Fprintf(&buf, "%s: %s\n", key, val)
} else {
fmt.Fprintf(&buf, "%s:\n", key)
}
return nil
}
lp.OnEscapeCode = func(typ loop.EscapeCodeType, data []byte) error {
if typ == loop.CSI && bytes.HasSuffix(data, []byte{'c'}) {
lp.Quit(0)
}
return nil
}
err = lp.Run()
rc = lp.ExitCode()
if err != nil {
return 1, err
}
ds := lp.DeathSignalName()
if ds != "" {
fmt.Println("Killed by signal: ", ds)
lp.KillIfSignalled()
return
}
os.Stdout.WriteString(buf.String())
if timed_out {
return 1, fmt.Errorf("timed out waiting for response from terminal")
}
return
}
func EntryPoint(parent *cli.Command) {
create_cmd(parent, main)
}

View file

@ -5,14 +5,11 @@ import re
import sys
from binascii import hexlify, unhexlify
from contextlib import suppress
from typing import Dict, Iterable, List, Optional, Type
from typing import Dict, Optional, Type
from kitty.cli import parse_args
from kitty.cli_stub import QueryTerminalCLIOptions
from kitty.constants import appname, str_version
from kitty.options.types import Options
from kitty.terminfo import names
from kitty.utils import TTYIO
class Query:
@ -228,31 +225,6 @@ def get_result(name: str, window_id: int, os_window_id: int) -> Optional[str]:
return q.get_result(get_options(), window_id, os_window_id)
def do_queries(queries: Iterable[str], cli_opts: QueryTerminalCLIOptions) -> Dict[str, str]:
actions = tuple(all_queries[x]() for x in queries)
qstring = ''.join(a.query_code() for a in actions)
received = b''
pat = re.compile(rb'\x1b\[\?.+?c')
def more_needed(data: bytes) -> bool:
nonlocal received
received += data
has_da1_response = pat.search(received) is not None
if has_da1_response:
return False
for a in actions:
if a.more_needed(received):
return True
return has_da1_response
with TTYIO() as ttyio:
ttyio.send(qstring)
ttyio.send('\x1b[c') # DA1 query https://vt100.net/docs/vt510-rm/DA1.html
ttyio.recv(more_needed, timeout=cli_opts.wait_for)
return {a.name: a.output_line() for a in actions}
def options_spec() -> str:
return '''\
--wait-for
@ -289,29 +261,8 @@ Available queries are:
usage = '[query1 query2 ...]'
def main(args: List[str] = sys.argv) -> None:
cli_opts, items_ = parse_args(
args[1:],
options_spec,
usage,
help_text,
f'{appname} +kitten query_terminal',
result_class=QueryTerminalCLIOptions
)
queries: List[str] = list(items_)
if 'all' in queries or not queries:
queries = sorted(all_queries)
else:
extra = frozenset(queries) - frozenset(all_queries)
if extra:
raise SystemExit(f'Unknown queries: {", ".join(extra)}')
for key, val in do_queries(queries, cli_opts).items():
print(f'{key}:', val)
if __name__ == '__main__':
main()
raise SystemExit('Should be run as kitten hints')
elif __name__ == '__doc__':
cd = sys.cli_docs # type: ignore
cd['usage'] = usage

View file

@ -16,7 +16,7 @@ func csi(csi string) string {
return "CSI " + strings.NewReplacer(":", " : ", ";", " ; ").Replace(csi[:len(csi)-1]) + " " + csi[len(csi)-1:]
}
func run_kitty_loop(opts *Options) (err error) {
func run_kitty_loop(_ *Options) (err error) {
lp, err := loop.New(loop.FullKeyboardProtocol)
if err != nil {
return err

View file

@ -12,7 +12,7 @@ class CLIOptions:
LaunchCLIOptions = AskCLIOptions = ClipboardCLIOptions = DiffCLIOptions = CLIOptions
HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions
ErrorCLIOptions = UnicodeCLIOptions = RCOptions = RemoteFileCLIOptions = CLIOptions
QueryTerminalCLIOptions = BroadcastCLIOptions = ShowKeyCLIOptions = CLIOptions
BroadcastCLIOptions = ShowKeyCLIOptions = CLIOptions
ThemesCLIOptions = TransferCLIOptions = LoadConfigRCOptions = ActionRCOptions = CLIOptions
@ -57,9 +57,6 @@ def generate_stub() -> None:
from kittens.icat.main import OPTIONS
do(OPTIONS, 'IcatCLIOptions')
from kittens.query_terminal.main import options_spec
do(options_spec(), 'QueryTerminalCLIOptions')
from kittens.panel.main import OPTIONS
do(OPTIONS(), 'PanelCLIOptions')

View file

@ -4279,7 +4279,7 @@ This will send "Special text" when you press the :kbd:`Ctrl+Alt+A` key
combination. The text to be sent decodes :link:`ANSI C escapes <https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html>`
so you can use escapes like :code:`\\\\e` to send control codes or :code:`\\\\u21fb` to send
Unicode characters (or you can just input the Unicode characters directly as
UTF-8 text). You can use ``kitten show_key`` to get the key escape
UTF-8 text). You can use ``kitten show-key`` to get the key escape
codes you want to emulate.
The first argument to :code:`send_text` is the keyboard modes in which to

View file

@ -24,7 +24,7 @@ exec_kitty() {
is_wrapped_kitten() {
wrapped_kittens="clipboard icat hyperlinked_grep ask hints unicode_input ssh themes diff show_key transfer"
wrapped_kittens="clipboard icat hyperlinked_grep ask hints unicode_input ssh themes diff show_key transfer query_terminal"
[ -n "$1" ] && {
case " $wrapped_kittens " in
*" $1 "*) printf "%s" "$1" ;;

View file

@ -12,6 +12,7 @@ import (
"kitty/kittens/hints"
"kitty/kittens/hyperlinked_grep"
"kitty/kittens/icat"
"kitty/kittens/query_terminal"
"kitty/kittens/show_key"
"kitty/kittens/ssh"
"kitty/kittens/themes"
@ -79,6 +80,8 @@ func KittyToolEntryPoints(root *cli.Command) {
show_error.EntryPoint(root)
// choose-fonts
choose_fonts.EntryPoint(root)
// query-terminal
query_terminal.EntryPoint(root)
// __pytest__
pytest.EntryPoint(root)
// __hold_till_enter__