Emacs中的模型/视图编辑 [英] Model/View-like editing in Emacs
问题描述
foo.json:
{
creation_timestamp:1411210038.000000,
description:lorem ipsum.\\\
dolor sit amet,
version:4
}
打开foo.json结果在这个缓冲区中:
code> lorem ipsum。
dolor sit amet。
将第一行更改为foo bar,并将文件保存为 foo.json
只有描述
字段更新:
code> {
creation_timestamp:1411210038.000000,
description:foo bar.\\\
dolor sit amet。,
version:4
}
这是最好的策略?我目前的尝试是这样的:
- 使用find-file打开JSON文件
- 创建一个不可见
- parse json
- 插入
描述的值
property at point-min,创建一个视图 - 添加一个本地写入文件钩子和一个保存后的钩子
本地写入文件
钩子杀死视图,更新叠加层中的json,并保存文件。 after-save
hook重新创建视图,以便用户可以继续编辑。
这是长期的并且脆弱。有没有更好的方法处理屏幕显示不同于磁盘表示的数据?
您可以定义自己的编码和解码在 format-alist
为此目的。
您的示例可以通过以下方式实现:
(defvar-local my -head nil
json-descr格式的json文件的头文件)
(defvar-local my-tail nil
json-descr格式)
(defun my-from-fn(BEGIN END)
`format-alist'
(save-restriction
(goto-char(point-min))
(let *((b(re-search-forward^ [[:blank:]] * \ description \:[[:blank:]] * \nil t))
(e(ignore-errors(1-(scan-sexps(1-b)1)))
(除非(和)
(错误原始模式下的错误));;&TODO一些更明智的错误消息
;;保存头部和尾部并删除相应的缓冲区域:
(setq-local my-head(buffer-substring-no-properties(point-min)b))
(setq-local my-tail(buffer-substring-no-properties e max)))
(de lete-region e(point-max))
(delete-region(point-min)b)
;;格式:
(goto-char(point-min))
(while(search-forward\\\\
nil t)
(replace-match\\\
) )
)
(point-max);;<
(defun my-to-fn(BEGIN END BUFFER)
`format-alist'
(保存 - 限制
(窄到区域BEGIN END)
;;格式:
(goto-char(point-min))
(while(search-forward\ nnt
(replace-match\\\\\\))
;;插入头和尾:
(let((head(with-current缓冲区缓冲区我的头))
(尾随(当前缓冲区BUFFER my-tail)))
(goto-char(point-min))
(插入头)
(goto-char(point-max))
(插入尾部))
(point-max)))
(add-to-list'format- alist
'(json-descr
用于编辑json对象的单个属性的文件格式
nil
my-from-fn
my-to- fn
t; MODIFY:my-to-fn修改缓冲区
nil
nil))
(define-derived-mode my-mode fundamental -modeJDescr
编辑json描述属性的主要模式
(format-decode-buffer'json-descr))
实际上,还可以将其解释为更一般的问题。将文件加载到隐藏的缓冲区。使用另一个可见缓冲区来编辑其转换的内容。在保存可见缓冲区实际上将内容转换回原始格式并保存隐藏的缓冲区。
我现在没有时间来实现所描述的一般情况以上。
以下代码大致涵盖了您的特殊情况。 (请注意,这是一个快速的黑客仅供演示用途。)
(defvar-local original-mode-other nil
与当前相关的其他缓冲区)
(定义派生模式原始模式特殊模式
在隐形辅助缓冲区中打开文件
(let *((b(re-search-forward)^ [[:blank:]] * \description\:[[:blank:] )
(e(ignore-errors(1-(scan-sexps(1-b)1))))
(original-name(buffer-name) )
(原始缓冲区(当前缓冲区))
str)
(除非(和)为
(错误原始模式下的错误));; TODO明确的错误消息
(窄到区域)
(setq str(buffer-substring-no-properties be))
(重命名缓冲区(concat*original-name) )
(with-current-buffer(switch-to-buffer(get-buffer-create original-name))
;;设置用于编辑变换的克隆缓冲区ed内容:
(set-visited-file-name(buffer-file-name original-buffer)t)
(setq original-mode-other original-buffer)
(insert str)
(set-buffer-modified-p nil)
;;将内容转换为克隆缓冲区的格式:
(goto-char(point-min))
(while(search-forward\\\\
nil t);; TODO:跳过转义
(replace-match\\\
))
(add-to-list'write-contents-functions(lambda()
;;将内容传输到原始缓冲区
(let((buffer-substring-no-properties(point-min)(point-max))))
(with-current-buffer original-mode-other
let((禁止只读t))
(删除区域(point-min)(point-max))
(insert str)
(goto-char )
;;将内容转换为原始缓冲区的格式:
(while(search-forward\\\
nil t)
(replace-match\\\\ \ n))
(save-buffer)
)))
(set-buffer-modified-p nil)
t))
(add-hook'kill -buffer-hook(lambda()
(kill-buffer original-mode-other))tt)
)))
I have some JSON files and I'm writing a mode that allows editing a single property of the JSON object independently from the rest. For example:
foo.json:
{
"creation_timestamp": "1411210038.000000",
"description": "lorem ipsum.\ndolor sit amet.",
"version": 4
}
Opening foo.json results in this buffer:
lorem ipsum.
dolor sit amet.
Changing the first line to "foo bar" and saving the file results in a foo.json
with only the description
field updated:
{
"creation_timestamp": "1411210038.000000",
"description": "foo bar.\ndolor sit amet.",
"version": 4
}
What's the best strategy for this? My current attempt is so:
- open the JSON file with find-file
- create an invisible overlay from point-min to point-max
- parse json
- insert the value of the
description
property at point-min, creating a "view" - add a local-write-file hook and an after-save hook
The local-write-file
hook kills the "view", updates the json in the overlay, and saves the file. The after-save
hook recreates the "view" so the user can keep editing.
This is long-winded and brittle. Is there a better way of working with data where the screen representation should be different than the disk representation?
You can define your own encoding and decoding in format-alist
for that purpose.
Your example could be implemented in the following way:
(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))
Actually, one can also interpret this as a more general problem. Load a file into a hidden buffer. Use another visible buffer to edit its transformed content. At saving the visible buffer actually transform the content back to the original format and save the hidden buffer.
I do not have the time right now to implement the general case as described above. The following code roughly covers your special case. (Note, that it is a fast hack for demonstration purposes only.)
(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)
)))
这篇关于Emacs中的模型/视图编辑的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!