16
16
from prompt_toolkit .interface import AbortAction , AcceptAction
17
17
from prompt_toolkit .key_binding .manager import KeyBindingManager
18
18
from prompt_toolkit .utils import Callback
19
+ from prompt_toolkit .auto_suggest import AutoSuggestFromHistory
19
20
20
21
from awsshell .ui import create_default_layout
22
+ from awsshell .config import Config
23
+ from awsshell .keys import KeyManager
24
+ from awsshell .style import StyleFactory
25
+ from awsshell .toolbar import Toolbar
21
26
22
27
23
28
LOG = logging .getLogger (__name__ )
24
29
25
30
26
- def create_aws_shell (completer , history , docs ):
27
- return AWSShell (completer , history , docs )
31
+ def create_aws_shell (completer , model_completer , history , docs ):
32
+ return AWSShell (completer , model_completer , history , docs )
33
+
34
+
35
+ class InputInterrupt (Exception ):
36
+ """Stops the input of commands.
37
+
38
+ Raising `InputInterrupt` is useful to force a cli rebuild, which is
39
+ sometimes necessary in order for config changes to take effect.
40
+ """
41
+ pass
28
42
29
43
30
44
class AWSShell (object ):
31
- def __init__ (self , completer , history , docs ):
45
+ """Encapsulates the ui, completer, command history, docs, and config.
46
+
47
+ Runs the input event loop and delegates the command execution to either
48
+ the `awscli` or the underlying shell.
49
+
50
+ :type refresh_cli: bool
51
+ :param refresh_cli: Flag to refresh the cli.
52
+
53
+ :type config_obj: :class:`configobj.ConfigObj`
54
+ :param config_obj: Contains the config information for reading and writing.
55
+
56
+ :type config_section: :class:`configobj.Section`
57
+ :param config_section: Convenience attribute to access the main section
58
+ of the config.
59
+
60
+ :type model_completer: :class:`AWSCLIModelCompleter`
61
+ :param model_completer: Matches input with completions. `AWSShell` sets
62
+ and gets the attribute `AWSCLIModelCompleter.match_fuzzy`.
63
+
64
+ :type enable_vi_bindings: bool
65
+ :param enable_vi_bindings: If True, enables Vi key bindings. Else, Emacs
66
+ key bindings are enabled.
67
+
68
+ :type show_completion_columns: bool
69
+ param show_completion_columns: If True, completions are shown in multiple
70
+ columns. Else, completions are shown in a single scrollable column.
71
+
72
+ :type show_help: bool
73
+ :param show_help: If True, shows the help pane. Else, hides the help pane.
74
+
75
+ :type theme: str
76
+ :param theme: The pygments theme.
77
+ """
78
+
79
+ def __init__ (self , completer , model_completer , history , docs ):
32
80
self .completer = completer
81
+ self .model_completer = model_completer
33
82
self .history = history
34
83
self ._cli = None
35
84
self ._docs = docs
36
85
self .current_docs = u''
86
+ self .refresh_cli = False
87
+ self .load_config ()
88
+
89
+ def load_config (self ):
90
+ """Loads the config from the config file or template."""
91
+ config = Config ()
92
+ self .config_obj = config .load ('awsshellrc' )
93
+ self .config_section = self .config_obj ['aws-shell' ]
94
+ self .model_completer .match_fuzzy = self .config_section .as_bool (
95
+ 'match_fuzzy' )
96
+ self .enable_vi_bindings = self .config_section .as_bool (
97
+ 'enable_vi_bindings' )
98
+ self .show_completion_columns = self .config_section .as_bool (
99
+ 'show_completion_columns' )
100
+ self .show_help = self .config_section .as_bool ('show_help' )
101
+ self .theme = self .config_section ['theme' ]
102
+
103
+ def save_config (self ):
104
+ """Saves the config to the config file."""
105
+ self .config_section ['match_fuzzy' ] = self .model_completer .match_fuzzy
106
+ self .config_section ['enable_vi_bindings' ] = self .enable_vi_bindings
107
+ self .config_section ['show_completion_columns' ] = \
108
+ self .show_completion_columns
109
+ self .config_section ['show_help' ] = self .show_help
110
+ self .config_section ['theme' ] = self .theme
111
+ self .config_obj .write ()
37
112
38
113
@property
39
114
def cli (self ):
40
- if self ._cli is None :
41
- self ._cli = self .create_cli_interface ()
115
+ if self ._cli is None or self .refresh_cli :
116
+ self ._cli = self .create_cli_interface (self .show_completion_columns )
117
+ self .refresh_cli = False
42
118
return self ._cli
43
119
44
120
def run (self ):
45
121
while True :
46
122
try :
47
123
document = self .cli .run ()
48
124
text = document .text
125
+ except InputInterrupt :
126
+ pass
49
127
except (KeyboardInterrupt , EOFError ):
128
+ self .save_config ()
50
129
break
51
130
else :
52
131
if text .strip () in ['quit' , 'exit' ]:
@@ -78,38 +157,118 @@ def run(self):
78
157
p = subprocess .Popen (full_cmd , shell = True )
79
158
p .communicate ()
80
159
81
- def create_layout (self ):
160
+ def stop_input_and_refresh_cli (self ):
161
+ """Stops input by raising an `InputInterrupt`, forces a cli refresh.
162
+
163
+ The cli refresh is necessary because changing options such as key
164
+ bindings, single vs multi column menu completions, and the help pane
165
+ all require a rebuild.
166
+
167
+ :raises: :class:`InputInterrupt <exceptions.InputInterrupt>`.
168
+ """
169
+ self .refresh_cli = True
170
+ self .cli .request_redraw ()
171
+ raise InputInterrupt
172
+
173
+ def create_layout (self , display_completions_in_columns , toolbar ):
82
174
return create_default_layout (
83
175
self , u'aws> ' , reserve_space_for_menu = True ,
84
- display_completions_in_columns = True )
176
+ display_completions_in_columns = display_completions_in_columns ,
177
+ get_bottom_toolbar_tokens = toolbar .handler )
85
178
86
179
def create_buffer (self , completer , history ):
87
180
return Buffer (
88
181
history = history ,
182
+ auto_suggest = AutoSuggestFromHistory (),
183
+ enable_history_search = True ,
89
184
completer = completer ,
90
185
complete_while_typing = Always (),
91
186
accept_action = AcceptAction .RETURN_DOCUMENT )
92
187
93
- def create_application (self , completer , history ):
94
- key_bindings_registry = KeyBindingManager (
95
- enable_vi_mode = True ,
96
- enable_system_bindings = False ,
97
- enable_open_in_editor = False ).registry
188
+ def create_key_manager (self ):
189
+ """Creates the :class:`KeyManager`.
190
+
191
+ The inputs to KeyManager are expected to be callable, so we can't
192
+ use the standard @property and @attrib.setter for these attributes.
193
+ Lambdas cannot contain assignments so we're forced to define setters.
194
+
195
+ :rtype: :class:`KeyManager`
196
+ :return: A KeyManager with callables to set the toolbar options. Also
197
+ includes the method stop_input_and_refresh_cli to ensure certain
198
+ options take effect within the current session.
199
+ """
200
+
201
+ def set_match_fuzzy (match_fuzzy ):
202
+ """Setter for fuzzy matching mode.
203
+
204
+ :type match_fuzzy: bool
205
+ :param match_fuzzy: The match fuzzy flag.
206
+ """
207
+ self .model_completer .match_fuzzy = match_fuzzy
208
+
209
+ def set_enable_vi_bindings (enable_vi_bindings ):
210
+ """Setter for vi mode keybindings.
211
+
212
+ If vi mode is off, emacs mode is enabled by default by
213
+ `prompt_toolkit`.
214
+
215
+ :type enable_vi_bindings: bool
216
+ :param enable_vi_bindings: The enable Vi bindings flag.
217
+ """
218
+ self .enable_vi_bindings = enable_vi_bindings
219
+
220
+ def set_show_completion_columns (show_completion_columns ):
221
+ """Setter for showing the completions in columns flag.
222
+
223
+ :type show_completion_columns: bool
224
+ :param show_completion_columns: The show completions in
225
+ multiple columns flag.
226
+ """
227
+ self .show_completion_columns = show_completion_columns
228
+
229
+ def set_show_help (show_help ):
230
+ """Setter for showing the help container flag.
231
+
232
+ :type show_help: bool
233
+ :param show_help: The show help flag.
234
+ """
235
+ self .show_help = show_help
236
+
237
+ return KeyManager (
238
+ lambda : self .model_completer .match_fuzzy , set_match_fuzzy ,
239
+ lambda : self .enable_vi_bindings , set_enable_vi_bindings ,
240
+ lambda : self .show_completion_columns , set_show_completion_columns ,
241
+ lambda : self .show_help , set_show_help ,
242
+ self .stop_input_and_refresh_cli )
243
+
244
+ def create_application (self , completer , history ,
245
+ display_completions_in_columns ):
246
+ self .key_manager = self .create_key_manager ()
247
+ toolbar = Toolbar (
248
+ lambda : self .model_completer .match_fuzzy ,
249
+ lambda : self .enable_vi_bindings ,
250
+ lambda : self .show_completion_columns ,
251
+ lambda : self .show_help )
252
+ style_factory = StyleFactory (self .theme )
98
253
buffers = {
99
254
'clidocs' : Buffer (read_only = True )
100
255
}
101
256
102
257
return Application (
103
- layout = self .create_layout (),
258
+ layout = self .create_layout (display_completions_in_columns , toolbar ),
259
+ mouse_support = False ,
260
+ style = style_factory .style ,
104
261
buffers = buffers ,
105
262
buffer = self .create_buffer (completer , history ),
106
263
on_abort = AbortAction .RAISE_EXCEPTION ,
107
264
on_exit = AbortAction .RAISE_EXCEPTION ,
108
265
on_input_timeout = Callback (self .on_input_timeout ),
109
- key_bindings_registry = key_bindings_registry ,
266
+ key_bindings_registry = self . key_manager . manager . registry ,
110
267
)
111
268
112
269
def on_input_timeout (self , cli ):
270
+ if not self .show_help :
271
+ return
113
272
document = cli .current_buffer .document
114
273
text = document .text
115
274
LOG .debug ("document.text = %s" , text )
@@ -129,11 +288,13 @@ def on_input_timeout(self, cli):
129
288
initial_document = Document (self .current_docs , cursor_position = 0 ))
130
289
cli .request_redraw ()
131
290
132
- def create_cli_interface (self ):
291
+ def create_cli_interface (self , display_completions_in_columns ):
133
292
# A CommandLineInterface from prompt_toolkit
134
293
# accepts two things: an application and an
135
294
# event loop.
136
295
loop = create_eventloop ()
137
- app = self .create_application (self .completer , self .history )
296
+ app = self .create_application (self .completer ,
297
+ self .history ,
298
+ display_completions_in_columns )
138
299
cli = CommandLineInterface (application = app , eventloop = loop )
139
300
return cli
0 commit comments