如何编辑elisp而不使括号丢失


13

我正在从linum.el修改一些elisp代码:

(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)。那很简单。

但是现在我想修改它,以便通过(concat " %" (number-to-string w) "2d ")在行数计数<10 的情况下添加if条件来最小宽度为2 。

这应该很容易!添加一个条件并复制/粘贴concat。小菜一碟吧?我的意思是,我知道我应该做的,但我很少过问elisp的,当我需要修改,有很多括号的任何东西我总是吓倒。

据我所知,“正确”的样式是基于缩进来构造代码,并在行的末尾而不是单独地将尾括号括起来。来自其他“ C”风格的语言,我很难以这种方式读写代码。所以我的问题是:我在做什么错?我应该如何编辑elisp并浏览代码,而不必坐在那里计算每个括号?

当我在elisp中处理过深的东西时,我必须关上门,拉开百叶窗,然后开始放置括号中的K&R样式,这样我不仅可以阅读而且可以修改令人毛骨悚然的东西而不会感到惊讶。

显然我做错了。我如何能这样无惧地触摸elisp?

请注意,我的问题是如何导航和编辑elisp而不是样式问题。我已经使用以下内容作为样式指南:https : //github.com/bbatsov/emacs-lisp-style-guide

更新:

在emacs.stackexchange上尴尬之前,如何正确格式化elisp:

标记您的elisp并执行M-x indent-region

问题解决方案:

对于那些想知道如何对最小宽度为2的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))

1
每个人都在努力阅读来自C的Lisp。这只是花时间,但最终它变得极为自然。另外,您也可以通过将linum-format更改为诸如“%2d”之类的格式字符串来解决您的问题。
Jordon Biondo

我不能只使用它,%2d因为一旦宽度翻转到3个或更多字符,它就会从右对齐变为左对齐。
Zhro 2015年

有一些库突出显示了有价值的匹配括号(在我看来),例如highlight-parenthesesrainbow-delimiters; 等。这是我自己的简化版本,highlight-parentheses它允许滚动而不删除上次着色的括号: stackoverflow.com/a/23998965/2112489 将来,这是每个客户/线程的一个问题。
法律列表

1
听起来很像您的问题确实是在阅读,而不是编辑Elisp代码。您听起来好像在理解Lisp的结构时遇到了问题,这削弱了您的编辑。
PythonNut

1
在您的辩护中,这是一段非常糟糕的缩进代码。因此,我自己前3次读错了。另外,paredit
马拉巴巴

Answers:


12

有很多附加软件包可能会有所帮助,例如paredit,smartparens和lispy。这些软件包使浏览和处理Lisp代码变得更加容易,因此您可以在s表达式中进行思考,并使编辑者不必担心平衡值。

Emacs还具有许多内置的命令,可用来处理性行为和列表,这是值得学习的,它们可以帮助您更熟悉lisp代码的结构。这些通常以C-M-前缀绑定,例如:

  • C-M-f/ C-M-b通过sexp前进/后退
  • 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,我先将点放在该sexp的开头,然后C-M-o在保留缩进的同时点击以分隔线。然后添加(if...,并使用上面的命令跳到列表的末尾以添加另一个结束括号,返回到开头以重新缩进,依此类推。

您可能还需要打开show-paren-mode,当点位于列表的开头或结尾时,它将突出显示相应的括号。您可能希望突出显示整个表达式,而不是匹配的括号,因此请尝试customizing show-paren-style

正如@Tyler在另一个答案的评论中提到的那样,您应该看一下手册以了解有关这些命令和相关命令的更多信息。


请注意,C-M-q可以使用前缀参数(C-u C-M-q)进行调用,以在点处漂亮地打印表达式。这会将所有内容分解为单独的行并缩进,以使嵌套非常明显。通常,您不希望Lisp代码看起来像这样,但是如果不手动进行完整的K&R,了解一些代码可能会有所帮助。(而且您随时C-x u可以撤消)。
glucas

6

编辑LISP的一种好方法是通过操纵AST而不是单个字符或行。我一直使用两年前开始的lispy软件包进行 此操作。我认为实际上要花很多时间才能学会如何做得很好,但是即使仅使用基础知识也应该已经对您有所帮助。

我可以解释一下如何进行更改:

第1步

起始位置通常是在顶级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))

您需要正确缩进代码,因此请按i一次。它将很好地缩进。

第2步

您将要标记"d "一个区域,因为其中的可操作对象lispy要么是不需要标记的列表,要么是需要标记区域的符号序列和/或列表。

按此at操作。该a命令允许标记父列表中的任何单个符号,并且t是该特定符号的索引。

第三步

  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-fif(> w 10C-f C-m M-m cidm2

请注意,如果您不考虑代码插入,则唯一与AST操作无关的键是2 C-f和1 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
泰勒(Tyler)

我确定您在猜测何时为我解决问题。谢谢您的尝试。我以您的代码为例,设法弄清楚了它的处理方式。请参阅我的更新以获取正确的解决方案。
Zhro

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

1
请注意,在Emacs中,按Tab键几乎总是按此处所述正确设置缩进-绝对不要通过手工添加空格来做到这一点!
泰勒(Tyler)

将“ Cm”绑定到newline-and-indentemacs-lisp-mode和lisp-interaction-mode可以解决您的第一个示例PythonNut。TAB将在其余时间完成这项工作。
Davor Cubranic 2015年

5

我将其作为不同的答案。

有时,缩进会使您失望。那么,您的方法是使用类似rainbow-delimiters

在此处输入图片说明

这使得任何paren的嵌套级别都明确且易于扫描。

但是,有一个很大的问题:parren令人分心。这里有重点的平衡。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))

这很酷!您出于什么原因选择了高亮括号而不是彩虹分隔符?
泰勒(Tyler)2015年

@Tyler没有特别的原因,尽管我现在仍然坚持使用彩虹定界符(但这可能只是因为我已经习惯了)。直到最近我才听说高亮括号。
PythonNut 2015年

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缩进a。一旦进行了一些培训,这就是一种易于识别的视觉模式。

代码中主要的可视化构建块是defadvicelet*以及一堆函数调用。将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

使用哪一个取决于参数有多长时间。我们不想将行设置得太长,并且每个arg各自的行允许我们拥有更多的结构。

因此,您需要使用这些模式编写代码,并且需要确保读者可以轻松地在代码中识别这些模式。

括号应直接将其构造括起来。

(sin 10)

(sin
  10)

避免使用空格示例:

(  sin 10  )

要么

(
   sin
)

要么

(sin 10
)

在Lisp中,括号不具有语言语法功能,它们是列表数据结构(s-表达式)语法的一部分。因此,我们编写列表并格式化列表。为了格式化列表内容,我们使用有关Lisp编程语言的知识。甲let表达应格式化不是函数调用不同。仍然都是列表,括号应直接将列表括起来。有一些示例在一行上有一个括号是有意义的,但很少使用。

使用括号可以帮助您查找表达式边界。让他们帮助您从列表移动到列表,编辑列表,剪切和复制列表,替换列表,缩进/格式化列表,选择列表...


2

要获得有关缩进的基本帮助,请使用TAB和“ CMq”(indent-pp-sexp); 将“ Cm”绑定到newline-and-indent将节省您在换行符后按TAB的麻烦。

您可以使用“ CM-left / right / up / down / home / end”按性别浏览,这非常有用。

如果您担心保持括号的平衡,请使用smartparens之类的东西(该paredit稍宽容一些,最初看起来像是穿紧身衣)。它还带有很多绑定,可用于在sexp级别进行导航和编辑。

最后,要突出显示括号,请先打开选项(“选项”菜单->“突出显示匹配括号”或“ show-paren-mode。”),您也可以使用lawlist在其注释中提到的软件包之一,例如“ highlight-括号”或“ rainbow-delimiters” ”。


1

除了别人给出的答案之外,这是我的2美分:

  • 提供的自动缩进emacs-lisp-mode是必不可少的。
  • blink-matching-parennil默认情况下处于开启状态(非)。别这样
  • 我使用show-paren-mode,在光标之前高亮显示与右括号对应的左括号。
  • 如果我想更好地查看匹配的左括号所在的位置,我会临时删除并在光标之前重新键入右括号。

使用电子配对或彩虹等任何“帮助”。对我来说,这些麻烦而不是帮助。

electric-pair-mode我看来,这尤其麻烦。但是有些人喜欢它。而且我想这可能会在Lisp括号之外的其他一些配对上下文中有所帮助(我想是的,但是我也不相信这种用例)。

您会发现最适合您的东西。我要说的主要内容是:(1)自动缩进是您的朋友,这是在点之前找出与右括号匹配的左括号的一种方法。

尤其是,看到自动缩进会为您带来什么,将立即告诉您,您再也不想自己将正确的括号放在一行上了。这种编码风格在其他语言中很普遍,只是嘈杂。

除了自动缩进你得到RETTAB,获得舒适的使用C-M-q。(使用C-h kemacs-lisp-mode找出这些键。)


1

我注意到有些人已经提到了Rainbow分隔符,这也是我在编辑Lisp代码时最喜欢的模式。

实际上,我喜欢括号,关于Lisp的最好的事情就是那些括号,如果您知道如何与它们打交道很有趣:

  • 安装邪恶模式及其插件邪恶环绕,因此您可以轻松地编辑括号,例如,按ci(将删除其中的所有内容()va(将选择由“()”和“()”本身包装的内容。您可以按一下%以在匹配的括号之间跳转

  • 使用flycheck或flymake,未匹配的括号将实时下划线


0

这里的困难在于,您想要在特定代码之前和之后添加一些代码sexp。在这种情况下,sext是(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))

和瞧。以这种方式获得的代码可能并不漂亮,但是丢失的机会较小。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.