我有一些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
}
最佳策略是什么?我目前的尝试是这样的:
description
属性的值,创建"视图" local-write-file
钩子杀死"视图",更新叠加层中的json,并保存文件。 after-save
挂钩重新创建"视图"所以用户可以继续编辑。
这是冗长而脆弱的。是否有更好的方法来处理屏幕表示应与磁盘表示不同的数据?
答案 0 :(得分:0)
您的用例是否真的像您描述的场景一样简单(不是解决方案大纲,而是问题/用例)?
如果是这样,你的解决方案听起来有点矫枉过正。如果用例就像编辑特定键的值一样简单,我可能会这样做:
在临时缓冲区中显示该字段的内容(与该键对应的值),以进行编辑。
绑定密钥(例如C-c C-c
)以将修改后的值保存回文件。
我在Bookmark+中执行此操作,例如用于编辑书签的标签(以及使用不同的命令编辑所有书签的字段)。编辑标签的命令是bmkp-edit-tags
。命令(绑定到编辑缓冲区中的C-c C-c
)为bmkp-edit-tags-send
。在上下文中,代码为here。这是脱离背景:
(defmacro bmkp-with-output-to-plain-temp-buffer (buf &rest body)
"Like `with-output-to-temp-buffer', but with no `*Help*' navigation stuff."
`(unwind-protect
(progn
(remove-hook 'temp-buffer-setup-hook 'help-mode-setup)
(remove-hook 'temp-buffer-show-hook 'help-mode-finish)
(with-output-to-temp-buffer ,buf ,@body))
(add-hook 'temp-buffer-setup-hook 'help-mode-setup)
(add-hook 'temp-buffer-show-hook 'help-mode-finish)))
(define-derived-mode bmkp-edit-tags-mode emacs-lisp-mode
"Edit Bookmark Tags"
"Mode for editing bookmark tags.
When you have finished composing, type \\[bmkp-edit-tags-send]."
:group 'bookmark-plus)
;; This binding must be defined *after* the mode, so `bmkp-edit-tags-mode-map' is defined.
;; (Alternatively, we could use a `defvar' to define `bmkp-edit-tags-mode-map' before
;; calling `define-derived-mode'.)
(define-key bmkp-edit-tags-mode-map "\C-c\C-c" 'bmkp-edit-tags-send)
(defun bmkp-edit-tags (bookmark) ; Bound to `C-x p t e'
"Edit BOOKMARK's tags, and maybe save the result.
The edited value must be a list each of whose elements is either a
string or a cons whose key is a string.
BOOKMARK is a bookmark name or a bookmark record."
(interactive (list (bookmark-completing-read "Edit tags for bookmark" (bmkp-default-bookmark-name))))
(setq bookmark (bmkp-get-bookmark-in-alist bookmark))
(let* ((btags (bmkp-get-tags bookmark))
(bmkname (bmkp-bookmark-name-from-record bookmark))
(edbuf (format "*Edit Tags for Bookmark `%s'*" bmkname)))
(setq bmkp-return-buffer (current-buffer))
(bmkp-with-output-to-plain-temp-buffer edbuf
(princ
(substitute-command-keys
(concat ";; Edit tags for bookmark\n;;\n;; \"" bmkname "\"\n;;\n"
";; The edited value must be a list each of whose elements is\n"
";; either a string or a cons whose key is a string.\n;;\n"
";; DO NOT MODIFY THESE COMMENTS.\n;;\n"
";; Type \\<bmkp-edit-tags-mode-map>`\\[bmkp-edit-tags-send]' when done.\n\n")))
(let ((print-circle bmkp-propertize-bookmark-names-flag)) (pp btags))
(goto-char (point-min)))
(pop-to-buffer edbuf)
(buffer-enable-undo)
(with-current-buffer (get-buffer edbuf) (bmkp-edit-tags-mode))))
(defun bmkp-edit-tags-send (&optional batchp)
"Use buffer contents as the internal form of a bookmark's tags.
DO NOT MODIFY the header comment lines, which begin with `;;'."
(interactive)
(unless (eq major-mode 'bmkp-edit-tags-mode) (error "Not in `bmkp-edit-tags-mode'"))
(let (bname)
(unwind-protect
(let (tags bmk)
(goto-char (point-min))
(unless (search-forward ";; Edit tags for bookmark\n;;\n;; ")
(error "Missing header in edit buffer"))
(unless (stringp (setq bname (read (current-buffer))))
(error "Bad bookmark name in edit-buffer header"))
(unless (setq bmk (bmkp-get-bookmark-in-alist bname 'NOERROR))
(error "No such bookmark: `%s'" bname))
(unless (bmkp-bookmark-type bmk) (error "Invalid bookmark"))
(goto-char (point-min))
(setq tags (read (current-buffer)))
(unless (listp tags) (error "Tags sexp is not a list of strings or an alist with string keys"))
(bookmark-prop-set bmk 'tags tags)
(setq bname (bmkp-bookmark-name-from-record bmk))
(bmkp-record-visit bmk batchp)
(bmkp-refresh/rebuild-menu-list bname batchp)
(bmkp-maybe-save-bookmarks)
(unless batchp (message "Updated bookmark file with edited tags")))
(kill-buffer (current-buffer)))
(when bmkp-return-buffer
(pop-to-buffer bmkp-return-buffer)
(when (equal (buffer-name (current-buffer)) "*Bookmark List*")
(bmkp-bmenu-goto-bookmark-named bname)))))
最相关的部分是这些:
定义一个启动编辑的命令和一个结束它的命令并保存更改。
使用bmkp-with-output-to-plain-temp-buffer
(基本上是with-output-to-temp-buffer
提供编辑缓冲区,但某些Emacs版本中的宏也会添加帮助模式,这里不需要这些内容。)
将编辑缓冲区置于一个简单的次要模式,将C-c C-c
绑定到save-and-exit命令。
使用要编辑的文本填充编辑缓冲区。弹出缓冲区进行编辑。
在save-and-exit命令(bmkp-edit-tags-send
)中,更新原始数据,用编辑缓冲区内容替换相关的字段内容。保存更新的数据。返回原始缓冲区。
答案 1 :(得分:0)
您可以在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)
)))