Como editar o elisp sem se perder entre parênteses

13

Estou modificando algum código elisp do 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))

Consegui consertar um erro em que o recuo era diferente, modificando (count-lines (point-min) (point-max))para (+ (count-lines (point-min) (point-max)) 1). Essa foi fácil.

Mas agora eu quero modificá-lo para que a largura mínima seja 2 adicionando um if-condicional em que (concat " %" (number-to-string w) "2d ")se a contagem de números de linha for <10.

Isso deve ser fácil! Adicione um condicional e copie / cole o concat. Pedaço de bolo, certo? Quero dizer, eu sei o que devo fazer, mas raramente toquei em elisp e sempre fico assustada quando tenho que modificar qualquer coisa com muitos parênteses.

O estilo "correto", pelo que entendi, é estruturar o código com base na indentação e agrupar os parênteses no final de uma linha, e não por si só. Vindo de outras linguagens no estilo 'C', luto tanto com a leitura quanto com a escrita de código dessa maneira. Então, minha pergunta é: o que estou fazendo de errado? Como devo editar o elisp e navegar pelo código para não precisar ficar sentado e contar todos os parênteses?

Quando trabalho com algo no elisp que fica muito profundo, tenho que fechar a porta, fechar as persianas e começar a posicionar o parêntese no estilo K&R, para que eu possa não apenas ler, mas modificar a coisa danada sem pirar.

Obviamente, estou fazendo tudo errado. Como posso tocar elisp assim sem medo?

Observe que minha pergunta é como navegar e editar o elisp não como uma questão de estilo. Já estou usando o seguinte como guia de estilo: https://github.com/bbatsov/emacs-lisp-style-guide

Atualizações:

Como formatar corretamente o elisp antes de se embaraçar no emacs.stackexchange:

Marque seu elisp e execute M-x indent-region.

A solução do problema:

Para aqueles que querem saber como executar uma justificação correta para linum com uma largura mínima de dois, aqui está a solução:

(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
fonte
1
Todo mundo luta com a leitura do cisco que vem do C. Isso leva tempo, mas eventualmente se torna extremamente natural. Além disso, você provavelmente pode resolver seu problema alterando o formato linum para uma string de formato como "% 2d".
Jordon Biondo
Não posso usar apenas %2dporque, uma vez que a largura passa para 3 ou mais caracteres, ela passa de justificado à direita para justificado à esquerda.
Zhro 5/08/15
Existem algumas bibliotecas que destacam parênteses correspondentes que são inestimáveis ​​(na minha opinião), por exemplo highlight-parentheses; rainbow-delimiters; etc. Aqui está minha própria versão simplificada highlight-parenthesesque permite rolar sem remover os parênteses que foram coloridos pela última vez: stackoverflow.com/a/23998965/2112489 No futuro, é uma pergunta por cliente / segmento.
lawlist
1
Parece muito que seu problema está realmente lendo , não editando o código Elisp. Parece que você tem problemas para entender a estrutura do Lisp e isso prejudica sua edição.
precisa saber é o seguinte
1
Em sua defesa, esse é um trecho de código bastante recuado. Eu mesmo li errado nas primeiras 3 vezes por causa disso. Além disso, paredit .
Malabarba 6/08

Respostas:

12

Existem vários pacotes de complementos que podem ajudar, como paredit, smartparens e lispy. Esses pacotes facilitam a navegação e a manipulação do código lisp para que você possa pensar em expressões-s e deixar o editor se preocupar com o equilíbrio de parênteses.

O Emacs também possui muitos comandos internos para lidar com sexps e listas que valem a pena aprender e podem ajudá-lo a se acostumar com a estrutura do código lisp. Geralmente, eles são vinculados a um C-M-prefixo, como:

  • C-M-f/ C-M-bavançar / retroceder por sexp
  • C-M-n/ C-M-ppara avançar / retroceder por lista
  • C-M-u/ C-M-dpara subir / descer um nível
  • C-M-t para trocar os dois sexps em torno do ponto
  • C-M-k matar um sexp
  • C-M-SPC para marcar um sexp
  • C-M-q reentrar um sexp

O código de exemplo que você forneceu pode ser mais claro se você corrigir o recuo: coloque um ponto no início do (defadvice...e pressione C-M-qpara re-recuar toda a expressão. Para substituir o (concat...eu começaria colocando um ponto no início desse sexp e pressionando C-M-opara dividir a linha, preservando o recuo. Em seguida, adicione seu (if...e use os comandos acima para ir para o final de uma lista e adicionar outro ponto de fechamento, de volta ao início para recuar, etc.

Você também pode querer ativar show-paren-mode, o que destacará o ponto correspondente quando o ponto estiver no início ou no final de uma lista. Você pode preferir destacar a expressão inteira em vez do parêntese correspondente, portanto, tente personalizar show-paren-style.

Como o @Tyler mencionou nos comentários de outra resposta, dê uma olhada no manual para aprender mais sobre esses e outros comandos relacionados.

glucas
fonte
Observe que C-M-qpode ser chamado com um argumento de prefixo ( C-u C-M-q) para imprimir a expressão no ponto. Isso interromperá tudo para separar linhas e recuar, de modo que o aninhamento seja muito óbvio. Geralmente, você não quer que seu código lisp seja assim, mas pode ser útil entender um pouco de código sem inserir manualmente K&R completo nele. (E você sempre pode C-x udesfazer).
glucas
6

Uma boa maneira de editar o LISP é manipulando o AST, em vez dos caracteres ou linhas individuais. Eu tenho feito isso com o meu pacote lispy que eu comecei há 2 anos. Eu acho que realmente é necessário um pouco de prática para aprender como fazer isso bem, mas mesmo usando apenas o básico já deve ajudá-lo.

Eu posso explicar como eu faria essa alteração:

Passo 1

A posição inicial é geralmente ter o ponto antes do sexp de nível superior. Aqui |está o ponto.

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

Você deseja que o código seja recuado corretamente, então pressione iuma vez. Ele irá reentrá-lo bem.

Passo 2

Você deseja marcar "d "com uma região, pois um objeto passível de manipulação lispyé uma lista, que não precisa ser marcada, ou uma sequência de símbolos ou / e listas que precisam ser marcadas com uma região.

Pressione atpara fazer isso. O acomando permite marcar qualquer símbolo único na lista pai e té o índice desse símbolo específico.

etapa 3

  1. Para quebrar a região atual (), pressione (.
  2. Pressione C-fpara avançar um caractere e inserir if.
  3. Pressione (novamente para inserir ().
  4. Inserir > w 10.
  5. C-f C-m para mover a string para uma nova linha.

Passo 4

Para clonar a sequência:

  1. Marque o símbolo ou a corda no ponto M-m.
  2. Pressione c.

Se você deseja desativar a região de maneira sofisticada e colocar o ponto no início da string, pressione idm:

  1. i selecionará o conteúdo interno da sequência.
  2. d trocará ponto e marca.
  3. m desativará a região

Ou você poderia fazer m C-b C-b C-bneste caso, ou C-g C-b C-b C-b. Eu acho que idmé melhor, pois é o mesmo, independentemente do comprimento da corda. Eu acho C-x C-x C-f C-g que também funcionaria independentemente do comprimento da string.

Por fim, insira 2para obter o 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))

Sumário

A sequência completa foi de: at( C-f, if, (, > w 10, C-f C-m M-m cidm, 2.

Observe que, se você contar a inserção do código, as únicas chaves não relacionadas à manipulação do AST serão duas C-fe uma C-m.

abo-abo
fonte
3
Lispy parece interessante! Para o OP: você pode desenvolver fluxos de trabalho semelhantes com o paredit: emacsrocks.com/e14.html e expandir a região: github.com/magnars/expand-region.el , ou apenas brincando com os comandos de correspondência entre parênteses embutidos: gnu. org / software / emacs / manual / html_node / emacs / Parentheses.html
Tyler
Tenho certeza que você estava adivinhando quando empatou para resolver o problema para mim; obrigado pela tentativa. Consegui me confundir usando o seu código como exemplo. Veja minha atualização para a solução correta.
Zhro 27/08/2015
6

Você se acostumará com o tempo, mas é claro que há muito o que fazer para ajudar a acelerar:

Indentação

Existe um método para essa loucura. No lisp você pode fazer isso:

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

Suponha que essa 1seja realmente uma expressão grande e que você queira recuá-la em sua própria linha.

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

Isso é enganador. 1é recuado apenas em um slot, apesar de serem três níveis inteiros de aninhamento mais alto! Melhor colocá-lo por seus pais.

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

Se usarmos esse esquema em seu próprio código, obtemos o seguinte:

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

Observe a forte correlação entre o recuo e o nível de aninhamento. Linhas com um nível maior de aninhamento sempre são recuadas mais do que linhas com menos.

Isso por si só vai ajudar muito. Você pode ver facilmente a "forma" do código, e a maioria dos outros idiomas o treinou para associar a indentação com blocos e blocos com alguma forma de aninhamento (explícita ou implícita).

Como exercício, removi os parênteses não essenciais do código. Você deve ler o código e fornecer os parênteses ausentes, considerando o conhecimento básico do Elisp (a saber, quais são as funções e valores e a estrutura 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
PythonNut
fonte
1
Observe que no Emacs, pressionar a tecla tab quase sempre define o recuo corretamente, conforme descrito aqui - você nunca deve fazer isso adicionando espaços manualmente!
Tyler
Vincule "Cm" newline-and-indentpara emacs-lisp-mode e lisp-interação-mode resolva seu primeiro exemplo, PythonNut. E o TAB fará o trabalho o resto do tempo.
Davor Cubranic
5

Estou incluindo isso como uma resposta diferente.

Às vezes, o recuo irá falhar. Seu recurso, então, é usar algo como rainbow-delimiters:

insira a descrição da imagem aqui

Isso torna o nível de aninhamento de qualquer parêntese explícito e facilmente verificado.

Porém, há um grande problema: os parênteses são ridiculamente perturbadores. Há um equilíbrio de ênfase aqui. rainbow-delimitersnormalmente colocará muita ênfase nos parâmetros às custas do restante do seu código! Mas se você tonificar o arco-íris voltado para baixo, eles se tornarão progressivamente mais difíceis de digitalizar.

Se você pode encontrar um conjunto de faces que equilibra isso bem, use-o de qualquer maneira.

Dito isto, uma solução (a que me faz rir de alegria) é ter dois pares de rostos e alternar entre eles rapidamente. Quando você faz outras coisas, os rostos são diluídos e desbotam em segundo plano:

insira a descrição da imagem aqui

Mas quando você coloca o ponto em um parêntese, ele perfura os parênteses para que você possa ver os níveis de aninhamento:

insira a descrição da imagem aqui

E aqui está o código para fazer isso:

(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
fonte
Isso é legal! Alguma razão para você escolher delimitadores de arco-íris em vez de parênteses de destaque?
Tyler
@ Tyler, por nenhuma razão em particular, apesar de eu ainda ficar com delimitadores de arco-íris agora (mas isso pode ser apenas porque estou acostumado). Simplesmente não tinha ouvido falar em parênteses de destaque até muito recentemente.
entanto
4

Se você recuar seu código, precisará realmente recortá-lo corretamente. Os desenvolvedores do Lisp têm expectativas de como deve ser o recuo adequado. Isso não significa que o código tenha a mesma aparência. Ainda existem maneiras diferentes de formatar o código.

  • quanto tempo deve ser uma linha?
  • como distribuir uma chamada de função / macro por linhas?
  • quantos espaços de indentação deveriam existir?
(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))

Eu esperaria que o recuo mostre contenção de alguma maneira. Mas não no seu código.

Por padrão, seu código pode ser recuado dessa maneira. Outras respostas descreveram como você pode fazer isso com a ajuda do 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))

Eu esperaria que as letvariáveis ​​ligadas iniciassem na mesma coluna vertical. Eu também esperaria que o corpo do leté menos recuado. Aprendemos como a letdeve ser recuado. Depois de treinar um pouco, é um padrão visual fácil de reconhecer.

Os principais componentes visuais do código são o defadvice, let*e várias chamadas de função. O defno nome me diz que é uma definição seguida por um nome, uma lista de argumentos e um corpo.

Então eu gosto de ver

defadvice name arglist
  body

o let* seria let*:, uma lista de ligações e um corpo.

Assim, eu gosto de ver:

let*  list of bindings
  body

Mais detalhado:

let*   binding1
       binding2
       binding3
  body

Para uma chamada de função, gosto de ver:

FUNCTIONNAME ARG1 ARG2 ARG3

ou

FUNCTIONNAME ARG1
             ARG2
             ARG3

ou

FUNCTIONNAME
  ARG1
  ARG2
  ARG3

Qual deles é usado depende de quanto tempo os argumentos são. Não queremos fazer linhas muito longas e cada argumento em sua própria linha nos permite ter mais estrutura.

Portanto, você precisa escrever seu código usando esses padrões e garantir que o leitor possa identificar facilmente esses padrões no seu código.

Os parênteses devem incluir diretamente suas construções.

(sin 10)

(sin
  10)

Evite exemplos de espaço em branco:

(  sin 10  )

ou

(
   sin
)

ou

(sin 10
)

No Lisp, os parênteses não têm função sintática da linguagem, eles fazem parte da sintaxe da estrutura de dados da lista (expressões-s). Assim, escrevemos listas e formatamos as listas. Para formatar o conteúdo da lista, usamos conhecimento sobre a linguagem de programação Lisp. UMAlet expressão deve ser formatada diferente de uma chamada de função. Ainda ambas são listas e os parênteses devem incluir as listas diretamente. Existem alguns exemplos em que faz sentido ter um único parêntese em uma linha, mas use isso raramente.

Use os parênteses para ajudá-lo a encontrar os limites da expressão. Deixe-os ajudá-lo a mover-se de lista em lista, editar listas, cortar e copiar listas, substituir listas, recuar / formatar listas, selecionar listas, ...

Rainer Joswig
fonte
2

Para obter ajuda básica com indentação, use TAB e "CMq" ( indent-pp-sexp); a ligação "Cm" para newline-and-indentevitará que você pressione TAB após uma nova linha.

Você pode navegar pelo sexp com "CM-esquerda / direita / cima / baixo / casa / fim", o que é muito útil.

Se você estiver preocupado com a manutenção do equilíbrio entre parênteses, use algo como smartparens (é um pouco mais perdoador que paritar , que pode inicialmente parecer usar uma camisa de força). Ele também vem com muitas ligações para navegar e editar no nível sexp.

Por fim, para realçar parênteses, comece ativando a opção (menu Opções-> realçar parênteses correspondentes ou show-paren-mode.) "

Davor Cubranic
fonte
1

Como complemento ao que outros deram como respostas, aqui estão meus 2 centavos:

  • O recuo automático fornecido por emacs-lisp-modeé essencial.
  • blink-matching-parenestá ativado (não nil) por padrão. Deixe assim.
  • Eu uso show-paren-modepara destacar o parêntese esquerdo correspondente ao parêntese direito antes do cursor.
  • Eu apago e redigito temporariamente o parêntese direito antes do cursor, se quiser ver melhor onde está o parêntese esquerdo correspondente.

Eu não utilizar uma conexão elétrica ou arco-íris qualquer coisa ou qualquer outro tal "ajuda". Para mim, isso é um incômodo, não um auxílio.

Em particular, electric-pair-modeé, para mim, um incômodo inconveniente. Mas algumas pessoas adoram. E imagino que possa ser útil em outros contextos de emparelhamento além dos parênteses do Lisp (imagino que sim, mas também não estou convencido de tais casos de uso).

Você encontrará o que funciona melhor para você. As principais coisas que quero dizer são: (1) o recuo automático é seu amigo, como é uma maneira de identificar o parêntese esquerdo que corresponde ao parêntese direito antes do ponto.

Em particular, ver o que o recuo automático faz por você ensina imediatamente que você nunca mais deseja colocar os parênteses em uma linha sozinhos. Esse estilo de codificação, que é onipresente em outros idiomas, é apenas barulhento.

Além do recuo automático que você utiliza RETe TABse sente confortável usando C-M-q. (Use C-h kem emacs-lisp-modedescobrir o que essas chaves fazer.)

Desenhou
fonte
0

Notei que algumas pessoas já mencionaram delimitadores de arco-íris, esse também é o meu modo favorito ao editar o código lisp.

Na verdade, eu amo parênteses, a melhor coisa sobre o Lisp são esses parênteses, é divertido lidar com eles se você souber como:

  • instale o modo evil e seu plug-in evil-surround, para que você possa editar parênteses facilmente, por exemplo, pressione ci(removerá tudo o que estiver dentro (), va(selecionará a coisa envolvida por "()" e "()". e você pode pressionar %para pular entre os parênteses correspondentes

  • use flycheck ou flymake, parênteses incomparáveis ​​serão sublinhados em tempo real

Chen Bin
fonte
0

A dificuldade aqui é que você deseja adicionar algum código antes e depois de um certo sexp. Nesta situação, o sexp é (concat " %" (number-to-string w) "d "). Você deseja inserir a instrução if (if (> w 1) antes e a instrução else " %2d ") depois.

Uma grande parte da dificuldade é isolar isso sexp, pessoalmente eu uso o comando abaixo para isolar umsexp

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

Basta colocar o cursor no início de (concat " %" (number-to-string w) "d "), ou seja, no primeiro (, e depois aplicar o acima isolate-sexp. Você obtém

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

Em seguida, adicione o código apropriado antes e depois disso sexp, ou seja,

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

E pronto. O código obtido dessa maneira talvez não seja bonito, mas a chance de se perder é menor.

Nome
fonte