Skip to content

Commit ef0bf2b

Browse files
committed
Add start of adding inline docs
Doesn't show just yet, but I think I have something working.
1 parent 6642eb0 commit ef0bf2b

File tree

2 files changed

+209
-8
lines changed

2 files changed

+209
-8
lines changed

awsshell/app.py

Lines changed: 207 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,28 @@
33
Main entry point to the AWS Shell.
44
55
"""
6-
from prompt_toolkit.buffer import Buffer
6+
from prompt_toolkit.document import Document
77
from prompt_toolkit.shortcuts import create_eventloop
8-
from prompt_toolkit.shortcuts import create_default_application
9-
from prompt_toolkit.shortcuts import create_default_layout
10-
from prompt_toolkit import Application, CommandLineInterface, AbortAction
11-
from prompt_toolkit.filters import Always
12-
from prompt_toolkit.interface import AcceptAction
8+
from prompt_toolkit.buffer import Buffer
9+
from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER
10+
from prompt_toolkit.filters import IsDone, HasFocus, Always, RendererHeightIsKnown, to_cli_filter
11+
from prompt_toolkit.interface import CommandLineInterface, Application, AbortAction, AcceptAction
1312
from prompt_toolkit.key_binding.manager import KeyBindingManager
13+
from prompt_toolkit.layout import Window, HSplit, VSplit, FloatContainer, Float
14+
from prompt_toolkit.layout.containers import ConditionalContainer
15+
from prompt_toolkit.layout.controls import BufferControl, TokenListControl, FillControl
16+
from prompt_toolkit.layout.dimension import LayoutDimension
17+
from prompt_toolkit.layout.menus import CompletionsMenu, MultiColumnCompletionsMenu
18+
from prompt_toolkit.layout.processors import PasswordProcessor, HighlightSearchProcessor, \
19+
HighlightSelectionProcessor, ConditionalProcessor
20+
from prompt_toolkit.layout.prompt import DefaultPrompt
21+
from prompt_toolkit.layout.screen import Char
22+
from prompt_toolkit.layout.toolbars import ValidationToolbar, SystemToolbar, ArgToolbar, SearchToolbar
23+
from prompt_toolkit.layout.utils import explode_tokens
24+
from prompt_toolkit.utils import Callback
25+
from pygments.token import Token
26+
27+
from awsshell.compat import text_type
1428

1529

1630
def create_layout():
@@ -32,22 +46,207 @@ def create_application(completer, history):
3246
enable_vi_mode=True,
3347
enable_system_bindings=False,
3448
enable_open_in_editor=False).registry
35-
49+
buffers = {
50+
'clidocs': Buffer(read_only=True)
51+
}
3652

3753
return Application(
3854
layout=create_layout(),
55+
buffers=buffers,
3956
buffer=create_buffer(completer, history),
4057
on_abort=AbortAction.RAISE_EXCEPTION,
4158
on_exit=AbortAction.RAISE_EXCEPTION,
59+
on_input_timeout=Callback(on_input_timeout),
4260
key_bindings_registry=key_bindings_registry,
4361
)
4462

4563

64+
def on_input_timeout(cli):
65+
buffer = cli.current_buffer
66+
document = buffer.document
67+
text = document.text
68+
cli.buffers['clidocs'].reset(initial_document=Document(text, cursor_position=0))
69+
70+
4671
def create_cli_interface(completer, history):
4772
# A CommandLineInterface from prompt_toolkit
4873
# accepts two things: an application and an
49-
# eventloop.
74+
# event loop.
5075
loop = create_eventloop()
5176
app = create_application(completer, history)
5277
cli = CommandLineInterface(application=app, eventloop=loop)
5378
return cli
79+
80+
81+
def create_default_layout(message='', lexer=None, is_password=False,
82+
reserve_space_for_menu=False,
83+
get_prompt_tokens=None, get_bottom_toolbar_tokens=None,
84+
display_completions_in_columns=False,
85+
extra_input_processors=None, multiline=False):
86+
"""
87+
Generate default layout.
88+
Returns a ``Layout`` instance.
89+
90+
:param message: Text to be used as prompt.
91+
:param lexer: Lexer to be used for the highlighting.
92+
:param is_password: `bool` or `CLIFilter`. When True, display input as '*'.
93+
:param reserve_space_for_menu: When True, make sure that a minimal height is
94+
allocated in the terminal, in order to display the completion menu.
95+
:param get_prompt_tokens: An optional callable that returns the tokens to be
96+
shown in the menu. (To be used instead of a `message`.)
97+
:param get_bottom_toolbar_tokens: An optional callable that returns the
98+
tokens for a toolbar at the bottom.
99+
:param display_completions_in_columns: `bool` or `CLIFilter`. Display the
100+
completions in multiple columns.
101+
:param multiline: `bool` or `CLIFilter`. When True, prefer a layout that is
102+
more adapted for multiline input. Text after newlines is automatically
103+
indented, and search/arg input is shown below the input, instead of
104+
replacing the prompt.
105+
"""
106+
assert isinstance(message, text_type)
107+
assert get_bottom_toolbar_tokens is None or callable(get_bottom_toolbar_tokens)
108+
assert get_prompt_tokens is None or callable(get_prompt_tokens)
109+
assert not (message and get_prompt_tokens)
110+
111+
display_completions_in_columns = to_cli_filter(display_completions_in_columns)
112+
multiline = to_cli_filter(multiline)
113+
114+
if get_prompt_tokens is None:
115+
get_prompt_tokens = lambda _: [(Token.Prompt, message)]
116+
117+
get_prompt_tokens_1, get_prompt_tokens_2 = _split_multiline_prompt(get_prompt_tokens)
118+
# Create processors list.
119+
# (DefaultPrompt should always be at the end.)
120+
input_processors = [ConditionalProcessor(
121+
# By default, only highlight search when the search
122+
# input has the focus. (Note that this doesn't mean
123+
# there is no search: the Vi 'n' binding for instance
124+
# still allows to jump to the next match in
125+
# navigation mode.)
126+
HighlightSearchProcessor(preview_search=Always()),
127+
HasFocus(SEARCH_BUFFER)),
128+
HighlightSelectionProcessor(),
129+
ConditionalProcessor(PasswordProcessor(), is_password)]
130+
131+
if extra_input_processors:
132+
input_processors.extend(extra_input_processors)
133+
134+
# Show the prompt before the input (using the DefaultPrompt processor.
135+
# This also replaces it with reverse-i-search and 'arg' when required.
136+
# (Only for single line mode.)
137+
input_processors.append(ConditionalProcessor(
138+
DefaultPrompt(get_prompt_tokens), ~multiline))
139+
140+
# Create bottom toolbar.
141+
if get_bottom_toolbar_tokens:
142+
toolbars = [ConditionalContainer(
143+
Window(TokenListControl(get_bottom_toolbar_tokens,
144+
default_char=Char(' ', Token.Toolbar)),
145+
height=LayoutDimension.exact(1)),
146+
filter=~IsDone() & RendererHeightIsKnown())]
147+
else:
148+
toolbars = []
149+
150+
def get_height(cli):
151+
# If there is an autocompletion menu to be shown, make sure that our
152+
# layout has at least a minimal height in order to display it.
153+
if reserve_space_for_menu and not cli.is_done:
154+
return LayoutDimension(min=8)
155+
else:
156+
return LayoutDimension()
157+
158+
# Create and return Layout instance.
159+
return HSplit([
160+
ConditionalContainer(
161+
Window(
162+
TokenListControl(get_prompt_tokens_1),
163+
dont_extend_height=True),
164+
filter=multiline,
165+
),
166+
VSplit([
167+
# In multiline mode, the prompt is displayed in a left pane.
168+
ConditionalContainer(
169+
Window(
170+
TokenListControl(get_prompt_tokens_2),
171+
dont_extend_width=True,
172+
),
173+
filter=multiline,
174+
),
175+
# The main input, with completion menus floating on top of it.
176+
FloatContainer(
177+
Window(
178+
BufferControl(
179+
input_processors=input_processors,
180+
lexer=lexer,
181+
# Enable preview_search, we want to have immediate feedback
182+
# in reverse-i-search mode.
183+
preview_search=Always()),
184+
get_height=get_height,
185+
),
186+
[
187+
Float(xcursor=True,
188+
ycursor=True,
189+
content=CompletionsMenu(
190+
max_height=16,
191+
scroll_offset=1,
192+
extra_filter=HasFocus(DEFAULT_BUFFER) &
193+
~display_completions_in_columns)),
194+
Float(xcursor=True,
195+
ycursor=True,
196+
content=MultiColumnCompletionsMenu(
197+
extra_filter=HasFocus(DEFAULT_BUFFER) &
198+
display_completions_in_columns,
199+
show_meta=Always()))
200+
]
201+
),
202+
]),
203+
ConditionalContainer(
204+
content=Window(height=LayoutDimension.exact(1),
205+
content=FillControl(u'\u2500', token=Token.Separator)),
206+
#filter=HasSignature(python_input) & ShowDocstring(python_input) & ~IsDone()),
207+
filter=~IsDone()),
208+
ConditionalContainer(
209+
content=Window(
210+
BufferControl(
211+
buffer_name='clidocs',
212+
),
213+
height=LayoutDimension(max=12)),
214+
#filter=HasSignature(python_input) & ShowDocstring(python_input) & ~IsDone(),
215+
filter=~IsDone(),
216+
),
217+
ValidationToolbar(),
218+
SystemToolbar(),
219+
220+
# In multiline mode, we use two toolbars for 'arg' and 'search'.
221+
ConditionalContainer(ArgToolbar(), multiline),
222+
ConditionalContainer(SearchToolbar(), multiline),
223+
] + toolbars)
224+
225+
226+
def _split_multiline_prompt(get_prompt_tokens):
227+
"""
228+
Take a `get_prompt_tokens` function. and return two new functions instead.
229+
One that returns the tokens to be shown on the lines above the input, and
230+
another one with the tokens to be shown at the first line of the input.
231+
"""
232+
def before(cli):
233+
result = []
234+
found_nl = False
235+
for token, char in reversed(explode_tokens(get_prompt_tokens(cli))):
236+
if char == '\n':
237+
found_nl = True
238+
elif found_nl:
239+
result.insert(0, (token, char))
240+
return result
241+
242+
def first_input_line(cli):
243+
result = []
244+
for token, char in reversed(explode_tokens(get_prompt_tokens(cli))):
245+
if char == '\n':
246+
break
247+
else:
248+
result.insert(0, (token, char))
249+
return result
250+
251+
return before, first_input_line
252+

awsshell/compat.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,7 @@
44

55
if PY3:
66
from html.parser import HTMLParser
7+
text_type = str
78
else:
89
from HTMLParser import HTMLParser
10+
text_type = unicode

0 commit comments

Comments
 (0)