Port all remaining hints matching tests
This commit is contained in:
parent
0e5ed29d83
commit
5b3f5dd02d
@ -457,8 +457,10 @@ 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 kitty.options.types import Options
|
from kitty.options.types import Options
|
||||||
from kitty.options.utils import allowed_shell_integration_values
|
from kitty.options.utils import allowed_shell_integration_values
|
||||||
|
del sys.modules['kittens.hints.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)
|
||||||
@ -481,6 +483,7 @@ const RC_ENCRYPTION_PROTOCOL_VERSION string = "{kc.RC_ENCRYPTION_PROTOCOL_VERSIO
|
|||||||
const IsFrozenBuild bool = false
|
const IsFrozenBuild bool = false
|
||||||
const IsStandaloneBuild bool = false
|
const IsStandaloneBuild bool = false
|
||||||
const HandleTermiosSignals = {Mode.HANDLE_TERMIOS_SIGNALS.value[0]}
|
const HandleTermiosSignals = {Mode.HANDLE_TERMIOS_SIGNALS.value[0]}
|
||||||
|
const HintsDefaultRegex = `{DEFAULT_REGEX}`
|
||||||
var Version VersionType = VersionType{{Major: {kc.version.major}, Minor: {kc.version.minor}, Patch: {kc.version.patch},}}
|
var Version VersionType = VersionType{{Major: {kc.version.major}, Minor: {kc.version.minor}, Patch: {kc.version.patch},}}
|
||||||
var DefaultPager []string = []string{{ {dp} }}
|
var DefaultPager []string = []string{{ {dp} }}
|
||||||
var FunctionalKeyNameAliases = map[string]string{serialize_go_dict(functional_key_name_aliases)}
|
var FunctionalKeyNameAliases = map[string]string{serialize_go_dict(functional_key_name_aliases)}
|
||||||
|
|||||||
@ -726,15 +726,10 @@ def main(args: List[str]) -> Optional[Dict[str, Any]]:
|
|||||||
|
|
||||||
|
|
||||||
def linenum_process_result(data: Dict[str, Any]) -> Tuple[str, int]:
|
def linenum_process_result(data: Dict[str, Any]) -> Tuple[str, int]:
|
||||||
lnum_pat = re.compile(r'(:\d+)$')
|
|
||||||
for match, g in zip(data['match'], data['groupdicts']):
|
for match, g in zip(data['match'], data['groupdicts']):
|
||||||
path, line = g['path'], g['line']
|
path, line = g['path'], g['line']
|
||||||
if path and line:
|
if path and line:
|
||||||
m = lnum_pat.search(path)
|
return path, int(line)
|
||||||
if m is not None:
|
|
||||||
line = m.group(1)[1:]
|
|
||||||
path = path.rpartition(':')[0]
|
|
||||||
return os.path.expanduser(path), int(line)
|
|
||||||
return '', -1
|
return '', -1
|
||||||
|
|
||||||
|
|
||||||
@ -782,7 +777,10 @@ def linenum_handle_result(args: List[str], data: Dict[str, Any], target_window_i
|
|||||||
|
|
||||||
@result_handler(type_of_input='screen-ansi', has_ready_notification=Hints.overlay_ready_report_needed)
|
@result_handler(type_of_input='screen-ansi', has_ready_notification=Hints.overlay_ready_report_needed)
|
||||||
def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int, boss: BossType) -> None:
|
def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int, boss: BossType) -> None:
|
||||||
if data['customize_processing']:
|
cp = data['customize_processing']
|
||||||
|
if data['type'] == 'linenum':
|
||||||
|
cp = '::linenum::'
|
||||||
|
if cp:
|
||||||
m = load_custom_processor(data['customize_processing'])
|
m = load_custom_processor(data['customize_processing'])
|
||||||
if 'handle_result' in m:
|
if 'handle_result' in m:
|
||||||
m['handle_result'](args, data, target_window_id, boss, data['extra_cli_args'])
|
m['handle_result'](args, data, target_window_id, boss, data['extra_cli_args'])
|
||||||
|
|||||||
@ -1,80 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from . import BaseTest
|
|
||||||
|
|
||||||
|
|
||||||
class TestHints(BaseTest):
|
|
||||||
|
|
||||||
def test_url_hints(self):
|
|
||||||
from kittens.hints.main import Mark, convert_text, functions_for, linenum_marks, linenum_process_result, mark, parse_hints_args, process_escape_codes
|
|
||||||
args = parse_hints_args([])[0]
|
|
||||||
pattern, post_processors = functions_for(args)
|
|
||||||
|
|
||||||
def create_marks(text, cols=20, mark=mark):
|
|
||||||
text = convert_text(text, cols)
|
|
||||||
text, _ = process_escape_codes(text)
|
|
||||||
return tuple(mark(pattern, post_processors, text, args))
|
|
||||||
|
|
||||||
def t(text, url, cols=20):
|
|
||||||
marks = create_marks(text, cols)
|
|
||||||
urls = [m.text for m in marks]
|
|
||||||
self.ae(urls, [url])
|
|
||||||
|
|
||||||
u = 'http://test.me/'
|
|
||||||
t(u, 'http://test.me/')
|
|
||||||
t(f'"{u}"', u)
|
|
||||||
t(f'({u})', u)
|
|
||||||
t(u + '\nxxx', u + 'xxx', len(u))
|
|
||||||
t(f'link:{u}[xxx]', u)
|
|
||||||
t(f'`xyz <{u}>`_.', u)
|
|
||||||
t(f'<a href="{u}">moo', u)
|
|
||||||
t('\x1b[mhttp://test.me/1234\n\x1b[mx', 'http://test.me/1234')
|
|
||||||
t('\x1b[mhttp://test.me/12345\r\x1b[m6\n\x1b[mx', 'http://test.me/123456')
|
|
||||||
|
|
||||||
def m(text, path, line, cols=20):
|
|
||||||
|
|
||||||
def adapt(pattern, postprocessors, text, *a):
|
|
||||||
return linenum_marks(text, args, Mark, ())
|
|
||||||
|
|
||||||
marks = create_marks(text, cols, mark=adapt)
|
|
||||||
data = {'groupdicts': [m.groupdict for m in marks], 'match': [m.text for m in marks]}
|
|
||||||
self.ae(linenum_process_result(data), (path, line))
|
|
||||||
|
|
||||||
args = parse_hints_args('--type=linenum'.split())[0]
|
|
||||||
m('file.c:23', 'file.c', 23)
|
|
||||||
m('file.c:23:32', 'file.c', 23)
|
|
||||||
m('file.cpp:23:1', 'file.cpp', 23)
|
|
||||||
m('a/file.c:23', 'a/file.c', 23)
|
|
||||||
m('a/file.c:23:32', 'a/file.c', 23)
|
|
||||||
m('~/file.c:23:32', os.path.expanduser('~/file.c'), 23)
|
|
||||||
|
|
||||||
def test_ip_hints(self):
|
|
||||||
from kittens.hints.main import convert_text, functions_for, mark, parse_hints_args
|
|
||||||
args = parse_hints_args(['--type', 'ip'])[0]
|
|
||||||
pattern, post_processors = functions_for(args)
|
|
||||||
|
|
||||||
def create_marks(text, cols=60):
|
|
||||||
text = convert_text(text, cols)
|
|
||||||
return tuple(mark(pattern, post_processors, text, args))
|
|
||||||
|
|
||||||
testcases = (
|
|
||||||
('100.64.0.0', ['100.64.0.0']),
|
|
||||||
('2001:0db8:0000:0000:0000:ff00:0042:8329', ['2001:0db8:0000:0000:0000:ff00:0042:8329']),
|
|
||||||
('2001:db8:0:0:0:ff00:42:8329', ['2001:db8:0:0:0:ff00:42:8329']),
|
|
||||||
('2001:db8::ff00:42:8329', ['2001:db8::ff00:42:8329']),
|
|
||||||
('2001:DB8::FF00:42:8329', ['2001:DB8::FF00:42:8329']),
|
|
||||||
('0000:0000:0000:0000:0000:0000:0000:0001', ['0000:0000:0000:0000:0000:0000:0000:0001']),
|
|
||||||
('::1', ['::1']),
|
|
||||||
# Invalid IPs won't match
|
|
||||||
('255.255.255.256', []),
|
|
||||||
(':1', []),
|
|
||||||
)
|
|
||||||
|
|
||||||
for testcase, expected in testcases:
|
|
||||||
with self.subTest(testcase=testcase, expected=expected):
|
|
||||||
marks = create_marks(testcase)
|
|
||||||
ips = [m.text for m in marks]
|
|
||||||
self.ae(ips, expected)
|
|
||||||
@ -20,12 +20,11 @@ var _ = fmt.Print
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
DEFAULT_HINT_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz"
|
DEFAULT_HINT_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz"
|
||||||
DEFAULT_REGEX = `(?m)^\s*(.+)\s*$`
|
FILE_EXTENSION = `\.(?:[a-zA-Z0-9]{2,7}|[ahcmo])(?:\b|[^.])`
|
||||||
FILE_EXTENSION = `\.(?:[a-zA-Z0-9]{2,7}|[ahcmo])(?!\.)`
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func path_regex() string {
|
func path_regex() string {
|
||||||
return fmt.Sprintf(`(?:\S*?/[\r\S]+)|(?:\S[\r\S]*{%s})\b`, FILE_EXTENSION)
|
return fmt.Sprintf(`(?:\S*?/[\r\S]+)|(?:\S[\r\S]*%s)\b`, FILE_EXTENSION)
|
||||||
}
|
}
|
||||||
|
|
||||||
func default_linenum_regex() string {
|
func default_linenum_regex() string {
|
||||||
@ -84,6 +83,7 @@ func process_escape_codes(text string) (ans string, hyperlinks []Mark) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PostProcessorFunc = func(string, int, int) (int, int)
|
type PostProcessorFunc = func(string, int, int) (int, int)
|
||||||
|
type GroupProcessorFunc = func(map[string]string)
|
||||||
|
|
||||||
func is_punctuation(b string) bool {
|
func is_punctuation(b string) bool {
|
||||||
switch b {
|
switch b {
|
||||||
@ -143,6 +143,15 @@ func matching_remover(openers ...string) PostProcessorFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func linenum_group_processor(gd map[string]string) {
|
||||||
|
pat := utils.MustCompile(`:\d+$`)
|
||||||
|
gd[`path`] = pat.ReplaceAllStringFunc(gd["path"], func(m string) string {
|
||||||
|
gd["line"] = m[1:]
|
||||||
|
return ``
|
||||||
|
})
|
||||||
|
gd[`path`] = utils.Expanduser(gd[`path`])
|
||||||
|
}
|
||||||
|
|
||||||
var PostProcessorMap = (&utils.Once[map[string]PostProcessorFunc]{Run: func() map[string]PostProcessorFunc {
|
var PostProcessorMap = (&utils.Once[map[string]PostProcessorFunc]{Run: func() map[string]PostProcessorFunc {
|
||||||
return map[string]PostProcessorFunc{
|
return map[string]PostProcessorFunc{
|
||||||
"url": func(text string, s, e int) (int, int) {
|
"url": func(text string, s, e int) (int, int) {
|
||||||
@ -211,7 +220,7 @@ var RelevantKittyOpts = (&utils.Once[KittyOpts]{Run: func() KittyOpts {
|
|||||||
return read_relevant_kitty_opts(filepath.Join(utils.ConfigDir(), "kitty.conf"))
|
return read_relevant_kitty_opts(filepath.Join(utils.ConfigDir(), "kitty.conf"))
|
||||||
}}).Get
|
}}).Get
|
||||||
|
|
||||||
func functions_for(opts *Options) (pattern string, post_processors []PostProcessorFunc) {
|
func functions_for(opts *Options) (pattern string, post_processors []PostProcessorFunc, group_processors []GroupProcessorFunc) {
|
||||||
switch opts.Type {
|
switch opts.Type {
|
||||||
case "url":
|
case "url":
|
||||||
var url_prefixes *utils.Set[string]
|
var url_prefixes *utils.Set[string]
|
||||||
@ -247,15 +256,17 @@ func functions_for(opts *Options) (pattern string, post_processors []PostProcess
|
|||||||
default:
|
default:
|
||||||
pattern = opts.Regex
|
pattern = opts.Regex
|
||||||
if opts.Type == "linenum" {
|
if opts.Type == "linenum" {
|
||||||
if pattern == DEFAULT_REGEX {
|
if pattern == kitty.HintsDefaultRegex {
|
||||||
pattern = default_linenum_regex()
|
pattern = default_linenum_regex()
|
||||||
}
|
}
|
||||||
|
post_processors = append(post_processors, PostProcessorMap()["brackets"], PostProcessorMap()["quotes"])
|
||||||
|
group_processors = append(group_processors, linenum_group_processor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func mark(r *regexp.Regexp, post_processors []PostProcessorFunc, text string, opts *Options) (ans []Mark) {
|
func mark(r *regexp.Regexp, post_processors []PostProcessorFunc, group_processors []GroupProcessorFunc, text string, opts *Options) (ans []Mark) {
|
||||||
sanitize_pat := regexp.MustCompile("[\r\n\x00]")
|
sanitize_pat := regexp.MustCompile("[\r\n\x00]")
|
||||||
names := r.SubexpNames()
|
names := r.SubexpNames()
|
||||||
for i, v := range r.FindAllStringSubmatchIndex(text, -1) {
|
for i, v := range r.FindAllStringSubmatchIndex(text, -1) {
|
||||||
@ -281,13 +292,16 @@ func mark(r *regexp.Regexp, post_processors []PostProcessorFunc, text string, op
|
|||||||
for x, name := range names {
|
for x, name := range names {
|
||||||
if name != "" {
|
if name != "" {
|
||||||
idx := 2 * x
|
idx := 2 * x
|
||||||
if s, e := v[idx], v[idx]+1; s > -1 && e > -1 {
|
if s, e := v[idx], v[idx+1]; s > -1 && e > -1 {
|
||||||
s = utils.Max(s, match_start)
|
s = utils.Max(s, match_start)
|
||||||
e = utils.Min(e, match_end)
|
e = utils.Min(e, match_end)
|
||||||
gd[name] = sanitize_pat.ReplaceAllLiteralString(text[s:e], "")
|
gd[name] = sanitize_pat.ReplaceAllLiteralString(text[s:e], "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, f := range group_processors {
|
||||||
|
f(gd)
|
||||||
|
}
|
||||||
ans = append(ans, Mark{
|
ans = append(ans, Mark{
|
||||||
Index: i, Start: match_start, End: match_end, Text: full_match, Groupdict: gd,
|
Index: i, Start: match_start, End: match_end, Text: full_match, Groupdict: gd,
|
||||||
})
|
})
|
||||||
@ -295,9 +309,22 @@ func mark(r *regexp.Regexp, post_processors []PostProcessorFunc, text string, op
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ErrNoMatches struct{ Type string }
|
||||||
|
|
||||||
|
func (self *ErrNoMatches) Error() string {
|
||||||
|
none_of := "matches"
|
||||||
|
switch self.Type {
|
||||||
|
case "urls":
|
||||||
|
none_of = "URLs"
|
||||||
|
case "hyperlinks":
|
||||||
|
none_of = "hyperlinks"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("No %s found", none_of)
|
||||||
|
}
|
||||||
|
|
||||||
func find_marks(text string, opts *Options) (ans []Mark, index_map map[int]*Mark, err error) {
|
func find_marks(text string, opts *Options) (ans []Mark, index_map map[int]*Mark, err error) {
|
||||||
text, hyperlinks := process_escape_codes(text)
|
text, hyperlinks := process_escape_codes(text)
|
||||||
pattern, post_processors := functions_for(opts)
|
pattern, post_processors, group_processors := functions_for(opts)
|
||||||
if opts.Type == "hyperlink" {
|
if opts.Type == "hyperlink" {
|
||||||
ans = hyperlinks
|
ans = hyperlinks
|
||||||
} else {
|
} else {
|
||||||
@ -305,17 +332,10 @@ func find_marks(text string, opts *Options) (ans []Mark, index_map map[int]*Mark
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("Failed to compile the regex pattern: %#v with error: %w", pattern, err)
|
return nil, nil, fmt.Errorf("Failed to compile the regex pattern: %#v with error: %w", pattern, err)
|
||||||
}
|
}
|
||||||
ans = mark(r, post_processors, text, opts)
|
ans = mark(r, post_processors, group_processors, text, opts)
|
||||||
}
|
}
|
||||||
if len(ans) == 0 {
|
if len(ans) == 0 {
|
||||||
none_of := "matches"
|
return nil, nil, &ErrNoMatches{Type: opts.Type}
|
||||||
switch opts.Type {
|
|
||||||
case "urls":
|
|
||||||
none_of = "URLs"
|
|
||||||
case "hyperlinks":
|
|
||||||
none_of = "hyperlinks"
|
|
||||||
}
|
|
||||||
return nil, nil, fmt.Errorf("No %s found", none_of)
|
|
||||||
}
|
}
|
||||||
largest_index := ans[len(ans)-1].Index
|
largest_index := ans[len(ans)-1].Index
|
||||||
offset := utils.Max(0, opts.HintsOffset)
|
offset := utils.Max(0, opts.HintsOffset)
|
||||||
|
|||||||
@ -3,8 +3,11 @@
|
|||||||
package hints
|
package hints
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"kitty"
|
||||||
"kitty/tools/utils"
|
"kitty/tools/utils"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
@ -14,13 +17,18 @@ var _ = fmt.Print
|
|||||||
|
|
||||||
func TestHintMarking(t *testing.T) {
|
func TestHintMarking(t *testing.T) {
|
||||||
|
|
||||||
opts := &Options{Type: "url"}
|
opts := &Options{Type: "url", UrlPrefixes: "default", Regex: kitty.HintsDefaultRegex}
|
||||||
|
cols := 20
|
||||||
r := func(text string, url ...string) {
|
r := func(text string, url ...string) {
|
||||||
ptext := convert_text(text, 20)
|
ptext := convert_text(text, cols)
|
||||||
marks, _, err := find_marks(ptext, opts)
|
marks, _, err := find_marks(ptext, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
var e *ErrNoMatches
|
||||||
|
if len(url) != 0 || !errors.As(err, &e) {
|
||||||
t.Fatalf("%#v failed with error: %s", text, err)
|
t.Fatalf("%#v failed with error: %s", text, err)
|
||||||
}
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
actual := utils.Map(func(m Mark) string { return m.Text }, marks)
|
actual := utils.Map(func(m Mark) string { return m.Text }, marks)
|
||||||
if diff := cmp.Diff(url, actual); diff != "" {
|
if diff := cmp.Diff(url, actual); diff != "" {
|
||||||
t.Fatalf("%#v failed:\n%s", text, diff)
|
t.Fatalf("%#v failed:\n%s", text, diff)
|
||||||
@ -29,4 +37,53 @@ func TestHintMarking(t *testing.T) {
|
|||||||
|
|
||||||
u := `http://test.me/`
|
u := `http://test.me/`
|
||||||
r(u, u)
|
r(u, u)
|
||||||
|
r(`"`+u+`"`, u)
|
||||||
|
r("("+u+")", u)
|
||||||
|
cols = len(u)
|
||||||
|
r(u+"\nxxx", u+"xxx")
|
||||||
|
cols = 20
|
||||||
|
r("link:"+u+"[xxx]", u)
|
||||||
|
r("`xyz <"+u+">`_.", u)
|
||||||
|
r(`<a href="`+u+`">moo`, u)
|
||||||
|
r("\x1b[mhttp://test.me/1234\n\x1b[mx", "http://test.me/1234")
|
||||||
|
r("\x1b[mhttp://test.me/12345\r\x1b[m6\n\x1b[mx", "http://test.me/123456")
|
||||||
|
|
||||||
|
opts.Type = "linenum"
|
||||||
|
m := func(text, path string, line int) {
|
||||||
|
ptext := convert_text(text, cols)
|
||||||
|
marks, _, err := find_marks(ptext, opts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%#v failed with error: %s", text, err)
|
||||||
|
}
|
||||||
|
gd := map[string]string{"path": path, "line": strconv.Itoa(line)}
|
||||||
|
if diff := cmp.Diff(marks[0].Groupdict, gd); diff != "" {
|
||||||
|
t.Fatalf("%#v failed:\n%s", text, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m("file.c:23", "file.c", 23)
|
||||||
|
m("file.c:23:32", "file.c", 23)
|
||||||
|
m("file.cpp:23:1", "file.cpp", 23)
|
||||||
|
m("a/file.c:23", "a/file.c", 23)
|
||||||
|
m("a/file.c:23:32", "a/file.c", 23)
|
||||||
|
m("~/file.c:23:32", utils.Expanduser("~/file.c"), 23)
|
||||||
|
|
||||||
|
opts.Type = "path"
|
||||||
|
r("file.c", "file.c")
|
||||||
|
r("file.c.", "file.c")
|
||||||
|
r("file.epub.", "file.epub")
|
||||||
|
r("(file.epub)", "file.epub")
|
||||||
|
r("some/path", "some/path")
|
||||||
|
|
||||||
|
cols = 60
|
||||||
|
opts.Type = "ip"
|
||||||
|
r(`100.64.0.0`, `100.64.0.0`)
|
||||||
|
r(`2001:0db8:0000:0000:0000:ff00:0042:8329`, `2001:0db8:0000:0000:0000:ff00:0042:8329`)
|
||||||
|
r(`2001:db8:0:0:0:ff00:42:8329`, `2001:db8:0:0:0:ff00:42:8329`)
|
||||||
|
r(`2001:db8::ff00:42:8329`, `2001:db8::ff00:42:8329`)
|
||||||
|
r(`2001:DB8::FF00:42:8329`, `2001:DB8::FF00:42:8329`)
|
||||||
|
r(`0000:0000:0000:0000:0000:0000:0000:0001`, `0000:0000:0000:0000:0000:0000:0000:0001`)
|
||||||
|
r(`::1`, `::1`)
|
||||||
|
r(`255.255.255.256`)
|
||||||
|
r(`:1`)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user