Cómo editar elisp sin perderse entre paréntesis


13

Estoy modificando un código de elisp de 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))

Pude corregir un error donde la sangría estaba desactivada modificando (count-lines (point-min) (point-max))a (+ (count-lines (point-min) (point-max)) 1). Eso fue fácil.

Pero ahora quiero modificarlo para que el ancho mínimo sea 2 agregando un if-condicional donde (concat " %" (number-to-string w) "2d ")si el número de líneas es <10.

¡Esto debería ser fácil! Agregue un condicional y copie / pegue el concat. Pedazo de pastel, ¿verdad? Quiero decir, sé lo que se supone que debo hacer, pero rara vez toco elisp y siempre me siento intimidado cuando tengo que modificar algo con muchos paréntesis.

El estilo "correcto", por lo que entiendo, es estructurar el código basado en la sangría y envolver el paréntesis final al final de una línea en lugar de hacerlo solo. Al provenir de otros lenguajes de estilo 'C', lucho con leer y escribir código de esta manera. Entonces mi pregunta es: ¿qué estoy haciendo mal? ¿Cómo se supone que edite elisp y navegue por el código de modo que no tenga que sentarme allí y contar cada paréntesis?

Cuando trabajo con algo en elisp que se vuelve demasiado profundo, tengo que cerrar la puerta, cerrar las persianas y comenzar a colocar el paréntesis al estilo K y R para que no solo pueda leer sino modificar lo maldito sin enloquecer.

Obviamente estoy haciendo todo esto mal. ¿Cómo puedo tocar elisp así sin miedo?

Tenga en cuenta que mi pregunta es cómo navegar y editar elisp, no como una pregunta sobre el estilo. Ya estoy usando lo siguiente como guía de estilo: https://github.com/bbatsov/emacs-lisp-style-guide

Actualizaciones:

Cómo formatear correctamente elisp antes de avergonzarse en emacs.stackexchange:

Marque su elisp y actúe M-x indent-region.

La solución del problema:

Para aquellos que desean saber cómo realizar una justificación correcta para linum con un ancho mínimo de dos, esta es la solución:

(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
Todos luchan con la lectura que proviene de C. Solo lleva tiempo, pero eventualmente se vuelve extremadamente natural. También es probable que pueda resolver su problema cambiando el formato linum a una cadena de formato como "% 2d".
Jordon Biondo

No puedo usarlo solo %2dporque una vez que el ancho se desplaza a 3 o más caracteres, se mueve de justificado a la derecha a justificado a la izquierda.
Zhro

Hay algunas bibliotecas que destacan los paréntesis coincidentes que son invaluables (en mi opinión), por ejemplo highlight-parentheses; rainbow-delimiters; etc. Aquí está mi propia versión simplificada highlight-parenthesesque permite desplazarse sin quitar los paréntesis que se colorearon por última vez: stackoverflow.com/a/23998965/2112489 En el futuro, es una pregunta por cliente / hilo.
leyes

1
Parece que su problema es realmente leer , no editar el código de Elisp. Parece que tiene problemas para comprender la estructura de Lisp, y eso dificulta su edición.
PythonNut

1
En su defensa, ese es un fragmento de código bastante mal sangrado. Lo leí mal las primeras 3 veces por eso. Además, el crédito .
Malabarba

Respuestas:


12

Hay varios paquetes de complementos que pueden ayudar, como paredit, smartparens y lispy. Estos paquetes hacen que sea más fácil navegar y manipular el código lisp para que pueda pensar en expresiones s y dejar que el editor se preocupe por equilibrar los parens.

Emacs también tiene muchos comandos incorporados para tratar sexps y listas que vale la pena aprender y que pueden ayudarlo a acostumbrarse más a la estructura del código lisp. Estos generalmente están vinculados con un C-M-prefijo, como:

  • C-M-f/ C-M-bpara avanzar / retroceder por sexp
  • C-M-n/ C-M-ppara avanzar / retroceder por lista
  • C-M-u/ C-M-dpara subir / bajar un nivel
  • C-M-t intercambiar los dos sexps alrededor del punto
  • C-M-k matar a un sexp
  • C-M-SPC para marcar un sexp
  • C-M-q volver a sangrar un sexp

El código de ejemplo que proporcionó podría ser más claro si corrige la sangría: coloque el punto al comienzo del (defadvice...y presione C-M-qpara volver a sangrar la expresión completa. Para reemplazar el (concat..., comenzaría colocando un punto al comienzo de ese sexp y presionando C-M-opara dividir la línea mientras se preserva la sangría. Luego agregue su (if..., y use los comandos anteriores para saltar al final de una lista para agregar otro par de cierre, volver al principio para volver a sangrar, etc.

También es posible que desee activar show-paren-mode, que resaltará el par correspondiente cuando el punto esté al principio o al final de una lista. Es posible que prefiera resaltar toda la expresión en lugar del par coincidente, así que intente personalizarlo show-paren-style.

Como @Tyler mencionó en los comentarios sobre otra respuesta, debería echar un vistazo al manual para obtener más información sobre estos y los comandos relacionados.


Tenga en cuenta que C-M-qse puede invocar con un argumento prefijo ( C-u C-M-q) para imprimir la expresión en el punto. Esto dividirá todo en líneas separadas y sangría para que el anidamiento sea muy obvio. Por lo general, no desea que su código lisp se vea así, pero podría ser útil comprender un poco de código sin tener que completar manualmente K&R. (Y siempre puedes C-x udeshacer).
glucas

6

Una buena forma de editar LISP es manipulando el AST, en lugar de los caracteres o líneas individuales. He estado haciendo eso con mi paquete lispy que comencé hace 2 años. Creo que en realidad puede tomar bastante práctica aprender cómo hacerlo bien, pero incluso el uso de lo básico ya debería ayudarlo.

Puedo explicar cómo haría para hacer este cambio:

Paso 1

La posición inicial suele ser tener el punto antes del sexp de nivel superior. Aquí |está el punto.

|(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))

Querrás tener el código correctamente sangrado, así que presiona iuna vez. Lo volverá a sangrar bien.

Paso 2

Deberá marcar "d "con una región, ya que un objeto que puede manipularse en lispyes una lista, que no necesita ser marcada, o una secuencia de símbolos o listas que deben marcarse con una región.

Presione atpara hacerlo. El acomando permite marcar cualquier símbolo individual dentro de la lista principal y tes el índice de este símbolo en particular.

Paso 3

  1. Para ajustar la región actual con (), presione (.
  2. Presione C-fpara avanzar un carácter e insertar if.
  3. Presione (nuevamente para insertar ().
  4. Insertar > w 10.
  5. C-f C-m para mover la cadena a una nueva línea.

Etapa 4

Para clonar la cadena:

  1. Marque el símbolo o cadena en el punto con M-m.
  2. Presione c.

Si desea desactivar la región de una manera elegante y poner el punto al comienzo de la cadena, puede presionar idm:

  1. i seleccionará el contenido interno de la cadena.
  2. d intercambiará punto y marca.
  3. m desactivará la región

O podrías hacer m C-b C-b C-ben este caso, o C-g C-b C-b C-b. Creo que idmes mejor, ya que es lo mismo independientemente de la longitud de la cadena. Creo C-x C-x C-f C-g que también funcionaría independientemente de la longitud de la cadena.

Finalmente, inserte 2para obtener el código final:

(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))

Resumen

La secuencia completa fue: at( C-f, if, (, > w 10, C-f C-m M-m cidm, 2.

Tenga en cuenta que si cuenta la inserción del código, las únicas claves no relacionadas con la manipulación AST fueron dos C-fy una C-m.


3
Lispy suena interesante! Para el OP: puede desarrollar flujos de trabajo similares con paredit: emacsrocks.com/e14.html y expandir la región: github.com/magnars/expand-region.el , o simplemente jugar con los comandos de coincidencia de paréntesis incorporados: gnu. org / software / emacs / manual / html_node / emacs / Parentheses.html
Tyler

Estoy seguro de que estabas adivinando cuando empataste para resolver el problema por mí; gracias por el intento Me las arreglé para abrirme camino usando su código como ejemplo. Vea mi actualización para la solución correcta.
Zhro

6

Te acostumbrarás con el tiempo, pero, por supuesto, hay muchas cosas que puedes hacer para acelerarlo:

Sangría

Hay un método para esta locura. En lisp puedes hacer esto:

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

Suponga que 1es realmente una expresión grande y desea sangrarla en su propia línea.

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

Esto es engañoso. 1¡está sangrado solo en una ranura, a pesar de que son tres niveles completos de anidamiento más altos! Mejor decirlo por su padre.

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

Si usamos este esquema en su propio código, obtenemos esto:

(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))

Tenga en cuenta la fuerte correlación entre la sangría y el nivel de anidamiento. Las líneas con un mayor nivel de anidamiento siempre tienen sangría más que las líneas con menos.

Esto solo ayudará mucho. Puede ver fácilmente la "forma" del código, y la mayoría de los otros idiomas lo han entrenado para asociar sangría con bloques y bloques con alguna forma de anidamiento (ya sea explícito o implícito).

Como ejercicio, eliminé los paréntesis no esenciales del código. Debería poder leer el código y proporcionar los paréntesis faltantes, dado el conocimiento básico de Elisp (es decir, qué cosas son funciones y valores, y la estructura de 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
Tenga en cuenta que en Emacs, presionar la pestaña casi siempre establecerá la sangría correctamente como se describe aquí: ¡nunca debe hacerlo agregando espacios a mano!
Tyler

Unir "Cm" a newline-and-indentpara emacs-lisp-mode y lisp-interactive-mode resuelve su primer ejemplo, PythonNut. Y TAB hará el trabajo el resto del tiempo.
Davor Cubranic

5

Estoy incluyendo esto como una respuesta diferente.

A veces, la sangría te fallará. Su recurso, entonces, es usar algo como rainbow-delimiters:

ingrese la descripción de la imagen aquí

Esto hace que el nivel de anidación de cualquier par sea explícito y escaneado fácilmente.

Sin embargo, hay un problema bastante grande: los padres distraen ridículamente. Hay un equilibrio de énfasis aquí. rainbow-delimiters¡normalmente pondrá mucho énfasis en los parens a expensas del resto de su código! Pero si tonifica el arco iris hacia abajo, se volverá progresivamente más difícil de escanear.

Si puede encontrar un conjunto de caras que equilibre esto bien, entonces úselo.

Dicho esto, una solución (la que me hace reír a carcajadas) es tener dos pares de caras y cambiar entre ellas sobre la marcha. Cuando haces otras cosas, las caras se diluyen y se desvanecen en el fondo:

ingrese la descripción de la imagen aquí

Pero cuando pones el punto en un par, golpea a los parens para que puedas ver los niveles de anidación:

ingrese la descripción de la imagen aquí

Y aquí está el código para hacer eso:

(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))

¡Esto es genial! ¿Alguna razón por la que elegiste delimitadores del arco iris sobre paréntesis destacados para empezar?
Tyler

@Tyler no hay una razón en particular, aunque todavía me quedaría con los delimitadores de arcoíris ahora (pero eso puede ser solo porque estoy acostumbrado). Simplemente no había oído hablar de paréntesis destacados hasta hace muy poco.
PythonNut

4

Si sangra su código, entonces realmente necesita sangrarlo correctamente. Los desarrolladores de Lisp tienen expectativas de cómo debería ser una sangría adecuada. Esto no significa que el código se vea igual. Todavía hay diferentes formas de formatear el código.

  • ¿Cuánto debe durar una línea?
  • ¿Cómo distribuir una función / llamada macro a través de líneas?
  • ¿Cuántos espacios de sangría debería haber?
(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))

Esperaría que la sangría muestre contención de alguna manera. Pero no en tu código.

Por defecto, su código puede tener sangría como esta. Otras respuestas describen cómo puede hacerlo con la ayuda de 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))

Esperaría que las letvariables enlazadas comiencen en la misma columna vertical. También esperaría que el cuerpo del letes menos sangrado. Aprendemos cómo se letdebe sangrar a. Una vez que entrenas un poco, es un patrón visual que es fácil de reconocer.

Los principales bloques de construcción visuales en el código son defadvice, let*y un montón de llamadas a funciones. El defen el nombre me dice que es una definición seguida de un nombre, una lista de argumentos y un cuerpo.

Por eso me gusta ver

defadvice name arglist
  body

El let*sería:, let*una lista de enlaces y un cuerpo.

Por eso me gusta ver:

let*  list of bindings
  body

Más detallado:

let*   binding1
       binding2
       binding3
  body

Para una llamada de función me gusta ver:

FUNCTIONNAME ARG1 ARG2 ARG3

o

FUNCTIONNAME ARG1
             ARG2
             ARG3

o

FUNCTIONNAME
  ARG1
  ARG2
  ARG3

El que se use depende de la duración de los argumentos. No queremos hacer líneas demasiado largas y cada argumento en su propia línea nos permite tener más estructura.

Por lo tanto, debe escribir su código utilizando esos patrones y debe asegurarse de que el lector pueda identificar fácilmente esos patrones en su código.

Los paréntesis deben encerrar directamente sus construcciones.

(sin 10)

(sin
  10)

Evite los ejemplos de espacios en blanco:

(  sin 10  )

o

(
   sin
)

o

(sin 10
)

En Lisp, los paréntesis no tienen una función sintáctica del lenguaje, son parte de la sintaxis de la estructura de datos de la lista (expresiones-s). Por lo tanto, escribimos listas y formateamos las listas. Para formatear el contenido de la lista, utilizamos el conocimiento sobre el lenguaje de programación Lisp. Una letexpresión debe tener un formato diferente al de una llamada a función. Aún así, ambas son listas y los paréntesis deben encerrar directamente las listas. Hay algunos ejemplos en los que tiene sentido tener un paréntesis único en una línea, pero lo usa raramente.

Usa los paréntesis para ayudarte a encontrar los límites de la expresión. Permita que lo ayuden a moverse de una lista a otra, editar listas, cortar y copiar listas, reemplazar listas, sangrar / formatear listas, seleccionar listas, ...


2

Para obtener ayuda básica con sangría, use TAB y "CMq" ( indent-pp-sexp); enlazar "Cm" newline-and-indentle ahorrará tener que presionar TAB después de una nueva línea.

Puede navegar por sexp con "CM-left / right / up / down / home / end", que es muy útil.

Si le preocupa mantener el equilibrio entre paréntesis, use algo como smartparens (es un poco más indulgente ese crédito , que inicialmente puede parecer una camisa de fuerza). También viene con muchos enlaces para navegar y editar a nivel de sexp.

Por último, para resaltar parens, comience activando la opción (Menú Opciones-> Resaltar paréntesis coincidentes, o show-paren-mode). También puede usar uno de los paquetes mencionados por lawlist en su comentario, como "resaltar-paréntesis" o "delimitadores de arcoíris ".


1

Como una adición a lo que otros han dado como respuestas, aquí están mis 2 centavos:

  • La sangría automática proporcionada por emacs-lisp-modees esencial.
  • blink-matching-parenestá activado (no nil) de forma predeterminada. Déjalo así.
  • Utilizo show-paren-mode, para resaltar el par izquierdo que corresponde al par derecho antes del cursor.
  • Borro y vuelvo a escribir temporalmente el paréntesis derecho antes del cursor, si quiero ver mejor dónde está el paréntesis izquierdo correspondiente.

Yo no usar la asociación eléctrico o arco iris de cualquier cosa o cualquier otro "ayuda". Para mí eso es una molestia, no una ayuda.

En particular, electric-pair-modees, para mí, una molestia inconveniente. Pero a algunas personas les encanta. E imagino que podría ser útil en otros contextos de emparejamiento además de los paréntesis de Lisp (me imagino que sí, pero tampoco estoy convencido de tales casos de uso).

Encontrarás lo que funciona mejor para ti. Las cosas principales que quiero decir son: (1) la sangría automática es su amigo, ya que es una forma de detectar el paréntesis izquierdo que coincide con el par derecho antes del punto.

En particular, ver lo que la sangría automática hace por usted le enseña de inmediato que nunca más querrá poner a los padres correctos en una línea por sí mismos. Ese estilo de codificación, que es omnipresente en otros idiomas, es simplemente ruidoso.

Además de la sangría automática con la que te encuentras RETy te sientes TABcómodo usando C-M-q. (Uso C-h kde emacs-lisp-modeaveriguar lo que hacen estas teclas.)


1

Noté que algunas personas ya mencionaron delimitadores del arco iris, ese también es mi modo favorito cuando edito el código lisp.

En realidad, me encantan los paréntesis, lo mejor de Lisp son esos paréntesis, es divertido tratar con ellos si sabes cómo:

  • instale evil-mode y su plugin evil-surround, para que pueda editar los paréntesis fácilmente, por ejemplo, al presionar ci(se eliminará todo el contenido (), va(se seleccionará la cosa envuelta por "()" y el "()". y puedes presionar %para saltar entre los paréntesis coincidentes

  • use flycheck o flymake, los paréntesis sin igual se subrayarán en tiempo real


0

La dificultad aquí es que desea agregar un código antes y después de cierto sexp. En esta situación, el sexp es (concat " %" (number-to-string w) "d "). Desea insertar la instrucción if (if (> w 1) antes y la instrucción else " %2d ") después.

Una parte importante de la dificultad es aislar esto sexp, personalmente utilizo el siguiente comando para aislar unsexp

(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 )      ))

Simplemente coloque el cursor al principio de (concat " %" (number-to-string w) "d "), es decir, al principio (, luego aplique lo anterior isolate-sexp. Tu obtienes

(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))

Luego agregue el código apropiado antes y después de esto sexp, es decir

(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))

Y voilá. El código obtenido de esta manera quizás no sea hermoso, pero la posibilidad de perderse es menor.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.