File: zsh_complete.py

package info (click to toggle)
crazy-complete 0.3.6-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 2,404 kB
  • sloc: python: 7,949; sh: 4,636; makefile: 74
file content (214 lines) | stat: -rw-r--r-- 6,610 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
'''This module contains code for completing arguments in Zsh.'''

from . import shell
from . import helpers
from .str_utils import join_with_wrap, indent
from .zsh_utils import escape_colon, escape_square_brackets
from .type_utils import is_dict_type


CHOICES_INLINE_THRESHOLD = 80
# The `choices` command can in Zsh be expressed inline in an optspec, like this:
#   (foo bar baz)
# or:
#   (foo\:"Foo value" bar\:"Bar value" baz\:"Baz value)
#
# This variable defines how big this string can get before a function
# is used instead.


class ZshCompleter(shell.ShellCompleter):
    '''Code generator for completing arguments in Zsh.'''

    # pylint: disable=too-many-public-methods
    # pylint: disable=missing-function-docstring

    def none(self, *_):
        return "' '"

    def integer(self, _ctxt):
        return '_numbers'

    def float(self, _ctxt):
        return "'_numbers -f'"

    def choices(self, ctxt, choices):
        if is_dict_type(choices):
            return self._choices_dict(ctxt, choices)

        return self._choices_list(ctxt, choices)

    def _choices_list(self, ctxt, choices):
        # Inline choices
        items   = [str(item) for item in choices]
        escaped = [shell.escape(escape_colon(item)) for item in items]
        action  = shell.escape('(%s)' % ' '.join(escaped))
        if len(action) <= CHOICES_INLINE_THRESHOLD:
            return action

        # Make a function
        metavar  = shell.escape(ctxt.option.metavar or '')
        escaped  = [shell.escape(escape_colon(c)) for c in choices]

        code  = 'local items=(\n'
        code += indent(join_with_wrap(' ', '\n', 78, escaped), 2)
        code += '\n)\n\n'
        code += f'_describe -- {metavar} items'

        funcname = ctxt.helpers.add_dynamic_func(ctxt, code)
        return funcname

    def _choices_dict(self, ctxt, choices):
        # Inline choices
        #   This does not work with `combine`; maybe we have to introduce
        #   a `combined` parameter to fix this
        #
        #   items   = [str(item) for item in choices.keys()]
        #   values  = [str(value) for value in choices.values()]
        #   colon   = any(':' in s for s in items + values)
        #   escaped = ['%s\\:%s' % (shell.escape(item), shell.escape(value)) for item, value in zip(items, values)]
        #   action  = shell.escape('((%s))' % ' '.join(escaped))
        #   if not colon and len(action) <= CHOICES_INLINE_THRESHOLD:
        #       return action

        # Make a function
        metavar  = shell.escape(ctxt.option.metavar or '')

        code  = 'local items=(\n'
        for item, desc in choices.items():
            item = shell.escape(escape_colon(str(item)))
            desc = shell.escape(str(desc))
            code += f'  {item}:{desc}\n'
        code += ')\n\n'
        code += f'_describe -- {metavar} items'

        funcname = ctxt.helpers.add_dynamic_func(ctxt, code)
        return funcname

    def command(self, _ctxt):
        return '_command_names'

    def directory(self, _ctxt, opts=None):
        directory = None if opts is None else opts.get('directory', None)

        if directory:
            return shell.escape('_directories %s' % shell.escape(directory))

        return '_directories'

    def file(self, _ctxt, opts=None):
        directory = None if opts is None else opts.get('directory', None)

        if directory:
            return shell.escape('_files -W %s' % shell.escape(directory))

        return '_files'

    def group(self, _ctxt):
        return '_groups'

    def hostname(self, _ctxt):
        return '_hosts'

    def pid(self, _ctxt):
        return '_pids'

    def process(self, _ctxt):
        return "'_process_names -a'"

    def range(self, _ctxt, start, stop, step=1):
        if step == 1:
            return f"'({{{start}..{stop}}})'"

        return f"'({{{start}..{stop}..{step}}})'"

    def user(self, _ctxt):
        return '_users'

    def variable(self, _ctxt):
        return '_vars'

    def environment(self, _ctxt):
        return '"_parameters -g \'*export*\'"'

    def exec(self, ctxt, command):
        funcname = ctxt.helpers.use_function('exec')
        return shell.escape('{%s %s}' % (funcname, shell.escape(command)))

    def exec_fast(self, ctxt, command):
        funcname = ctxt.helpers.use_function('exec')
        return shell.escape('{%s %s}' % (funcname, shell.escape(command)))

    def exec_internal(self, _ctxt, command):
        return shell.escape(command)

    def value_list(self, ctxt, opts):
        desc   = ctxt.option.metavar or ''
        values = opts['values']

        if is_dict_type(values):
            values_arg = ' '.join(
                shell.escape('%s[%s]' % (
                    escape_square_brackets(item),
                    escape_square_brackets(desc))
                ) for item, desc in values.items()
            )
        else:
            values_arg = ' '.join(shell.escape(escape_square_brackets(i)) for i in values)

        cmd = '_values -s %s %s %s' % (
            shell.escape(opts.get('separator', ',')),
            shell.escape(desc),
            values_arg
        )

        return shell.escape(cmd)

    def combine(self, ctxt, commands):
        completions = []
        for command_args in commands:
            command, *args = command_args
            c = getattr(self, command)(ctxt, *args)
            completions.append(c)

        #metavar = shell.escape(ctxt.option.metavar or '')

        code  = '_alternative \\\n'
        for completion in completions:
            code += '  %s \\\n' % completion
        code = code.rstrip(' \\\n')

        funcname = ctxt.helpers.add_dynamic_func(ctxt, code)
        return funcname

    def history(self, ctxt, pattern):
        func = ctxt.helpers.use_function('history')
        cmd = '{%s %s}' % (func, shell.escape(pattern))
        return shell.escape(cmd)

    # =========================================================================
    # Bonus
    # =========================================================================

    def net_interface(self, _ctxt):
        return '_net_interfaces'

    def timezone(self, _ctxt):
        return '_time_zone'

    def locale(self, _ctxt):
        return '_locales'

    def charset(self, _ctxt):
        return '_charset'

    def login_shell(self, _ctxt):
        return '_shells'

    def alsa_card(self, ctxt):
        func = ctxt.helpers.use_function('alsa_complete_cards')
        return func

    def alsa_device(self, ctxt):
        func = ctxt.helpers.use_function('alsa_complete_devices')
        return func