mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-08 22:28:24 +02:00
Move the query_terminal implementation to Go
This commit is contained in:
@@ -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
|
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.
|
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>`__
|
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
|
for *XTGETTCAP* to see the syntax for the escape code. The kitty specific keys
|
||||||
this kitten to find the values of the keys for the various queries.
|
are all documented below, when sent via escape code they must be prefixed with
|
||||||
|
``kitty-query-``.
|
||||||
|
|
||||||
|
|
||||||
.. include:: ../generated/cli-kitten-query_terminal.rst
|
.. include:: ../generated/cli-kitten-query_terminal.rst
|
||||||
|
|||||||
@@ -570,10 +570,12 @@ def load_ref_map() -> Dict[str, Dict[str, str]]:
|
|||||||
|
|
||||||
def generate_constants() -> str:
|
def generate_constants() -> str:
|
||||||
from kittens.hints.main import DEFAULT_REGEX
|
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.config import option_names_for_completion
|
||||||
from kitty.fast_data_types import FILE_TRANSFER_CODE
|
from kitty.fast_data_types import FILE_TRANSFER_CODE
|
||||||
from kitty.options.utils import allowed_shell_integration_values, url_style_map
|
from kitty.options.utils import allowed_shell_integration_values, url_style_map
|
||||||
del sys.modules['kittens.hints.main']
|
del sys.modules['kittens.hints.main']
|
||||||
|
del sys.modules['kittens.query_terminal.main']
|
||||||
ref_map = load_ref_map()
|
ref_map = load_ref_map()
|
||||||
with open('kitty/data-types.h') as dt:
|
with open('kitty/data-types.h') as dt:
|
||||||
m = re.search(r'^#define IMAGE_PLACEHOLDER_CHAR (\S+)', dt.read(), flags=re.M)
|
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)
|
url_prefixes = ','.join(f'"{x}"' for x in Options.url_prefixes)
|
||||||
option_names = '`' + '\n'.join(option_names_for_completion()) + '`'
|
option_names = '`' + '\n'.join(option_names_for_completion()) + '`'
|
||||||
url_style = {v:k for k, v in url_style_map.items()}[Options.url_style]
|
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'''\
|
return f'''\
|
||||||
package kitty
|
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 RefMap = map[string]string{serialize_go_dict(ref_map['ref'])}
|
||||||
var DocTitleMap = map[string]string{serialize_go_dict(ref_map['doc'])}
|
var DocTitleMap = map[string]string{serialize_go_dict(ref_map['doc'])}
|
||||||
var AllowedShellIntegrationValues = []string{{ {str(sorted(allowed_shell_integration_values))[1:-1].replace("'", '"')} }}
|
var AllowedShellIntegrationValues = []string{{ {str(sorted(allowed_shell_integration_values))[1:-1].replace("'", '"')} }}
|
||||||
|
var QueryNames = []string{{ {query_names} }}
|
||||||
var KittyConfigDefaults = struct {{
|
var KittyConfigDefaults = struct {{
|
||||||
Term, Shell_integration, Select_by_word_characters, Url_excluded_characters, Shell string
|
Term, Shell_integration, Select_by_word_characters, Url_excluded_characters, Shell string
|
||||||
Wheel_scroll_multiplier int
|
Wheel_scroll_multiplier int
|
||||||
|
|||||||
82
kittens/query_terminal/main.go
Normal file
82
kittens/query_terminal/main.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -5,14 +5,11 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
from binascii import hexlify, unhexlify
|
from binascii import hexlify, unhexlify
|
||||||
from contextlib import suppress
|
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.constants import appname, str_version
|
||||||
from kitty.options.types import Options
|
from kitty.options.types import Options
|
||||||
from kitty.terminfo import names
|
from kitty.terminfo import names
|
||||||
from kitty.utils import TTYIO
|
|
||||||
|
|
||||||
|
|
||||||
class Query:
|
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)
|
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:
|
def options_spec() -> str:
|
||||||
return '''\
|
return '''\
|
||||||
--wait-for
|
--wait-for
|
||||||
@@ -289,29 +261,8 @@ Available queries are:
|
|||||||
usage = '[query1 query2 ...]'
|
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__':
|
if __name__ == '__main__':
|
||||||
main()
|
raise SystemExit('Should be run as kitten hints')
|
||||||
elif __name__ == '__doc__':
|
elif __name__ == '__doc__':
|
||||||
cd = sys.cli_docs # type: ignore
|
cd = sys.cli_docs # type: ignore
|
||||||
cd['usage'] = usage
|
cd['usage'] = usage
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func csi(csi string) string {
|
|||||||
return "CSI " + strings.NewReplacer(":", " : ", ";", " ; ").Replace(csi[:len(csi)-1]) + " " + csi[len(csi)-1:]
|
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)
|
lp, err := loop.New(loop.FullKeyboardProtocol)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class CLIOptions:
|
|||||||
LaunchCLIOptions = AskCLIOptions = ClipboardCLIOptions = DiffCLIOptions = CLIOptions
|
LaunchCLIOptions = AskCLIOptions = ClipboardCLIOptions = DiffCLIOptions = CLIOptions
|
||||||
HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions
|
HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions
|
||||||
ErrorCLIOptions = UnicodeCLIOptions = RCOptions = RemoteFileCLIOptions = CLIOptions
|
ErrorCLIOptions = UnicodeCLIOptions = RCOptions = RemoteFileCLIOptions = CLIOptions
|
||||||
QueryTerminalCLIOptions = BroadcastCLIOptions = ShowKeyCLIOptions = CLIOptions
|
BroadcastCLIOptions = ShowKeyCLIOptions = CLIOptions
|
||||||
ThemesCLIOptions = TransferCLIOptions = LoadConfigRCOptions = ActionRCOptions = CLIOptions
|
ThemesCLIOptions = TransferCLIOptions = LoadConfigRCOptions = ActionRCOptions = CLIOptions
|
||||||
|
|
||||||
|
|
||||||
@@ -57,9 +57,6 @@ def generate_stub() -> None:
|
|||||||
from kittens.icat.main import OPTIONS
|
from kittens.icat.main import OPTIONS
|
||||||
do(OPTIONS, 'IcatCLIOptions')
|
do(OPTIONS, 'IcatCLIOptions')
|
||||||
|
|
||||||
from kittens.query_terminal.main import options_spec
|
|
||||||
do(options_spec(), 'QueryTerminalCLIOptions')
|
|
||||||
|
|
||||||
from kittens.panel.main import OPTIONS
|
from kittens.panel.main import OPTIONS
|
||||||
do(OPTIONS(), 'PanelCLIOptions')
|
do(OPTIONS(), 'PanelCLIOptions')
|
||||||
|
|
||||||
|
|||||||
@@ -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>`
|
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
|
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
|
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.
|
codes you want to emulate.
|
||||||
|
|
||||||
The first argument to :code:`send_text` is the keyboard modes in which to
|
The first argument to :code:`send_text` is the keyboard modes in which to
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ exec_kitty() {
|
|||||||
|
|
||||||
|
|
||||||
is_wrapped_kitten() {
|
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" ] && {
|
[ -n "$1" ] && {
|
||||||
case " $wrapped_kittens " in
|
case " $wrapped_kittens " in
|
||||||
*" $1 "*) printf "%s" "$1" ;;
|
*" $1 "*) printf "%s" "$1" ;;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"kitty/kittens/hints"
|
"kitty/kittens/hints"
|
||||||
"kitty/kittens/hyperlinked_grep"
|
"kitty/kittens/hyperlinked_grep"
|
||||||
"kitty/kittens/icat"
|
"kitty/kittens/icat"
|
||||||
|
"kitty/kittens/query_terminal"
|
||||||
"kitty/kittens/show_key"
|
"kitty/kittens/show_key"
|
||||||
"kitty/kittens/ssh"
|
"kitty/kittens/ssh"
|
||||||
"kitty/kittens/themes"
|
"kitty/kittens/themes"
|
||||||
@@ -79,6 +80,8 @@ func KittyToolEntryPoints(root *cli.Command) {
|
|||||||
show_error.EntryPoint(root)
|
show_error.EntryPoint(root)
|
||||||
// choose-fonts
|
// choose-fonts
|
||||||
choose_fonts.EntryPoint(root)
|
choose_fonts.EntryPoint(root)
|
||||||
|
// query-terminal
|
||||||
|
query_terminal.EntryPoint(root)
|
||||||
// __pytest__
|
// __pytest__
|
||||||
pytest.EntryPoint(root)
|
pytest.EntryPoint(root)
|
||||||
// __hold_till_enter__
|
// __hold_till_enter__
|
||||||
|
|||||||
Reference in New Issue
Block a user