到此为止,我们关于文件和目录的内容就介绍完了,Emacs中的文件与目录操作和其他编程语言相比唯一可能需要注意的是emacs 中的普通的文件读写操作与buffer和一些hook相关,读写也是基于buffer在进行的。
主要知识点回顾
首先我们介绍了一些可能最常用的读写方式
find-filefind-file-noselectsave-buffersave-some-buffers
因为这些函数涉及到一些额外的操作,例如绑定到buffer并设置buffer的信息,加上一些hook,在用户不关注它的内容时显得比较笨重,所以又介绍了轻量级的读写文件的方式:
insert-file-contentsinsert-file-contents-literallywrite-regionappend-file
这些函数配合 with-temp-buffer 可以创建临时buffer作为读取文件的缓冲区。或者可以通过 with-temp-file 来做到将创建的临时buffer中的内容写入文件。
后面我们介绍了一些通过路径获取属性的函数:
-
expand-file-name -
file-name-directory -
file-name-nondirectory -
file-name-extension -
file-name-concat -
file-exists-p -
file-regular-p -
file-symlink-p -
file-readable-p/file-writeable-p -
file-attributes
关于目录部分的内容,我们介绍了下面几个函数:
file-directory-pfile-name-as-directorydirectory-filesdirectory-files-and-attributes
在目录下,有这么几个操作文件或者目录的函数:
make-directoryrename-filecopy-filecopy-directorydelete-filedelete-directory
练习
有什么练习能做到既实用又能使用上面大部分的函数呢?当然是通过快捷键快速操作文件和目录了。将这个事情做到极致的应该是Emacs的 dired-mode。我使用Emacs有一段时间了,目前我在Windows平台下使用,对于Windows平台来说,最让我离不开的功能主要是 dired-mode 和 org mode。所以今天我们的练习就是实现一个简易版的 dired-mode。它的主要功能如下:
- 由用户输入一个目录,通过打开新的buffer显示目录中文件的属性
- 用户选择某个目录可以进入该目录
- 用户选择某个文件可以打开文件
- 绑定一些快捷键来快速执行操作
我们绑定如下快捷键:
a/A(append),添加文件或者目录c/C(copy) 拷贝文件或者目录d/D(delete) 删除文件或者目录r/R(rename) 重命名或者文件
我们首先在上一节的基础上做一些修改,后续多次切换目录,buffer要显示不同目录的内容,所以我们先提供一个刷新buffer的函数
emacs-lisp
(defun my/refresh-dired-buffer()
(let ((buffer (current-buffer))
(dir buffer-dir-path))
(when (and dir (file-directory-p dir))
(erase-buffer)
(dolist (file-and-attr (directory-files-and-attributes dir nil nil t 'string))
(let* ((filename (car file-and-attr))
(attr (cdr file-and-attr))
(modes (file-attribute-modes attr))
(owner (file-attribute-user-id attr))
(group (file-attribute-group-id attr))
(size (file-attribute-size attr))
(modified-time (format-time-string "%Y-%m-%d %H:%M:%S" (file-attribute-modification-time attr)))
(file-information (format "%-11s %-8s %-8s %8d Bytes %s %s"
modes owner group size modified-time filename)))
(insert file-information)
(add-text-properties (line-beginning-position) (line-end-position) `(file-name ,filename))
(newline))))))
(defun my/active-dired-mode(dir-path)
(interactive "fdir path: ")
(let ((abs-dir-path (expand-file-name dir-path)))
(if (and (file-exists-p abs-dir-path) (file-directory-p abs-dir-path))
(let ((buffer (get-buffer-create "*MyDiredBuffer*")))
(if buffer
(progn
(switch-to-buffer buffer)
(setq-local buffer-dir-path abs-dir-path)
;;设置并绑定快捷键
(keymap-local-set "a" #'my/dired-create-new-file)
(keymap-local-set "A" #'my/dired-create-new-directory)
(keymap-local-set "c" #'my/dired-copy-file)
(keymap-local-set "C" #'my/dired-copy-directory)
(keymap-local-set "d" #'my/dired-delete-file)
(keymap-local-set "D" #'my/dired-delete-directory)
(keymap-local-set "r" #'my/dired-rename-file)
(keymap-local-set "R" #'my/dired-rename-directory)
(my/refresh-dired-buffer)))))))
接着我们再来编写这些关联的函数
emacs-lisp
(defun my/dired-create-new-file(filename)
(interactive (list (read-file-name "New File: " buffer-dir-path)))
(let* ((abs-file-name (expand-file-name filename))
(abs-file-directory (file-name-as-directory (file-name-directory abs-file-name))))
(if (and (file-directory-p abs-file-directory)
(file-exists-p abs-file-directory)
(not (file-exists-p abs-file-name)))
(progn
(write-region "" nil abs-file-name)
(my/refresh-dired-buffer))
(message "目录或者文件名无效"))))
(defun my/dired-create-new-directory(dirname)
(interactive (list (read-directory-name "New Directory: " buffer-dir-path)))
(let ((abs-dir-name (file-name-as-directory (expand-file-name dirname))))
(if (not (file-exists-p abs-dir-name))
(progn
(make-directory abs-dir-name t)
(my/refresh-dired-buffer))
(message "目录已经存在"))))
(defun my/dired-copy-file (dest-file-name)
(interactive (list (read-file-name "Destination file: " buffer-dir-path)))
(let* ((abs-dest-file-name (expand-file-name dest-file-name))
(abs-dest-file-directory (file-name-as-directory (file-name-directory abs-dest-file-name)))
(src-file-name (expand-file-name (get-text-property (point) 'file-name) buffer-dir-path)))
(if (and (file-directory-p abs-dest-file-directory)
(file-exists-p abs-dest-file-directory)
(not (file-exists-p abs-dest-file-name))
(file-regular-p src-file-name))
(progn
(copy-file src-file-name abs-dest-file-name)
(my/refresh-dired-buffer)))))
(defun my/dired-copy-directory (dest-dir-name)
(interactive (list (read-directory-name "Destionation directory: " buffer-dir-path)))
(let ((abs-dest-dir-name (expand-file-name dest-dir-name))
(src-dir-name (expand-file-name (get-text-property (point) 'file-name) buffer-dir-path)))
(if (not (file-exists-p abs-dest-dir-name))
(progn
(copy-directory src-dir-name abs-dest-dir-name)
(my/refresh-dired-buffer))
(message "目标目录已存在"))))
(defun my/dired-delete-directory (dirname)
(interactive (list (read-directory-name "Delete directory: " buffer-dir-path)))
(if (file-exists-p dirname)
(progn
(delete-directory dirname t)
(my/refresh-dired-buffer))
(message "目标目录不存在")))
(defun my/dired-rename-file(rename-file-name)
(interactive (list (read-file-name "Rename file name: " buffer-dir-path)))
(let* ((abs-new-file-name (expand-file-name rename-file-name))
(abs-new-dir-name (file-name-as-directory (file-name-directory abs-new-file-name)))
(src-file-name (expand-file-name (get-text-property (point) 'file-name) buffer-dir-path)))
(if (and (file-exists-p abs-new-dir-name)
(file-directory-p abs-new-dir-name)
(not (file-exists-p abs-new-file-name)))
(progn
(rename-file src-file-name abs-new-file-name)
(my/refresh-dired-buffer))
(message "新文件名错误"))))
(defun my/dired-rename-directory(rename-dir-name)
(interactive (list (read-directory-name "Rename directory name: " buffer-dir-path)))
(let ((abs-new-dir-name (expand-file-name rename-dir-name))
(src-dir-name (expand-file-name (get-text-property (point) 'file-name) buffer-dir-path)))
(if (not (file-exists-p abs-new-dir-name))
(progn
(rename-file src-dir-name abs-new-dir-name)
(my/refresh-dired-buffer))
(message "重命名的目录名称错误"))))
到这里我们完成了目录中基本的拷贝、重命名、删除、新增操作,我们还需要一个打开目录或者文件的函数,并将它们做一个绑定
emacs-lisp
(defun my/dired-open-file-or-directory()
(interactive)
(let ((abs-path (expand-file-name (get-text-property (point) 'file-name) buffer-dir-path)))
(if (file-exists-p abs-path)
(cond ((file-regular-p abs-path)
(find-file abs-path))
((file-directory-p abs-path)
(let ((full-path (file-truename abs-path))) ;; 获取真实路径,主要用来处理类似 "." , ".." 这样的路径
(setq-local buffer-dir-path full-path)
(my/refresh-dired-buffer)))
(t
(message "无法识别的条目"))))))
我们可以在 my/active-dired-mode 函数中添加一条快捷键绑定, (keymap-local-set "RET" #'my/dired-open-file-or-directory)
到此为止,我们就完成了这么一个简单的 dired-mode。读者如果想更进一步的练习,可以考虑实现一个写入的功能,用户可以随意改变这个buffer中的内容,写入时根据修改的文件名或者目录名批量的修改真实文件或者目录。