Como posso aninhar uma tabela de sintaxe em outra?

8

Eu escrevi um modo simples para lidar com JSON. Ele usa o mecanismo derivado para reutilizar a maior parte do código do modo json. No entanto, uma adição é que você pode inserir elisp no texto JSON que é avaliado no momento do envio do JSON. Por exemplo, um trecho do json se parece com isso:

{
    "parameters": {
        "IRC_USER": "stsquad",
        "PUB_KEY": `(format "\"%s\"" (s-trim (shell-command-to-string "cat ~/.ssh/id_rsa.pub")))`
    }
}

Atualmente, o realce da sintaxe deste texto é interrompido à medida que o hightlighter da sintaxe JSON é lançado pelo elisp. Gostaria de configurar uma tabela de sintaxe aninhada para que o elisp seja reconhecido corretamente como elisp quando estiver dentro dos caracteres de escape (eu escolhi `neste caso). Eu entendo que você pode associar char-tables (das quais são criadas tabelas de sintaxe) com algo como:

(defvar lava-mode-syntax-table
  (let ((json-table (copy-syntax-table json-mode-syntax-table))
        (elisp-table (copy-syntax-table lisp-mode-syntax-table)))
    (set-char-table-parent elisp-table json-table)
    (modify-syntax-entry ?` "(`" json-table)
    (modify-syntax-entry ?` ")`" json-table)
    json-table)
  "LAVA Mode syntax table.
This is a combination of json-mode-syntax-table with an escape into
  lisp-mode-syntax table for the embedded elisp.")

Mas não entendo como posso modificar a tabela de sintaxe para começar a usar a tabela de sintaxe filho (elisp) enquanto estiver entre os caracteres de escape?

stsquad
fonte
1
A sintaxe destaca o único objetivo? Nesse caso, algumas regras inteligentes de bloqueio de fonte podem ser muito mais fáceis do que mexer com as tabelas de sintaxe.
Malabarba
@ Malabarba: principalmente embora seria bom se os comandos de movimento funcionassem como esperado nos bits lispy. Eu tentei mexer com o bloqueio de fonte, mas não consegui fazê-lo funcionar corretamente: git.linaro.org/people/alex.bennee/ lava-mode.git / blob / HEAD: /…
stsquad 13/11/14

Respostas:

7

Você não deseja aninhar uma tabela de sintaxe (que é uma estrutura vetorial) dentro de outra, deseja configurar um buffer no qual, dependendo da posição, uma tabela de sintaxe seria usada em vez da outra.

A outra resposta descreve como fazer isso usando a syntax-tablepropriedade text. Veja como fazer isso usando um dos pacotes "vários modos principais", mmm-mode . Ele usará tudo do modo primário no nível superior do buffer, e a tabela de sintaxe do submodo, regras de bloqueio de fonte, mapa de chaves etc. nas "sub-regiões".

(require 'mmm-auto)
(setq mmm-global-mode 'auto)

(mmm-add-classes
 '((eljson :submode emacs-lisp-mode
           :front ": *\\(`\\)" :back "`"
           :front-match 1
           :face mmm-code-submode-face)))

(mmm-add-mode-ext-class 'json-mode "\\.el\\.json\\'" 'eljson)

Isso pressupõe que seus arquivos de modo misto sejam nomeados *.el.json. Ajuste conforme apropriado.

Agora, instale mmm-mode, avalie o acima e (somente então) abra um dos arquivos em questão.

Dmitry
fonte
Acho que ainda não entendi completamente como as tabelas de sintaxe funcionam. Eu presumi que eles aninhavam, pois deve haver um estado dependente dos caracteres anteriores? Minha última experiência no mmm-mode foi no modo nxhtml, mas achei bastante desajeitado, e foi por isso que mudei para o modo web.
Stsquad
nxhtml-modeé bastante desajeitado, sim. mmm-modemenos, mas ainda é complexo. Não tenho certeza se você pode fazer algo assim web-mode; pergunte ao autor, talvez.
Dmitry
As tabelas de sintaxe não contêm nenhum estado. De maneira semelhante, o syntax-ppsscache faz.
Dmitry
desculpe, ainda não aceitei a resposta. Não tive tempo de olhar para o modo mmm. Não tenho certeza se isso significa que devo reformular a pergunta ou deixá-la continuar travada.
Stsquad
Eu não me importo. Mas se você quiser que eu apresente uma resposta mais completa usando o modo mmm, inclua o trecho de exemplo da sua marcação dupla na pergunta.
Dmitry
7

Ok, vamos esclarecer algumas noções básicas.

É possível aninhar tabelas de sintaxe

As tabelas de sintaxe não precisam ser globais para todo o buffer. Você pode aplicá-los como propriedades de texto a regiões específicas. Isso significa que você pode realmente aplicar a elisptabela de sintaxe apenas às regiões cercadas por backticks.

Como você faz isso?

Aqui está uma maneira de fazer isso. Esse método faz isso imediatamente antes do bloqueio de fonte ser executado no buffer, portanto, deve evitar especificamente os problemas de bloqueio de fonte.

(defun endless/set-syntax-then-fontify (beg end loudly)
  "Apply elisp syntax table to relevant regions before calling font-lock."
  (save-match-data
    (save-excursion
      (save-restriction
        (widen)
        (narrow-to-region beg end)
        (while (search-forward "`" nil 'noerror)
          ;; Using `end' here excludes the `, I don't know which syntax you
          ;; want to apply to that.
          (let ((left (match-end 0)))
            (when (search-forward "`" nil 'noerror)
              (add-text-properties
               left (match-beginning 0)
               (list 'syntax-table emacs-lisp-mode-syntax-table))))))))
  (font-lock-default-fontify-region beg end loudly))

Na sua definição de modo principal, você precisará adicionar:

(set (make-local-variable 'font-lock-fontify-region-function)
     #'endless/set-syntax-then-fontify)

As tabelas de sintaxe não são iguais ao destaque de sintaxe

O marcador de sintaxe (o sistema de bloqueio de fonte) usa Tabelas de sintaxe como parte de suas informações; portanto, a solução acima deve impedir o marcador de ficar louco.

No entanto, isso é apenas parte dos dados. Se você também quiser que o texto nos backticks seja colorido exatamente como veria em um buffer elisp, será necessário estender a função acima para fazer isso especificamente.

(defun endless/set-syntax-then-fontify (beg end loudly)
  "Apply elisp syntax table to relevant regions before calling font-lock."
  (save-match-data
    (save-excursion
      (save-restriction
        (widen)
        (narrow-to-region beg end)
        (while (search-forward "`" nil 'noerror)
          ;; Using `end' here excludes the `, I don't know which syntax you
          ;; want to apply to that.
          (let ((left (match-end 0)))
            (when (search-forward "`" nil 'noerror)
              (add-text-properties
               left (match-beginning 0)
               (list 'syntax-table emacs-lisp-mode-syntax-table))))))))
  (font-lock-default-fontify-region beg end loudly)
  ;; Do some specific elisp fontifying here
  (save-match-data
    (save-excursion
      (save-restriction
        (widen)
        (narrow-to-region beg end)
        (while (search-forward "`" nil 'noerror)
          (let ((left (match-end 0)))
            (when (search-forward "`" nil 'noerror)
              ;; Call some function to fontify elisp between `left' and (match-beginning 0)
              )))))))
Malabarba
fonte
você pode ser um pouco mais específico? Preciso aninhar uma chamada para o bloqueio de fonte para fazer a coloração elisp?
Stsquad
@stsquad Estendi a resposta com um exemplo de onde você pode começar. Eu estava indo para fornecer uma solução completa, mas me deparei com um obstáculo após o outro e, finalmente, cheguei à conclusão de que essa seria uma questão totalmente diferente.
Malabarba
@ Malabarba Obrigado pela informação, mas sua solução completa parece equivocada: as propriedades de texto fazem parte do texto do buffer, então você está modificando o buffer sempre que for tipificado. Isso invalidará vários caches e fará com que o Emacs consuma mais CPU sem uma boa razão. Você deve pelo menos usar with-silent-modifications(como font-lock-default-fontify-regionfaz), mas ainda melhor seria mudar esse add-text-propertiesnegócio para syntax-propertize-function.
Dmitry