Completed
Push — master ( 27a45c...f38886 )
by Lambda
57s
created

_insert_special()   A

Complexity

Conditions 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
1
"""Action module."""
2
from typing import Callable, Optional, Dict, Tuple, Sequence    # noqa: F401
3
from .prompt import Prompt, Status
4
from .context import Context
5
from .digraph import Digraph
6
from .util import safeget, int2repr
7
8
9
ActionCallback = Callable[[Prompt], Optional[int]]
10
ActionRules = Sequence[Tuple[str, ActionCallback]]
11
12
digraph = Digraph()
13
14
15
class Action:
16
    """Action class which holds action callbacks.
17
18
    Attributes:
19
        registry (dict): An action dictionary.
20
    """
21
22
    __slots__ = ('registry',)
23
24
    def __init__(self) -> None:
25
        """Constructor."""
26
        self.registry = {}  # type: Dict[str, ActionCallback]
27
28
    def register(self, name: str, callback: ActionCallback=None) -> None:
29
        """Register action callback to a specified name.
30
31
        Args:
32
            name (str): An action name which follow {namespace}:{action name}
33
            callback (Callable): An action callback which take a
34
                ``prompt.prompt.Prompt`` instance and return None or int.
35
36
        Example:
37
            >>> from neovim_prompt.prompt import Status
38
            >>> action = Action()
39
            >>> action.register('prompt:accept', lambda prompt: Status.accept)
40
        """
41
        self.registry[name] = callback
42
43
    def register_from_rules(self, rules: ActionRules) -> None:
44
        """Register action callbacks from rules.
45
46
        Args:
47
            rules (Iterable): An iterator which returns rules. A rule is a
48
                (name, callback) tuple.
49
50
        Example:
51
            >>> from neovim_prompt.prompt import Status
52
            >>> action = Action()
53
            >>> action.register_from_rules([
54
            ...     ('prompt:accept', lambda prompt: Status.accept),
55
            ...     ('prompt:cancel', lambda prompt: Status.cancel),
56
            ... ])
57
        """
58
        for rule in rules:
59
            self.register(*rule)
60
61
    def call(self, prompt: Prompt, name: str) -> Optional[Status]:
62
        """Call a callback of specified action.
63
64
        Args:
65
            prompt (Prompt): A ``prompt.prompt.Prompt`` instance.
66
            name (str): An action name.
67
68
        Example:
69
            >>> from unittest.mock import MagicMock
70
            >>> from neovim_prompt.prompt import Status
71
            >>> prompt = MagicMock()
72
            >>> action = Action()
73
            >>> action.register_from_rules([
74
            ...     ('prompt:accept', lambda prompt: Status.accept),
75
            ...     ('prompt:cancel', lambda prompt: Status.cancel),
76
            ... ])
77
            >>> action.call(prompt, 'prompt:accept')
78
            <Status.accept: 1>
79
80
        Returns:
81
            None or Status: None or int which represent the prompt status.
82
        """
83
        if name in self.registry:
84
            fn = self.registry[name]
85
            return fn(prompt)
86
        elif name.startswith('call:'):
87
            return _call(prompt, name[5:])
88
        raise AttributeError(
89
            'No action "%s" has registered.' % name
90
        )
91
92
    @classmethod
93
    def from_rules(cls, rules: ActionRules) -> 'Action':
94
        """Create a new action instance from rules.
95
96
        Args:
97
            rules (Iterable): An iterator which returns rules. A rule is a
98
                (name, callback) tuple.
99
100
        Example:
101
            >>> from neovim_prompt.prompt import Status
102
            >>> Action.from_rules([
103
            ...     ('prompt:accept', lambda prompt: Status.accept),
104
            ...     ('prompt:cancel', lambda prompt: Status.cancel),
105
            ... ])
106
            <neovim_prompt.action.Action object at ...>
107
108
        Returns:
109
            Action: An action instance.
110
        """
111
        action = cls()
112
        action.register_from_rules(rules)
113
        return action
114
115
116
# Special actions -------------------------------------------------------------
117
def _call(prompt, fname):
118
    result = prompt.nvim.call(fname, prompt.context.to_dict())
119
    if result is None:
120
        return Status.progress
121
    elif isinstance(result, int):
122
        return result
123
    elif isinstance(result, dict):
124
        prompt.context.extend(result)
125
    elif isinstance(result, (list, tuple)):
126
        status = safeget(result, 0, default=Status.progress)
127
        prompt.context.extend(safeget(result, 1, default={}))
128
        return status
129
    else:
130
        raise AttributeError(
131
            "A function '%s' does not return a correct value." % fname
132
        )
133
134
135
# Default actions -------------------------------------------------------------
136
def _accept(prompt):
137
    from .prompt import Status
138
    return Status.accept
139
140
141
def _cancel(prompt):
142
    from .prompt import Status
143
    return Status.cancel
144
145
146
def _toggle_insert_mode(prompt):
147
    from .prompt import InsertMode
148
    if prompt.insert_mode == InsertMode.insert:
149
        prompt.insert_mode = InsertMode.replace
150
    else:
151
        prompt.insert_mode = InsertMode.insert
152
153
154
def _delete_char_before_caret(prompt):
155
    if prompt.caret.locus == 0:
156
        return
157
    prompt.context.text = ''.join([
158
        prompt.caret.get_backward_text()[:-1],
159
        prompt.caret.get_selected_text(),
160
        prompt.caret.get_forward_text(),
161
    ])
162
    prompt.caret.locus -= 1
163
164
165
def _delete_char_under_caret(prompt):
166
    prompt.context.text = ''.join([
167
        prompt.caret.get_backward_text(),
168
        prompt.caret.get_forward_text(),
169
    ])
170
171
172
def _delete_text_after_caret(prompt):
173
    prompt.context.text = prompt.caret.get_backward_text()
174
    prompt.caret.locus = prompt.caret.tail
175
176
177
def _delete_entire_text(prompt):
178
    prompt.context.text = ''
179
    prompt.caret.locus = prompt.caret.tail
180
181
182
def _move_caret_to_left(prompt):
183
    prompt.caret.locus -= 1
184
185
186
def _move_caret_to_right(prompt):
187
    prompt.caret.locus += 1
188
189
190
def _move_caret_to_head(prompt):
191
    prompt.caret.locus = prompt.caret.head
192
193
194
def _move_caret_to_lead(prompt):
195
    prompt.caret.locus = prompt.caret.lead
196
197
198
def _move_caret_to_tail(prompt):
199
    prompt.caret.locus = prompt.caret.tail
200
201
202
def _assign_previous_text(prompt):
203
    prompt.text = prompt.history.previous()
204
205
206
def _assign_next_text(prompt):
207
    prompt.text = prompt.history.next()
208
209
210
def _assign_previous_matched_text(prompt):
211
    prompt.text = prompt.history.previous_match()
212
213
214
def _assign_next_matched_text(prompt):
215
    prompt.text = prompt.history.next_match()
216
217
218
def _paste_from_register(prompt):
219
    prompt.nvim.command(r'echon "\""')
220
    reg = prompt.nvim.eval('nr2char(getchar())')
221
    val = prompt.nvim.call('getreg', reg)
222
    prompt.update_text(val)
223
224
225
def _paste_from_default_register(prompt):
226
    val = prompt.nvim.call('getreg', prompt.nvim.vvars['register'])
227
    prompt.update_text(val)
228
229
230
def _yank_to_register(prompt):
231
    prompt.nvim.command(r'echon "\""')
232
    reg = prompt.nvim.eval('nr2char(getchar())')
233
    prompt.nvim.call('setreg', reg, prompt.text)
234
235
236
def _yank_to_default_register(prompt):
237
    prompt.nvim.call('setreg', prompt.nvim.vvars['register'], prompt.text)
238
239
240
def _insert_special(prompt):
241
    prompt.nvim.command('echon "^"')
242
    try:
243
        code = prompt.nvim.call('getchar')
244
    except:
245
        # KeyboardInterrupt is wrapped so could not be 'expect'...
246
        code = 3
247
    char = int2repr(prompt.nvim, code)
248
    prompt.update_text(char)
249
250
251
def _insert_digraph(prompt):
252
    char = digraph.retrieve(prompt.nvim)
253
    prompt.update_text(char)
254
255
256
DEFAULT_ACTION = Action.from_rules([
257
    ('prompt:accept', _accept),
258
    ('prompt:cancel', _cancel),
259
    ('prompt:toggle_insert_mode', _toggle_insert_mode),
260
    ('prompt:delete_char_before_caret', _delete_char_before_caret),
261
    ('prompt:delete_char_under_caret', _delete_char_under_caret),
262
    ('prompt:delete_text_after_caret', _delete_text_after_caret),
263
    ('prompt:delete_entire_text', _delete_entire_text),
264
    ('prompt:move_caret_to_left', _move_caret_to_left),
265
    ('prompt:move_caret_to_right', _move_caret_to_right),
266
    ('prompt:move_caret_to_head', _move_caret_to_head),
267
    ('prompt:move_caret_to_lead', _move_caret_to_lead),
268
    ('prompt:move_caret_to_tail', _move_caret_to_tail),
269
    ('prompt:assign_previous_text', _assign_previous_text),
270
    ('prompt:assign_next_text', _assign_next_text),
271
    ('prompt:assign_previous_matched_text', _assign_previous_matched_text),
272
    ('prompt:assign_next_matched_text', _assign_next_matched_text),
273
    ('prompt:paste_from_register', _paste_from_register),
274
    ('prompt:paste_from_default_register', _paste_from_default_register),
275
    ('prompt:yank_to_register', _yank_to_register),
276
    ('prompt:yank_to_default_register', _yank_to_default_register),
277
    ('prompt:insert_special', _insert_special),
278
    ('prompt:insert_digraph', _insert_digraph),
279
])
280