#!/usr/bin/env python # License: GPLv3 Copyright: 2022, Kovid Goyal import json import os import shlex import subprocess import tempfile from kitty.constants import kitten_exe as kitten from . import BaseTest class TestCompletion(BaseTest): def test_completion(self): with tempfile.TemporaryDirectory() as tdir: completion(self, tdir) def get_all_words(result): all_words = set() for group in result.get('groups', ()): for m in group['matches']: all_words.add(m['word']) return all_words def has_words(*words): def t(self, result): q = set(words) missing = q - get_all_words(result) self.assertFalse(missing, f'Words missing. Command line: {self.current_cmd!r}') return t def does_not_have_words(*words): def t(self, result): q = set(words) all_words = get_all_words(result) self.assertFalse(q & all_words, f'Words unexpectedly present. Command line: {self.current_cmd!r}') return t def all_words(*words): def t(self, result): expected = set(words) actual = get_all_words(result) self.assertEqual(expected, actual, f'Command line: {self.current_cmd!r}') return t def is_delegate(num_to_remove: int = 0, command: str = ''): q = {} if num_to_remove: q['num_to_remove'] = num_to_remove if command: q['command'] = command def t(self, result): d = result['delegate'] self.assertEqual(d, q, f'Command line: {self.current_cmd!r}') return t def completion(self: TestCompletion, tdir: str): all_cmds = [] all_argv = [] all_tests = [] def add(cmdline: str, *tests): all_cmds.append(cmdline) new_word = cmdline.endswith(' ') if new_word: cmdline = cmdline[:-1] all_argv.append(shlex.split(cmdline)) if new_word: all_argv[-1].append('') all_tests.append(tests) def run_tool(): env = os.environ.copy() env['PATH'] = os.path.join(tdir, 'bin') env['HOME'] = os.path.join(tdir, 'sub') env['KITTY_CONFIG_DIRECTORY'] = os.path.join(tdir, 'sub') cp = subprocess.run( [kitten(), '__complete__', 'json'], check=True, stdout=subprocess.PIPE, cwd=tdir, input=json.dumps(all_argv).encode(), env=env ) self.assertEqual(cp.returncode, 0, f'kitten __complete__ failed with exit code: {cp.returncode}') return json.loads(cp.stdout) add('kitty ', has_words('@', '@ls', '+', '+open')) add('kitty @ l', has_words('ls', 'last-used-layout', 'launch')) add('kitty @l', has_words('@ls', '@last-used-layout', '@launch')) def make_file(path, mode=None): with open(os.path.join(tdir, path), mode='x') as f: if mode is not None: os.chmod(f.fileno(), mode) os.mkdir(os.path.join(tdir, 'bin')) os.mkdir(os.path.join(tdir, 'sub')) make_file('bin/exe1', 0o700) make_file('bin/exe-not1') make_file('exe2', 0o700) make_file('exe-not2.jpeg') make_file('sub/exe3', 0o700) make_file('sub/exe-not3.png') add('kitty x', all_words()) add('kitty e', all_words('exe1', 'exe2')) add('kitty ./', all_words('./bin/', './sub/', './exe2')) add('kitty ./e', all_words('./exe2')) add('kitty ./s', all_words('./sub/')) add('kitty ~', all_words('~/exe3')) add('kitty ~/', all_words('~/exe3')) add('kitty ~/e', all_words('~/exe3')) add('kitty @ goto-layout ', has_words('tall', 'fat')) add('kitty @ goto-layout spli', all_words('splits')) add('kitty @ goto-layout f f', all_words()) add('kitty @ set-window-logo ', all_words('exe-not2.jpeg', 'sub/')) add('kitty @ set-window-logo e', all_words('exe-not2.jpeg')) add('kitty @ set-window-logo e e', all_words()) add('kitty +ope', has_words('+open')) add('kitty +open -', has_words('-1', '-T')) add('kitty -', has_words('-c', '-1', '--'), does_not_have_words('--config', '--single-instance')) add('kitty -c', all_words('-c')) add('kitty --', has_words('--config', '--single-instance', '--')) add('kitty --s', has_words('--session', '--start-as')) add('kitty --start-as', all_words('--start-as')) add('kitty --start-as ', all_words('minimized', 'maximized', 'fullscreen', 'normal')) add('kitty -1 ', does_not_have_words('@ls', '@')) add('kitty --directory ', all_words('bin/', 'sub/')) add('kitty -1d ', all_words('exe1')) add('kitty -1d', all_words('-1d')) add('kitty -o a', has_words('allow_remote_control=')) add('kitty --listen-on ', all_words('unix:', 'tcp:')) add('kitty --listen-on unix:b', all_words('unix:bin/')) add('kitty --directory=', all_words('--directory=bin/', '--directory=sub/')) add('kitty --start-as=m', all_words('--start-as=minimized', '--start-as=maximized')) add('kitty @launch --ty', has_words('--type')) add('kitty @launch --type ', has_words('window', 'background', 'overlay')) add('kitty @launch --cwd ', has_words('current', 'oldest', 'last_reported')) add('kitty @launch --logo ', all_words('exe-not3.png')) add('kitty @launch --logo ~', all_words('~/exe-not3.png')) add('kitten ', has_words('@')) add('kitten ', does_not_have_words('__complete__')) add('kitten @launch --ty', has_words('--type')) add('kitty + ', has_words('launch', 'kitten')) add('kitty + kitten ', has_words('icat', 'diff')) add('kitty +kitten icat ', has_words('sub/', 'exe-not2.jpeg')) add('kitty + kitten icat --pr', has_words('--print-window-size')) add('kitty + kitten diff ', has_words('exe-not2.jpeg')) add('kitty + kitten themes --', has_words('--cache-age')) add('kitty + kitten themes D', has_words('Default')) add('kitty + kitten hyperlinked_grep ', is_delegate(3, 'rg')) add('kitty +kitten hyperlinked_grep ', is_delegate(2, 'rg')) add('clone-in-kitty --ty', has_words('--type')) add('kitty bash ', is_delegate(1, 'bash')) add('kitty -1 bash ', is_delegate(2, 'bash')) add('kitty -1 bash --n', is_delegate(2, 'bash')) add('kitty @launch --type tab bash --n', is_delegate(4, 'bash')) add('kitty +kitten hyperlinked_grep --s', is_delegate(2, 'rg')) add('kitty @launch e', all_words('exe1', 'exe2')) for cmd, tests, result in zip(all_cmds, all_tests, run_tool()): self.current_cmd = cmd for test in tests: test(self, result)