json - Emacs 中类似模型/ View 的编辑

标签 json model-view-controller emacs elisp

我有一些 JSON 文件,我正在编写一种模式,允许独立于其余部分编辑 JSON 对象的单个属性。例如:

foo.json:

{
  "creation_timestamp": "1411210038.000000",
  "description": "lorem ipsum.\ndolor sit amet.",
  "version": 4
}

打开 foo.json 会产生这个缓冲区:
lorem ipsum.

dolor sit amet.

将第一行更改为“foo bar”并保存文件会导致 foo.json只有 description字段更新:
{
  "creation_timestamp": "1411210038.000000",
  "description": "foo bar.\ndolor sit amet.",
  "version": 4
}

最好的策略是什么?我目前的尝试是这样的:
  • 使用 find-file
  • 打开 JSON 文件
  • 创建从最小点到最大点的不可见覆盖
  • 解析 json
  • 插入 description 的值点属性,创建“ View ”
  • 添加本地写入文件 Hook 和保存后 Hook
  • local-write-file钩子(Hook)杀死“ View ”,更新覆盖中的json,并保存文件。 after-save钩子(Hook)重新创建“ View ”,以便用户可以继续编辑。

    这是冗长而脆弱的。有没有更好的方法来处理屏幕表示应该不同于磁盘表示的数据?

    最佳答案

    您可以在 format-alist 中定义自己的编码和解码。为了这个目的。
    您的示例可以通过以下方式实现:

    (defvar-local my-head nil
      "Header of json file cut off by json-descr format.")
    
    (defvar-local my-tail nil
      "Tail of json file cut off by json-descr format.")
    
    (defun my-from-fn (BEGIN END)
      "`format-alist'"
      (save-restriction
        (narrow-to-region BEGIN END)
        (goto-char (point-min))
        (let* ((b (re-search-forward "^[[:blank:]]*\"description\":[[:blank:]]*\"" nil t))
           (e (ignore-errors (1- (scan-sexps (1- b) 1)))))
          (unless (and b e)
        (error "Error in original mode")) ;;< TODO some more sensible error message
          ;; Save head and tail and delete corresponding buffer regions:
          (setq-local my-head (buffer-substring-no-properties (point-min) b))
          (setq-local my-tail (buffer-substring-no-properties e (point-max)))
          (delete-region e (point-max))
          (delete-region (point-min) b)
          ;; Formatting:
          (goto-char (point-min))
          (while (search-forward "\\n" nil t)
        (replace-match "\n"))
          )
        (point-max) ;;< required by `format-alist'
        ))
    
    (defun my-to-fn (BEGIN END BUFFER)
      "`format-alist'"
      (save-restriction
        (narrow-to-region BEGIN END)
        ;; Formatting:
        (goto-char (point-min))
        (while (search-forward "\n" nil t)
          (replace-match "\\\\n"))
        ;; Insert head and tail:
        (let ((head (with-current-buffer BUFFER my-head))
          (tail (with-current-buffer BUFFER my-tail)))
          (goto-char (point-min))
          (insert head)
          (goto-char (point-max))
          (insert tail))
        (point-max)))
    
    (add-to-list 'format-alist
             '(json-descr
               "File format for editing a single property of a json object."
               nil
               my-from-fn
               my-to-fn
               t ; MODIFY: my-to-fn modifies the buffer
               nil
               nil))
    
    (define-derived-mode my-mode fundamental-mode "JDescr"
      "Major mode for editing json description properties."
      (format-decode-buffer 'json-descr))
    

    实际上,也可以将其解释为更普遍的问题。将文件加载到隐藏缓冲区中。使用另一个可见缓冲区来编辑其转换后的内容。在保存可见缓冲区时,实际上将内容转换回原始格式并保存隐藏缓冲区。

    我现在没有时间实现上述一般情况。
    以下代码大致涵盖了您的特殊情况。 (请注意,这是一个快速破解,仅用于演示目的。)

    (defvar-local original-mode-other nil
      "Other buffer related to the current one.")
    
    
    (define-derived-mode original-mode special-mode ""
      "Opens file in invisible auxiliary buffer."
      (let* ((b (re-search-forward "^[[:blank:]]*\"description\":[[:blank:]]*\"" nil t))
         (e (ignore-errors (1- (scan-sexps (1- b) 1))))
         (original-name (buffer-name))
         (original-buffer (current-buffer))
         str)
        (unless (and b e)
          (error "Error in original mode")) ;; TODO some more sensible error message
        (narrow-to-region b e)
        (setq str (buffer-substring-no-properties b e))
        (rename-buffer (concat " *" original-name))
        (with-current-buffer (switch-to-buffer (get-buffer-create original-name))
          ;; Set-up the clone buffer for editing the transformed content:
          (set-visited-file-name (buffer-file-name original-buffer) t)
          (setq original-mode-other original-buffer)
          (insert str)
          (set-buffer-modified-p nil)
          ;; Transform content to the format of the clone buffer:
          (goto-char (point-min))
          (while (search-forward "\\n" nil t) ;; TODO: Skip escaped \n.
        (replace-match "\n"))
          (add-to-list 'write-contents-functions  (lambda ()
                            ;; Transfer content to original buffer
                            (let ((str (buffer-substring-no-properties (point-min) (point-max))))
                              (with-current-buffer original-mode-other
                                (let ((inhibit-read-only t))
                                  (delete-region (point-min) (point-max))
                                  (insert str)
                                  (goto-char (point-min))
                                  ;; Transform content to the format of the original buffer:
                                  (while (search-forward "\n" nil t)
                                (replace-match "\\\\n"))
                                  (save-buffer)
                                  )))
                            (set-buffer-modified-p nil)
                            t))
          (add-hook 'kill-buffer-hook (lambda ()
                        (kill-buffer original-mode-other)) t t)
          )))
    

    关于json - Emacs 中类似模型/ View 的编辑,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26488808/

    相关文章:

    python - Pandas:合并数据帧并将多个连接值合并到一个数组中

    CSS 与 Spring MVC 和 JBoss AS 的链接

    ruby-on-rails - 如何让 View 更简单, Controller 更有用?

    emacs helm M-x : how to jump to the list of historical command

    emacs - 如何在Windows 7 x64上使用SLIME和Steel Bank Common Lisp开始编程?

    emacs - 如何指定一个窗口作为 Emacs 中所有链接的目标?

    javascript - 响应与配置的参数不匹配 :

    android - 将 google-api-java-client 库与 android 一起使用时,转换为 Dalvik 格式失败并出现错误 1

    javascript - 从响应对象获取 JSON

    java - 如何在spring mvc中使 session 失效以实现注销