Автодополнение текста в командной строке табуляцией.
Модуль
readline
может быть использован для улучшения интерактивных программ командной строки, чтобы упростить их использование. Он в основном используется для обеспечения завершения текста в командной строке или "автодополнения табуляцией". Настройки, сделанные с помощью этого модуля, влияют на поведение как интерактивной подсказки переводчика, так и подсказок, предлагаемых
встроенной функцией input()
.
Примечание: так как
модуль readline
взаимодействует с содержимым консоли, печать отладочных сообщений затрудняет просмотр того, что происходит в примере кода.
Содержание:
Конфигурирование.
Самый простой способ включить завершение табуляции - через вызов функции модуля parse_and_bind()
. Другие параметры могут быть установлены одновременно. В этом примере изменяются элементы управления редактированием для использования режима vi
вместо значения по умолчанию emacs
. Чтобы редактировать текущую строку ввода, нажмите ESC, затем используйте обычные клавиши навигации vi
, такие как j , k , l и h.
import readline
readline.parse_and_bind('tab: complete')
readline.parse_and_bind('set editing-mode vi')
while True:
line = input('Prompt ("stop" to quit): ')
if line == 'stop':
break
print (f'ENTERED: "{line}"')
Та же самая конфигурация может быть сохранена как инструкции в файле, читаемом библиотекой за один вызов. Если myreadline.rc
содержит:
tab: complete
set editing-mode vi
import readline
readline.read_init_file('myreadline.rc')
while True:
line = input('!("stop" to quit) Ввод текста: =>')
if line == 'stop':
break
print (f'Отправлено: "{line}"')
Автодополнение текста.
В качестве примера того, как построить завершение командной строки, можно взглянуть на программу, которая имеет встроенный набор возможных команд и использует завершение табуляции, когда пользователь вводит инструкции.
import readline
class SimpleCompleter():
def __init__(self, options):
self.options = sorted(options)
return
def complete(self, text, state):
response = None
if state == 0:
# Создание списка соответствий.
if text:
self.matches = [s
for s in self.options
if s and s.startswith(text)]
else:
self.matches = self.options[:]
# Вернуть элемент состояния из списка совпадений,
# если их много.
try:
response = self.matches[state]
except IndexError:
response = None
return response
def inputing():
line = ''
while line != 'stop':
line = input('!("stop" to quit) Ввод текста: => ')
print (f'Отправка: {line}')
# Регистрация класса 'SimpleCompleter'
readline.set_completer(SimpleCompleter(['start', 'stop', 'list', 'print']).complete)
# Регистрация клавиши `tab` для автодополнения
readline.parse_and_bind('tab: complete')
# Запрос текста
inputing()
Класс
SimpleCompleter
хранит список параметров, которые являются кандидатами на автозаполнение. Метод
complete()
предназначен для регистрации в
функции set_completer()
модуля
readline
в качестве источника дополнений. Аргументами являются строка
text
для завершения и значение
state
, указывающее, сколько раз функция была вызвана с одним и тем же текстом. Функция вызывается многократно, состояние увеличивается каждый раз. Класс
SimpleCompleter
должен вернуть строку, если есть кандидат для этого значения состояния, или
None
, если нет кандидатов. Реализация
метода complete()
ищет набор совпадений, когда состояние равно
0
, а затем возвращает все совпадения кандидатов по одному при последующих вызовах.
Если дважды нажать клавишу TAB , список параметров будет напечатан.
# Дважды нажата клавиша 'TAB'
!("stop" to quit) Ввод текста: =>
# list print start stop
# Введите 'li' и дважды нажмите 'TAB'
!("stop" to quit) Ввод текста: => list
# Введите 'pr' и дважды нажмите 'TAB'
!("stop" to quit) Ввод текста: => print
# Отправка: print
Доступ к буферу автодополнения текста.
Функции модуля readline
можно использовать для манипулирования текстом входного буфера. В примере выполняются команды с вложенными опциями. Метод complete()
должен посмотреть на положение завершения в буфере ввода, чтобы определить, является ли оно частью первого слова или более позднего слова. Если целью является первое слово, то в качестве кандидатов используются ключи словаря опций. Если это не первое слово, то первое слово используется для поиска кандидатов из словаря опций.
Будем обрабатывать три команды верхнего уровня, две из которых имеют подкоманды:
import readline
class BufferAwareCompleter():
def __init__(self, options):
self.options = options
self.current_candidates = []
return
def complete(self, text, state):
response = None
if state == 0:
# Создание списка соответствий.
origline = readline.get_line_buffer()
begin = readline.get_begidx()
end = readline.get_endidx()
being_completed = origline[begin:end]
words = origline.split()
if not words:
self.current_candidates = sorted(self.options.keys())
else:
try:
if begin == 0:
# Первое слово
candidates = self.options.keys()
else:
# Последующие слова
first = words[0]
candidates = self.options[first]
if being_completed:
# параметры сопоставления с частью ввода
self.current_candidates = [ w for w in candidates
if w.startswith(being_completed) ]
else:
# соответствие пустой строке, используются все кандидаты
self.current_candidates = candidates
except IndexError as err:
self.current_candidates = []
except KeyError as err:
self.current_candidates = []
try:
response = self.current_candidates[state]
except IndexError:
response = None
return response
def inputing():
line = ''
while line != 'stop':
line = input('!("stop" to quit) Ввод текста: => ')
print (f'Отправка: {line}')
# Регистрация класса 'BufferAwareCompleter'
readline.set_completer(BufferAwareCompleter(
{'list':['files', 'directories'],
'print':['byname', 'bysize'],
'stop':[],
}).complete)
# Регистрация клавиши `tab` для автодополнения
readline.parse_and_bind('tab: complete')
# Запрос текста
inputing()
Если запустить программу то можно увидеть что-то подобное:
# Дважды нажата клавиша 'TAB'
!("stop" to quit) Ввод текста: =>
list print stop
# Введите 'li' и дважды нажмите 'TAB'
!("stop" to quit) Ввод текста: => list
# Дважды нажата клавиша 'TAB'
!("stop" to quit) Ввод текста: => list
directories files
# Введите 'di' и дважды нажмите 'TAB'
!("stop" to quit) Ввод текста: => list directories
Отправка: list directories
История пользовательского ввода.
import readline
import os
HISTORY_FILENAME = 'completer.hist'
def get_history_items():
return [ readline.get_history_item(i)
for i in range(1, readline.get_current_history_length() + 1)
]
class HistoryCompleter(object):
def __init__(self):
self.matches = []
return
def complete(self, text, state):
response = None
if state == 0:
history_values = get_history_items()
if text:
self.matches = sorted(h
for h in history_values
if h and h.startswith(text))
else:
self.matches = []
try:
response = self.matches[state]
except IndexError:
response = None
return response
def inputing():
if os.path.exists(HISTORY_FILENAME):
readline.read_history_file(HISTORY_FILENAME)
print(f'Максимальная длина файла истории: {readline.get_history_length()}')
print(f'История запуска:{get_history_items()}')
try:
while True:
line = input('!("stop" to quit) Ввод текста: => ')
if line == 'stop':
break
if line:
print(f'Добавление "{line}" в файл истории.')
finally:
print(f'Конец записи истории: {get_history_items()}')
readline.write_history_file(HISTORY_FILENAME)
# Регистрация класса 'HistoryCompleter'
readline.set_completer(HistoryCompleter().complete)
# Регистрация клавиши `tab` для автодополнения
readline.parse_and_bind('tab: complete')
# Запрос текста
inputing()
Запустим программу:
Максимальная длина файла истории: -1
История запуска:[]
!("stop" to quit) Ввод текста: => print
Добавление "print" в файл истории.
!("stop" to quit) Ввод текста: => sort
Добавление "sort" в файл истории.
# Введите 'pr' и дважды нажмите 'TAB'
!("stop" to quit) Ввод текста: => print
# Введите 'so' и дважды нажмите 'TAB'
!("stop" to quit) Ввод текста: => sort
!("stop" to quit) Ввод текста: => stop
Конец записи истории: ['print', 'sort', 'stop']
Когда скрипт запускается во второй раз, вся история читается из файла.
Максимальная длина файла истории: -1
История запуска:['print', 'sort', 'stop']
# Введите 's' и дважды нажмите 'TAB'
!("stop" to quit) Ввод текста: => s
sort stop
Hooks (Хуки).
Есть несколько доступных уловок/хуков для запуска действий как часть последовательности взаимодействия. Хук немедленно вызывается перед печатью подсказки, после приглашения перед чтением текста от пользователя.
import readline
def startup_hook():
readline.insert_text('from startup_hook')
def pre_input_hook():
readline.insert_text(' from pre_input_hook')
readline.redisplay()
readline.set_startup_hook(startup_hook)
readline.set_pre_input_hook(pre_input_hook)
readline.parse_and_bind('tab: complete')
line = ''
while line != 'stop':
line = input('!("stop" to quit) Ввод текста: => ')
print (f'Отправка: {line}')
Любой хук является потенциально хорошим местом для использования функцию модуля
insert_text()
для изменения входного буфера.
!("stop" to quit) Ввод текста: => from startup_hook from pre_input_hook
Если буфер изменен внутри хука предварительного ввода, вам нужно вызвать функцию модуля
redisplay()
для обновления экрана.