《elisp 每日一练》——文件与目录的回顾与综合练习

到此为止,我们关于文件和目录的内容就介绍完了,Emacs中的文件与目录操作和其他编程语言相比唯一可能需要注意的是emacs 中的普通的文件读写操作与buffer和一些hook相关,读写也是基于buffer在进行的。

主要知识点回顾

首先我们介绍了一些可能最常用的读写方式

  • find-file
  • find-file-noselect
  • save-buffer
  • save-some-buffers

因为这些函数涉及到一些额外的操作,例如绑定到buffer并设置buffer的信息,加上一些hook,在用户不关注它的内容时显得比较笨重,所以又介绍了轻量级的读写文件的方式:

  • insert-file-contents
  • insert-file-contents-literally
  • write-region
  • append-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-p
  • file-name-as-directory
  • directory-files
  • directory-files-and-attributes

在目录下,有这么几个操作文件或者目录的函数:

  • make-directory
  • rename-file
  • copy-file
  • copy-directory
  • delete-file
  • delete-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中的内容,写入时根据修改的文件名或者目录名批量的修改真实文件或者目录。

相关推荐
Masimaro7 天前
《elisp 每日一练》——minibuffer输入读取和set-transient-map 临时热键状态机
emacs
Masimaro10 天前
《elisp 每日一练》——Window 树与布局状态恢复
emacs
典典分享指南12 天前
Windows 自动化踩坑实录
python·intellij-idea·pyqt·emacs·visual studio
辰辉创聚13 天前
重组细胞因子全解析:定义、分类与应用指南
人工智能·eclipse·emacs·白细胞介素·重组蛋白·重组细胞因子
Masimaro15 天前
《elisp 每日一练》—— 前缀按键
emacs
带娃的IT创业者18 天前
Emacs 31:一场静默而深刻的范式演进
编辑器·文本编辑器·开源软件·emacs·lisp·软件架构
Masimaro20 天前
《elisp 每日一练》——syntax-table
emacs
Masimaro1 个月前
《elisp 每日一练》—— overlay 机制
emacs
Masimaro1 个月前
《elisp 每日一练》——buffer属性
emacs