Comment imbriquer une table de syntaxe dans une autre?

8

J'ai écrit un mode simple pour gérer JSON. Il utilise la machinerie dérivée pour réutiliser la plupart du code de json-mode. Cependant, un ajout est que vous pouvez insérer elisp dans le texte JSON qui est évalué au moment de la soumission JSON. Par exemple, un extrait du json ressemble à ceci:

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

Actuellement, la mise en évidence de la syntaxe de ce texte est interrompue, car le surligneur de syntaxe JSON est lancé par l'elisp. Je voudrais configurer une table de syntaxe imbriquée pour que l'elisp soit correctement reconnu comme elisp à l'intérieur des caractères d'échappement (j'ai choisi `dans ce cas). Je comprends que vous pouvez joindre des tables de caractères (à partir desquelles les tables de syntaxe sont construites) avec quelque chose comme:

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

Mais je ne comprends pas comment modifier la table de syntaxe pour commencer à utiliser la table de syntaxe enfant (elisp) entre les caractères d'échappement?

stsquad
la source
1
La syntaxe mettant en évidence le seul objectif? Si c'est le cas, certaines règles de verrouillage de police intelligent pourraient être beaucoup plus faciles que de jouer avec les tables de syntaxe.
Malabarba
@Malabarba: surtout si ce serait bien si les commandes de mouvement fonctionnaient comme prévu dans les bits lispy.J'ai essayé de jouer avec font-lock mais je ne pouvais pas le faire fonctionner correctement: git.linaro.org/people/alex.bennee/ lava-mode.git / blob / HEAD: /…
stsquad

Réponses:

7

Vous ne voulez pas imbriquer une table de syntaxe (qui est une structure vectorielle) dans une autre, vous voulez mettre en place un tampon où, selon la position, une table de syntaxe serait utilisée à la place de l'autre.

L'autre réponse décrit comment procéder à l'aide de la syntax-tablepropriété text. Voici comment le faire en utilisant l'un des packages "mode multiple multiple", mmm-mode . Il utilisera tout le mode principal au niveau supérieur du tampon, et la table de syntaxe du sous-mode, les règles de verrouillage de police, le clavier, etc. dans les "sous-régions".

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

Cela suppose que vos fichiers en mode mixte sont nommés *.el.json. Ajustez comme approprié.

Maintenant, installez mmm-mode, évaluez ce qui précède et (seulement alors) ouvrez l'un des fichiers en question.

Dmitry
la source
Je suppose que je ne me suis pas complètement concentré sur le fonctionnement des tables de syntaxe. Je suppose qu'ils se sont imbriqués car il doit y avoir un état dépendant des caractères précédents? Ma dernière expérience du mode mmm était le mode nxhtml mais je l'ai trouvé plutôt maladroit, c'est pourquoi je suis passé au mode web.
stsquad
nxhtml-modeest plutôt maladroit, oui. mmm-modemoins, mais c'est toujours complexe. Je ne sais pas si vous pouvez faire quelque chose comme ça dans web-mode; demandez à son auteur, peut-être.
Dmitry
Les tables de syntaxe ne contiennent aucun état. De même, le syntax-ppsscache le fait.
Dmitry
désolé, je n'ai pas encore accepté la réponse. Je n'ai pas eu le temps de regarder le mode mmm. Je ne sais pas si cela signifie que je devrais reformuler la question ou la laisser continuer à se bloquer?
stsquad
Ça ne me dérange pas. Mais si vous souhaitez que je présente une réponse plus complète en utilisant le mode mmm, vous devez inclure l'exemple d'extrait de votre double balisage dans la question.
Dmitry
7

Ok, passons à l'essentiel.

L'imbrication de tables de syntaxe est possible

Les tables de syntaxe n'ont pas besoin d'être globales à l'ensemble du tampon. Vous pouvez les appliquer en tant que propriétés de texte à des régions spécifiques. Cela signifie que vous ne pouvez en effet appliquer la elisptable de syntaxe qu'aux régions entourées de backticks.

Comment tu fais ça?

Voici une façon de procéder. Cette méthode le fait immédiatement avant l'exécution du verrouillage de police dans le tampon, elle devrait donc spécifiquement empêcher vos problèmes de verrouillage de police.

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

Dans votre définition de mode principal, vous devrez ajouter:

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

Les tables de syntaxe ne sont pas identiques à la mise en évidence de la syntaxe

Le surligneur de syntaxe (le système de verrouillage de police) utilise des tables de syntaxe dans le cadre de ses informations, de sorte que la solution ci-dessus devrait empêcher le surligneur de devenir fou.

Cependant, ce n'est qu'une partie des données, si vous souhaitez également que le texte des backticks soit coloré exactement comme vous le verriez dans un tampon elisp, vous devrez étendre la fonction ci-dessus pour le faire spécifiquement.

(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
la source
pouvez-vous être un peu plus précis? Dois-je imbriquer un appel au verrouillage de police pour faire la coloration elisp?
stsquad
@stsquad J'ai étendu la réponse avec un exemple par où vous pouvez commencer pour cela. J'allais fournir une solution complète, mais je suis tombé sur un obstacle après l'autre et j'ai finalement conclu que ce serait une toute autre question.
Malabarba
@Malabarba Merci pour l'info, mais votre solution complète semble erronée: les propriétés du texte font partie du texte du tampon, donc vous faites le tampon modifié chaque fois qu'il est fontifié. Cela invalidera plusieurs caches et fera qu'Emacs consommera plus de CPU sans raison valable. Vous devriez au moins utiliser with-silent-modifications(comme le font-lock-default-fontify-regionfait), mais mieux serait de déplacer cette add-text-propertiesentreprise vers syntax-propertize-function.
Dmitry