首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
123 阅读
2
nvim番外之将配置的插件管理器更新为lazy
90 阅读
3
2018总结与2019规划
67 阅读
4
PDF标准详解(五)——图形状态
44 阅读
5
为 MariaDB 配置远程访问权限
35 阅读
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
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
elisp
linux
文本编辑器
Java
反汇编
读书笔记
OLEDB
数据库编程
数据结构
Masimaro
累计撰写
321
篇文章
累计收到
31
条评论
首页
栏目
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
Thinking
FIRE
菜谱
页面
归档
友情链接
关于
搜索到
25
篇与
的结果
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日
1 阅读
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日
8 阅读
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日
5 阅读
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日
3 阅读
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日
6 阅读
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日
6 阅读
0 评论
0 点赞
2025-04-22
Emacs 折腾日记(二十一)——编辑能力提升
上一篇文章,我们补充了一些基本的配置,并且关闭了一些默认的行为。这里我们继续对它进行配置。本篇将要使用一些插件来修改默认的编辑行为进一步提高编辑文本的效率。avy 插件基础用法在vim中有 easymotion 可以使用,在Emacs中可以使用 avy 插件。它的功能于前面介绍的easymotion 类似。通过下面的代码来安装(use-package avy :ensure t :after general ;; 确保 general 插件已经安装 :config (setq avy-timeout-seconds 0.5) (my-leader-def "f" 'avy-goto-char-timer))我们在安装配置的时候使用之前定义的leader键来定义它的一些行为。首先定义 SPC-f 来进行快速跳转。avy-goto-char-timer 的功能与 easymotion 类似,将要查找的字符使用不同的字母进行标识,然后根据下一步的输入来确定光标的位置,例如下面的例子我们可以连续输入一段内容减少待筛选项。当然这个速度要快,否则在一定时间内没有输入文本之后,avy会认为已经结束输入了。这个等待的时间我们在上面通过 avy-timeout-seconds 定义的是 0.5。当然也可以扩大这个时间范围进阶用法这个简单的功能只是 avy 功能的冰山一角。它还有许多有用的功能,如果能熟练使用将会极大的提高编辑文本的效率。实际上avy 在筛选、跳转之前可以执行用户指定的动作,它支持哪些动作呢?我们可以在输入筛选的部分文本后输入?,查看支持的动作。我们来举一个例子:下面有一段文本,我希望将text1复制到最后一行,那么可以这么操作使用<leader>f 激活 avy,然后输入筛选文本输入 Y 表示复制整行输入对应字符表示选中text1 所在行此时我们会发现,text1 已经被复制到光标所在行了多行操作和块操作在vim中我们介绍了多行操作,主要是使用C-v 来选中某些行,然后通过使用A 或者 I 来选中行尾或者行首,并进入插入模式。这种方式可以同时在对应位置插入多个相同的字符。emacs 中的 evil 插件也可以进行相同的操作。但是对于行间或者每行在不同位置插入的情况就不适用了。我们可以使用插件 evil-multiedit 来达到这一效果。evil-multiedit 深度绑定了vim的快捷键,在选中区域之后可以直接使用vim中的 I/A 来编辑选中区域的首部或者尾部。也可以使用 ciw 之类的同时修改多个选中区域。在选区时既可以使用vim 中的 * 来查找并选中,也可使用 / 来搜索并且同时选中多个。这个插件相当于增强了vim的多行编辑功能我们使用下列的代码来安装和简单的配置(use-package evil-multiedit :ensure t :after evil :config (evil-multiedit-default-keybinds) (my-leader-def "m m" #'evil-multiedit-match-and-next ; 标记当前符号并跳转下一个 "m M" #'evil-multiedit-match-and-prev ; 标记当前符号并跳转上一个 "m a" #'evil-multiedit-match-all ; 标记所有相同符号 "m r" #'evil-multiedit-restore ; 恢复单光标模式 "m c" #'evil-multiedit-toggle-or-restrict ; 切换选区/限制编辑区域 ))我们可以使用 <leader> mm 来选中符合条件的项也可以使用 <leader> ma 来选择所有符合条件的项好了,本节到此也就结束了,本节依靠两个插件,进一步模拟vim相关的功能,并且对vim原有的功能进行了一定程度的补强。熟练使用这两个插件将会对编程的效率有一个进一步的提升。作为一个普通的文本编辑器也足够了。
2025年04月22日
7 阅读
0 评论
0 点赞
2025-04-12
使用CMake跨平台的一些经验
使用CMake构建工程的一个原因是不希望Windows上有一个工程文件,Linux又单独配置一套工程文件。我们希望对于整个工程只配置一便,就可以直接编译运行。CMake本身也具备这样的特性,脚本只用写一次,在其他平台能自动构建工程。这里谈的跨平台主要是如何组织工程和编写CMake来针对Windows和Linux进行一些特殊的处理,在这里说说我常用的几种办法介绍这些方法的前提是有一些代码只能在Windows上跑,例如调用Win32 API或者使用GDI/GDI+,而在Linux上也有一些Windows不支持的代码。我们就需要一些方法来隔离开这两套代码。假设现在有一个项目,它有一套共同的接口对外提供功能,而在Windows上和Linux上各有一份代码来实现这些接口。可以假设有一套图形相关的功能,对外采用统一的接口,具体实现时Windows上使用GDI+,而Linux上使用其他方案来实现。现在整个项目的目录结构如下. ├── include └── platform ├── linux └── windowsinclude 目录用来对外提供接口,是导出的头文件。platform隔离了Windows和Linux上的实现代码。使用宏来控制我们知道Windows和Linux平台有特定编译器定义的宏,根据这些宏是否定义我们可以知道当前是在Linux还是Windows上编译,是需要编译成32位或者64位程序,又或者编译成debug版本或者release版本。例如下面是我用的简单的判断版本的方式#define MY_PLATFORM_WINDOWS 0 #define MY_PLATFORM_LINUX 1 #define MY_PLATFORM_APPLE 2 #define MY_PLATFORM_ANDROID 3 #define MY_PLATFORM_UNIX 4 #define MY_ARCH32 1 #define MY_ARCH64 2 #if defined(_WIN32) || defined(_WIN64) #define MY_PLATFORM MY_PLATFORM_WINDOWS #ifdef _WIN64 #define PLATFORM_NAME "Windows 64-bit" #define MY_ARCH MY_ARCH64 #else #define PLATFORM_NAME "Windows 32-bit" #define MY_ARCH MY_ARCH32 #endif #elif defined(__APPLE__) #include "TargetConditionals.h" #define MY_PLATFORM MY_PLATFORM_APPLE #ifdef ARCHX64 #define MY_ARCH MO_ARCH64 #else #define MY_ARCH MO_ARCH32 #endif #if TARGET_IPHONE_SIMULATOR #define PLATFORM_NAME "iOS Simulator" #elif TARGET_OS_IPHONE #define PLATFORM_NAME "iOS" #elif TARGET_OS_MAC #define PLATFORM_NAME "macOS" #endif #elif defined(__linux__) #define MY_PLATFORM MY_PLATFORM_LINUX #if defined (ARCHX64) || defined (__x86_64__) #define MY_ARCH MY_ARCH64 #else #define MY_ARCH MY_ARCH32 #endif #define PLATFORM_NAME "Linux" #elif defined(__unix__) #ifdef ARCHX64 #define MY_ARCH MY_ARCH64 #else #define MY_ARCH MY_ARCH32 #endif #define PLATFORM_NAME "Unix" #define MY_PLATFORM MY_PLATFORM_UNIX #else #error "Unknown platform" #endif上面代码根据一些常见的编译器宏来决定是什么版本,并且根据不同的版本将MY_PLATFORM 进行赋值。后面只需要使用 MY_PLATFORM 进行版本判断即可。同样的关于架构使用 MY_ARCH 来判断。例如根据架构来定义不同的数据长度#if (MY_PLATFORM == MY_PLATFORM_WINDOWS) typedef __int64 MY_INT64; typedef unsigned __int64 MY_UINT64; #else typedef long long MY_INT64; typedef unsigned long long MY_UINT64; #endif #if (MY_ARCH == MY_ARCH64) typedef MY_UINT64 MY_ULONG_PTR; typedef MY_INT64 MY_INT_PTR; #else typedef MY_UINT MY_ULONG_PTR; typedef MY_INT MY_INT_PTR; #endif定义的常见的数据结构之后,对于一些接口的视线就可以利用宏来隔开// platform/windows/image.c #if (MY_PLATFORM == MY_PLATFORM_WINDOWS) // todo something #endif// platform/linux/image.c #if (MY_PLATFORM == MY_PLATFORM_LINUX) // todo something #endif这样我们可以利用上一节介绍过的 cmake 的 file 或者 aux_source_directory 将整个platform目录都包含到工程里面。使用cmake来判断版本除了在C/C++ 源码中利用编译器特定的宏来判断版本,其实CMake自身也有一些方式来判断编译的版本。CMake 检测操作系统使用 CMAKE_SYSTEM_NAME 来判断。这里要提一句,CMake中的变量本质上都是一个字符串值,没有严格的区分类型,所以 set(variable 1) 和 set(variable "1") 在底层存储都是字符串 1。所以cmake在定义变量的时候可以不使用双引号,但是对于特殊的字符串,例如带有空格的字符串,为了避免语法上的歧义,可以带上双引号。虽然说底层存储的都是字符串,但是在上层判断变量是否相等的时候却又区分数字和字符串。判断变量是否等于某个值可以使用 EQUAL 或者 STREQUAL。EQUAL 是用来判断数字类型是否相等,一般判断版本号或者数字参数。而 STREQUAL 来判断字符串是否相等,一般用来判断配置选项、路径、平台标识符。例如这里的 CMAKE_SYSTEM_NAME 就需要采用 STREQUAL 来判断# 检测平台 set(PLATFORM_WINDOWS 1) set(PLATFORM_LINUX 2) set(PLATFORM_MACOS 3) if(CMAKE_SYSTEM_NAME STREQUAL "Windows") set(PLATFORM ${PLATFORM_WINDOWS}) elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") set(PLATFORM ${PLATFORM_LINUX}) elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") set(PLATFORM ${PLATFORM_MACOS}) endif()判断架构可以使用 CMAKE_SIZEOF_VOID_P。顾名思义,它表示一个void* 指针变量的大小,8位就是64位,4位则是32位架构。if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(PLATFORM_ARCH "x64") elseif(CMAKE_SIZEOF_VOID_P EQUAL 4) set(PLATFORM_ARCH "x86") else() message(FATAL_ERROR "Unsupported architecture pointer size: ${CMAKE_SIZEOF_VOID_P}") endif()至于判断当前编译的版本是debug 还是 release 可以使用 CMAKE_BUILD_TYPE 来判断,它的值主要有4个,分别是 Debug、RelWithDebInfo、MinSizeRel、Release。它们四个各有特色。其中 RelWithDebInfo 是带有符号表的发布版,便于调试,它的优化级别最低。MinSizeRel和Release在优化上各有千秋,前者追求最小体积,后者追求最快的速度,所以后者有时候会为了运行速度添加一些额外的内容导致体积变大。我们可以在cmake文件中判断对应的值以便做出一些额外的设置。例如if(CMAKE_BUILD_TYPE STREQUAL "Debug") add_compile_definitions(-D_DEBUG) else() add_compile_definitions(-DNDEBUG) endif()有了这些基础,我们可以在不同的条件下,定义不同的编译宏,然后根据编译宏的不同在C/C++ 源码中判断这些宏从而隔离不同平台的实现代码通过cmake list 来隔离不同平台的代码使用 file 或者 aux_source_directory 的到的是一个源代码文件的列表。我们可以操作这个列表来达到控制编译文件的需求。cmake 中针对列表的操作符是 list,它的定义如下:Reading list(LENGTH <list> <out-var>) list(GET <list> <element index> [<index> ...] <out-var>) list(JOIN <list> <glue> <out-var>) list(SUBLIST <list> <begin> <length> <out-var>) Search list(FIND <list> <value> <out-var>) Modification list(APPEND <list> [<element>...]) list(FILTER <list> {INCLUDE | EXCLUDE} REGEX <regex>) list(INSERT <list> <index> [<element>...]) list(POP_BACK <list> [<out-var>...]) list(POP_FRONT <list> [<out-var>...]) list(PREPEND <list> [<element>...]) list(REMOVE_ITEM <list> <value>...) list(REMOVE_AT <list> <index>...) list(REMOVE_DUPLICATES <list>) list(TRANSFORM <list> <ACTION> [...]) Ordering list(REVERSE <list>) list(SORT <list> [...])官方提供了这么一些操作list的操作符,但是在这个需求中我们只需要两个操作符APPEND 和 REMOVE_ITEM 即可。后面的参数分别是源列表,以及需要增加或者删除的项,它们都可以传入多项。但是删除时它是根据传入字符串,从列表中进行字符串比较,如果相等则进行删除。所以在传入路径的时候需要特别注意,不能一个传入全路径一个传入相对路径或者一个传入 / 开头的路径,另一个传入 ~ 开头的路径。上述两个操作都可以传入多个单个的字符串也可以传入一个列表。如果我们有下列目录结构src/platform/windows src/platform/linux src/*.cpp src/other/*.cpp也就说我们将不同平台的代码放入到src目录,并且src目录也有其他代码,我们如果使用 file 操作符来查找src目录中的源码文件必定会包含两个平台的实现代码。这个时候就可以考虑使用REMOVE_ITEM 根据平台来舍弃一些代码,例如file(GLOB_RECURSE SOURCES ${PROJECT_SOURCE_DIR}/src/*.c ) if(CMAKE_SYSTEM_NAME STREQUAL "Windows") file(GLOB_RECURSE NOT_INCLUDE ${PROJECT_SOURCE_DIR}/src/platform/linux/*.cpp ) elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") file(GLOB_RECURSE NOT_INCLUDE ${PROJECT_SOURCE_DIR}/src/platform/windows/*.cpp ) endif() list( REMOVE_ITEM SOURCES SOURCE ${NOT_INCLUDE} )又或者我们采用最上面的给出的目录结构,也就是说platform 目录位于src目录之外,相对于src来说是额外添加的代码文件,那么就可以使用 APPEND 来进行添加if(CMAKE_SYSTEM_NAME STREQUAL "Windows") aux_source_directory(${PROJECT_SOURCE_DIR}/platform/windows PLATFORM_SOURCE) elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") aux_source_directory(${PROJECT_SOURCE_DIR}/platform/linux PLATFORM_SOURCE) endif() list(APPEND SOURCES ${PLATFORM_SOURCE})通过 toolchain 文件来组织平台特殊配置cmake 允许我们在生成Makefile的时候指定toolchain 文件来实现一些自定义的配置。例如可以根据平台的不同将生成路径指定在对应的toolchain中。toolchain 的语法与cmake语法相同,例如针对Windows和Linux可以创建 win32_toolchain.cmake win64_toolchain.cmake linux_86_toolchain.cmake 和 linux_x64_toolchain.cmake 文件来区别我还是以上一篇文章中多工程嵌套的例子作为示例来演示如何使用,它的目录结构如下. ├── calc │ ├── add.cpp │ ├── CMakeLists.txt │ ├── div.cpp │ ├── mult.cpp │ └── sub.cpp ├── CMakeLists.txt ├── include │ ├── calc.h │ └── sort.h ├── sort │ ├── CMakeLists.txt │ ├── insert_sort.cpp │ └── select_sort.cpp ├── test_calc │ ├── CMakeLists.txt │ └── main.cpp └── test_sort ├── CMakeLists.txt └── main.cpp这个例子我们只需要改动根目录下的生成库和可执行程序的路径。cmake_minimum_required(VERSION 3.15) project(test) add_subdirectory(sort) add_subdirectory(calc) add_subdirectory(test_calc) add_subdirectory(test_sort)这个文件只需要保留最基础的配置即可,而生成程序的路劲都在 toolchain 中。下面是 linux_x86_toolchain.cmake 文件的内容set(LIBPATH ${PROJECT_SOURCE_DIR}/lib/linux/x86) set(EXECPATH ${PROJECT_SOURCE_DIR}/bin/linux/x86) set(HEADPATH ${PROJECT_SOURCE_DIR}/include) if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(CALCLIB calc_d) set(SORTLIB sort_d) set(CALCAPP test_calc_d) set(SORTAPP test_sort_d) else() set(CALCLIB calc) set(SORTLIB sort) set(CALCAPP test_calc) set(SORTAPP test_sort) endif() set(CMAKE_SYSTEM_PROCESSOR i686) set(CMAKE_C_FLAGS "-m32 -L/usr/lib32" CACHE STRING "" FORCE) set(CMAKE_CXX_FLAGS "-m32 -L/usr/lib32" CACHE STRING "" FORCE) set(CMAKE_EXE_LINKER_FLAGS "-m32 -L/usr/lib32" CACHE STRING "" FORCE)这个文件我们定义了debug和release的库名称和生成的路径,并且指定相关参数用于生成32位程序。CMAKE 中定义了一堆 CMAKE_LANGUAGE_FLAGS 这些都是相关工具的参数,这里的FLAGS 分别是 gcc 和 ld 编译链接的参数。在使用时直接用命令 cmake .. -DCMAKE_TOOLCHAIN_FILE=../linux_x32_toolchain.cmake -DCMAKE_BUILD_TYPE=Debug 生成Makefile。Windows平台上上面的参数稍微有些差距。例如下面是 windows_x32_toolchain.cmake 的定义# 静态库生成路径 set(LIBPATH ${PROJECT_SOURCE_DIR}/lib/windows/x86) # 可执行程序的存储目录 set(EXECPATH ${PROJECT_SOURCE_DIR}/bin/windows/x86) # 头文件路径 set(HEADPATH ${PROJECT_SOURCE_DIR}/include) if(CMAKE_BUILD_TYPE STREQUAL "Debug") # calc库名称 set(CALCLIB calc_d) # sort 库名称 set(SORTLIB sort_d) # 测试程序的名字 set(CALCAPP test_calc_d) set(SORTAPP test_sort_d) else() # calc库名称 set(CALCLIB calc) # sort 库名称 set(SORTLIB sort) # 测试程序的名字 set(CALCAPP test_calc) set(SORTAPP test_sort) endif() set(CMAKE_GENERATOR_PLATFORM "Win32" CACHE STRING "Target Platform") # 指定32位编译器路径 set(CMAKE_C_COMPILER "$ENV{VCToolsInstallDir}/bin/Hostx86/x86/cl.exe") set(CMAKE_CXX_COMPILER "$ENV{VCToolsInstallDir}/bin/Hostx86/x86/cl.exe") set(CMAKE_SYSTEM_PROCESSOR x86)需要注意的是 CMAKE_GENERATOR_PLATFORM 对应的是VS 中的解决方案平台,也就是 Win32 和 x64 这两个选项。 而 CMAKE_SYSTEM_PROCESSOR 对应的是VS中的目标计算机选项,一般是X86、X64 或者 ARM64 和 AMD64。我们可以使用命令 cmake -G "Visual Studio 15 2017" -A "win32" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE="..\windows_x86_toolchain.cmake" .. 这里指定使用 VS 2017 进行构建,构建架构是32位,版本是Debug。命令成功执行之后会生成一个.sln 文件,我们可以用VS打开然后在VS中编译,也可以执行使用命令 cmake --build . --config Debug 来编译。一般来说我习惯使用后者,过去使用vs 打开.sln 可以在vs中进行开发,如今vs 已经可以打开并编译cmake工程,所以现在我基本不使用 .sln 文件了,除非公司项目要求使用 .sln。好了,目前我掌握的关于cmake的内容就是这些了,我利用这些知识已经完成了公司项目的跨平台开发和部署。后续如果有新的需求说不定我会学点新的内容,到时候再来更新这一系列文章吧!!!
2025年04月12日
8 阅读
0 评论
0 点赞
2025-03-17
Emacs 折腾日记(十八)——改变Emacs的样貌
截止到上一篇文章为止,之前教程 的内容都看完了,虽然它的后记部分提供了一些后续进阶的内容需要我们自己读手册。但是我不太想继续在elisp上死磕了。看着自己学了那么久的elisp,但是自己的emacs仍然没有半点改变,这个时候各位读者的兴趣一定会大打折扣,是时候试试配置一下自己的emacs了。教程后记中提到的内容等配置的时候涉及到了再来了解吧所谓人靠衣装马靠鞍,一个编辑器好不好用首先要看的就是它好不好看,对于难看的编辑器可能一眼就要给它发卡了——“你是一个很优秀的编辑器,但是我们不合适”。所有配置时第一件事就是将emacs变帅变好看。emacs的基础配置Emacs 在加载的时候会首先读取 ~/.emcas.d/init.el 中的代码。整个配置程序的入口就在这里。但是如果我们一股脑将所有代码都写在这个文件中日后想要维护肯定是不方便的,所以在写配置之前需要了解一些它的模块化提供一个模块,我们只需要在代码文件最后的位置写上 (provide 'package-name) 这样的代码即可。这里的 provide。可以理解为导出,后面是导出模块的名称。在需要引入模块时,只需要添加一行 (require 'basic)。但是与其他语言类似,有时候会出现找不到对应的模块,这里涉及到一个查找路径的问题。Emacs 中加载路径被保存在变量 load-path 中。该变量是一个list,我们可以将指定路径放入到这个变量中来添加用户定义代码的路径。load-path 中的目录顺序决定了 Emacs 搜索文件的优先级。如果多个目录中存在同名的 Lisp 文件,Emacs 会优先加载 load-path 中靠前的目录中的文件。因此,你可以通过调整 load-path 的顺序来控制加载的优先级。为了添加路径到 load-path 中,我们要了解一个新的函数, add-to-list。为什么这里我们不使用 push 或者其他之前学过的操作list的函数呢?最关键的一点是 add-to-list 具有去重的功能,能避免多次重复加入同一个路径。如果我们将用户代码放入到 ~/.emacs.d/lisp 这个目录中,我们可以使用下面的代码;; init.el (add-to-list 'load-path "~/.emacs.d/lisp")前面的知识介绍完了,现在我们新建 ~/.emacs.d/lisp/basic.el 文件,进行基础的配置。目前添加的代码主要是取消Emacs上的滚动条、菜单栏、工具栏、以及每次打开的开始界面。;; basic.el ;; 禁止菜单栏、工具栏、滚动条模式,禁止启动屏幕和文件对话框 (menu-bar-mode -1) (tool-bar-mode -1) (scroll-bar-mode -1) (setq inhibit-splash-screen t) ;; 禁止启动画面 ;; 显示行号 (setq display-line-numbers-type 'relative) ;;显示相对行号 (global-display-line-numbers-mode 1) (provide 'basic)然后在启动的时候使用它;; init.el (add-to-list 'load-path "~/.emacs.d/lisp") (require 'basic)重启Emacs就能看到具体的效果了使用包管理器要想它变的好看,最好的办法是加载开源大佬提供的主题。作为一个小菜鸡不太可能自己开发重型的功能,我们要做的这是将大佬提供的包整合到自己的配置中。所以我们先来介绍包和包管理器。这里的包我们可以理解为提供了某种功能的模块,有点类似与C/C++ 的静态库或者Java的类库。Emacs中的包管理主要通过 package.el 模块提供。它包含了模块的查找,下载,更新以及删除等操作。它的一些常用命令如下:M-x list-packages:列出所有可用的包,并进入包管理界面。M-x package-install:安装指定的包。M-x package-refresh-contents:刷新包列表,获取最新的包信息。M-x package-upgrade:更新所有已安装的包。M-x package-delete:删除指定的包。Emacs中默认的仓库是 Emacs 默认使用 MELPA(Milkypostman’s Emacs Lisp Package Archive)作为主要的包仓库。MELPA 提供了大量高质量的第三方包。除此之外,还有其他仓库,如:GNU ELPA:官方仓库,包含 Emacs 自带的包。MELPA Stable:提供稳定版本的包。NonGNU ELPA:包含一些非 GNU 的包。因为国内的网络环境,我们常常需要使用国内的源。这里我们创建一个新的文件 package 用来管理包。;; package-conf.el (require 'package) (setq package-enable-at-startup nil) (setq package-archives '(("gnu" . "https://mirrors.tuna.tsinghua.edu.cn/elpa/gnu/") ("nongnu" . "https://mirrors.tuna.tsinghua.edu.cn/elpa/nongnu/") ("melpa" . "https://mirrors.tuna.tsinghua.edu.cn/elpa/melpa/"))) (package-initialize) ;; You might already have this line (provide 'package-conf)这里我使用清华源,各位读者可以选择自己喜欢的源。上面的代码我们使用 package-enable-at-startup 来控制Emacs是否自动初始化package包管理器。这里为了更灵活我们禁止它自动初始化,改由手动初始化。我们可以在代码中使用类似于 (package-install 'package-name) 的方式来自动安装包,但是这里介绍更加高级的包管理器——use-package。如果使用Emacs原生的包管理器,那么就是先安装,然后想办法组织包配置的代码,这样将安装与配置分散起来了,不利于管理。使用use-package 可以方便的将它们组织起来。本质上use-package 提供了一系列的宏将包的安装和包的配置组合到一起,方便维护。而且它还提供了一些高级的特性方便我们灵活的控制各种配置生效的时间。我们可以使用如下语句进行安装;; package-conf.el (unless (package-installed-p 'use-package) (package-refresh-contents) (package-install 'use-package))它的基本语法如下:(use-package package-name :keyword1 value1 :keyword2 value2 ...)它的常用关键字如下::ensure: 确保包已安装。如果包未安装,use-package 会自动安装它,一般使用Emacs自带的包这里设置成nil,安装第三方的包,这里设置成t:init:在包加载之前执行的代码:config: 在包加载之后执行的代码:bind: 为包中的函数绑定快捷键:mode: 为特定文件类型启用包:hook: 在特定模式下启用包:defer: 延迟加载包,直到首次使用包:custom: 设置包的变量除了这些我们可以使用 :if 或 :when 关键字实现更复杂的条件加载。或者通过 :requires 关键字来指定包的依赖项配置主题说了这么多,我们使用 use-package 来安装一个主题来提高一下Emacs的颜值。这里我选用 doom-themes 包中的 doom-dracula 主题。关于ui部分的配置,我们都放在 ~/.emacs.d/lisp/init-ui.el 中;; init-ui.el (use-package doom-themes :ensure t :config (load-theme 'doom-dracula t)) (provide 'init-ui)我们在init.el 中加载init-ui之后,再次打开效果如下:设置字体我们在介绍文本属性的时候使用过face这个属性,Emacs中跟文字显示相关的属性都是face,它包括:字体、字号、颜色、背景。我们之前介绍了一系列的函数来处理字体字号,但是之前介绍的只能绑定到具体的文字上,默认的字体字号使用那些函数是无法设置的。我们可以使用 set-face-attribute 来设置字体属性。该函数的定义如下(set-face-attribute FACE FRAME &rest ARGS)参数face 表示设置的是哪个部分的字体属性,例如 default(默认字体) 、mode-line (状态栏字体)、region (选中区域字体)等。我们可以使用 M-x list-faces-display 来查看支持的face参数 frame 表示需要设置哪个窗口框架(通常用 nil 表示当前窗口或所有窗口)的字体属性。参数 ARGS 来设置具体的字体属性。以下属性可用于控制字体和样式:属性名功能描述示例值:family字体名称(需系统已安装)"Fira Code", "Consolas":height字号(以百分比或绝对点数表示,默认 100 = 10pt)120(12pt), 14(14pt):weight字重(如正常、加粗)'normal, 'bold:slant字体倾斜'normal, 'italic:width字体宽度(如压缩或扩展)'normal, 'condensed:underline下划线样式nil(无), t(实线):foreground前景色(文本颜色)"#FFFFFF", "red":background背景色"#333333":inherit继承其他 FACE 的属性'fixed-pitch这里我打算使用 Source Code Pro 字体,可以在init-ui.el 中这么设置(set-face-attribute 'default nil :family "Source Code Pro" :height 120)到此为止我们已经给Emacs做了基本的美化,日常使用也不那么碍眼了。
2025年03月17日
7 阅读
0 评论
0 点赞
2025-03-16
Emacs 折腾日记(十七)——文本属性
我们在上一篇中介绍了如何对文件中的文本进行操作,本篇主要来介绍关于文本的属性。是的,文本也有属性。这里的文本属性有点类似于Word中的文字属性,文本中对应的字符只是文本属性的一种,它还包括文本大小、字体、颜色等等内容。Emacs中的文本也是类似的。于符号的属性类似,文本的属性也是由键值对构成。名 字和值都可以是一个 lisp 对象,但是通常名字都是一个符号,这样可以用这个 符号来查找相应的属性值。复制文本通常都会复制相应的字符的文本属性,但是 也可以用相应的函数只复制文本字符串,比如 substring-no-properties、 insert-buffer-substring-no-properties、buffer-substring-no-properties。产生一个带属性的字符串可以用 propertize 函数。(propertize "abc" 'face 'bold) ;; ⇒ #("abc" 0 3 (face bold))这里我们使用 face 来设置它的字体为粗体。或者我们可以使用C-x C-f 任意的打开或者创建一个文本文件,在文件中输入(insert (propertize "abc" 'face 'bold))我们可以看到它会在当前光标后面插入一个粗体的 abc 字符串。需要注意的是,我们在*scratch* buffer 中是无法看到这个效果的。因为*scratch* buffer 中开启了font-lock-mode。正如它的名字表示的那样,它锁定了字体,它里面的字体属性都是实时计算出来的。在插入文本之后它的属性很快就被修改了。因为*scratch* buffer 本质上还是一个elisp的编程环境,它里面有关于elisp的语法高亮、自动对齐等特性。它会自动的修改输入的文本属性。我们可以使用 (font-lock-mode - 1) 来关闭这个mode,然后执行上述代码就可以看到具体的效果了。虽然文本属性的名字可以是任意的,但是一些名字是有特殊含义的。属性名含义category值必须是一个符号,这个符号的属性将作为这个字符的属性face控制文本的字体和颜色font-lock-face和 face 相似,可以作为 font-lock-mode 中静态文本的 facemouse-face当鼠标停在文本上时的文本 facefontified记录是否使用 font lock 标记了 facedisplay改变文本的显示方式,比如高、低、长短、宽窄,或者用图片代替help-echo鼠标停在文本上时显示的文字keymap光标或者鼠标在文本上时使用的按键映射local-map和 keymap 类似,通常只使用 keymapsyntax-table字符的语法表read-only不能修改文本,通过 stickness 来选择可插入的位置invisible不显示在屏幕上intangible把文本作为一个整体,光标不能进入field一个特殊标记,有相应的函数可以操作带这个标记的文本cursor(不知道具体用途)pointer修改鼠标停在文本上时的图像line-spacing新的一行的距离line-height本行的高度modification-hooks修改这个字符时调用的函数insert-in-front-hooks与 modification-hooks 相似,在字符前插入调用的函数insert-behind-hooks与 modification-hooks 相似,在字符后插入调用的函数point-entered当光标进入时调用的函数point-left当光标离开时调用的函数composition将多个字符显示为一个字形这些东西我觉得也不需要记住,在需要的时候查查文档就好了。但是我参考的教程把它列出来了,那么我也在这里列出来把。由于字符串和缓冲区都可以有文本属性(如果没有特别指定文本对象的属性,那么默认使用缓冲区定义的文本属性),所以下面的函数通常不提供特定参数就是检 查当前缓冲区的文本属性,如果提供文本对象,则是操作对应的文本属性。查看文本属性查看文本对象在某处的文本属性可以用 get-text-property 函数。(setq foo (propertize "abc" 'face 'bold)) ;; ⇒ #("abc" 0 3 (face bold)) (get-text-property 0 'face foo) ;; ⇒ bold这里有两个问题需要注意一下,首先我们使用 propertize 为abc设置了文本属性face的值为bold,也就是将字符串设置为粗体。但是其中的0 和 3 代表什么呢?这里的0和3代表的是采用这个属性的字符在字符串中的范围,上面的代码中,整个abc字符串都采用整个属性,所以它的范围是[0, 3) 这个区间。要验证这一点我们可以使用下列代码(setq foo (concat "abc" (propertize "cde" 'face 'bold))) ;; ⇒ #("abccde" 3 6 (face bold)) (insert foo) 我们插入foo发现,它会插入 "abccde" 这么几个字符串,但是只有 "cde" 三个是加粗的根据这个提示,很明显的,get-text-property 中输入的0代表的就是“abc”字符串第0个,也就是字符a的属性。get-char-property 和 get-text-property 相似,但是它是先查找 overlay 的 文本属性。overlay 是缓冲区文字在屏幕上的显示方式,它属于某个缓冲区,具 有起点和终点,也具有文本属性,可以修改缓冲区对应区域上文本的显示方式。get-text-property 是查找某个属性的值,用 text-properties-at 可以得到某 个位置上文本的所有属性。修改文本属性put-text-property 可以给文本对象添加一个属性。它也是需要传入一个范围值,例如我们在前面foo的基础上使用以下代码(put-text-property 0 3 'face 'italic foo)我们再针对 foo 执行插入操作,此时会发现 abc 这个子串变成斜体了。和 put-text-property 类似,add-text-properties 可以给文本对象添加一系列的属性。和 add-text-properties 不同,可以用 set-text-properties 直接设置文本属性列表。你可以用 (set-text-properties start end nil) 来除去 某个区间上的文本属性。也可以用 remove-text-properties 和 remove-list-of-text-properties 来除去某个区域的指定文本属性。这两个函数的属性列表参数只有名字起作用,值是被忽略的。以下的例子还是建立在之前的 foo 变量之上,此时它的值为 #("baccde" 0 3 (face italic) 3 6 (face bold))。也就是前三个字符是斜体,后三个是加粗(set-text-properties 0 1 nil foo) ;; 取消了 a 字符的文本属性 foo ;; ⇒ #("abccde" 1 3 (face italic) 3 6 (face bold)) (remove-text-properties 2 4 '(face nil) foo) foo ;; ⇒ #("abccde" 1 2 (face italic) 4 6 (face bold)) (remove-list-of-text-properties 4 6 '(face nil) foo) foo ;; ⇒ #("abccde" 1 2 (face italic))查找文本属性文本属性通常都是连成一个区域的,所以查找文本属性的函数是查找属性变化的 位置。这些函数一般都不作移动,只是返回查找到的位置。使用这些函数时最好 使用 LIMIT 参数,这样可以提高效率,因为有时一个属性直到缓冲区末尾也没 有变化,在这些文本中可能就是多余的。next-property-change 查找从当前位置起任意一个文本属性发生改变的位置。 next-single-property-change 查找指定的一个文本属性改变的位置。 next-char-property-change 把 overlay 的文本属性考虑在内查找属性发生改 变的位置。next-single-property-change 类似的查找指定的一个考虑 overlay 后文本属性改变的位置。这四个函数都对应有 previous- 开头的函数,用于查找当前位置之前文本属性改变的位置(setq foo (concat "abc" (propertize "edf" 'face 'bold) (propertize "hij" 'pointer 'hand))) ;; ⇒ #("abcdefhji" 3 6 (face italic) 6 9 (face bold)) (next-property-change 1 foo) ;; ⇒ 3 (next-single-property-change 1 'pointer foo) ;; ⇒ 6text-property-any 查找区域内第一个指定属性值为给定值的字符位置。 text-property-not-all 和它相反,查找区域内第一个指定属性值不是给定值的 字符位置。(text-property-any 0 9 'face 'bold foo) ;; ⇒ 3 (text-property-not-all 3 9 'face 'bold foo) ;; ⇒ 6
2025年03月16日
7 阅读
0 评论
0 点赞
1
2
3