首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
158 阅读
2
nvim番外之将配置的插件管理器更新为lazy
107 阅读
3
2018总结与2019规划
76 阅读
4
从零开始配置 vim(15)——状态栏配置
74 阅读
5
PDF标准详解(五)——图形状态
51 阅读
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
linux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
Thinking
FIRE
菜谱
登录
Search
标签搜索
c++
c
学习笔记
windows
文本操作术
编辑器
NeoVim
Vim
win32
VimScript
emacs
linux
elisp
读书笔记
文本编辑器
Java
反汇编
OLEDB
数据库编程
数据结构
Masimaro
累计撰写
337
篇文章
累计收到
32
条评论
首页
栏目
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
linux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
Thinking
FIRE
菜谱
页面
归档
友情链接
关于
搜索到
28
篇与
的结果
2025-12-14
Emacs折腾日记(三十三)——org capture
在日常的工作生活中有各种各样的琐事,如果不及时记录下来很可能后面就忘了。或者在日常软件开发中有很多想法想要实现但是时间一长自己就忘了。这种情况下我们需要一个系统来记录收集想法并且后续需要追踪甚至回顾。我们需要一套适合的管理系统让大脑摆脱待办事项的纠缠,让大脑专注于当前要做的事物。目前我采取的方式是GTD+ 番茄工作法。GTD 简介GTD(Getting Things Done),是由戴维·艾伦于2002年提出的时间管理方法。其核心在于通过收集、整理、组织、回顾、执行五个步骤,将任务从大脑中移出,减轻心理压力以提升效率,强调两分钟内可完成事项立即处理的原则。用我的理解,我们需要事先准备几个速记的笔记本,一旦脑海中出现了想法就将它记录下来,这步叫做收集。在一天中的空闲时间例如下班前或者睡觉前打开看看之前记录的内容,将内容按照一定的规则重新组织例如分为工作、生活、学习之类的想法,将它们转化成可以执行的任务。并且为每个事项定一个紧急程度,这个就是整理、组织。在每天开始的时候或者结束的时候可以根据上面的分类安排一下当前或者明天的任务,这个是执行任务。在每周或者每天的时候回忆一下当前的任务看看哪些完成了、哪些未完成、从任务中有何感想,有哪些可以记录下来作为重要的日志或者日后博客的灵感来源,这个就是最后的回顾总结过程。番茄工作法再完美的系统,如果不执行或者不认真执行也是白搭的。对我来说GTD主要解决的是要做哪些事,如何安排时间的问题。至于如何高效的执行任务专注到任务上,我采用的番茄工作法。番茄工作法的简单理解就是将任务的时间拆分成几个不可分割的最小单元,在这个时间片单元内一心一意、专心到任务上。一旦分心就需要终止当前番茄钟,并记录分心的原因,如果是有新来的新任务就需要采用记录的方式记录,如果是紧急且重要的任务可能还要修改当前的任务计划。当然番茄工作法有一整套的收集、整理、执行、归纳总结的流程,但是对我来说我喜欢GTD的流程和番茄工作法的时间管理方式,所以我采用的是它们二者的结合。但是主要用在工作中,日常生活中的场景都是一些简单的琐事,例如收拾卫生、做饭、对我来说不合适这种形式。org capture目前来说市面上没有完全符合我心目中最佳时间管理理论的工具,但是Emacs中的org 是十分强大的,完全可以稍加改造成符合自己心意的时间管理系统。首先要解决的就是如何收集想法的问题。这个功能可以使用 org-capture,顾名思义它就是使用org的格式来快速记录当前的想法。我们使用 org-capture 命令可以看到下面的界面下方提示我们可以使用前面的字母符号来选择一个模板,默认只提供了一个名为 Task 的模板。我们可以使用 org-capture 中 来实现GTD中的分类记录的功能。在我们选定需要使用的模板之后,Emacs会调用 org-capture-select-template 函数来选中模板并且根据模板来创建默认的org 模板。默认的Task 模板如下:("t" "Task" entry (file+headline "" "Tasks") "* TODO %?\n %u\n %a")它们的含义如下t 标识选择模板时的快捷键。Task 模板的描述,让用户了解当前模板作用的说明文字。后面一个部分就是新增的内容新增的内容比较复杂,我们一个个的说。第一个部分是新增模板的type。type 可以是下面的内容type描述entry带有 headline 的一个 Org mode 节点item一个列表项checkitem一个 checkbox 列表项table-line一个表格行plain普通文本type 的不同,后面跟的内容也不一样。例如如果当前是 entry 的话,后面需要添加 "* headline" 作为标题行的形式,如果是 item 后面需要跟上 - item 作为一个项。如果是 checkitem 需要跟 [ ] item 作为一个checkbox项。如果使用 table-line,需要跟 | colum 1 | colum 2 | colum3 | 作为一个表格项目。如果是 plain 则没有特殊要求。接下来看看 (file+headline "" "Tasks") 中各个部分的含义,前面的 file+headline 表示将记录的内容保存到哪个位置。除了这两个标识还有其他的一些常用标识标识含义file文件id某个特定id的headlinefile+headline某个文件中的某个headlinefile+olp文件中某个headline的路径file+regexp文件以及被正则匹配的headlinefile+datetree文件中当日所在的datetreefile+datetree+prompt文件中的datetree 会弹出日期选择file+weektree文件中当日所属的 weektreefile+weektree+prompt文件中 weektree 会弹出日期选择file+function文件中被函数匹配的headlineclock当前正在计时中的任务所在位置function自定义函数的所定义的位置后面两个参数根据前面的内容来决定,例如如果使用 file+headline 的话,第二个参数就是文件路径,第三个参数就是具体的 headline 的名称。如果文件名参数为空的话,那么会使用 org-default-notes-file 所对应的文件路径为 ~/.notes 这些内容如果觉得有些比较难以理解的,可以查看 org-capture-tempetes 变量的详细说明"* TODO %?\n %u\n %a" 部分都是一些格式字符串和占位符,在选中模板之后会自动生成我们想要的模板文字。这里的占位符如果写过代码应该很容易理解,首先占位符都是 以%开头,后面根据不同的字符表示不同的含义时间相关的占位符占位符含义%<>表示自定义时间的时间戳,两个<>之间可以使用字符表示不同的时间格式,例如 %<%Y-%m-%d> 表示年月日的格式,年是4位字符的形式%t当前仅包含日期的时间戳%T当前包含日期时间的时间戳%u包含当前未激活的时间戳%U表示当前未激活的包含日期时间的时间戳%^t类似于 %t,但是会弹出日历供用户选择%^T类似于%T,但是会弹出日期和时间供用户选择%^u与上面的类似%^U与上面类似这里所谓的激活,在Emacs的文档中使用的词是 interactive 我不知道翻译为激活是否准确。未激活的时间戳日后不会出现在 org-agenda 中,至于什么是 org-agenda 请继续往下看剪切板相关的占位符占位符含义%c当前 kill ring 中的第一条内容%x当前系统剪贴板中的内容%^C交互式地选择 kill ring 或剪贴板中的内容%^L类似 %^C,但是将选中的内容作为链接插入后面还有更多更复杂的占位符,但是目前来说我用不到那么多,也就不一一列举了,各位读者如果感兴趣可以查看 org-capture-templetes 来查阅更多类型的占位符,至于 %?它什么也不往文件里面添加,它会在模板文本添加成功后将光标移动到它所在位置,方便用于自己输入内容除此以外,它还支持自定义函数的形式来插入内容,它的形式为 %(sexp) 括号中间就是lisp函数,假设我们有一个 custom-placeholder 那么这里就可以写成 %(custom-placeholder) 有了这些内容我们就可以定义自己的各种任务的模板个人日常任务模板对我个人来说,日常任务主要有:阅读清单、博客计划、工作任务。下面我希望通过这三大类任务来定制一些模板阅读清单首先是阅读清单,阅读清单主要记录一些我通过各种渠道了解到的并计划在后续需要读的书。根据我之前读的《如何有效阅读一本书:超实用笔记读书法》 的说法,记录要读的书,可以记录下面几个内容:书名作者出版社其他(可以是ISBN号)如果只根据书名可能会有书名重复的现象,所以根据这几条信息就可以唯一确认一本书因为Emacs提供的默认模板我不太喜欢,所以这里我先使用 (setq org-capture-templates nil) 来清空模板,后续由我自己实现各种模板(use-package org-capture :ensure nil :custom (org-capture-templates nil) (org-capture-templates '(("r" "Reading List" entry (file+olp "~/org/reading.org" "Reading" "Book") "*** TODO 《%^{书名}》\n作者:%^{作者}\n出版社:%^{出版社}:\n备注:%^{备注}\n添加时间:%U\n" :clock-int t :clock-resume t))))我们按照之前分析的模板中各个部分的组成来看看它是什么意思。首先第一个部分,我们定义通过快捷键 r 来选中并生成这个模板,Reading List 表示在模板展示时显示的内容。后面的entry 表示我需要添加一个带有 headline 的 org mode 节点,它后面跟着的子列表里面使用 file+olp 表示我需要在文件中的某个headline 下添加这条记录作为它的子内容部分。后面跟的就是文件路径、以及需要插入到当前文件下Reading 里面的 Book 下,作为子节点。最后定义的就是具体的内容了, 因为Reading 作为一级标题,它下面的 Book 作为二级标题,所以这里添加的阅读清单记录就是三级标题,所以我在它前面加了一个 *** 作为三级标题。前面的TODO是关键词,表示待做。后面就是输入的常规的内容了,包括出版社、作者等信息。它的运行效果如下:新建博客文章org-mode实际上是支持转换成markdown并通过hugo之类的静态网站生成程序来生成博客的。对于hugo 来说如果希望将markdown格式的内容转化成博客,我们需要在markdown文件头位置加入这些内容title: "Test" date: categories: blog tags: blog draft: true根据这些信息我们可以很容易的搭建基础的模板。针对这种场景,我希望它能在固定的位置生成一个以时间命名的 .md文档,这个位置一般位于hugo安装目录下的 posts 目录下。我们可以写下这样的配置 (add-to-list 'org-capture-templates `("b" "Blog" plain (file ,(concat "~/org/" (format-time-string "%Y-%m-%d.md"))) ,(concat "---\n" "tile: %^{标题}\n" "date: %U\n" "categories: %^{分类}\n" "tags: %^{标签}\n" "draft: %^{草稿|true|false}\n" "---\n" "%?")))它的效果如下:最终在我们保存之后它会在~/org 目录下生成一个以时间命名的markdown文件这种模式我个人不太喜欢,因为对我而言org-capture 仅仅只是一个收集的动作,我们应该快速记录想要写的博客以及博客的主要论点,而不应该开始编写具体的内容。至于如何使用org-mode来写博客,后面我想单独写一篇博客来介绍创建工作计划作为一个程序员来说,日常工作总会与程序打交道,比如开发新功能、写接口文档、修改bug、搭建开发测试环境、开会等等。有时候需要一个人负责多个多个项目。对于工作任务我们还需要分一个轻重缓急,我一般习惯于将它们分为:重要且紧急、重要但不紧急、紧急但不重要、不重要不紧急。那么对于工作任务的需求就很明确了。首先任务可以归属到不同的项目中,日后可以根据任务进行筛选任务可以细分为开发新功能、修复bug、搭建环境、写文档、开会等等任务可以划分轻重缓急日后通过todolist可以看到任务的轻重缓急,目前使用重要且紧急、重要但不紧急、不重要但紧急、不紧急不重要我们采用下面的代码 (add-to-list 'org-capture-templates `("w" "Work Task" entry (file+headline "~/org/working.org" "Work") "* TODO %^{任务描述} :%^{任务类型|dev|bugfix|env|doc|meeting}:\n SCHEDULED: %^t\n PRIORITY: %^{优先级|A|B|C|D}\n %?\n %i")))它的效果如下:到目前为止,介绍了关于org capture的相关内容,并演示了如何利用capture 实现一个任务的捕捉功能。gtd 后面的流程还有任务的分配、执行、记录等等功能,这些内容将在后面介绍。感谢各位读者的支持
2025年12月14日
2 阅读
0 评论
0 点赞
2025-10-12
Emacs折腾日记(三十二)——org mode的基本美化
在上一篇,已经介绍了org mode的基础知识,它与markdown非常相似,并且也十分容易上手,但是它的可扩展性比markdown要强很多。如果将来打算重度使用org mode,那么此时可以对它进行一些基本的配置和美化基本配置org mode 的配置可以通过Emacs自带的org 包来进行管理,可以配置一些标签显示的图形以及一些特殊语句块的高亮(use-package org :ensure nil :mode ("\\.org\\'" . org-mode) :hook ((org-mode . visual-line-mode) (org-mode . my/org-prettify-symbols)) :commands (org-find-exact-headline-in-buffer org-set-tags) :custom-face ;; 设置org mode标题以及美级标题行的大小 (org-document-title ((t (:height 1.75 :weight bold)))) (org-level-1 ((t (:height 1.4 :weight bold)))) (org-level-2 ((t (:height 1.35 :weight bold)))) (org-level-3 ((t (:height 1.3 :weight bold)))) (org-level-4 ((t (:height 1.25 :weight bold)))) (org-level-5 ((t (:height 1.2 :weight bold)))) (org-level-6 ((t (:height 1.15 :weight bold)))) (org-level-7 ((t (:height 1.1 :weight bold)))) (org-level-8 ((t (:height 1.05 :weight bold)))) (org-level-9 ((t (:height 1.0 :weight bold)))) ;; 设置代码块用上下边线包裹 (org-block-begin-line ((t (:underline t :background unspecified)))) (org-block-end-line ((t (:overline t :underline nil :background unspecified)))) :config ;; 设置org mode中某些标签的显示字符 (defun my/org-prettify-symbols() (setq prettify-symbols-alist '(("[ ]" . 9744) ;; ☐ ("[x]" . 9745) ;; ☑ ("[-]" . 8863) ;; ⊟ ("#+begin_src" . 9998) ;; ✎ ("#+end_src" . 9633) ;; □ ("#+results:" . 9776) ;; ☰ ("#+attr_latex:" . "🄛") ("#+attr_html:" . "🄗") ("#+attr_org:" . "🄞") ("#+name:" . "🄝") ("#+caption:" . "🄒") ("#+date:" . 128197) ;; 📅 ("#+author:" . 128100) ;; 💁 ("#+setupfile:" . 128221) ;;📝 ("#+email:" . 128231) ;;📧 ("#+startup" . 10034) ;; ✲ ("#+options:" . 9965) ;; ⛭ ("#+title:" . 10162) ;; ➲ ("#+subtitle:" . 11146) ;; ⮊ ("#+downloaded" . 8650) ;; ⇊ ("#+language:" . 128441) ;;🖹 ("#+begin_quote" . 187) ;; » ("#+end_quote" . 171) ;; « ("#+begin_results" . 8943) ;; ⋯ ("#+end_results" . 8943) ;; ⋯ )) (setq prettify-symbols-unprettify-at-point t) (prettify-symbols-mode 1)) :custom (org-fontify-whole-heading-line t) ;; 设置折叠符号 (org-ellipsis " ▾") ) 上述配置比较简单,核心部分就是我们使用 prettify-symbols-alist 来使将这些特定的 property 字符串替换成更加美观的图标。它是一个列表,列表种的每个子元素又是一个cell,用cell的两个元素来表示替换关系。配置之后,一个org文件大致的效果如下:org-modern 美化为了使文档的显示效果更好,我们需要依靠一个名为 org-modern 的插件,它是一个为Emacs Org模式提供现代化视觉美化的项目,它通过精心设计的样式和布局,能够显著的提升Org文档的可读性和美观度。我们可以使用 use-package 直接安装(use-package org-modern :ensure t :hook (after-init . (lambda () (setq org-modern-hide-stars 'leading) (global-org-modern-mode t))) :config ;; 定义各级标题行字符 (setq org-modern-star ["◉" "○" "✸" "✳" "◈" "◇" "✿" "❀" "✜"]) (setq-default line-spacing 0.1) (setq org-modern-label-border 1) (setq org-modern-table-vectical 2) (setq org-modern-table-horizontal 0) ;; 复选框美化 (setq org-modern-checkbox '((?X . #("▢✓" 0 2 (composition ((2))))) (?- . #("▢–" 0 2 (composition ((2))))) (?\s . #("▢" 0 1 (composition ((1))))))) ;; 列表符号美化 (setq org-modern-list '((?- . "•") (?+ . "◦") (?* . "▹"))) ;; 代码块左边加上一条竖边线 (setq org-modern-block-fringe t) ;; 属性标签使用上述定义的符号,不由 org-modern 定义 (setq org-modern-block-name nil) (setq org-modern-keyword nil) )现在org 文档显示的就更加漂亮了到此我们对org-mode 显示的效果进行了初步的美化,现在的文档看起来比原始的要好看多了,用org来编写文档至少也显得赏心悦目了。
2025年10月12日
5 阅读
0 评论
0 点赞
2025-08-08
Emacs 折腾日记(二十九)—— 打造C++ IDE
在介绍vim配置的时候介绍过lsp的相关基础知识。简单来说lsp是一个协议,它以C/S架构的形式进行组织,lsp负责分析语法,给出具体的语法单元,完成跳转等功能的核心实现。而客户端则负责接收用户的操作请求并呈现具体结果。这样做的好处是将核心服务和客户端显示分离出来,核心部分重用,客户端则可以由各个编辑器自己实现,使各种编辑器都有相同的核心功能体验。对于Emacs来说,由lsp-mode 提供核心客户端库,管理服务器生命周期、消息路由及基础功能。另外也有 lsp-ui 这种增强UI模块,提供实时信息侧边栏(lsp-ui-sideline)、代码透镜(Code Lens)、悬浮文档等下面来介绍如何使用它们配置一个基础的lsp功能lsp-mode根据官方给出的配置,我们可以组一个基础的配置(use-package lsp-mode :ensure t :init ;; set prefix for lsp-command-keymap (few alternatives - "C-l", "C-c l") (setq lsp-keymap-prefix "C-c l") :hook ( ;; if you want which-key integration (lsp-mode . lsp-enable-which-key-integration)) :commands (lsp lsp-deferred))这里我们只是安装了一个客户端,想要真正实现lsp的功能,还需要针对具体的语言下载一个服务端,可以通过 lsp-install-server 来下载如果我们希望能像vscode那样,以悬浮窗口的形式显示符号的定义、声明或者注释文档,那么我们需要使用 lsp-ui 这个插件(use-package lsp-ui :ensure t :after (lsp-mode) :config (setq lsp-ui-doc-position 'top))这里我通过 lsp-ui-doc-position 来定义显示的窗口在上方,一般的编辑器默认是显示在光标所在的位置,但是我觉得显示在光标位置会影响我阅读后续的代码,所以我将它显示在上面,如果各位读者希望它像其他编辑器那样显示在光标位置可以修改参数为 at-point项目管理上述的lsp配置完之后,它只能使用当前buffer中的内容进行语法补全提示,也就是说我在其他的位置定义的函数和类在当前buffer中是无法识别到的。我们需要结合项目一起来使用。我们可以使用名为 projectile 的插件来进行项目管理(use-package projectile :ensure t :init (setq projectile-project-search-path '("~/projects/" "~/work/" "~/playground")) :config (define-key projectile-mode-map (kbd "C-c C-p") 'projectile-command-map) (global-set-key (kbd "C-c p") 'projectile-command-map) (projectile-mode +1)) (use-package counsel-projectile :ensure t :after(projectile) :init (counsel-projectile-mode))这里我们额外安装了 counsel-projectile 用来与 counsel 结合进行搜索语法检查语法检查方面,目前主流的插件是 flycheck 和 flymake,但是好像 flycheck 使用的人多一些,所以这里我也采用 flycheck。(use-package flycheck :ensure t :config (setq truncate-lines nil) ; 如果单行信息很长会自动换行 :hook (prog-mode . flycheck-mode))这里我们仅在编程的时候开启语法检查。同样的, flycheck 是一个前端,用来显示结果的,具体检查的核心功能是通过后端来实现的。后端的程序可以在官方网站上找到。状态栏显示相关的lsp信息现在是时候来修改以下状态栏的显示了,根据配置vim的经验,我还是希望主要显示当前编辑的模式、文件名、文件编码、lsp服务器、语法错误信息等。这里我使用 doom-modeline 来完成这个功能(use-package doom-modeline :ensure t :init (doom-modeline-mode 1) :config (setq doom-modeline-height 30) (setq doom-modeline-bar-width 5) (setq doom-modeline-icon t) (setq doom-modeline-lsp t) (setq doom-modeline-major-mode-icon t) (setq doom-modeline-buffer-state-icon t) (setq doom-modeline-buffer-file-name-style 'truncate-with-project) (setq doom-modeline-check-simple-format t) )通过上述代码来简单的配置一下,就可以有丰富的显示信息每种语言都对应了一个lsp的后端程序,lsp-mode官方网站 给出了每种语言对应的lsp后端程序,我们可以使用 lsp-install-server 来安装,这里我准备安装 clangd 这个后端在安装了lsp之后,再次打开一个cpp文件,可以发现它的界面如下:上面我们完成了lsp配置的基础准备工作,下面将要来针对具体的语言探索一下实际的使用方式和使用体验状态栏下分别显示了当前的模式(N代表normal模式)、文件类型、文件名称、当前编码方式等等,最后一个是flycheck 语法检查的结果,这里的代码比较简单,所以它没有检测出来任何问题,最后以绿色圆圈中的一个勾来显示。需要注意的是状态栏中的小火箭表示lsp服务已启用,如果没有这个标志可以手动的执行 lsp 命令选中项目的根目录C++ 项目实例C/C++ 项目需要明确的编译信息,clangd 通过 compile_commands.json 文件获取这些信息。对于cmake构建的项目cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1另外我希望每种语言对应的lsp配置放入到不同的配置文件中,做到模块化处理,因此我在配置中新建了一个lsp目录,并且按编程语言每种语言一个配置文件。目前c++语言的配置如下:(require 'lsp-mode) (add-hook 'c++-mode-hook (lambda () (setq lsp-project-identification-methods '(:root ("compile_commands.json" ".git" ".clangd" "CMakeLists.txt" "Makefile"))) (setq lsp-clients-clangd-args '("-background-index" "--clang-tidy" "--completion-style=detailed")) (setq-local completion-at-point-functions '( cape-file cape-keyword cape-dabbrev)) (lsp-deferred))) (provide 'cpp)为了实现不同的语言加载不同的配置,这里通过对应模式的hook来实现。一般每种模式都有一个对应的hook变量,它里面保存了一个函数,当启用该模式时会执行对应的代码,这里c++-mode 对应的是 c++-mode-hook。在这个hook中,主要做了四件事:第一,我们定义了项目根目录的标识;第二,定义了clangd启动的一些参数;最后也是最关键的一点,我们重新定义了补全端的函数。在配置Emacs的补全时,提供了一大堆补全方式,很多在编程时是用不到的,而且Emacs在补全时会依次根据 completion-at-point-functions 列表中保存的补全函数来查找补全项,一旦前一个函数返回了补全项,那么就不再往后查找。在编程的过程中前面定义的很多补全功能并没有lsp提供的补全好用,而且还会影响lsp的补全导致它出不来,所以我们去掉一些没用的,仅仅保留关键的部分。lsp-mode 会提供一个名为 lsp-completion-at-point 的补全函数注册到 completion-at-point-functions 中,因此这里我们不需要单独的写出来,当然写出来也没问题。我们可以通过 describe-variable 命令来查看 completion-at-point-functions 的值。Its value is (lsp-completion-at-point cape-file cape-keyword cape-dabbrev) Local in buffer main.cpp; global value is (cape-line cape-elisp-symbol cape-dict cape-ispell cape-keyword cape-file cape-dabbrev tags-completion-at-point-function) 最后一件事就是调用 lsp-deferred 来延迟加载lsp。因为 lsp-deferred 需要等到缓冲区完全加载完成之后才加载,所以将它放到最后面代码跳转在vim中,有几个关于跳转的关键的快捷键,gd 表示 goto declaration 跳转到定义,gD 表示 goto definition 跳转到定义,使用 gf 来跳转到头文件,跳转之后可以使用 ` 再次回来,这些快捷键 evil` 也为我们保留了,我们不需要额外的配置只要lsp能加载起来就能使用。显示符号文档信息vim配置中定义了 gh 显示符号的文档信息,我们可以使用 define-key 来定义快捷键。既然显示文档的功能由 lsp-ui 来提供,这里就将代码放入到 lsp-ui 位置(define-key lsp-ui-mode-map (kbd "gh") #'lsp-ui-doc-glance)tree-sitter在配置vim的时候提到过tree-sitter 是轻量级的语法分析器,而lsp是进行语义分析的重量级工具,tree-sitter 是对 lsp 的一个补充,并且我们用tree-sitter 进行了语法高亮和代码片段的折叠,这里尝试在Emacs中使用tree-sitter(use-package tree-sitter :ensure t :hook (prog-mode . tree-sitter-hl-mode) ;;启用语法高亮 :config (global-tree-sitter-mode)) (use-package tree-sitter-langs :ensure t :after tree-sitter)安装完成之后,我们通过命令 tree-sitter-langs-install-grammars 来安装对应的语言支持,默认是安装所有语言的支持,这里我就使用默认的就好下面尝试启用在vim中 zc、和 zo的功能。它们主要是用来折叠和展开代码块。我们来安装一个名为 ts-fold 的插件,该插件没有被放入到 elpa,所以需要手动下载然后加载(use-package ts-fold :load-path "~/.emacs.d/ts-fold" :config (global-ts-fold-mode 1) (with-eval-after-load 'evil (evil-define-key 'normal 'global "zc" #'ts-fold-close "zo" #'ts-fold-open)))最终的效果如下:基于tree-sitter,我们还可以实现一个非常重要的功能——增量选择代码块。在vim配置中我使用回车来扩大选区,使用退格键来减小选区,这里仍然沿用这种配置增量选择的功能可以使用 expand-region(use-package expand-region :ensure t ; 从ELPA自动安装 :bind ("C-=" . er/expand-region) :config (defun my/incremental-expand-region() (interactive) (if (region-active-p) (er/expand-region 1) (er/mark-word)) (setq deactivate-mark nil)) (defun my/contract-region() (interactive) (if (region-active-p) (er/contract-region 1) (call-interactively 'evil-backward-char))) (with-eval-after-load 'evil (dolist (state '(normal visual motion)) (evil-define-key state 'global (kbd "RET") nil) (evil-define-key state 'global (kbd "<backspace>") nil)) (evil-define-key 'normal 'global (kbd "RET") #'my/incremental-expand-region) (evil-define-key 'normal 'global (kbd "<backspace>") #'my/contract-region)))原版的增量选择,如果没有选中某个部分无法执行增量,所以对它进行稍微的改造,如果当前没有选中任何部分,先选中当前光标所在的单词。然后继续进行增量选择。与增加选区类似的,减少选区也是先判断如果已经没有选中区域的话还是延续vim中退格键的功能。本篇到此就先结束了,但是我们的IED的功能并没有配置完,后面我计划加上自动编译运行、调试等功能,这些就在后面的文章中给出
2025年08月08日
8 阅读
0 评论
0 点赞
2025-07-11
Emacs折腾日记(二十八)——代码片段
在前面的章节中,介绍了 vertico 体系的补全,已经实现了在各个buffer中的补全功能,但是作为程序员,使用Emacs主要用来编程,对于编程来说上述的补全体系仍然不够完整,我们还需要基于lsp的补全以及基于代码片段模板的补全,这里先介绍代码片段。在之前写vim的配置时已经介绍过代码片段的概念以及基于vim 的配置,感兴趣的读者可以自行在我往期的博客中寻找,这里就不对它进行详细的介绍了。直接来使用它。在Emacs中,我们使用插件 yasnippet 来支持这个功能(use-package yasnippet :ensure t :hook ((after-init . yas-reload-all) ((prog-mode LaTeX-mode org-mode) . yas-minor-mode)) :config (setq yas-snippet-dirs '("~/.emacs.d/snippets")))我们通过上面的代码就能把它启用起来,yas-reload-all 表示在Emacs启动时重新加载所有已启动的代码片段;yas-minor-mode代表启动代码片段补全的功能。但是我们做了限制,只在编写代码、LaTeX和org-mode 文件时启动。如果各位读者希望在markdown 中也启动的话,也可以加上 markdown-mode 的选项。但是既然使用了Emacs,当然要用比 markdown 更加强大的 org-mode 了现在Emacs已经支持了代码片段,它的快捷键与vim中的一致都是使用tab 来进行补全。那么接下来就是通过代码片段文件来提高我们编程的效率了。添加代码片段文件我们可以使用已经写好的,也可以自己根据自己的习惯来定制。下载通用的代码片段官方虽然提供了一些代码片段,但是数量较少,使用起来并不那么方便,我们可以下载网络上的一些公共库来丰富我们的代码片段。这里我介绍一下 Doom Emacs Snippets Library。它是专门为doom emacs 用户打造的代码片段库,使用者不少。其他的代码片段库的安装与使用与它类似,读者可以依葫芦画瓢来安装。 首先找到官方的代码仓库,然后将它克隆下来,我将它放到 ~/.emacs.d/emacs-snippets 目录中。然后通过(use-package doom-snippets :ensure nil :load-path "~/.emacs.d/doom-snippets" :after yasnippet)来加载它。之后需要 Yasnippet 插件能找到这个目录,所以我们修改一下上面的配置(use-package yasnippet :ensure t :hook ((after-init . yas-reload-all) ((prog-mode LaTeX-mode org-mode) . yas-minor-mode)) :config (setq yas-snippet-dirs '("~/.emacs.d/snippets" "~/.emacs.d/doom-snippets"))) ;; 添加一行代码片段的加载路径此时再编写代码发现多了一些代码片段自定义代码片段与 doom emacs snippets library 代码片段类似,我们也需要按照具体的mode来组织代码片段的文件目录。每一个关键字一个文件例如~/.emacs.d/snippets/ ├── python-mode/ │ ├── for ; for 循环模板 │ └── class ; 类定义模板 └── c++-mode/ └── foreach ; for-each 循环模板有了这些准备的前提条件,我们可以自行创建自己的代码片段了。在emacs 中执行 yas-new-snippet 可以打开一个新的buffer,并显示代码片段的模板# -*- mode: snippet -*- # name: # key: # -- name后面跟着的是代码片段的名称,在补全时会以补全项显示这个名称;key代表关键字,表示用哪个缩写来触发。例如我们定义一个 prt 来展开成 printf 语句,那么就可以写成 name:prt key: prt尝试生成第一个代码片段后面的写具体展开的代码,例如我们编写一个自动生成hello world的模板# -*- mode: snippet -*- # name: create-hello # key: hello # -- #include <stdio.h> int main(int argc, char* argv[]) { printf("hello world\n"); return 0; } 在编写完成之后使用快捷键 C-c C-c 来保存,Emacs在保存时会让你输入对应的 mode 并且给出需要保存的位置。当我们修改完适用的 mode 和 保存的位置之后,我们发现它在对应位置生成了文件。重启Emacs或者 使用命令 yas-reload-all 来重新加载所有的代码片段。之后输入hello就可以看到它为我们生成了 c 版本的 hello world 程序占位符我们希望的代码大体上不变但是关键地方由我们自己来填充,这个时候可以使用占位符。占位符的格式为 ${} 大括号中填入数字序号。或者使用 ${1: default} 来表示这个位置默认填写default字符。下面是一个具体的例子# -*- mode: snippet -*- # name: qt-class # key: qcls # -- class ${1} : public ${2:QObject} { Q_OBJECT public: ${1}(${2}* parent = nullptr); ${1}(const ${1}& other); ${1}& operator=(const ${1}& other); ~${1}(); signals: slots: protected: }上面我们定义了一个快速生成继承自 QObject 类的子类的模板,上面有两个占位符,第一个占位符表示名称,它没有默认值,一旦确定了它的也就是类型,它会自动生成类的无参构造、拷贝构造、赋值运算符重载和析构函数的定义。第二个占位符指出了它的父类,它的默认值是 QObject 类,它的效果如下使用elisp代码生成动态信息我们可以使用 `` 来包裹elisp代码,生成动态信息。一般在C/C++ 代码的头文件中会包含一些注释信息,用来说明文件的用途包含的接口信息等等。我们希望生成这么一段注释的代码片段# -*- mode: snippet -*- # name: Custom File Header # key: hdoc # -- /** * @file `(file-name-nondirectory (buffer-file-name))` * @bref ${1:brief description} * @details ${2:detailed explanation} * @date `(format-time-string "%Y-%m-%d")` * @commit history: * \t v${3:1.0}: ${4:Initial version} */ $0上面我们使用 elisp代码 (file-name-nondirectory (buffer-file-name)) 来获取对应的文件名,通过 (format-time-string "%Y-%m-%d") 来获取时间。并且最后使用 $0 来将光标定位到这个位置,这个位置将会在我们使用tab处理过所有tab之后自动跳转。本文到此就结束了。本文主要介绍了在Emacs中使用代码片段,并且介绍了编写代码片段的一些方式。有了这些在编写代码时会更加得心应手。
2025年07月11日
8 阅读
0 评论
0 点赞
2025-07-01
Emacs 折腾日记(二十七)——终端管理
Emacs 号称一个伪装成文本编辑器的操作系统,你几乎可以在Emacs中干任何事情。在Emacs中运行终端自然是小菜一碟。Emacs中有许多其他类型的shell,例如 eshell、term、vetrmterm 终端其中 term 是Emacs中自带的shell,它最终是调用系统中安装的其他shell环境。我们可以直接使用 M-x 调用 term命令,来选择启用一个系统终端。我们可以在这个终端上进行一些操作。但是还有一些执行细节需要优化。控制term窗口的显示首先我们看到在启动终端之后,它占用了一整个窗口,这对于我来说并不友好。一般情况下我只是希望在编写代码之后临时的调用终端执行一些命令,例如编译或者运行代码之类的操作。因此我希望它可以放到下方并且可以快速的关闭退出我们可以通过 shackle 插件来控制窗口的行为。主要有:方向、大小和弹出方式等等。配置 shackle 插件最重要的是变量 shackle-rules 。它的构成如下: CONDITION(:regexp) :select :inhibit-window-quit :size+:align|:other :same|:popupCONDITION 是相关的条件,表示我们要定义哪一类窗口的行为。这个条件可以是正则表达式,可以是字符串,可以是modeselect 控制弹出窗口后是否选中size: 0到1间的数字,控制窗口宽度和高度占整个窗口区域的百分比inhibit-window-quit: 按q退出时不删除这个缓冲区align: 弹出窗口的对齐方式,取值有 ’left, ‘right, ‘below, ‘aboveother: 如果当前 frame 有多个 window,是否复用另外一个 windowpopup: 弹出一个新的 window,而不是复用当前 windowsame: 不弹出 window,复用当前 windowignore: 禁止显示该窗口我们当前的配置可以这么写(use-package shackle :ensure t :hook (after-init . shackle-mode) :custom (shackle-default-size 0.5) (shackle-default-alignment 'below) :config (setq shackle-rules '((term-mode :regexp t :select t :size 0.3 :align t :popup t :quit t))))这里表示 term 窗口在弹出之后占据下方30%的区域进行显示,并且退出后直接关闭窗口。这样我们可以看到使用 term 之后窗口显示在下方,并且像普通buffer那样可以使用vim的命令 :q 来退出快速切换显示和隐藏状态对于终端这种临时窗口,我们一般不会让它长时间驻留在buffer中。想要的时候随时调用、不要的时候随时关闭或者隐藏。对于这种快速打开关闭的需求,我们可以使用 popper 插件。它通过赋予任意缓冲区“弹出”状态,让这些缓冲区不打扰你的核心工作区,只需一键即可调用或隐藏,完美适用于那些需要即时访问但又希望不影响视线的场景。popper-toggle、popper-cycle 和 popper-toggle-type 是三个核心命令。popper-toggle: 快速切换最新一个弹出缓冲区的显示/隐藏状态popper-cycle: 循环切换所有标记为弹出的缓冲区popper-toggle-type: 切换特定类型缓冲区的显示/隐藏状态另外它需要通过 popper-reference-buffers 来定义特定类型的缓冲区,插件会将符合这些规则的缓冲区标记为 POP并且进行管理。popper-reference-buffers 支持正则表达式来匹配缓冲区名。我们可以使用下面简单的配置(use-package popper :ensure t :hook (after-init . popper-mode) :init (setq popper-reference-buffers '("\\*Messages\\*" "\\*Async Shell Command\\*" term-mode help-mode helpful-mode "^\\*eshell.*\\*$" eshell-mode "^\\*shell.*\\*$" shell-mode ("\\*corfu\\*" . hide))) :config (my-leader-def "/" #'popper-toggle "t" #'popper-cycle "T" #'popper-toggle-type) )最终它的效果如下:eshelleshell 与 term 最大的不同在于eshell 是Emacs 自己通过elisp实现的一个仿制的终端,而 term 是真实的系统终端,它调用系统中的shell环境。目前来说使用 term 来打开系统终端能应付大部分的情况,但是学习Emacs,eshell似乎是一个绕不开的坎,并且eshell配置好了,同样好用。eshell里面实现了普通shell里面常用的命令,例如 ls、cd、cp 等等。同时也可以执行elisp代码,例如可以通过find-file 打开一个文件。或者输入简单的算术表达式像 (+ 1 1) 来进行一些数学计算。封装shell命令到eshelleshell里面实现了常见的shell 命令,但是我们在后续会经常性的安装一些好用的工具,来简化我们的工作流程,所以在使用eshell的第一件事就是将shell命令封装到eshell这里我以 autojump 为例。autojump 是一个快速跳转到指定目录的工具,当你通过cd进入过的目录会被记录下来,下一次通过 autojump 不再需要输入全路径,而是直接模糊匹配进行跳转,例如我们多次进入了 ~/.emacs.d/lisp 目录,下一次不管当前目录在哪里只需要输入 j lisp 即可进入。我们希望复刻这种行为到eshell中。定义eshell 中的命令可以通过定义一个以 eshell/ 开头的函数来实现,函数名后面的名称就是可以输入eshell的命令名称,所以这里我们要定义一个名为 eshell/j 的函数。同理,我们想要使用eshll中定义好的命令例如 cd ,可以直接调用 eshell/cd 函数。我们还是使用 use-package 来管理关于 eshell 的配置,最终实现的代码如下:(use-package eshell :ensure nil :config (defun eshell/j (&rest args) (let* ((argstr (mapconcat 'identity args " ")) (target (shell-command-to-string (concat "autojump " argstr)))) (setq target (replace-regexp-in-string "\n" "" target)) (if (file-directory-p target) (eshell/cd target) (error "目录不存在: %s" target)))))上述代码主要通过 autojump 命令来根据输入的目录名称来匹配一个实际对应的路径全名,然后通过 eshell/cd 来完成跳转操作。为什么不直接执行 j 来实现跳转呢?eshell是emacs模拟的shell并不是真实的shell,它是与Emacs绑定的,外部的shell命令无法改变 eshell的环境包括它当前所在的目录。因此我们需要调用eshell自身的命令来改变它的环境。最终的效果如下eshell 的 alias在普通的shell中,我们可以通过 alias 定义命令的别名,例如常用的 ll 或者我喜欢将 vi 和 vim 改成 nvim。eshell中也可以设置别名eshell 中使用 alias 来设置别名,它的语法如下:alias 别名 '命令 [参数] $@*'参数部分传入 $1、$2 等等,代表传入的第一个、第二个参数,或者直接使用 $@* 表示接受输入的所有参数,例如我们定义一下 ll 这个别名alias ll 'ls -l $@*' ; 输入 `ll` 等价于 `ls -l`在 eshell 中输入的 alias 命令仅仅在当前 eshell 环境中生效,想要永久生效可以将上述命令写入到 ~/.emacs/eshell/alias 文件中。eshell 会通过函数 eshell-read-aliases-list 加载对应文件中定义的别名,保存别名的文件路径被保存在 eshell-aliases-file 中,它默认的值为 ~/.emacs.d/eshell/aliaseshell history普通shell中有一个 history 命令可以查看保存的历史,并且可以使用方向键的上和下来输入上一条或者下一条命令。eshell 虽然也能这么干,但是eshell 因为是elisp模拟的终端,背靠Emacs这座大山,它可以配合Emacs的相关插件更高效的利用history命令。这里我们可以配合 consult-history + orderless 来模糊匹配命令(use-package em-history :ensure nil :defer t :custom (eshell-history-size 1024) (eshell-his-ignoredups t) (eshell-save-history-on-exit t)) (use-package esh-mode :ensure nil :hook (eshell-mode . (lambda () (local-set-key (kbd "C-r") #'consult-history))) :bind (:map eshell-mode-map ("C-r" . consult-history)) :config (with-eval-after-load 'evil (evil-define-key '(normal insert) eshell-mode-map (kbd "C-r") #'consult-history))) eshell 直接使用 term 命令上面我们提到,想要调用shell命令,可以通过编写lisp代码封装进行封装,能否直接使用shell命令呢?答案是可以的,eshell中有 eshell-visual-commands、eshell-visual-subcommands、eshell-visual-options 这么三个变量来共同决定哪些命令需要启用term 终端.eshell-visual-commands 接受一个字符串的列表,我们输入的命令在这个字符串列表中,那么就会启用term终端来执行命令eshell-visual-subcommands 来处理带子命令的复杂命令,在特定子命令下才需终端模拟eshell-visual-options 根据命令选项动态启用终端模拟。某些命令仅在特定选项(如 -i 交互式模式)出现时才需终端支持三者优先级:eshell-visual-commands > eshell-visual-subcommands > eshell-visual-options。Eshell 按此顺序检查,匹配任意条件即触发终端模拟。例如我们常见的 git、man、lazygit 等命令无法使用 eshell 中的 elisp 进行模拟,我们可以通过上述函数来封装这些命令。(use-package em-term :ensure nil :defer t :custom (eshell-visual-commands '("top" "htop" "less" "more" "lazygit")) (eshell-visual-subcommands '(("git" "help" "lg" "log" "diff" "show"))) (eshell-visual-options '(("git" "--help" "--paginate"))) (eshell-destroy-buffer-when-process-dies t))结合上述对这些函数的说明,来分析一下这段代码。首先是 eshell-visual-command 中定义的 top、htop、less、more 这些命令需要完成的term终端支持。当运行这些命令时,Eshell 自动切换到term终端,确保交互式界面正常显示(如分页、高亮等)eshell-visual-subcommands 为特定命令(如 git)的子命令启用图形终端,也就是说我们执行 git help 时会触发切换到term终端的操作,而执行 git status 时不会eshell-visual-options 当命令携带特定选项时会切换到term 终端。下面是在eshell 中执行 lazygit 的界面eshell 美化上面介绍了eshell的一些基本用法,现在来介绍如何将eshell进行简单的美化。首先介绍的是 eshell-git-prompt 。它提供了好多好看的主题在安装完插件后,可以在eshell中输入 use-theme 来列出它支持的所有主题,或者输入 use-theme name 来设定一个主题。想要持久化,可以通过函数 eshell-git-prompt-use-theme 来指定一个主题(use-package eshell-git-prompt :ensure t :after esh-mode :config (eshell-git-prompt-use-theme 'powerline))接着介绍 eshell-syntax-highlighting,它是eshell中语法高亮的插件,它是继承自 zsh 中的 zsh-syntax-highlighting。(use-package eshell-syntax-highlighting :after eshell-mode :ensure t ;; Install if not already installed. :config ;; Enable in all Eshell buffers. (eshell-syntax-highlighting-global-mode +1))接下来要介绍的插件是 capf-autosuggest 。它是一个自动补全的插件(use-package capf-autosuggest :ensure t :hook ((eshell-mode comint-mod) . capf-autosuggest-mode) )这样基本上就把我在终端上常用的一些插件和操作习惯给挪到eshell中了。当然我常用的还有一个名为 autojump 的插件。对应的 eshell 中也有一个名为 eshell-autojump 的插件。但是我已经通过别名简单的实现了一个eshell 上的 j 命令,这个插件的实现原理也是这样的,这里就不介绍这个插件了。
2025年07月01日
8 阅读
0 评论
0 点赞
2025-05-29
Emacs 折腾日记(二十六)——buffer与窗口管理
本节我们将介绍如何在Emacs中的buffer与窗口管理,目标是快速管理窗口,以及快速在不同buffer中进行切换基本概念介绍Emacs与vim相比的一个特点是,Emacs是一个窗口程序,或者说是一个gui程序。而vim是一个终端字符界面程序(当然Emacs也可以启用终端模式),那么与vim相比,Emacs多了了一个frame的概念。有时候有些初学者(包括我自己)总是将frame当作窗口。frame:Emacs整个程序,包括标题栏、工具栏、显示文本的界面等等部分window: 真正用来显示文本的区域被称之为window。我们经常说的分屏就是创建了一个window,frame里可以包含多个windowbuffer: Emacs 从磁盘中读取的文本保存在buffer中,buffer不一定都在window上显示。也不是所有buffer都对应一个文件针对frame来说,虽然也有相关的函数可以控制,但是一般我不太喜欢在多个frame之间进行切换,也不习惯创建多个frame。所以这里先略过window 管理关于 window。在之前介绍了window相关的函数,包括 split-window、selected-window、delete-window、以及跟窗口设置相关的 current-window-configuration、set-window-configuration。基于evil插件,我们可以使用vim的窗口创建命令。例如可以使用 :vsp 来将窗口进行纵向分割。但是我发现 :sp 和 :vsp 都是纵向分割的,这个跟vim就不太一样了。好在evil提供了一个名为 evil-ex-define-cmd 函数用来修改ex命令。所以这里我们在evil的配置中进行修改,添加下面的代码(evil-ex-define-cmd "sp" 'split-window-below) (evil-ex-define-cmd "vsp" 'split-window-right)窗口创建完成之后,我们可以使用vim 的方式来关闭窗口,例如使用 :q 命令来关闭当前窗口,或者使用 :on (或者它的全称 :only) 命令来关闭其他所有窗口。在窗口之间的移动,当初我在介绍vim配置的时候介绍过我习惯使用 leader + 'h/j/k/l' 的方式来在各个窗口间移动。但是在Emacs中,我们有比这更高效的移动方式。ace-window 是一个在各个窗口之间进行快速跳转的插件,在激活ace-window 之后,它会在各个窗口上标记一个数字,我们可以按下这个数字快速跳转到对应的窗口(use-package ace-window :ensure t :after general :hook (dired-mode . (lambda () (setq-local aw-ignore-on t))) ;; dired-mode 下禁用 ace-window :config (my-leader-def :states '(normal visual) ";" #'ace-window "l" #'evil-window-left "h" #'evil-window-right "j" #'evil-window-down "k" #'evil-window-up) )这里我们绑定 leader + ; 来调用 ace-window 执行窗口的跳转buffer 管理在介绍neovim 的配置的时候,介绍过一个名为 buffer-line 的插件,它可以很方便的将 buffer 以 tab 的形式给列出来,并且可以根据当前打开的buffer上的编号来进行跳转,我们绑定了 leader + 1~9 的数字来分别跳转到编号为 1~9 的buffer。Emacs中我也希望实现这样的功能,可以将vim上的操作无缝的转移到Emacs上来。要实现这个功能,我们可以安装 awesome-tab 包。它并没有被包含在Emacs 的官方源中,我们需要自行下载git clone --depth=1 https://github.com/manateelazycat/awesome-tab.git将它下载到对应的位置之后,如果要加载它,则需要将它所在的路径放到 load-path 中如果使用 use-package 来加载可以使用如下的代码(use-package awesome-tab :load-path "path/to/your/awesome-tab" :config (awesome-tab-mode t))启用之后,当我们多开了几个buffer之后,它们被显示到不同的tab上了。如果希望能像 buffer-line 那样根据编号跳转,首先可以通过 awesome-tab-show-tab-index 来显示tab上的编号,然后绑定数字键到 awesome-tab-select-visible-tab 进行跳转。解决了这个问题,剩下的函数官方文档上都有介绍,所以我们还是沿用vim上的快捷键,最终的配置如下:(use-package awesome-tab :load-path "~/.emacs.d/awesome-tab" :after general :custom (awesome-tab-show-tab-index 1) :config (awesome-tab-mode t) (my-leader-def :states '(normal) "gb" #'awesome-tab-ace-jump "gT" #'awesome-tab-backward-tab "gt" #'awesome-tab-forward-tab "1" #'awesome-tab-select-visible-tab "2" #'awesome-tab-select-visible-tab "3" #'awesome-tab-select-visible-tab "4" #'awesome-tab-select-visible-tab "5" #'awesome-tab-select-visible-tab "6" #'awesome-tab-select-visible-tab "7" #'awesome-tab-select-visible-tab "8" #'awesome-tab-select-visible-tab "9" #'awesome-tab-select-visible-tab))awesome-tab还可以针对不同的buffer进行分组,然后对分组的buffer进行统一的操作,这里就不介绍了,有兴趣的读者可以自行阅读相关的文档。本节到此就结束了,本节通过 ace-window 和 awesome-tab 插件基本复刻了vim中有关窗口的配置和快捷键。
2025年05月29日
10 阅读
0 评论
0 点赞
2025-05-22
Emacs 折腾日记(二十五)——目录管理
在之前的文章中,分了几篇着重介绍了Emacs编辑方面的功能改造。作为一个文本编辑器,要想坚持用下去首先应该改造成自己熟悉的编辑方式。本节我们来介绍Emacs的目录管理Dired ModeEmacs自带一个名为 Dired (Directory Editor) 的插件,它负责对目录进行操作。默认的,我们可以使用 C-x d 来进入Dired Mode。启动后它会等待用户输入想进入的目录,默认是当前目录。Dired Mode 是提供了一种类似操作文本的方式来操作目录。我们可以像操作文本那样来对目录进行类似于拷贝剪切粘贴删除创建跳转查找重命名批量操作得益于前面的篇章配置的一些插件,可以很方便的使用Dired Modedired 美化在正式介绍dired 使用之前,先稍微对它进行一些美化,原始的界面太素了,看着不太好看。首先介绍 diredfl ,原始的dired 只能使用两种颜色来区别文件和目录,而 diredfl 可以使用多种颜色,让dired显示的更加漂亮(use-package diredfl :ensure t :hook (dired-mode . diredfl-mode))接着我们再使用 all-the-icons-dired 来给dired显示的前面加上一个图标。这个插件依赖 all-the-icons 插件。(use-package all-the-icons :ensure t :when (display-graphic-p) :commands all-the-icons-install-fonts) ;; 安装完成之后需要执行 all-the-icons-install-fonts 命令安装对应字体 (use-package all-the-icons-dired :ensure t :hook (dired-mode . all-the-icons-dired-mode))我们也可以通过安装 all-the-icons-completion 插件,给minibuffer中的补全系统也加上图标(use-package all-the-icons-completion :ensure t :hook ((after-init . all-the-icons-completion-mode) (marginalia-mode . all-the-icons-completion-mode)))dirvish 增强direddirvish 是在dired 基础之上的文件管理增强插件。相对与dired 它提供快速跳转、实时预览、并且能兼容对dired的一些扩展。(use-package dirvish :ensure t :hook (after-init . dirvish-override-dired-mode) :bind( ("C-x d" . dirvish) )) 我们使用命令 dirvish 就能打开对应的窗口,或者像上面那样绑定快捷键来打开对应的窗口使用vim的方式来操作目录这里的使用vim的方式来操作多少有点标题党的意思。我无法做到完全按照vim编辑文本那样来编辑目录,但是这里我可以修改以下对应的快捷键已达到某些操作可以使用vim的快捷键。(use-package dirvish :ensure t :hook (after-init . dirvish-override-dired-mode) :bind(:map dired-mode-map ("C-x d" . dirvish) ("y" . dired-do-copy) ;; 拷贝粘贴 ("d" . dired-do-delete) ;; 删除 ("r" . dired-do-rename) ;; 重命名 ("a" . dired-create-empty-file) ;; 创建空文件 ("+" . dired-create-directory) ;; 创建文件 ("SPC" . nil) ;; 取消空格键的绑定 ) :config (with-eval-after-load 'evil (evil-define-key 'normal dired-mode-map "r" 'dired-do-rename)) ;; 排除evil模式下默认键的覆盖 (my-leader-def "j" #'dired-goto-file) )在上述的配置中,我绑定的快捷键如下快捷键功能C-x d打开diredy拷贝d删除r重命名a创建空文件+创建空目录SPC j跳转到指定文件需要注意的是,在 config 中调用了这样的语句 (with-eval-after-load 'evil (evil-define-key 'normal dired-mode-map "r" 'dired-do-rename))with-eval-after-load 表示在某个插件加载之后,这句代码的意思是,在evil插件加载后,在normal模式下,我们定义dired mode 下快捷键 r 绑定到 dired-do-rename,也就是重命名这个功能。之所以要这么做是因为在evil插件加载后会覆盖我们定义的快捷键。另外为了正常使用 leader键,这里特意取消了空格键在dired 中的定义,原本它被定义为跳转到下一行。这些功能都比较简单,所以这里就不演示了。搜索文件并跳转正常情况下,当项目文件和代码量上来之后,再一个目录一个目录的找就不太现实了。常见的是根据代码中函数定义来找文件或者根据文件来找函数定义。前者我们通过consult-rg 已经实现了。这里介绍以下如何通过文件名来查找并快速打开文件我们可以使用 consult-locate 来搜索文件,结合前面介绍的orderless,我们只需要对文件有一个相对的映像就可以找到。想要使用 consult-locate 需要安装 locate 程序,在Arch Linux 中可以使用下面的命令安装sudo pacman -S mlocate sudo updatedb ;; 更新数据库我们以打开配置文件中的 init-completion.el 为例。我们可以直接使用 M-x 输入命令 consult-locate并回车 , 接着在命令提示符后输入一个大概的内容,然后在minibuffer的候选项中找到对应的文件即可我们也可以结合 embark-act 命令来做到跳转到文件所在的 dired 中,这里我们在 embark 的配置中添加一个快捷键定义(use-package embark :ensure t :after consult :bind (("C-e" . embark-export) ("C-;" . embark-act))) ;; 添加 embark-act 快捷键这样在定位到文件之后,可以直接使用 C-; 调出对应的动作,最后使用 j 来完成进入dired的动作有了 consult-locate 和 embark-act,前面介绍的 dired中的跳转操作的实用性就大大降低了,如果我们记得文件的全路径,直接使用 find-file 打开就好了。如果只有一个模糊的映像,那么使用 consult-locate 配合 orderless,比进入dired 然后执行跳转要快的多到这里,Emacs中关于目录管理的部分就介绍完了。使用dired配合键盘操作能极大的提升的效率
2025年05月22日
9 阅读
0 评论
0 点赞
2025-05-16
Emacs 折腾日记(二十四)——帮助信息的一些优化
Emacs 是一个自文档的系统,任何有关Emacs的信息都可以通过Emacs自身查阅。包括基础的入门手册、elisp手册、以及各种插件的相关说明信息。一般遇到不懂的变量直接使用 C-h v。查看它的说明。使用 C-h f 查阅相关函数、使用 C-h k 来查找对应快捷键绑定的函数。一般来说阅读官方一手的文档比从任何论坛或者搜索引擎来的更快更准确,而且有不少Emacs高手都推荐多多阅读Emacs的官方文档。本节就着重介绍一下我了解的关于阅读文档的一些姿势。查找相关定义最开始介绍过使用 C-h f 之类的查看相关文档,Emacs所谓的子文档不光指的是查看文档,而且还可以查看相关的源代码。有些我们可以通过文档中给出的源码链接点进去看,但是还是不如直接查看源码。直接查看源码可以使用 find-function、find-variable、find-function-on-key。它们分别对应着查找函数、变量、以及快捷键绑定的函数定义。我们可以绑定一些快捷键(global-set-key (kbd "C-h C-f") 'find-function) (global-set-key (kbd "C-h C-v") 'find-variable) (global-set-key (kbd "C-h C-k") 'find-function-on-key)对默认文档显示的优化我觉得官方的文档显得比较素,比较原始。为了提高阅读的体验我们可以对文档进行美化。这里可以使用 helpful来增强一下。(use-package helpful :ensure t :bind (("C-h f" . helpful-callable) ("C-h v" . helpful-variable) ("C-h k" . helpful-key) ("C-h s" . helpful-symbol) ))我们针对 C-h k 这个快捷键来对比一下原始的文档和 helpful 增强之后的文档显示效果对比发现,helpful 不光对显示效果进行了一些改进,而且显示的内容更加丰富。特别是它已经可以直接显示出相关定义的源代码,在某种程度上替换默认的 find-function 等函数。which-key在介绍配置 neovim 的时候,介绍了 which-key 插件,它可以根据用户输入的快捷键前缀显示所有可能的匹配,使我们记忆快捷键的负担减轻。Emacs上也有类似的插件。在Emacs 30以后内置了 which-key 插件,我们只需要启用(use-package which-key :ensure t :hook (after-init . which-key-mode) :custom (which-key-idle-delay 0.7))
2025年05月16日
4 阅读
0 评论
0 点赞
2025-05-14
Emacs 折腾日记(二十三)——进一步提升编辑效率
在前面的几篇,我们完成了Emacs的vim模拟器、中文输入、多行编辑以及基本的补全功能的添加。这一篇没有具体的提升哪一方面的能力,这一篇我想整合我在其他教程中看到的我认为对我比较有用的用法和插件,算是对前期功能的一个总结。让Emacs记住一些信息一般的编辑器都会在下次打开时记住上次的一些信息,例如记住之前打开过的文件,执行过的命令,或者记住上次的窗口布局。记住上次执行的命令我们每次使用 M-x 执行命令时,minibuffer中显示的提示都是一样的,那些常用命令要么不在上面要么太靠下了,我们希望能记住某些命令,以便能快速找到它。记住上次执行的命令可以使用 savehist 插件。它是一个Emacs自带的插件,默认是关闭的状态,我们可以通过将 use-package 来加载它,但是因为是自带的,不需要从镜像中下载所以它的 :ensure 项应该设置为 nil(use-package savehist :ensure nil :hook (after-init . savehist-mode) :custom (savehist-file (locate-user-emacs-file "custom/savehist")) ;; 设置保存文件的位置 (savehist-additional-variables '(kill-rings shell-command-history)) ;; 额外保存剪切板和shell命令行历史 (savehist-ignored-variables '(message-history)) ;; 不保存消息历史 (history-delete-duplicates t) ;; 自动去重 (history-length 1000) ;; 保存历史数据条目 )在执行一些操作关掉Emacs之后,我们会发现它在 ~/.emacs.d/custom 生成了一个名为 savehist 的文件,它记录了之前在minibuffer中执行的命令。为了保持git工程的干净,我打算将这种历史文件排除在git管理之外,所以单独将它放到custom目录,并忽略它其实该插件不光可以保留执行的命令,minibuffer中的许多信息它都可以保存和持久化。minibuffer-history (所有 minibuffer 输入历史)command-history (执行过的命令)search-ring (搜索历史)regexp-search-ring (正则搜索历史)extended-command-history (M-x 命令历史)file-name-history (文件路径历史)记住上次打开的文件一般的编辑器都可以记录上次打开的文件,并列出来。Emacs也有一个类似的内置插件—— recentf(use-package recentf :ensure nil :hook (after-init . recentf-mode) :custom (recentf-max-menu-item 10) ;; 最多只记录10条历史记录 (recentf-save-file (locate-user-emacs-file "custom/.recentf")) ;; 定义保存历史记录的临时文件路径 )搜索功能的增强实现全局搜索我们可以依赖Linux上的命令行工具 grep 和最近(也不算近了)的 ripgrep。之前在介绍vim的时候,vim内部集成了 grep。但是我们使用更为强大的 ripgrep。在Emacs中可以配合插件 consult 和 ripgrep,调用 consult-ripgrep 来进行全局搜索。它会自动搜索当前项目下的所有目录。我们对之前 consult 插件的配置代码进行一些修改,并添加 ripgrep 的配置(use-package consult :ensure t) (use-package ripgrep :ensure t :after consult :bind (("C-s" . consult-ripgrep)) )这里我们将 C-s 绑定的快捷键修改为 consult-ripgrep。神奇的是,配合之前安装的orderless,我们只需要按照一个模糊的记忆来匹配想要的内容。同时它也能支持输入中文批量替换批量替换这个功能,根据我找到的教程,它需要依赖 embark、consult、和 wgrep 这么三个插件。其中 consult 用来进行搜索,而 embark 可以为不同场景下的文本/候选项(如搜索结果、补全列表、文件路径等)提供动态的快捷操作菜单。简化了minibuffer上的一些操作。而 wgrep 则是其中的核心插件,用来批量修改内容并保存(use-package embark :ensure t :after consult :bind (("C-e" . embark-export))) (use-package embark-consult :ensure t :after embark) (use-package wgrep :ensure t :custom (wgrep-auto-save-buffer t) ;; 自动保存修改 )这里我们使用 :after 来保证插件的加载顺序依次为 consult、embark、embark-consult,特别是 embark-consult,它依赖 consult 和 embark,一定要将它放到后面加载。下面来演示如何进行批量替换,这里我们将配置中所有 use-package 修改为 package-install,修改之前记得使用git等版本管理工具进行备份首先,使用 C-s 搜索 use-package 关键字接着使用 C-e, 也就是上面绑定的快捷键来将结果从 minibuffer 导出到 buffer然后使用 C-c C-p 调用 wgrep-change-to-wgrep-mode 将 buffer 的mode由 grep-mode 修改为 wgrep-mode接着使用 M-% 调用 query-replace 进行替换,这个时候它需要输入被替换的字符和替换后的字符确定后,对于每个待替换的位置使用 y 或者 n 来表示替换或者不替换。也可以使用 ! 替换所有最后使用 C-c C-c 调用 wgrep-finish-edit 来结束编辑,配置之前设置的自动保存,此时修改内容已经被保存了修改之后如何不满意,可以使用 C-c C-k 撤销修改小节这应该是最后一篇关于Emacs自身编辑功能的增强了,在这一块我没有使用太多的Emacs经验。倒是在vim上有点经验,所以很多东西我不自觉地就往vim上面靠,总想着vim在编辑上有些功能Emacs上还没有,该如何进行添加,这几篇就显得比较分散,总是想到什么功能就往上面堆。为此造成各位读者阅读体验不佳,我表示道歉。谢谢各位读者的支持和鼓励!
2025年05月14日
9 阅读
0 评论
0 点赞
2025-05-07
Emacs 折腾日记(二十二)——补全强化
在之前的一系列文章中,我们对Emacs做了一些小范围的定制,目前它已经可以很好的模拟vim的一些基础功能。我们也在模拟vim基础功能之上做了一些能力的提升。本篇我们将对Emacs自带的补全系统做一个升级,并且给出一些搜索和替换的方案,进一步提升Emacs的效率Emacs上有很多很好用的补全插件,著名的有前期的 ivy 体系和当前社区比较火的vertico 体系。为了与时俱进,而且Emacs-China中的很多帖子也推荐使用vertico,所以这里我也介绍这个体系中的插件。vertico 体系中包括下面几个插件:verticoconsultcorfumarginaliaorderlessconsultconsult 插件提供了一系列的查找和补全命令(use-package consult :ensure t :bind (("C-s" . consult-line)))这样我们可以通过使用 C-s 来进行搜索vertico默认情况下,我们使用M-x 输入命令时没有补全提示,但是可以使用TAB 键补全。我们可以通过命令 icomplete-mode 来启用这个mode,以便在输入命令时能拥有一个补全。但是这个补全采用的是横向排版的方式,显示上也不太直观。这里我们可以通过vertico 插件对补全进行增强。vertico 提供了一个垂直样式的补全系统。我们可以通过下列代码来安装并启用它(use-package vertico :ensure t :hook (after-init . vertico-mode))重启emacs之后,再执行 M-x 之后发现它已经可以竖直的显示命令,并且会列出可能的命令了。可以使用 C-n、C-p 来选择下一个或者上一个命令orderless顾名思义,orderless 提供一种无序补全。它可以将一个搜索的范式变成数个以空格分隔的部分,各部分之间没有顺序,你要做的就是根据记忆输入关键词、空格、关键词。它改变了我们使用和思考的习惯,我们不再需要关心信息的顺序,我们只需要在脑海中搜索关键信息片段,然后把这些片段组合起来即可,剩下的都交给Emacs。例如我们要输入 package-refresh-contents 来刷新包管理里面的源。常规的做法我们需要先输入 pack 等等字符,然后由补全信息给我们提示,加入 orderless 之后,可以凭借模糊的记忆输入类似 refre pack 这样的片段来进行匹配(use-package orderless :ensure t :init (setq completion-styles '(orderless)))orderless 是针对整个minibuffer进行增强的,只要是使用minibuffer的地方都可以使用。例如我们上面使用了 consult 插件并且绑定了 C-s 来进行搜索,这里我们就可以使用orderless 来配合完成搜索功能marginaliamarginalia 可以给minibuffer中候选条目显示一段注释或者其他信息。其实不光是执行命令的时候marginalia是启用的,现在只要是minibuffer中的选项,marginalia都是可以使用的,例如使用 switch-buffer 和 find-file 或者使用帮助信息的时候也可以展示相关信息corfucorfu 可以让我们通过弹窗进行补全。(use-package corfu :ensure t :hook (after-init . global-corfu-mode) :custom (corfu-auto t) (corfu-auto-deply 0) (corfu-min-width 1) :init (corfu-history-mode) (corfu-popupinfo-mode))在安装完成之后,我们在编写相关配置的时候可以配合orderless,只输入函数的部分,仅仅凭借模糊的记忆让Emacs自己来匹配我们想要的内容,极大的提高了输入的效率capecorfu 插件仅仅是一个补全的前端,它需要补全后端提供数据。好在Emacs 自己提供了有关elisp 的补全后端,所以上面在测试corfu补全的时候可以出现。但是在其他文本类型不会产生补全选项。而cape则是集成了多种补全后端,它与corfu联合起来可以起到很好的补全效果(use-package cape :ensure t :init (add-to-list 'completion-at-point-functions #'cape-dabbrev) (add-to-list 'completion-at-point-functions #'cape-file) (add-to-list 'completion-at-point-functions #'cape-keyword) (add-to-list 'completion-at-point-functions #'cape-ispell) (add-to-list 'completion-at-point-functions #'cape-dict) (add-to-list 'completion-at-point-functions #'cape-symbol) (add-to-list 'completion-at-point-functions #'cape-line))上述代码中 completion-at-point-functions 保存的是Emacs在补全时调用的相关函数来获取补全项,我们将cape 的相关函数添加到这个列表中,供Emacs在触发补全时调用。到此为止,我们对Emacs自身的补全进行了加强。进一步提升了编辑的效率
2025年05月07日
9 阅读
0 评论
0 点赞
1
2
3