Wie kann ich eine Syntaxtabelle in eine andere verschachteln?

8

Ich habe einen einfachen Modus für den Umgang mit JSON geschrieben. Es verwendet die abgeleitete Maschinerie, um den größten Teil des Codes von json-mode wiederzuverwenden. Eine Ergänzung ist jedoch, dass Sie elisp in den JSON-Text einfügen können, der zum Zeitpunkt der JSON-Übermittlung ausgewertet wird. Zum Beispiel sieht ein Auszug aus dem json so aus:

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

Derzeit ist die Syntaxhervorhebung dieses Textes unterbrochen, da der JSON-Syntax-Hightlighter vom Elisp geworfen wird. Ich möchte eine verschachtelte Syntaxtabelle einrichten, damit das Elisp innerhalb der Escape-Zeichen ordnungsgemäß als Elisp erkannt wird (in diesem Fall habe ich `gewählt). Ich verstehe, dass Sie char-Tabellen (aus denen Syntax-Tabellen erstellt werden) mit etwas verbinden können wie:

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

Aber ich verstehe nicht, wie ich die Syntaxtabelle ändern kann, um die untergeordnete Syntax-Tabelle (elisp) zwischen den Escape-Zeichen zu verwenden?

stsquad
quelle
1
Ist das Hervorheben der Syntax das einzige Ziel? In diesem Fall sind einige Regeln für die intelligente Schriftsperre möglicherweise viel einfacher als das Durcheinander mit den Syntaxtabellen.
Malabarba
@ Malabarba: Meistens, obwohl es schön wäre, wenn Bewegungsbefehle wie erwartet in den lispy Bits funktionieren würden. Ich hatte versucht, mit der Schriftsperre herumzuspielen, konnte sie aber nicht richtig zum Laufen bringen
stsquad

Antworten:

7

Sie möchten nicht eine Syntaxtabelle (bei der es sich um eine Vektorstruktur handelt) in eine andere verschachteln, sondern einen Puffer einrichten, in dem je nach Position eine Syntaxtabelle anstelle der anderen verwendet wird.

Die andere Antwort beschreibt, wie dies mithilfe der syntax-tabletext-Eigenschaft durchgeführt wird. Hier erfahren Sie, wie Sie dies mit einem der "Multiple Major Mode" -Pakete, dem mmm-Modus , tun . Es wird alles im Primärmodus auf der obersten Ebene des Puffers und die Syntaxtabelle des Submodus, die Regeln für die Schriftsperre, die Keymap usw. in den "Subregionen" verwendet.

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

Dies setzt voraus, dass Ihre Dateien im gemischten Modus benannt sind *.el.json. Passen Sie die Einstellungen entsprechend an.

Installieren mmm-mode, evaluieren Sie nun das oben Gesagte und öffnen Sie (nur dann) eine der fraglichen Dateien.

Dmitry
quelle
Ich glaube, ich habe mich noch nicht vollständig mit der Funktionsweise der Syntaxtabellen befasst. Ich nahm an, dass sie verschachtelt sind, da es einen abhängigen Status von den vorherigen Zeichen geben muss. Meine letzte Erfahrung mit dem mmm-Modus war der nxhtml-Modus, aber ich fand ihn ziemlich klobig, weshalb ich auf den Web-Modus umgestiegen bin.
stsquad
nxhtml-modeist ziemlich klobig, ja. mmm-modeweniger, aber es ist immer noch komplex. Ich bin mir nicht sicher, ob Sie so etwas in tun können web-mode. Fragen Sie vielleicht den Autor.
Dmitry
Syntaxtabellen enthalten keinen Status. In ähnlicher Weise syntax-ppssfunktioniert der Cache.
Dmitry
Entschuldigung, ich habe die Antwort noch nicht akzeptiert. Ich hatte keine Zeit, mir den mmm-Modus anzuschauen. Ich bin mir nicht sicher, ob dies bedeutet, dass ich die Frage umformulieren oder weiter hängen lassen soll.
Stsquad
Es macht mir nichts aus. Wenn Sie jedoch möchten, dass ich im mmm-Modus eine vollständigere Antwort präsentiere, sollten Sie das Beispiel-Snippet Ihres doppelten Markups in die Frage aufnehmen.
Dmitry
7

Ok, lassen Sie uns einige Grundlagen klarstellen.

Das Verschachteln von Syntaxtabellen ist möglich

Syntaxtabellen müssen nicht für den gesamten Puffer global sein. Sie können sie als Texteigenschaften auf bestimmte Regionen anwenden. Dies bedeutet, dass Sie dieelisp Syntaxtabelle tatsächlich nur auf Regionen anwenden können , die von Backticks umgeben sind.

Wie machst du das?

Hier ist eine Möglichkeit, wie Sie das tun können. Diese Methode führt dies unmittelbar vor dem Durchlaufen der Schriftsperre durch den Puffer durch, sodass Ihre Probleme beim Sperren von Schriftarten speziell vermieden werden sollten.

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

In Ihrer Definition für den Hauptmodus müssen Sie Folgendes hinzufügen:

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

Syntaxtabellen sind nicht dasselbe wie Syntaxhervorhebung

Der Syntax-Textmarker (das Font-Lock-System) verwendet Syntaxtabellen als Teil seiner Informationen, daher sollte die obige Lösung verhindern, dass der Textmarker verrückt wird.

Dies ist jedoch nur ein Teil der Daten. Wenn Sie auch möchten, dass der Text in Backticks genau so gefärbt wird, wie Sie es in einem Elisp-Puffer sehen würden, müssen Sie die obige Funktion erweitern, um dies speziell zu tun.

(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
quelle
Kannst du etwas genauer sein? Muss ich einen Aufruf der Schriftsperre verschachteln, um die Elisp-Färbung durchzuführen?
Stsquad
@stsquad Ich habe die Antwort um ein Beispiel erweitert, wo Sie damit anfangen können. Ich wollte eine vollständige Lösung anbieten, stieß aber auf ein Hindernis nach dem anderen und kam schließlich zu dem Schluss, dass dies eine ganz andere Frage sein würde.
Malabarba
@Malabarba Vielen Dank für die Informationen, aber Ihre vollständige Lösung sieht falsch aus: Texteigenschaften sind Teil des Puffertextes, sodass Sie den Puffer jedes Mal ändern, wenn er in Schriftarten geschrieben wird. Dadurch werden mehrere Caches ungültig und Emacs verbraucht ohne guten Grund mehr CPU. Sie sollten zumindest with-silent-modifications(wie font-lock-default-fontify-regionauch) verwenden, aber noch besser wäre es, dieses add-text-propertiesGeschäft zu verlagern syntax-propertize-function.
Dmitry