| Server IP : 3.96.16.70 / Your IP : 216.73.216.15 Web Server : Apache System : Linux ip-172-31-26-103.ca-central-1.compute.internal 6.1.163-186.299.amzn2023.x86_64 #1 SMP PREEMPT_DYNAMIC Tue Feb 24 16:35:42 UTC 2026 x86_64 User : ec2-user ( 1000) PHP Version : 8.4.18 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /lib/python3.9/site-packages/awscli/autoprompt/ |
Upload File : |
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
import os
from prompt_toolkit.buffer import Buffer
from prompt_toolkit.document import Document
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.key_binding.bindings.focus import focus_next
from prompt_toolkit.keys import Keys
from prompt_toolkit.layout import Float, FloatContainer, HSplit, VSplit, Window
from prompt_toolkit.layout.controls import BufferControl
from prompt_toolkit.layout.dimension import Dimension
from prompt_toolkit.layout.layout import ConditionalContainer, Layout
from prompt_toolkit.layout.menus import (
CompletionsMenu,
MultiColumnCompletionsMenu,
)
from prompt_toolkit.layout.processors import BeforeInput
from prompt_toolkit.widgets import SearchToolbar, VerticalLine
from awscli.autoprompt.filters import (
doc_section_visible,
doc_window_has_focus,
input_buffer_has_focus,
is_history_mode,
is_multi_column,
is_one_column,
output_section_visible,
)
from awscli.autoprompt.history import HistoryCompleter, HistoryDriver
from awscli.autoprompt.widgets import (
DebugPanelWidget,
HelpPanelWidget,
TitleLine,
ToolbarWidget,
)
class PrompterKeyboardInterrupt(KeyboardInterrupt):
pass
class CLIPromptBuffer(Buffer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._completer = self.completer
self._history_completer = HistoryCompleter(self)
self.history_mode = False
def switch_history_mode(self):
self.history_mode = not self.history_mode
if self.history_mode:
self.completer = self._history_completer
self.start_completion()
else:
self.completer = self._completer
class PromptToolkitFactory:
DIMENSIONS = {
'doc_window_height_max': 40,
'doc_window_height_pref': 30,
'input_buffer_height_min': 10,
'menu_height_max': 16,
'menu_scroll_offset': 2,
}
def __init__(self, completer, history_driver=None):
self._completer = completer
self._history_driver = history_driver
@property
def history_driver(self):
if self._history_driver is None:
cache_dir = os.path.expanduser(
os.path.join('~', '.aws', 'cli', 'cache')
)
history_filename = os.path.join(cache_dir, 'prompt_history.json')
self._history_driver = HistoryDriver(history_filename)
return self._history_driver
def create_input_buffer(self, on_text_changed_callback=None):
return CLIPromptBuffer(
name='input_buffer',
completer=self._completer,
history=self.history_driver,
complete_while_typing=True,
on_text_changed=on_text_changed_callback,
)
def create_doc_buffer(self):
return Buffer(name='doc_buffer', read_only=True)
def create_output_buffer(self):
return Buffer(name='output_buffer', read_only=True)
def create_input_buffer_container(self, input_buffer):
return FloatContainer(
Window(
BufferControl(
buffer=input_buffer,
input_processors=[BeforeInput('> aws ')],
),
height=Dimension(
min=self.DIMENSIONS['input_buffer_height_min']
),
wrap_lines=True,
),
[
Float(
xcursor=True,
ycursor=True,
content=MultiColumnCompletionsMenu(
extra_filter=is_multi_column
),
),
Float(
xcursor=True,
ycursor=True,
content=CompletionsMenu(
extra_filter=is_one_column,
max_height=self.DIMENSIONS['menu_height_max'],
scroll_offset=self.DIMENSIONS['menu_scroll_offset'],
),
),
],
)
def create_bottom_panel(self, doc_window, output_window):
return VSplit(
[
ConditionalContainer(doc_window, doc_section_visible),
ConditionalContainer(
VerticalLine(),
output_section_visible & doc_section_visible,
),
ConditionalContainer(output_window, output_section_visible),
]
)
def create_searchable_window(self, title, output_buffer):
search_field = SearchToolbar()
return HSplit(
[
TitleLine(title),
Window(
content=BufferControl(
buffer=output_buffer,
search_buffer_control=search_field.control,
),
height=Dimension(
max=self.DIMENSIONS['doc_window_height_max'],
preferred=self.DIMENSIONS['doc_window_height_pref'],
),
wrap_lines=True,
),
search_field,
]
)
def create_layout(
self,
on_input_buffer_text_changed=None,
input_buffer_container=None,
doc_window=None,
output_window=None,
):
# This is the main layout, which consists of:
# - The main input buffer with completion menus floating on top of it.
# - A separating line between the input buffer and the doc window.
# - A doc window to hold documentation.
# - A separating line between the doc window and the toolbar.
# - A toolbar denoting key bindings.
# - A help panel
# - A debug panel in case debug mode enabled
if input_buffer_container is None:
input_buffer = self.create_input_buffer(
on_input_buffer_text_changed
)
input_buffer_container = self.create_input_buffer_container(
input_buffer
)
if doc_window is None:
doc_buffer = self.create_doc_buffer()
doc_window = self.create_searchable_window('Doc panel', doc_buffer)
if output_window is None:
output_buffer = self.create_output_buffer()
output_window = self.create_searchable_window(
'Output panel', output_buffer
)
bottom_panel = self.create_bottom_panel(doc_window, output_window)
return Layout(
HSplit(
[
VSplit(
[
HSplit([input_buffer_container, bottom_panel]),
HelpPanelWidget(),
DebugPanelWidget(),
]
),
ToolbarWidget(),
]
)
)
def create_key_bindings(self):
return PromptToolkitKeyBindings()
class PromptToolkitKeyBindings:
def __init__(self, keybindings=None):
if keybindings is None:
keybindings = KeyBindings()
self._kb = keybindings
@self._kb.add(Keys.Enter, filter=input_buffer_has_focus)
def _(event):
buffer = event.app.current_buffer
is_completing = getattr(buffer, 'complete_state', False)
current_document = buffer.document
if not is_completing:
buffer.append_to_history()
event.app.exit()
else:
# I didn't find better way to make a choice and close prompter
# than reset buffer and change it to selected part
if buffer.history_mode:
buffer.switch_history_mode()
buffer.cancel_completion()
event.app.current_buffer.reset()
updated_document = Document(
text=current_document.text,
cursor_position=current_document.cursor_position,
)
buffer.set_document(updated_document)
# If prompter suggested us something ended with slash and
# started with 'file://' or 'fileb://' it should be path ended
# with directory then we run completion again
cur_word = current_document.get_word_under_cursor(WORD=True)
if cur_word.endswith(os.sep) and cur_word.startswith(
('file://', 'fileb://')
):
buffer.start_completion()
@self._kb.add(Keys.Escape, filter=is_history_mode)
def _(event):
buffer = event.app.current_buffer
buffer.cancel_completion()
buffer.switch_history_mode()
@self._kb.add(' ', filter=is_history_mode)
def _(event):
"""Exit from history mode if something was selected or
just add space to the end of the text and keep suggesting"""
buffer = event.app.current_buffer
if (
buffer.complete_state
and buffer.complete_state.current_completion
):
buffer.switch_history_mode()
buffer.insert_text(' ')
@self._kb.add(Keys.F3)
def _(event):
current_buffer = event.app.current_buffer
if current_buffer.name == 'doc_buffer':
layout = event.app.layout
input_buffer = layout.get_buffer_by_name('input_buffer')
layout.focus(input_buffer)
event.app.show_doc = not getattr(event.app, 'show_doc')
@self._kb.add(Keys.F4)
def _(event):
event.app.multi_column = not event.app.multi_column
@self._kb.add(Keys.F5)
def _(event):
event.app.show_output = not event.app.show_output
if event.app.current_buffer.name == 'output_buffer':
layout = event.app.layout
input_buffer = layout.get_buffer_by_name('input_buffer')
layout.focus(input_buffer)
@self._kb.add(Keys.F1)
def _(event):
event.app.show_help = not event.app.show_help
@self._kb.add(Keys.ControlR, filter=input_buffer_has_focus)
def _(event):
buffer = event.app.current_buffer
buffer.cancel_completion()
buffer.switch_history_mode()
@self._kb.add(Keys.ControlC)
@self._kb.add(Keys.ControlD)
def _(event):
layout = event.app.layout
input_buffer = layout.get_buffer_by_name('input_buffer')
text = f'> aws {input_buffer.document.text}'
event.app.exit(exception=PrompterKeyboardInterrupt(text))
@self._kb.add(Keys.F2)
def _(event):
focus_next(event)
@self._kb.add('q', filter=~input_buffer_has_focus)
def _(event):
layout = event.app.layout
input_buffer = layout.get_buffer_by_name('input_buffer')
layout.focus(input_buffer)
@self._kb.add('w', filter=doc_window_has_focus)
def _(event):
# Scroll up one unit equal to the height of the doc window.
# Note: The scroll isn't exactly equal to the height of the doc
# window. As there doesn't appear to be a way to get the exact
# height of the doc window, the next best we can do is to grab the
# preferred height of the Window.
window_height = event.app.layout.current_window.height.preferred
event.app.current_buffer.cursor_up(window_height)
@self._kb.add('z', filter=doc_window_has_focus)
def _(event):
# Scroll down one unit equal to the height of the doc window.
# Note: The scroll isn't exactly equal to the height of the doc
# window. As there doesn't appear to be a way to get the exact
# height of the doc window, the next best we can do is to grab the
# preferred height of the Window.
window_height = event.app.layout.current_window.height.preferred
event.app.current_buffer.cursor_down(window_height)
@self._kb.add('k', filter=doc_window_has_focus)
def _(event):
# Scroll up one line in the doc window.
event.app.layout.current_buffer.cursor_up(1)
@self._kb.add('j', filter=doc_window_has_focus)
def _(event):
# Scroll down one line in the doc window.
event.app.layout.current_buffer.cursor_down(1)
@self._kb.add('g', filter=doc_window_has_focus)
def _(event):
# Go to top of the doc window.
document = event.app.layout.current_buffer.document
current_row = document.cursor_position_row
# `cursor_up() throws an error if its input is < 1`
if current_row >= 1:
event.app.layout.current_buffer.cursor_up(current_row)
@self._kb.add('G', filter=doc_window_has_focus)
def _(event):
# Go to bottom of the doc window.
document = event.app.layout.current_buffer.document
current_row = document.cursor_position_row
num_rows = document.line_count
lines_to_bottom_delta = num_rows - current_row
event.app.layout.current_buffer.cursor_down(lines_to_bottom_delta)
@property
def keybindings(self):
return self._kb