Conversation
Repeated presses cycle through progressively older history entries, replacing the previously inserted argument, like readline yank-last-arg. With empty command line Alt+. keeps its old panel fast-find behavior.
|
В far исторически Alt+символ - начало быстрого поиска файла на панели и скрытые файлы, начинающиеся с точки, частое явление в linux. Поэтому при скрытых панелях предлагаемое использование Alt+. точно полезно. Но при видимости хотя бы одной панели уже неоднозначно - возможно вынести настройку в F9->Options->Panel settings или Command line settings, предлагая выбор: |
Options -> Command line settings, three modes: only when panels are hidden / when panels are hidden or command line is not empty (default, matches previous behavior and the Ctrl-D precedent) / always. Requested in review by akruphi.
|
Согласен, сделал настройкой. F9 → Параметры → Настройки командной строки, выпадающий список «Alt+. вставляет аргумент последней команды» с тремя вариантами:
По умолчанию — второй вариант: он повторяет уже существующий в far2l прецедент Ctrl-D (непустая строка → командная строка, иначе панель), и при пустой командной строке быстрый поиск по точке полностью сохраняется. При скрытых панелях (Ctrl-O) вставка аргумента работает во всех трёх режимах — быстрый поиск там всё равно недоступен. Все три режима покрыты автотестом на pty (обновлённый скрипт добавил в PR): вставка и циклирование при непустой строке, быстрый поиск при пустой, быстрый поиск при видимых панелях в режиме «только при скрытых», вставка после Ctrl-O. |
|
При непустой командной строке тоже не вариант, я например часто начинаю писать команду потом с помощью alt+ нахожу файлы которые вставляю по ctrl+enter. Все же не стоит из фара делать все остальное вместе взятое. |
Тогда вместо Alt+. вешать на Ctrl+Alt+.? Фича всё-таки полезная. |
|
@elfmz согласен, ваш сценарий (Alt+. → fast-find по файлу при непустой командной строке → Ctrl+Enter вставляет имя, дописанный текст команды остаётся) — это ровно тот кейс, который наш дефолт ломал. Спасибо, что описали конкретно. @akruphi да, Ctrl+Alt+. — чистое решение, а не просто обходной путь: комбинация в far2l сейчас ничем не занята, и её декодирование уже существует в keyboard.cpp рядом с Ctrl+. / Alt+. / Ctrl+Shift+. ( Сегодня-завтра пришлю обновление. Добавлю в справку эту строку: |
Alt+. returns to its historical panel fast-find behavior without exceptions. Ctrl+Alt+. always inserts/cycles the last argument of previous command. Config option and dialog entry removed as no longer needed. Help entry added (Eng/Rus/Ukr/Hun). Requested in review.
|
Сделано, как договорились:
Обновлённый автотест (pty, комбинация подаётся kitty-последовательностью Замечание о доставке клавиш: в GUI-версии Ctrl+Alt+. работает везде; в TTY-режиме — при kitty keyboard protocol / win32-input-mode / far2l extensions. В «голом» legacy-вводе xterm такая комбинация неотличима от Alt+. — это общее свойство Ctrl+Alt+символ в legacy tty, не специфичное для этой фичи. обновлённый скрипт автотеста (python3, требует pyte)#!/usr/bin/env python3
# Tests Ctrl+Alt+. yank-last-arg and that Alt+. is back to panel fast-find.
# Usage: python3 test_ctrlaltdot_pty.py /path/to/far2l
import os, pty, select, time, fcntl, termios, struct, sys, shutil, pwd
import pyte
FAR2L = sys.argv[1] if len(sys.argv) > 1 else 'far2l'
ROWS, COLS = 30, 100
CTRL_ALT_DOT = b'\x1b[46;7u' # kitty keyboard protocol: '.' (46) + Ctrl+Alt (1+2+4)
ALT_DOT = b'\x1b.'
results = []
real_home = pwd.getpwuid(os.geteuid()).pw_dir
cfg = real_home + '/.config/far2l'
shutil.rmtree(cfg, ignore_errors=True)
os.makedirs(cfg + '/settings')
with open(cfg + '/settings/config.ini', 'w') as f:
f.write('[Cmdline]\n') # profile exists -> no first-start dialogs
screen = pyte.Screen(COLS, ROWS)
stream = pyte.ByteStream(screen)
pid, fd = pty.fork()
if pid == 0:
os.environ['TERM'] = 'xterm-256color'
os.execv(FAR2L, ['far2l', '--tty'])
fcntl.ioctl(fd, termios.TIOCSWINSZ, struct.pack('HHHH', ROWS, COLS, 0, 0))
def feed(timeout=2.0):
end = time.time() + timeout
while time.time() < end:
r, _, _ = select.select([fd], [], [], 0.3)
if r:
try:
d = os.read(fd, 65536)
except OSError:
return
if not d:
return
stream.feed(d)
feed(4.0)
for _ in range(10):
if screen.display[ROWS - 1].lstrip().startswith('1'):
break
if any('Input instance index' in l for l in screen.display):
os.write(fd, b'\r')
feed(2.0)
else:
print('FAIL: panels did not appear')
sys.exit(1)
def cmdline():
return screen.display[ROWS - 2].rstrip()
def check(name, cond, detail):
results.append(cond)
print(('OK ' if cond else 'FAIL') + ' ' + name + ' -> ' + detail)
ARG = '/tmp/altdot_probe_dir'
os.makedirs(ARG, exist_ok=True)
os.write(fd, b'echo hello_from_history\r'); feed(2.5)
os.write(fd, b'ls ' + ARG.encode() + b'\r'); feed(2.5)
# 1-2: Ctrl+Alt+. inserts and cycles with non-empty line
os.write(fd, b'cp '); feed(1.0)
os.write(fd, CTRL_ALT_DOT); feed(1.5)
check('Ctrl+Alt+. inserts', ('cp ' + ARG) in cmdline(), cmdline())
os.write(fd, CTRL_ALT_DOT); feed(1.5)
check('Ctrl+Alt+. cycles', 'cp hello_from_history' in cmdline(), cmdline())
os.write(fd, b'\x1b'); feed(1.0) # clear line
# 3: Ctrl+Alt+. works on empty line too (always-on now)
os.write(fd, CTRL_ALT_DOT); feed(1.5)
check('Ctrl+Alt+. on empty line', ARG in cmdline(), cmdline() or '<empty>')
os.write(fd, b'\x1b'); feed(1.0)
# 4: Alt+. is back to panel fast-find even with non-empty command line
os.write(fd, b'cp '); feed(1.0)
os.write(fd, ALT_DOT); feed(1.5)
fastfind = any(('Поиск' in l) or ('Search' in l) for l in screen.display)
check('Alt+. restored to fast-find', fastfind and ARG not in cmdline(), cmdline())
os.write(fd, b'\x1b'); feed(1.0)
try:
os.kill(pid, 9)
except ProcessLookupError:
pass
print('---')
sys.exit(0 if all(results) else 1) |
1) wx 3.2.8/GTK does not generate EVT_CHAR for Ctrl+Alt+punctuation, so waiting for OnChar (workaround for wx #23421) swallowed such keystrokes; when Ctrl is pressed, OnKeyDown unicode value is trustworthy - enqueue right away. 2) wxGetKeyState() on wx 3.2.8 may falsely report a modifier as released while the just-received event carries it as pressed, making CheckForSuddenModifiersUp inject phantom modifier-up on key repeats within a held chord; trust the event over the poll. Both bugs affected all Ctrl+Alt+punctuation combos in GUI on recent distros, including the new Ctrl+Alt+. of this PR.
|
Обновление: при живой проверке фичи в GUI-сборке на wx 3.2.8 (Debian 13) обнаружились и починены два бага wx-бэкенда, из-за которых все комбинации Ctrl+Alt+<знак препинания> (не только новая Ctrl+Alt+.) не работали в GUI на свежих дистрибутивах. Оба не специфичны для этого PR, но без них фича в GUI мертва, поэтому фиксы включены сюда отдельным коммитом — если удобнее вынести в отдельный PR, вынесу. Баг 1: нажатие тонет в ожидании EVT_CHAR, который не приходит. Для layout-dependent клавиш с зажатым Alt обработчик OnKeyDown не ставит событие в очередь, полагаясь на последующий OnChar (обход wx issue #23421). Но wx 3.2.8/GTK не генерирует EVT_CHAR для Ctrl+Alt+пунктуации вовсе — нажатие пропадает. Подтверждено логами Баг 2: при повторных нажатиях в зажатом аккорде теряется Alt. Симптом-лесенка: первое Ctrl+Alt+. работает, второе — ничего (far2l получает голый Ctrl+. и тихо начинает запись макроса), третье — открывается диалог макрокоманды. Причина в Проверено: wx 3.2.8 (Debian 13) — одиночные и повторные Ctrl+Alt+. в зажатом аккорде работают, вставка и циклирование корректны; wx 3.2.4 — регрессий нет (лог: три идентичных |
|
Уточнение к фразе «TTY-режим не затронут (работал и работает)», чтобы не создать ложных ожиданий: речь о том, что фиксы не задели терминальный путь ввода, и обработка Ctrl+Alt+. в TTY исправна — когда терминал способен передать такую комбинацию (kitty keyboard protocol, far2l extensions — например, вложенный far2l или ssh из far2l). В терминалах с классическим вводом (VTE и т.п.) Ctrl+Alt+. на уровне самого терминала кодируется неотличимо от Alt+. — проверено |
|
В название PR стоит поправить на актуальный "Ctrl+Alt+." |
Ctrl+Alt+, cycles backwards through the last-argument history, in case Ctrl+Alt+. was pressed one time too many. Not Ctrl+Alt+Shift+.: on many X11 setups holding Ctrl+Alt triggers AltGr emulation, and the subsequent Shift press is consumed as an ISO_Level3_Shift chooser instead of reaching the application - confirmed by live testing.
|
Небольшое улучшение по итогам собственного использования: добавил обратное направление циклирования — Ctrl+Alt+, (запятая) — на случай, если Ctrl+Alt+. пролистал дальше, чем нужно. Комбинация уже декодируется существующим кодом ( Выбрал именно запятую, а не Shift поверх Ctrl+Alt: на многих X11-системах удержание Ctrl+Alt само по себе эмулирует AltGr, и последующее нажатие Shift перехватывается как переключатель уровня раскладки (ISO_Level3_Shift), не доходя до приложения как обычный модификатор. Запятая не накладывает третий модификатор и работает надёжно везде. Проверено pty-тестом: три Ctrl+Alt+. дают three → two → one, два Ctrl+Alt+, возвращают two → three, третье нажатие назад (уже на самой новой записи) — no-op, ничего не портит. |
|
Добавил автотест на всякий случай, если кто-то захочет проверить фичу независимо. Написал и в формате штатного JS-харнесса far2l ( Вместо этого — отдельный воспроизводимый скрипт (python3, требует test_yank_last_arg.py#!/usr/bin/env python3
"""
Automated pty test for the "bash-like yank-last-arg" cmdline feature:
Ctrl+Alt+. - insert/cycle forward through last-argument history
Ctrl+Alt+, - cycle backward (undo an over-shot Ctrl+Alt+.)
Alt+. - unaffected, retains its historical panel fast-find behavior
Usage: python3 test_yank_last_arg.py /path/to/far2l
Requires: pyte (pip install pyte --break-system-packages)
"""
import os, pty, select, time, fcntl, termios, struct, sys, shutil, pwd
import pyte
FAR2L = sys.argv[1] if len(sys.argv) > 1 else 'far2l'
ROWS, COLS = 30, 100
CMDLINE_ROW = ROWS - 2
# kitty keyboard protocol sequences: <unicode-codepoint>;<modifiers>u
# modifiers bitmask: shift=1, alt=2, ctrl=4 (encoded value = bitmask+1)
CTRL_ALT_DOT = b'\x1b[46;7u' # '.' (46) + ctrl+alt (4+2+1=7)
CTRL_ALT_COMMA = b'\x1b[44;7u' # ',' (44) + ctrl+alt
ALT_DOT = b'\x1b.' # legacy encoding: ESC + '.'
results = []
def setup_profile():
real_home = pwd.getpwuid(os.geteuid()).pw_dir
cfg = real_home + '/.config/far2l'
shutil.rmtree(cfg, ignore_errors=True)
os.makedirs(cfg + '/settings')
# empty section: profile exists (no first-start dialogs), option keeps default
with open(cfg + '/settings/config.ini', 'w') as f:
f.write('[Cmdline]\n')
def start_far2l():
screen = pyte.Screen(COLS, ROWS)
stream = pyte.ByteStream(screen)
pid, fd = pty.fork()
if pid == 0:
os.environ['TERM'] = 'xterm-256color'
os.execv(FAR2L, ['far2l', '--tty'])
fcntl.ioctl(fd, termios.TIOCSWINSZ, struct.pack('HHHH', ROWS, COLS, 0, 0))
return pid, fd, screen, stream
def feed(fd, stream, screen, timeout=2.0):
end = time.time() + timeout
while time.time() < end:
r, _, _ = select.select([fd], [], [], 0.3)
if r:
try:
d = os.read(fd, 65536)
except OSError:
return
if not d:
return
stream.feed(d)
def cmdline(screen):
return screen.display[CMDLINE_ROW].rstrip()
def check(name, cond, detail):
results.append(cond)
print(('OK ' if cond else 'FAIL') + ' ' + name + ' -> ' + detail)
def stop(pid, fd):
try:
os.kill(pid, 9)
except ProcessLookupError:
pass
try:
os.close(fd)
except OSError:
pass
def wait_for_panels(fd, stream, screen):
feed(fd, stream, screen, 4.0)
for _ in range(10):
keybar = screen.display[ROWS - 1]
if '2UserMn' in keybar: # real panel keybar; the About/Getting-Started
return True # dialog's keybar has this slot blank
if any('Input instance index' in l for l in screen.display):
os.write(fd, b'\r')
# closes a possible first-run "About/Getting Started" dialog
os.write(fd, b'\x1b')
feed(fd, stream, screen, 1.5)
return False
setup_profile()
pid, fd, screen, stream = start_far2l()
if not wait_for_panels(fd, stream, screen):
print('FAIL: panels did not appear')
stop(pid, fd)
sys.exit(1)
for cmd in (b'echo one\r', b'echo two\r', b'echo three\r'):
os.write(fd, cmd)
feed(fd, stream, screen, 2.0) # slow down to avoid a history-write race
os.write(fd, b'cp ')
feed(fd, stream, screen, 0.8)
os.write(fd, CTRL_ALT_DOT)
feed(fd, stream, screen, 0.8)
check('1x forward inserts most recent arg', 'cp three' in cmdline(screen), cmdline(screen))
os.write(fd, CTRL_ALT_DOT)
feed(fd, stream, screen, 0.8)
check('2x forward cycles to older arg', 'cp two' in cmdline(screen), cmdline(screen))
os.write(fd, CTRL_ALT_DOT)
feed(fd, stream, screen, 0.8)
check('3x forward cycles to oldest arg', 'cp one' in cmdline(screen), cmdline(screen))
os.write(fd, CTRL_ALT_COMMA)
feed(fd, stream, screen, 0.8)
check('1x backward returns to previous arg', 'cp two' in cmdline(screen), cmdline(screen))
os.write(fd, CTRL_ALT_COMMA)
feed(fd, stream, screen, 0.8)
check('2x backward returns to most recent arg', 'cp three' in cmdline(screen), cmdline(screen))
os.write(fd, CTRL_ALT_COMMA)
feed(fd, stream, screen, 0.8)
check('3x backward at newest entry is a no-op', 'cp three' in cmdline(screen), cmdline(screen))
os.write(fd, b'\x1b')
feed(fd, stream, screen, 1.0)
# Alt+. must retain its historical panel fast-find behavior, unaffected by
# the feature, even with a non-empty command line (this was the concern
# raised in code review that led to moving the feature off Alt+.)
os.write(fd, b'cp ')
feed(fd, stream, screen, 0.8)
os.write(fd, ALT_DOT)
feed(fd, stream, screen, 1.2)
fastfind_open = any(('Search' in l) or ('Поиск' in l) for l in screen.display)
check('Alt+. still opens panel fast-find (non-empty line)',
fastfind_open and 'cp three' not in cmdline(screen), cmdline(screen))
stop(pid, fd)
print('---')
sys.exit(0 if all(results) else 1)Запуск: |
|
По CI: прогон С тех пор ветка обновилась, так что сборка на актуальном коде ещё не проверялась. Запустите, пожалуйста, workflow, когда дойдут руки до PR. |
# Conflicts: # far2l/src/filepanels.cpp
Что это
Alt+
.в командной строке — как в bash/readline (yank-last-arg): вставляет в позицию курсора последний аргумент предыдущей команды; каждое следующее нажатие Alt+.замещает вставленное последним аргументом всё более старой команды из истории.Очень привычный из шелла приём, особенно экономит время на длинных путях.
Реализация
History::GetRecentStr(Index, strStr)— доступ к N-й записи истории с конца. Не использует и не меняетCurrentItem, поэтому навигация Ctrl-E/Ctrl-X не затрагивается.CommandLine::ProcessKey_YankLastArg()— извлечение последнего аргумента (аргументы в кавычках"..."/'...'берутся целиком) и циклирование; состояние цикла сбрасывается любой другой клавишей. Состояние Ctrl-End (LastCmdPartLength) при изменении строки инвалидируется..уходит в командную строку только когда она не пуста — по образцу уже существующей обработки Ctrl-D вfilepanels.cpp(diff при непустой строке / cmdline иначе). При пустой командной строке прежнее поведение Alt+.(fast-find по именам на точку) полностью сохранено.KEY_ALTDOTуже был определён в farkeys.h и декодировался из ESC-последовательности TTY (console/keyboard.cpp), но ни к чему не был привязан.Тестирование
.при пустой строке не изменился, Ctrl-E/Ctrl-X работают как раньше.Скрипт автотеста (python3, требует pyte)
Обновление по ревью (@akruphi): поведение Alt+
.сделано настраиваемым — F9 → Параметры → Настройки командной строки, три режима (только при скрытых панелях / при скрытых панелях или непустой строке — по умолчанию / всегда).Скрипт автотеста трёх режимов (python3, требует pyte)