Как редактировать elisp, не теряясь в скобках

13

Я изменяю некоторый код elisp из linum.el:

(custom-set-variables '(linum-format 'dynamic))
(defadvice linum-update-window (around linum-dynamic activate)
   (let* ((w (length (number-to-string
      (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) "d ")))
            ad-do-it))

Я смог исправить ошибку, когда отступ был один за другим, изменив (count-lines (point-min) (point-max))на (+ (count-lines (point-min) (point-max)) 1). Это было легко.

Но теперь я хочу изменить его так, чтобы минимальная ширина равнялась 2, добавляя условное условие (concat " %" (number-to-string w) "2d ")if, если число строк равно <10.

Это должно быть легко! Добавьте одно условие и скопируйте / вставьте конкат. Кусок пирога, верно? Я имею в виду, я знаю, что я должен делать, но я редко прикасаюсь к elisp, и я всегда пугаюсь, когда мне нужно что-то изменить с большим количеством скобок.

«Правильный» стиль, насколько я понимаю, состоит в том, чтобы структурировать код на основе отступа и обернуть завершающие скобки в конце строки, а не самостоятельно. Исходя из других языков стиля C, я борюсь с чтением и написанием кода таким образом. Итак, мой вопрос: что я делаю не так? Как я должен редактировать elisp и перемещаться по коду так, чтобы мне не приходилось сидеть там и считать все скобки?

Когда я работаю с чем-то в elisp, которое становится слишком глубоким, мне приходится закрывать дверь, открывать жалюзи и начинать располагать скобки в стиле K & R, чтобы я мог не только читать, но и изменять чертову вещь, не сходя с ума.

Очевидно, я делаю все это неправильно. Как я могу без страха дотронуться до elisp?

Обратите внимание, что мой вопрос заключается в том, как перемещаться и редактировать elisp, а не как вопрос стиля. В качестве руководства по стилю я уже использую следующее: https://github.com/bbatsov/emacs-lisp-style-guide

Обновления:

Как правильно отформатировать elisp перед тем, как смущать себя на emacs.stackexchange:

Отметьте свой elisp и выполните M-x indent-region.

Решение проблемы:

Для тех, кто хочет знать, как выполнить выравнивание по правому краю для linum с минимальной шириной два, вот решение:

(defadvice linum-update-window (around linum-dynamic activate)
  (let* ((w (length (number-to-string
                     (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (if (> w 1)
                           (concat " %" (number-to-string w) "d ")
                         " %2d ")))
    ad-do-it))
Zhro
источник
1
Каждый борется с чтением шуток, который исходит от C. Это просто требует времени, но в конечном итоге это становится чрезвычайно естественным. Также вы, вероятно, можете решить вашу проблему, изменив формат linum на строку формата, например "% 2d".
Джордон Биондо,
Я не могу просто использовать, %2dпотому что, как только ширина переходит к 3 или более символам, она переходит от «выровнять по правому краю» к «выровненному по левому краю»
Жро
Есть несколько библиотек, которые выделяют соответствующие круглые скобки, которые неоценимы (на мой взгляд), например highlight-parentheses; rainbow-delimiters; и т.д. Вот моя собственная упрощенная версия, highlight-parenthesesкоторая разрешает прокрутку без удаления скобок, которые были раскрашены в последний раз: stackoverflow.com/a/23998965/2112489 В будущем это один вопрос для каждого клиента / потока.
юрист
1
Похоже, ваша проблема в том, что вы действительно читаете , а не редактируете код Elisp. Похоже, у вас проблемы с пониманием структуры Lisp, и это ухудшает ваше редактирование.
PythonNut
1
В вашу защиту, это довольно плохо с отступом кусок кода. Я прочитал это неправильно сам первые 3 раза из-за этого. Также, Паредит .
Малабарба

Ответы:

12

Существует ряд дополнительных пакетов, которые могут помочь, такие как paredit, smartparens и lispy. Эти пакеты облегчают навигацию и манипулирование кодом lisp, так что вы можете думать в s-выражениях и позволить редактору беспокоиться о балансировке символов в скобках.

В Emacs также есть множество встроенных команд для работы с полами и списками, которые стоит изучить и которые могут помочь вам лучше привыкнуть к структуре кода lisp. Они обычно связаны с C-M-префиксом, таким как:

  • C-M-f/ C-M-bдвигаться вперед / назад по полу
  • C-M-n/ C-M-pдвигаться вперед / назад по списку
  • C-M-u/ C-M-dдвигаться вверх / вниз на один уровень
  • C-M-t поменять местами два пола
  • C-M-k убить секс
  • C-M-SPC отметить секс
  • C-M-q сделать отступ в сексе

Пример кода, который вы предоставили, может быть более понятным, если вы исправите отступ: поставьте точку в начале (defadvice...и нажмите, C-M-qчтобы заново сделать отступ для всего выражения. Чтобы заменить (concat...I, я должен начать указывать точку в начале этого секса и нажимать, C-M-oчтобы разделить строку, сохраняя отступ. Затем добавьте свой (if...и используйте приведенные выше команды, чтобы перейти к концу списка, добавить еще один закрывающий элемент, вернуться к началу, сделать отступ и т. Д.

Вы также можете захотеть включить show-paren-mode, что будет выделять соответствующую пареню, когда точка находится в начале или конце списка. Вы можете выделить целое выражение, а не соответствующее имя, поэтому попробуйте выполнить настройку show-paren-style.

Как упомянул @Tyler в комментариях к другому ответу, вы должны взглянуть на руководство, чтобы узнать больше об этих и связанных с ними командах.

glucas
источник
Обратите внимание, что он C-M-qможет быть вызван с префиксным аргументом ( C-u C-M-q), чтобы красиво печатать выражение в точке. Это разбьет все на отдельные строки и отступ, так что вложение будет очень очевидным. Как правило, вы не хотите, чтобы ваш код lisp выглядел так, но может быть полезно понять немного кода, не добавляя в него полный код K & R. (И вы всегда C-x uможете отменить).
glucas
6

Хороший способ редактировать LISP - это манипулировать AST вместо отдельных символов или строк. Я делал это с моим пакетом LISPY, который я начал 2 года назад. Я думаю, что на самом деле может потребоваться немало практики, чтобы научиться делать это хорошо, но даже использование только основ должно уже помочь вам.

Я могу объяснить, как бы я сделал это изменение:

Шаг 1

Стартовая позиция обычно должна иметь точку перед сексом на высшем уровне. Здесь |суть.

|(defadvice linum-update-window (around linum-dynamic activate)
   (let* ((w (length (number-to-string
      (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) "d ")))
            ad-do-it))

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

Шаг 2

Вы захотите пометить "d "область, так как объект, способный к манипулированию, - lispyэто либо список, который не нужно помечать, либо последовательность символов и / или списки, которые необходимо пометить областью.

Нажмите, atчтобы сделать это. Команда aпозволяет пометить любой отдельный символ в родительском списке и tявляется индексом этого конкретного символа.

Шаг 3

  1. Чтобы обернуть текущий регион (), нажмите (.
  2. Нажмите, C-fчтобы перейти вперед на один символ и вставить if.
  3. Нажмите еще (раз, чтобы вставить ().
  4. Вставить > w 10.
  5. C-f C-m переместить строку на новую строку.

Шаг 4

Чтобы клонировать строку:

  1. Пометьте символ или строку в точке с помощью M-m.
  2. Нажмите c.

Если вы хотите причудливо деактивировать регион и поставить точку в самом начале строки, вы можете нажать idm:

  1. i выберет внутреннее содержимое строки.
  2. d поменять точку и пометить.
  3. m дезактивирует регион

Или вы могли бы сделать m C-b C-b C-bв этом случае, или C-g C-b C-b C-b. Я думаю, что idmлучше, так как это одинаково, независимо от длины строки. Я думаю, C-x C-x C-f C-g что также будет работать независимо от длины строки.

Наконец, вставьте, 2чтобы получить код конца:

(defadvice linum-update-window (around linum-dynamic activate)
  (let* ((w (length (number-to-string
                     (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) (if (> w 10)
                                                             "2|d "
                                                           "d "))))
    ad-do-it))

Резюме

Полная последовательность была: at( C-f, if, (, > w 10, C-f C-m M-m cidm, 2.

Обратите внимание, что если вы посчитаете вставку кода, единственными ключами, не связанными с манипулированием AST, были два C-fи один C-m.

Або-або
источник
3
Лиспи звучит интересно! Для OP: вы можете разрабатывать аналогичные рабочие процессы с помощью paredit: emacsrocks.com/e14.html и расширять регион: github.com/magnars/expand-region.el , или просто поиграться с помощью встроенных команд сопоставления скобок: gnu. org / software / emacs / manual / html_node / emacs / Parentheses.html
Тайлер
Я уверен, что вы догадывались, когда пытались решить проблему для меня; спасибо за попытку. Мне удалось запутаться, используя ваш код в качестве примера. Смотрите мое обновление для правильного решения.
Жро
6

Со временем вы привыкнете к этому, но, конечно, вы можете многое сделать, чтобы ускорить его:

вдавливание

В этом безумии есть метод. В lisp вы можете сделать это:

(a-fun (another-fun (magic-macro 1)))

Предположим, что 1это действительно большое выражение, и вы хотите сделать отступ в отдельной строке.

(a-fun (another-fun (magic-macro 
  1)))

Это вводит в заблуждение. 1отступ в один слот, хотя это целых три уровня вложенности выше! Лучше поставить его своим родителем.

(a-fun (another-fun (magic-macro 
                      1)))

Если мы используем эту схему в вашем собственном коде, мы получим это:

(custom-set-variables '(linum-format 'dynamic))
(defadvice linum-update-window (around linum-dynamic activate)
  (let* ((w (length (number-to-string
                      (+ (count-lines (point-min) (point-max)) 1))))       
          (linum-format 
            (concat " %" (number-to-string w) "d ")))
     ad-do-it))

Обратите внимание на сильную корреляцию между отступом и уровнем вложенности. Строки с большим уровнем вложенности всегда имеют больший отступ, чем строки с меньшим.

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

В качестве упражнения я удалил несущественные скобки из кода. Вы должны быть в состоянии как прочитать код, так и указать пропущенные скобки, учитывая базовые знания Elisp (а именно, что такое функции и значения, а также структуру let*).

custom-set-variables '(linum-format 'dynamic)
defadvice linum-update-window (around linum-dynamic activate)
  let* w length number-to-string
                  + 
                    count-lines (point-min) (point-max) 
                    1       
         linum-format 
           concat " %" (number-to-string w) "d "
     ad-do-it
PythonNut
источник
1
Обратите внимание, что в Emacs нажатие на вкладку почти всегда правильно устанавливает отступ, как описано здесь - вы никогда не должны делать это, добавляя пробелы вручную!
Тайлер
Привязка «Cm» newline-and-indentдля emacs-lisp-mode и lisp-взаимодействия-mode решает ваш первый пример, PythonNut. И TAB сделает всю оставшуюся работу.
Давор Кубраник
5

Я включил это как другой ответ.

Иногда отступы вас не подведут. Тогда вы можете использовать что-то вроде rainbow-delimiters:

введите описание изображения здесь

Это делает уровень вложенности любого парена явным и легко сканируемым.

Однако есть одна довольно большая проблема: паренсы смешно отвлекают. Здесь есть баланс внимания. rainbow-delimitersобычно уделяет большое внимание паренсу за счет остальной части вашего кода! Но если вы тонируете радугу лицом вниз, их становится все труднее сканировать.

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

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

введите описание изображения здесь

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

введите описание изображения здесь

И вот код для этого:

(defvar rainbow-delimiters-switch nil
  "t if rainbow-delimiters are currently punched")
(defvar rainbow-delimiters-face-cookies nil
  "a list of face-remap-add-relative cookies to reset")

(make-variable-buffer-local 'rainbow-delimiters-switch)
(make-variable-buffer-local 'rainbow-delimiters-face-cookies)

(add-hook 'prog-mode-hook #'rainbow-delimiters-mode)
(add-hook 'text-mode-hook #'rainbow-delimiters-mode)

(with-eval-after-load 'rainbow-delimiters
  (set-face-foreground 'rainbow-delimiters-depth-1-face "#889899")
  (set-face-foreground 'rainbow-delimiters-depth-2-face "#9b7b6b")
  (set-face-foreground 'rainbow-delimiters-depth-3-face "#7b88a5")
  (set-face-foreground 'rainbow-delimiters-depth-4-face "#889899")
  (set-face-foreground 'rainbow-delimiters-depth-5-face "#839564")
  (set-face-foreground 'rainbow-delimiters-depth-6-face "#6391aa")
  (set-face-foreground 'rainbow-delimiters-depth-7-face "#9d748f")
  (set-face-foreground 'rainbow-delimiters-depth-8-face "#7b88a5")
  (set-face-foreground 'rainbow-delimiters-depth-9-face "#659896")

  (defun rainbow-delimiters-focus-on ()
    "Punch the rainbow-delimiters"
    (setq rainbow-delimiters-face-cookies
      (list
        (face-remap-add-relative 'rainbow-delimiters-depth-1-face
          '((:foreground "#3B9399") rainbow-delimiters-depth-1-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-2-face
          '((:foreground "#9B471D") rainbow-delimiters-depth-2-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-3-face
          '((:foreground "#284FA5") rainbow-delimiters-depth-3-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-4-face
          '((:foreground "#3B9399") rainbow-delimiters-depth-4-face))
            (face-remap-add-relative 'rainbow-delimiters-depth-5-face
          '((:foreground "#679519") rainbow-delimiters-depth-5-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-6-face
          '((:foreground "#0E73AA") rainbow-delimiters-depth-6-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-7-face
          '((:foreground "#9D2574") rainbow-delimiters-depth-7-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-8-face
          '((:foreground "#284FA5") rainbow-delimiters-depth-8-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-9-face
          '((:foreground "#199893") rainbow-delimiters-depth-9-face)))
      rainbow-delimiters-switch t))

  (defun rainbow-delimiters-focus-off ()
    "Reset the rainbow-delimiters faces"
    (mapc #'face-remap-remove-relative rainbow-delimiters-face-cookies)
    (setq rainbow-delimiters-switch nil))

  (defun rainbow-delimiters-focus-on-maybe ()
    "Punch the rainbow-delimiters if the point is on a paren"
    (when (looking-at "[][(){}]")
      (unless (or rainbow-delimiters-switch (minibufferp))
        (rainbow-delimiters-focus-on))))

  (defun rainbow-delimiters-focus-off-maybe ()
    "Reset the rainbow-delimiters if the point is not on a paren"
    (unless (looking-at "[][(){}]")
      (when rainbow-delimiters-switch
        (rainbow-delimiters-focus-off))))

  (run-with-idle-timer 0.6 t 'rainbow-delimiters-focus-on-maybe)
  (run-with-idle-timer 0.1 t 'rainbow-delimiters-focus-off-maybe))
PythonNut
источник
Это здорово! По какой причине вы выбрали для начала радужные разделители вместо выделенных скобками?
Тайлер
@ Тайлер не имеет особой причины, хотя я бы все еще придерживался разделителей радуги (но это может быть только потому, что я к этому привык). Я просто не слышал о выделении скобками до недавнего времени.
PythonNut
4

Если вы делаете отступ для своего кода, тогда вам действительно нужно правильно сделать отступ. Разработчики Lisp ожидают, как должен выглядеть правильный отступ. Это не значит, что код выглядит одинаково. Есть еще разные способы форматирования кода.

  • как долго должна быть линия?
  • как распределить вызов функции / макроса по строкам?
  • сколько должно быть отступов?
(defadvice linum-update-window (around linum-dynamic activate)
   (let* ((w (length (number-to-string
      (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) "d ")))
            ad-do-it))

Я ожидал бы, что отступ показывает сдерживание в некотором роде. Но не в вашем коде.

По умолчанию ваш код может иметь такой отступ. Другие ответы описывают, как вы можете сделать это с помощью Emacs:

(defadvice linum-update-window (around linum-dynamic activate)
  (let* ((w (length (number-to-string
                     (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) "d ")))
    ad-do-it))

Я ожидаю, что letсвязанные переменные начинаются в том же вертикальном столбце. Я хотел бы также ожидать , что тело из letменее отступа. Мы узнаем, как letдолжен быть отступ. Если вы немного потренируетесь, это визуальный шаблон, который легко распознать.

Основные визуальные блоки в коде являются defadvice, let*и куча вызовов функций. В defимени говорится, что это определение, за которым следуют имя, список аргументов и тело.

Так мне нравится видеть

defadvice name arglist
  body

let*Будет: let*, список привязок и тела.

Так мне нравится видеть:

let*  list of bindings
  body

Более подробный:

let*   binding1
       binding2
       binding3
  body

Для вызова функции мне нравится видеть:

FUNCTIONNAME ARG1 ARG2 ARG3

или

FUNCTIONNAME ARG1
             ARG2
             ARG3

или

FUNCTIONNAME
  ARG1
  ARG2
  ARG3

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

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

Круглые скобки должны напрямую заключать их конструкции.

(sin 10)

(sin
  10)

Избегайте пробельных примеров:

(  sin 10  )

или

(
   sin
)

или

(sin 10
)

В Лиспе круглые скобки не имеют синтаксической функции языка, они являются частью синтаксиса структуры данных списка (s-выражений). Таким образом мы пишем списки и форматируем списки. Для форматирования содержимого списка мы используем знания о языке программирования Lisp. letВыражение должно быть отформатирован иначе , чем вызов функции. Тем не менее, оба списка являются списками, и скобки должны непосредственно включать списки. Есть несколько примеров, в которых имеет смысл иметь одну скобку в строке, но используйте это редко.

Используйте скобки, чтобы помочь вам найти границы выражения. Позвольте им помочь вам переходить от списка к списку, редактировать списки, вырезать и копировать списки, заменять списки, делать отступы / форматировать списки, выбирать списки, ...

Райнер Йосвиг
источник
2

Для базовой помощи с отступом используйте TAB и "CMq" ( indent-pp-sexp); Привязка "Cm" newline-and-indentизбавит вас от необходимости нажимать клавишу TAB после новой строки.

Вы можете перемещаться по полу с помощью «CM-left / right / up / down / home / end», что очень полезно.

Если вы беспокоитесь о сохранении баланса скобку, использовать что - то вроде smartparens (это немного более прощающий , что paredit , который первоначально может чувствовать себя как носить смирительную). Он также поставляется с большим количеством привязок для навигации и редактирования на уровне пола.

Наконец, для выделения паренов, начните с включения опции (меню Параметры-> Выделить соответствующие скобки или show-paren-mode.) Вы также можете использовать один из пакетов, упомянутых в списке комментариев в его комментарии, например, «подсветка скобок» или «разделители радуги». ».

Давор Кубраник
источник
1

В дополнение к тому, что другие дали в качестве ответов, вот мои 2 цента:

  • Автоматическое отступание, обеспеченное, emacs-lisp-modeявляется существенным.
  • blink-matching-parenвключен (не nil) по умолчанию. Оставь это так.
  • Я использую show-paren-mode, чтобы выделить левую часть, соответствующую правой части перед курсором.
  • Я временно удаляю и заново набираю правую часть перед курсором, если я хочу лучше увидеть, где находится соответствующая левая часть.

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

В частности electric-pair-mode, мне неудобно беспокоиться. Но некоторые люди любят это. И я полагаю, что это может быть полезно в некоторых других контекстах сопряжения, кроме скобок Lisp (я так думаю, но я не убежден и в таких случаях использования).

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

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

Помимо автоматического отступа, который вы получаете с RETи TAB, получите удобное использование C-M-q. (Использование C-h kв , emacs-lisp-modeчтобы узнать, что делают эти ключи.)

Нарисовалась
источник
1

Я заметил, что некоторые люди уже упоминали радужные разделители, это также мой любимый режим при редактировании кода lisp.

На самом деле я люблю круглые скобки, лучше всего в Lisp это круглые скобки, с ними интересно иметь дело, если ты знаешь как:

  • установите evil-mode и его плагин evil -round, чтобы вы могли легко редактировать круглые скобки, например, press ci(удалит все внутри (), va(выберет вещи, завернутые в «()» и «()». и вы можете нажать, %чтобы перейти между соответствующими скобками

  • используйте flycheck или flymake, непрямые скобки будут подчеркнуты в реальном времени

чен бен
источник
0

Сложность в том, что вы хотите добавить некоторый код до и после определенного sexp. В этой ситуации секс есть (concat " %" (number-to-string w) "d "). Вы хотите вставить оператор if (if (> w 1) перед ним и оператор else " %2d ") после него.

Основная сложность заключается в том, чтобы изолировать это sexp, лично я использую приведенную ниже команду для изоляцииsexp

(defun isolate-sexp () (interactive)
       (save-excursion (forward-sexp) (if (not (eolp-almost))
               (split-line) nil)))
(defun eolp-almost () (interactive)
(save-excursion (while (equal (char-after) ? ) (forward-char 1))
        (if (eolp) t nil )      ))

Просто поместите курсор в начало (concat " %" (number-to-string w) "d "), т. Е. На первое (, а затем примените вышеуказанное isolate-sexp. Вы получаете

(defadvice linum-update-window (around linum-dynamic activate)
   (let* ((w (length (number-to-string
      (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) "d ")
                                  ))
            ad-do-it))

Затем добавьте соответствующий код до и после этого sexp, т.е.

(defadvice linum-update-window (around linum-dynamic activate)
   (let* ((w (length (number-to-string
      (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (if (> w 1) (concat " %" (number-to-string w) "d ") " %2d ")
                                  ))
            ad-do-it))

И вуаля. Полученный таким образом код, возможно, не красив, но вероятность потеряться меньше.

имя
источник