Рабочий стол OSX: консистентные горячие клавиши

Сергей хочет открыть Slack. Запускает Spotlight и в нем начинает писать «Slack». Печатается «Ыдфсл». Ок, нужно стереть «Ыдфсл», а для этого эксплуатировать лучезапястный сустав в попытке дотянуться до BACKSPACE… Когда Slack наконец-то (!) открылся, Сергей пишет сообщение и хочет вставить ссылку в текст, используя привычный , но Slack начинает поиск по личным сообщениям.

bced8859b81661d2b028442ba15c597c.png

Как помочь Сергею, мы сейчас и разберемся. Статья не является туториалом — скорее, набором идей, которые желающий сможет забрать себе.

Проблемы, о решении которых пойдет речь

  1. Долго добираться до нужного приложения.

  2. Болят кисти из-за постоянных сгибов, когда тянешься мизинцем до BACKSPACE, ENTER, =, ], SHIFT, ; …

  3. Во всех приложениях разные горячие клавиши.

Словарь терминов и сокращений

  • Homerow — клавиши, на которых лежат пальцы в базовой позиции: asdfjkl;

  • Hotkey — горячие клавиши;

  • Mapping (маппинг) — соответствие нажимаемых клавиш действию.

  •  — значит, что клавиши нажаты вместе, причем KEY_1 нажимается первой (является модификатором).

  • Vim (вим) — модальный текстовый редактор, идеи которого легко переносимы в другие приложения.

  • Normal mode — режим в виме, который позволяет манипулировать курсором. Например, нажатие на «j» перемащает курсор на строчку вниз, а не вставляет символ «j».

  • Insert mode — режим ввода текста в виме, когда нажатие на «j» пишет «j».

  • Buffer — абстракция хранения открытого файла в виме.

  • Буфер обмена — место хранения скопированного (clipboard).

  • Slack — программа, hotkeys которой поручили проектировать тому, кто никогда не пользовалсявв hotkeys других программ.

Инструменты

  • Karabiner для настройки глобальных горячих клавиш.

  • Neovim + Neovide для работы с текстом.

  • VimiumC для vim-подобных горячих клавиш в браузере и pdf-файлах.

  • WezTerm для возможности использовать normal и visual modes вима в терминале.

  • Alfred — умный поиск с консистентной раскладкой, хранение регистров.

Открываем то, что нужно, минимальным количеством действий

Поясню, что имеется в виду, через описание двух задач, с которыми часто сталкивается Сергей.

Первая задача: перевести слово с английского на русский. Ее обычно решают так: переходят в браузер и в нем уже открывают переводчик. Когда переводчик загрузится, пишут слово.

Вторая задача: загуглить вопрос. Открывают браузер. Открывают новый таб. В нем пишут запрос.

В обоих примерах есть шанс наткнуться не на ту раскладку. Например, вы можете начать писать переводимое слово на английском с выставленной русской раскладкой. Или же начать писать «Googl» в Spotlight, чтобы обнаружить «Пщщпд».

Один мой приятель сделал hotkey, вроде , чтобы «прыгнуть» в браузер из любого места, но на мой взгляд, это неверный подход, так как решение не универсально, занимает hotkey, не решает проблему случайной раскладки и требует на одно действие больше, чем то решение, которое опишу ниже.

Alfred — то же самое, что Spotlight, только позволяет гарантировать, что английская раскладка уже выбрана: опция Force Keyboard в Advanced settings. Покажу, как с Alfred решаются описанные выше задачи:

Первая задача: запускаем Aflred нажатием , пишем «e ». Открывается браузер c сайтом-переводчиком с введенным запросом. Чтобы «e » работало как запрос в переводчик, нужно всего лишь добавить Custom search шаблон. Я использую Яндекс для этого: «https://translate.yandex.ru/? text={query}».

Обратите внимание, насколько короче стал workflow. Было:

  1. Открыть браузер.

  2. Зайти на Google Translate.

  3. Подождать его загрузку (если не настроен custom search engine или другое решение).

  4. Написать текст.

  5. Дождаться результата.

Стало:

  1. Открыть Alfred.

  2. Написать текст.

  3. Дождаться результата.

С переводом русского слова нужно будет после открытия Alfred поменять раскладку, но я все равно вижу плюс в том, что действие детерминировано — вы всегда оказываетесь в Alfred с английской раскладкой. Не будет случаев, когда ваш «поток» прервется из-за неожиданного ввода не того, что вы хотели.

Вторая задача: запускаем Aflred, пишем запрос. Открывается Google в браузере с введенным запросом. Aflred сам понимает, что текст без команд нужно загуглить.

Перечислю, для чего еще Aflred является универсальной точкой входа (все работает по умолчанию, а если нет, то легко гуглится):

  • Поиск по истории браузера.

  • Поиск по закладкам браузера.

  • Глобальный поиск по внутреннему содержанию файлов (включая PDF).

  • Открытие программы по названию.

  • Калькулятор.

  • Общий с вимом буффер обмена.

Последний пункт заслуживает особого обсуждения.

Консистентный буфер обмена

Большинство людей, как и Сергей, живет в парадигме одной ячейки буффера обмена. Вим же помещает последние 9 «копирований» в регистры, а так же позволяет использовать в качестве регистра любой символ (есть исключения, но не будем об этом).

Регистры вима — удобная фича, но она не доступна за его пределами. Alfred по умолчанию складывает все скопированное к себе, и позволяет искать по нему (Clipboard History).

acfcb99f548281a33727d033b95a7571.png

Сюда же сохраняются сделанные скриншоты. Вы можете настроить, сколько элементов может храниться и как долго.

Я пришел к тому, что чаще всего для копирования и вставки нескольких вещей за раз удобнее использовать Alfred даже в виме. Чтобы это работало, надо всего лишь написать маппинг . Как это сделать, подробнее написано в Пример1 и Пример2 ниже.

Расположение клавиш не должно приносить боль

Когда я пользуюсь дефолтной клавиатурой на макбуке, мои руки расположены под углом к ней:

aa4d1e0eb4122c29356272e5b4a803fd.png

Когда нужно нажать BACKSPACE, ENTER, SHIFT, SLASH, =, приходится делать небольшой сгиб в лучезапястном суставе (так же делал и Сергей во введении)

cc4d7eab20319a297eded302a80f5a79.png

Об этой и множестве других проблем хорошо написано тут. Идея из статьи (поддерживаю обеими руками, благо, я перенастроил клавиатуру, — и они целы):

в мелких движениях кистью даже лишних два сантиметра — это разница между жизнью и смертью.

Решается проблема двумя способами — специальная клавиатура и/или ремаппинг клавиш. Мне важно иметь возможность работать на ноутбуке, и всегда носить с собой клавиатуру не очень удобно, остается модификация клавиш.

Поможет в этом Karabiner — могущественный и стабильный кастомизатор, как пишут на сайте. Вопрос в том, какие клавиши исправлять. Мой подход — сперва заменить то, без чего жить нельзя, вроде BACKSPACE и ENTER, затем, по мере столкновения с неудобствами, добавлять новые маппинги.

Тут важен один принцип — чем чаще используется клавиша, тем ближе она должна быть к homerow.

В 2017 я об этом не подумал, и использовал функциональные клавиши — например, backspace поставил на f9. Возникло 2 проблемы — на макбуках исчезли функциональные клавиши, да и тянуться до их отсутствия них долго и неудобно. Тем не менее, привычка жать на backspace в стороне f9 безымянным пальцем правой руки осталась, и это повлияло на мою текущую конфигурацию клавиатуры.

Рассказываю об этом, чтобы вы настроили раскладку под себя, например, как это сделано тут:

Мой подход точно такой же. Часть маппингов — попытка избавиться от сгибов запястья с соблюдением принципа близости к homerow:

—> BACKSPACE,
—> RIGHT_SHIFT,
—> LEFT_SHIFT,
—> ENTER,
—> =,
—> , чтобы получить |.
CAPSLOCK —> ESCAPE
ESCAPE —> CAPSLOCK, потому что ESCAPE нажимается на несколько порядков чаще.

Часто приходится пользоваться стрелками — например, чтобы выделить слово левее курсора или сдвинуть фокус в Finder. Для этого использую относительно популярное решение (уже более пяти лет не представляю свою жизнь без него): RIGHT_CMD + клавиши hjkl означают стрелки (как в виме). Настраивается включением доступной по умолчанию в Karabiner модификацией — «Change right_command+hjkl to arrow keys».

—> "+", потому что + по умолчанию — это , а мои SHIFT и = — получаются другими сочетаниями с CMD.

Если вы программист, то скобки определенно должны быть на homerow:

—> "(",
—> ")",
—> "{",
—> "}".

Используется RIGHT, потому что LEFT уже занят привычными сочетаниями, например,  — поиск.

—> "[",
—> "]",

Как описывать и ставить модификации, можно посмотреть в документации, там довольно простой UI. Можно попробовать поискать и что-то готовое, например, биндинги как в Emacs. Ниже по тексту я еще вернусь к Karabiner с детальными примерами.

В целом, это все. Добавлю только, что можно столкнуться с проблемой приоритета (документация), о которой можно не читать, пока все и так работает. В моем случае, сломался (стрелка налево) + ALT в виме, чтобы двигать курсор через слово. Решилось добавлением по приоритету выше, чем .

Еще одна потенциальная проблем в том, что Karabiner-модификации программы в фокусе применяются и к Alfred. Например, у меня для Neovide меняется на , и если во время использования Neovide открыть Alfred, то  работать не будет. Исправляется в настройках Alfred: Appearance —> внизу: Options —> Focusing —> Compatibility Mode.

Везде должны быть одни и те же горячие клавиши

По умолчанию это совсем не так. Несколько примеров, которые первыми приходят на ум:

  1. В виме удалить слово в insert mode — , в любом другом месте — .

  2. В Slack вставить ссылку — , а практически везде — .

  3. Отмена отмены в большинстве программ , а в виме .

Во время пользования компьютером я хочу, чтобы мое состояние потока не спотыкалось о то, в каком я сейчас приложении и какой hotkey используется конкретно тут. Хочется вынести нажатия клавиш на уровень бессознательной привычки. Если надо открыть новый таб, жмем и таб открывается — не важно, вим это, терминал, идея или браузер.

Не всегда можно изменить hotkey по умолчанию, например, Slack не дает простого способа это сделать. Я безуспешно пытался настроить это несколько лет назад. Недавно попробовал «Interactivity & Shortcuts», но постоянно натыкаюсь на «There’s been a glitch…» page.

Иногда же нажатие вообще не доходит до программы, как в случае с вимом, который открывается внутри терминала.

Пример 1. Вставка ссылки и в Slack, и в Vim, как везде

В Яндекс Почте, Gmail, Google docs, Telegram, MS Word, MS Excel, Notion, Github, Bitbucket, на Хабре, в конце-то концов, для вставки ссылки в текст используется . Давайте настроим то же самое и в Slack.

Karabiner позволяет «на лету» менять клавиши, которые были нажаты. Вот пример перевода — только для приложения Slack — в :

slack.json

{
  "title": "Slack",
  "rules": [
    {
      "description": "Insert link",
      "manipulators": [
        {
          "type": "basic",
          "from": {
            "key_code": "k",
            "modifiers": {
              "mandatory": [ "left_command" ]
            }
          },
          "to": [
            {
              "key_code": "u",
              "modifiers": [ "left_shift", "left_command" ]
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_if",
              "bundle_identifiers": [
                "^com.tinyspeck.slackmacgap$"
              ]
            }
          ]
        }
      ]
    }
  ]
}

Узнать название frontmost_application можно с помощью утилиты Karabiner Event Viewer, который идет в пакете с Karabiner. Символы^ и $ в bundle_identifiers— означают начало и конец строки (обычный regexp).

Файл положить в ~/.config/karabiner/assets/complex_modifications/slack.json.

С вимом же достаточно пары маппингов:

nnoremap   'viw"9di[9](*)'
xnoremap   '`>a](*)`<['

О том, как заставить работать CMD, написано в следующем параграфе.

Если вам, как и Сергею, совсем не интересен вим, прошу дать мне шанс убедить вас — посмотрите только пример 5 из этого раздела.

Пример 2. Как выйти из вима

 — типичный hotkey для закрытия программы. Основная проблема возникает при использовании вима, так как последний открывается из терминала. Нажатия крадет терминал.

Мне известны три подхода к решению проблемы выхода из вима отлавливания CMD в виме, я опишу все.

Первый — разобраться, как достичь этого в вашем терминале или поменять терминал. Kitty умеет так делать (issue), WezTerm тоже.

Второй — открывать вим в специальном клиенте, а не в терминале. Я пользуюсь Neovide — клиентом для neovim. определен по умолчанию и будет убивать процесс программы. Так же можно маппить любые CMD+ сочетания, например, сохранение файла на , как это сделано в большинстве программ.

Vimscript example:

nmap  :w
imap  :wa

Пример 3. Основные hotkey должны работать и в виме

Таким же способом (через ) я описал, чтобы и в insert, и в normal модах вима работали:

 — закрыть окно.
 — выделить весь буффер.
 — отменить действие.
 — копировать.
 — вставить.
 — поиск.
 — создать таб.
 — уменьшить шрифт.
 — увеличить шрифт.
 — установить дефолтный шрифт.

Если вас не пугает Fennel (Lisp в мире Lua), то можно посмотреть на мою кофигурацию Neovim тут. Большая часть CMD-маппингов в init.fnl.

Что касается , то сочетание клавиш убьет процесс приложения, в котором запущен вим, и вы не можете дописать какую-то свою логику — например, сохранить все открытые буферы. Тут мы подходим к третьему подходу.

Опишем модификацию в Karabiner: когда открыт Neovide или VimR (другой клиент), нужно отлавливать сочетание клавиш и вместо этого отправлять (который мы замаппить в виме уже сможем):

vim.json

{
  "title": "VimQ",
  "rules": [
    {
      "description": "Cmd key",
      "manipulators": [
        {
          "type": "basic",
          "from": {
            "key_code": "q",
            "modifiers": {
              "mandatory": [
                "left_command"
              ]
            }
          },
          "to": [
            {
              "key_code": "q",
              "modifiers": [
                "left_option"
              ]
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_if",
              "bundle_identifiers": [
                "^com.qvacua.VimR$",
                "^com.neovide.neovide$"
              ]
            }
          ]
        }
      ]
    }
  ]
}

А в .vimrc пропишем маппинг на , чтобы сохранить и закрыть все буфферы:

nnoremap  :silent! waqa!

По такому же принципу я настроил*, чтобы в Neovide превращались в , а  — в , удаляя целое слово и целую строку соответственно.

(перемещение курсора на слово левее везде, кроме вима), для Neovide превращается в (hotkey в виме). То же самое для RIGHT. Пример.

* Пояснение:  — удаляет слово в вим,  — мой маппинг на BACKSPACE.

Пример 4. Смена раскладки в виме

Использовать глобальную раскладку не получится, даже не стоит тратить время. Как-то Сергей попробовал это сделать, но оказалось, что после выхода в normal mode ничего не работает. Vim знает, что «i» — это insert mode, но не знает, что «ш» должно означать то же самое.

Решается это дело по-разному, мы с Сергеем остановились на таком решении.

  1. По пути ~/.vim/keymap/russian-jcukenmac.vim кладем вот этот файл.

  2. В Karabiner создаем модификацию, чтобы глобальный hotkey на смену раскладки вызывал  — hotkey смены раскладки в виме по умолчанию. В моем случае  — меняет раскладку глобально.

  3. Дописываем hotkey для смены раскладки из вима на  в normal mode:

set keymap=russian-jcukenmac
set iminsert=0
set imsearch=0

function! s:SetLangCmd(langName)
    let n = (a:langName == "en_US") ? 0 : 1
    execute "set iminsert=" . n . " imsearch=" . n
    execute "lang " . a:langName . ".UTF-8"
endfunction

function! s:ToggleKeyboard()
    call SetLangCmd(&iminsert == 0 ? "ru_RU" : "en_US")
endfunction

xnoremap  :call ToggleKeyboard()
nnoremap  :call ToggleKeyboard()

Я использую все то же самое, только оно на Fennel.

Пример 5. Normal mode вима в терминале и в браузере

Даже если вы не пользуетесь вимом, имеет смысл разобраться с базовыми командами normal mode и внедрить их в свое окружение. К слову, так я и сам пришел к виму, поставив сперва Vimium-плагин в браузер, затем IdeaVim-плигин в Intellij Idea.

Сейчас у меня стоит VimiumC, который добавляет hotkey вима еще и в PDF.

Чем так хорош этот normal mode?

Удобные клавиши для скролла вниз/вверх (j/k, d/u — down/up), перехода в формы ввода (gi — go insert), перехода в начало/конец страницы (gg/G) и просто эмуляции нажатия мышью в любое место (f — find), а также смена и перемещение табов, закрытие страницы, поиск по тексту.

Все это начинает иметь больше смысла, когда вы замечаете, что те же самые hotkeys доступны и в терминале, и в редакторе кода.

Но давайте по порядку. Редакторы всякие бывают, но почти все дают возможность подключить биндинги вима — IdeaVim для Intellij-продуктов, Vintage Mode для Sublime, Evil для Emacs, Neovim интегрируется в VSCode напрямую.

В терминале у вас уже есть типичные маппинги вима. Запустите man ssh, и вам откроется интерфейс, где d/u и j/k будут скроллить, / искать, gg — переходить в начало строки и т.д.

В терминале легко настроить вим-биндинги и для строки ввода: set keymap vi-command в ~/.inputrc, или plugin vim, елси вы используете oh-my-zsh.

Возможность увести курсор за пределы строки ввода в терминале — например, копировать кусочек напечатанного текста, доступна в WezTerm — Copy Mode.

Я выделил кусочек текста без мыши, те ми же hotkey, что пользуюсь везде

Я выделил кусочек текста без мыши, те ми же hotkey, что пользуюсь везде

Как войти в normal mode консистентно

В обычном vim переход в normal mode осуществляется нажатием на ESCAPE. В терминале, открытом из Neovim, используется . В normal mode WezTerm — .

Переназначить hotkey для WezTerm и для терминала, открытого внутри Neovim, на ESCAPE нельзя, потому что ESCAPE нужен для команды отмены и для перехода в normal mode, предоставляемого настрокой set keymap vi-command или vim плагином в oh-my-zsh.

Мое решение — использовать (напоминаю, что ESCAPE находится на месте CAPSLOCK).

Для терминала Neovim в Lua конфигах приписываем:

vim.keymap.set("t", "", "", {desc = "Normal mode"})

В самом Neovim это будет работать без всяких изменений.

Для WezTerm в .wezterm и тоже на Lua пишем:

return {
  keys = { { key = 'Escape', mods = 'CMD', action = act.ActivateCopyMode }, },
}

Ссылка на документацию, и еще одна на мою конфигурацию WezTerm.

Пример 6. Горячие клавиши для табов

В большинстве приложений откроет новый таб,  — выберет следующий таб. Последний маппинг кажется слишком громоздки, давайте заменим его на  (мнемоника для этого hotkey: на клавише ».» нарисован знак »>» — как стрелка направо, показывающая, куда перенесется фокус).

Вот пример настройки смены таба для WezTerm:

return {
  keys = { 
    { key = '.', mods = 'CMD', action = act.MoveTabRelative(1) },
    { key = ',', mods = 'CMD', action = act.MoveTabRelative(-1) },
  },
}

В браузере это можно настроить через Vimium или VimiumC, но не во всех вкладках плагин будет работать. По этой причине я создал модификацию Karabiner для всех приложений, кроме тех, в которых открываю Neovim (подробнее было в примере 1):

Пример Karabiner:

{
  "description": "right cmd + ,/. —>  cmd + shift + [/]",
  "manipulators": [
    {
      "type": "basic",
      "from": {
        "key_code": "comma",
        "modifiers": {
          "mandatory": [
            "right_command"
          ]
        }
      },
      "to": [
        {
          "key_code": "open_bracket",
          "modifiers": [
            "shift",
            "right_command"
          ]
        }
      ],
      "conditions": [
        {
          "type": "frontmost_application_unless",
          "bundle_identifiers": [
            "^com.qvacua.VimR$",
            "^com.neovide.neovide$"
          ]
        }
      ]
    },
    {
      "type": "basic",
      "from": {
        "key_code": "period",
        "modifiers": {
          "mandatory": [
            "right_command"
          ]
        }
      },
      "to": [
        {
          "key_code": "close_bracket",
          "modifiers": [
            "shift",
            "right_command"
          ]
        }
      ],
      "conditions": [
        {
          "type": "frontmost_application_unless",
          "bundle_identifiers": [
            "^com.qvacua.VimR$",
            "^com.neovide.neovide$"
          ]
        }
      ]
    }
  ]
}

Вим — исключение, потому что везде табы — это табы, а в виме табы — это что-то вроде layout (набор буфферов). Поэтому есть плагин, который визуально показывает буфферы как табы — bufferline. Используя предоставляемые плагином команды, можно замаппить hotkey на , , и т.д. Как маппить CMD, я писал в примере 2.

Пример моего конфига табов для neovim на fennel.

Но можно и без плагинов с вимовскими табами, используя команды: gt, :tabnew, :tabmove +1.

FAQ

— Есть ли альтернатива karabiner для линукса?
— Есть программы вроде input-remapper. В счастливые времена до пандемии я пытался настроить все, что у меня есть, через них, но потерпел фиаско (в Firefox не работали WIN + hjkl, когда к ним добавлялся SHIFT). Не знаю, как сейчас, но всегда можно настроить xkb по аналогии с тем, как сделано в этом репозитории (и еще раз ссылка на YouTube обзор).

— Karabiner for Windows?
— Microsof powertoys.

— Разве проблема раскладки не решается Punto Switcher?
— Частично решается, но придется настраивать, чтобы он не реагировал на шаблоны автозамены, сокращения из мира программирования, пароли и т.п. Я ходил этой дорогой и не раз, но в итоге отказался.

— Можно интегрировать Vim в текстовые поля браузера (firenvim, wasavi), да и вообще в любое поле ввода текста OSX (kindavim). Почему об этом не сказано?
— Я пользовался всем из вышеперечисленного, но решил, что проблем, багов и неконсистентных маппингов от этого создается больше, чем пользы. Может быть, попробую еще раз позже.

— Если тебе так важно эффективно пользоваться клавиатурой и держать пальцы на homerow, то почему не Colemak или Halmak?
— Меня пугает объем работы: перемапливание вима, поиск альтернативных решений для русской раскладки, полная несовместимость при работе на чужих компьютерах, изменение всех горячих клавиш всех программ.

— Конфиругации Karabiner — громоздки.
— Есть Goku, позволяющий их описать значительно короче.

В заключение

Ряд подходов, что я исползьовал, далеко не идеальны и не зафиксированы. Время от времени дописываются новые hotkey, удаляются старые, меняются утилиты, которые стоят в основе. Например, я больше не пользуюсь приложением Slack — открываю его в браузере (чтобы получить фичи VimiumC и проверку грамматики, которая у меня уже настроена в браузере).

Мои дотфайлы лежат тут, но не рекомендую их брать себе такими, какие они есть. Лучше пользоваться ссылками, что указал выше в статье.

Если найдется время и накопится материала, напишу о том, как настроить управление окнами и темами в OSX. Личность же Сергея оставляю до тех пор не раскрытой.

© Habrahabr.ru