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

相关推荐
智购科技自动售货机工厂13 小时前
数字人民币硬钱包支付失败,排查发现是NFC读卡器功率不足~YH
python·面试·架构·eclipse·emacs
Masimaro2 天前
《elisp 每日一练》—— define-derived-mode 管理 syntax-table
emacs
Masimaro10 天前
《elisp 每日一练》—— Major Mode 管理 abbrev-table
emacs
AI情绪识别开源11 天前
检信AI学业生涯智选系统技术方案概述
emacs
Masimaro15 天前
《elisp 每日一练》—— Minor Mode 回顾与综合练习
emacs
aluluka15 天前
《elisp 每日一练》——状态栏显示管理与modeline 动态更新
emacs·lisp·elisp
aluluka16 天前
《elisp 每日一练》——minor mode 快捷键管理
emacs·lisp·elisp
Masimaro16 天前
《elisp 每日一练》——状态栏显示管理与modeline 动态更新
emacs
Masimaro19 天前
《elisp 每日一练》——minor mode 开关变量的管理
emacs
Masimaro20 天前
《elisp 每日一练》—— Minor Mode 变量生成机制与状态控制
emacs