{ "hasFoldAllCodeBlocks": false, "svgColor": "#6c757d", "en": false, "dark": false }

搜索

[{"content":"指针和动态内存管理是c语言学习中的重点和难点,本篇主要总结自己对指针的理解,深入浅出,言简意赅。\n指针的本质 用最简洁的话来描述指针的本质就是:一个存储了内存地址的变量。所以说指针变量和普通变量并没有本质的区别,只不过指针中存储的是一个内存地址,而普通变量存储的是具体的数值(其实内存地址也是个数值)。当然,如果仅仅是存值类型的差异,并不能给指针带来如此大的威力。指针厉害的地方在于,基于存储地址这一特性:指针可以访问存储在这个地址中的内容并修改;另外,可以通过给指针赋不同的地址来改变指针指向的变量。指针也支持四则、比较等运算,这更丰富了指针的用途。\n这听起来没什么了不起,毕竟知道了地址,支持查看和修改这个地址中的值,再理所当然了。没错,这就是计算机底层cpu和内存模型的工作方式,而c语言是最接近底层硬件的高级语言。所以说,c语言的精髓是指针,指针是c语言的一切。\n指针的表示 定义一个指针变量就是在变量名前面加上一个*,然后将一个变量的地址赋给指针变量。下面有一个详细的例子,相信看完对指针的理解会有一个质的提升。\n看例子前,让我们先了解一些符号的含义。*除了可以用来定义一个指针,单独用在指针变量名前面表示“解引”一个指针。\u0026ldquo;指针解引\u0026quot;的含义就是获取指针所指向的变量的值。\u0026amp;是取址符号,表示获取该符号后面的变量的地址。\n#include\u0026lt;stdio.h\u0026gt; #include\u0026lt;stdlib.h\u0026gt; int main () { int a = 2; int *ptr = \u0026amp;a; printf(\u0026quot;a的地址是:%p\\n\u0026quot;, \u0026amp;a); printf(\u0026quot;a的内容是:%d\\n\u0026quot;, a); printf(\u0026quot;ptr指针的地址是:%p\\n\u0026quot;, \u0026amp;ptr); // 指针变量自己所在的地址 printf(\u0026quot;ptr指针的内容是:%p\\n\u0026quot;, ptr); // a的地址 printf(\u0026quot;ptr指针所指向的内容是:%d\\n\u0026quot;, *ptr); // 指针指向的a的值 printf(\u0026quot;----------\\n\u0026quot;); *ptr = 5; // 修改指针指向的变量a的值 printf(\u0026quot;修改值后,a的地址是:%p\\n\u0026quot;, \u0026amp;a); printf(\u0026quot;修改值后,a的内容是:%d\\n\u0026quot;, a); printf(\u0026quot;修改值后,ptr指针的地址是:%p\\n\u0026quot;, \u0026amp;ptr); // 指针变量自己所在的地址 printf(\u0026quot;修改值后,ptr指针的内容是:%p\\n\u0026quot;, ptr); // 指针的指向未变:还是a的地址 printf(\u0026quot;修改值后,ptr指针所指向的内容是:%d\\n\u0026quot;, *ptr); // 指针指向的a的值,此时被修改了 printf(\u0026quot;----------\\n\u0026quot;); int b = 3; ptr = \u0026amp;b; // 修改指针的指向为变量b printf(\u0026quot;修改指向后,a的地址是:%p\\n\u0026quot;, \u0026amp;a); // 指针已经不指向a了 printf(\u0026quot;修改指向后,a的内容是:%d\\n\u0026quot;, a); // 指针已经不指向a了 printf(\u0026quot;修改指向后,b的地址是:%p\\n\u0026quot;, \u0026amp;b); printf(\u0026quot;修改指向后,b的内容是:%d\\n\u0026quot;, b); printf(\u0026quot;修改指向后,ptr指针的地址是:%p\\n\u0026quot;, \u0026amp;ptr); // 指针变量自己所在的地址,未修改 printf(\u0026quot;修改指向后,ptr指针的内容是:%p\\n\u0026quot;, ptr); // b的地址 printf(\u0026quot;修改指向后,ptr指针所指向的内容是:%d\\n\u0026quot;, *ptr); // 指针指向的b的值 printf(\u0026quot;----------\\n\u0026quot;); } 打印结果:\na的地址是:0x30981757c a的内容是:2 ptr指针的地址是:0x309817570 ptr指针的内容是:0x30981757c ptr指针所指向的内容是:2 ---------- 修改值后,a的地址是:0x30981757c 修改值后,a的内容是:5 修改值后,ptr指针的地址是:0x309817570 修改值后,ptr指针的内容是:0x30981757c 修改值后,ptr指针所指向的内容是:5 ---------- 修改指向后,a的地址是:0x30981757c 修改指向后,a的内容是:5 修改指向后,b的地址是:0x30981756c 修改指向后,b的内容是:3 修改指向后,ptr指针的地址是:0x309817570 修改指向后,ptr指针的内容是:0x30981756c 修改指向后,ptr指针所指向的内容是:3 ---------- 值得注意的是,例子中有一个“ptr指针的地址”,注意这是指针变量所在的地址,不要和指针所存储的地址混淆了。前面提到指针的本质也是一个变量,因此当然也可以把这个指针变量的地址赋值给另一个指针,得到的这个指针就叫“指针的指针”,再赋值一次,就是“指针的指针的指针”\u0026hellip; 依次类推,万变不离其宗。\n指针的用法 通常我们用*定义的指针叫做:指向非常量的指针。就像我们前面使用的那样,“指向非常量的指针”可以修改指针的指向(地址),也可以通过解引来修改指针指向的数据。但在一些特殊情况下,我们可能需要控制指针这两种操作的权限。这就需要引入\u0026quot;常量\u0026quot;的概念。c语言中,用const关键字定义一个常量。\n下面介绍三种和常量相关的指针:\n指向常量的指针: 可以修改指针的指向,但是不能修改指向的数据。 指向非常量的常量指针:不可以修改指针的指向,但可以修改指向的数据。 指向常量的常量指针:即不可以修改指针的指向,也不可以修改指向的数据。 要想理解上面的几种指针,我们要先理解“常量”这个概念:在计算机世界中,“常量”即意味着不可修改。因此,我们只需要关注常量这个词是用来 修饰指针,还是修饰指针所指向的那个对象就可以很好的区分。\n常量指针指针本身是不可修改的,即指针的值(所指向的地址)不可修改。因此,常量指针表示不可以修改指针的指向,即不能给指针赋值一个新的地址。\n指向常量和指向非常量说的是指针所指向的那个对象是否可修改。如果指针指向的那个对象本身是不可以修改的,自然也就不可以通过指针解引的方式修改它的数据。\n总结一下:\n指针类型 指针(的指向)是否可修改 指针指向的数据是否可修改 指向非常量的指针(普通指针) 是 是 指向常量的指针 是 否 指向非常量的常量指针 否 是 指向常量的常量指针 否 否 几种指针的定义:\n指向常量的指针:\nconst int a = 2; const int *ptr = \u0026amp;a; // 等价:int const *ptr = \u0026amp;a; 指向非常量的常量指针:\nint a = 2; int *const ptr = \u0026amp;a; 指向常量的常量指针:\nconst int a = 2; const int *const ptr = \u0026amp;a; 总结 掌握本质后,指针也并不难理解。下一篇会介绍一些指针在c语言中的具体应用,比如:数组,字符串、结构体和函数等。\n","date":"2022-06-26","permalink":"https://kinneyzhang.github.io/post/summary-of-c-pointer/","summary":"指针和动态内存管理是C语言学习中的重点和难点,本篇主要总结自己对指针的理解,深入浅出,言简意赅。 指针的本质 用最简洁的话来描述指针的本质就是:一个存储了内存地址的","title":"深入理解c指针"},{"content":"moinmoin 是一个基于python环境的wiki引擎程序。本篇主要简单记录部署流程和遇到的坑。\n概念介绍 部署之前先来了解几个概念:\nwsgi: 全称是 python web server gateway interface(python web服务器网关接口),它为python web应用 和 web服务器之间的通信提供了标准接口。本质是一个通信协议。wsgi 对 python 的意义就如同 servlets 对 java。\nuwsgi: uwsgi是一个是实现了wsgi协议的web服务器。\nnginx: nginx是一个开源的高性能的http和反向代理web服务器,支持负载均衡、高并发和高效处理静态文件。\n一个典型的python应用的部署方式是:http 请求发送到 nginx服务器,如果请求的是静态资源,nginx可以直接读取返回资源;如果是动态资源,就转交给 uwsgi服务器,uwsgi 和 python应用通信后返回资源给uwsgi,再返回给nginx,最后由nginx返回给浏览器。\n因此部署 moinwiki,我们需要做的就是安装和配置好 nginx 和 uwsgi。如何安装就不介绍了,主要贴一下两者的配置。\nmoinwiki 目录结构 moin源码下载后来后,我们需要重新组织一下需要用到的文件的目录结构,然后改变相关文件中变量路径,保证和实际的路径一致。\n需要关注的几个目录文件:\nwiki/data: 用户数据 wiki/underlay: 页面支撑 wiki/config/wikiconfig.py: wiki配置文件 wiki/server/moin.wsgi: 与wsgi通信的文件 moinmoin: moinwiki源码文件 moinmoin/web/static/htdocs: 主题静态文件 建议将上面的5个文件或目录拉出来放到单独的文件夹里,例如 mywiki。这样我们得到的新的目录结构为:\nmywiki/moinmoin/.. mywiki/data/.. mywiki/underlay/.. mywiki/wikiconfig.py mywiki/moin.wsgi mywiki/htdocs moinwiki 配置 接下来修改 wikiconfig.py 的配置:必须要设置的变量有 data_dir, data_underlay_dir, 通过这两个变量找到 data 和 underlay 目录的路径。其余变量根据需求设置,每个变量配置中都有详细的解释。\n重命名 moin.wsgi 为 moin_wsgi.py,目的是方便python执行该文件。修改 moin_wsgi.py 中,添加:sys.path.insert(0, '/path/to/wikiconfigdir') 和 sys.path.insert(0, '/path/to/moinsrc'), wikiconfigdir指的是配置文件(wikiconfig.py)的目录,moinsrc值得是moinwiki源码的目录,在我们的新的结构中就是同一个 mywiki 实例目录。如果你的结构和本文的示例不一致,修改为实际使用的目录就可以了。\n通过 moin_wsgi.py 的设置就可以让wsgi找到我们wiki的配置文件和源码目录,然后再从配置文件中找到对应的 data 和 underlay 目录。\n事实上,你可以以任意的结构组织 moinwiki 的目录,只需要在 wikiconfig.py 和 moin_wsgi.py 中配置好对应的路径,确保外围的 uwsgi.ini 文件可以找到 moin_wsgi.py,moin_wsgi.py 可以找到 wikiconfig.py。\nuwsgi 配置 在 mywiki 目录中创建 uwsgi.ini 文件 和 uwsgi目录。uwsgi.ini文件是uwsgi的配置文件,uwsgi文件夹用来存放uwsgi相关的等文件,比如日志等。\n[uwsgi] chmod-socket = 660 chdir = /path/to/mywiki # moinwiki 实例目录,修改实际使用的路径 wsgi-file = moin_wsgi.py # moinwiki和wsgi 通信的文件 master workers = 3 socket = /run/uwsgi/moin.sock # 和nginx中的配置一致 plugins = python max-requests = 200 harakiri = 30 daemonize = %(chdir)/uwsgi/uwsgi.log stats = %(chdir)/uwsgi/uwsgi.status pidfile = %(chdir)/uwsgi/uwsgi.pid die-on-term nginx 配置 建议在 nginx/conf.d 目录中单独建立 moin.conf 文件,该文件会自动被包含在 nginx 的主配置文件 nginx/nginx.conf 中\nupstream moin { server unix:/run/uwsgi/moin.sock; # 文件目录不存在时需要手动创建 } server { listen 8088; # 修改为实际使用的端口 server_name 10.19.33.136; # 修改为实际使用的服务器ip或域名 access_log /var/log/nginx/moin.access_log main; error_log /var/log/nginx/moin.error_log info; # 错误日志,对于问题调试很有帮助 location / { include uwsgi_params; uwsgi_pass moin; } location ~ ^/moin_static[0-9]+/(.*) { alias /path/to/mywiki/htdocs/$1; # /path/to/修改为实际目录 } location /favicon.ico { alias /path/to/mywiki/htdocs/favicon.ico; # /path/to/修改为实际目录 } } 运行 运行uwsgi: sudo uwsgi -d --ini uwsgi.ini 运行nginx: sudo systemctl start nginx 停止uwsgi: sudo pkill -f uwsgi -9 重启nginx: sudo systemctl restart nginx 检查uwsgi进程: ps aux | grep uwsgi 检查ngnix状态: sudo systemctl status nginx 每次修改了配置,需要重启 uwsgi 和 nginx。\n总结 官方文档:https://moinmo.in/ 。 遇到问题,多看日志。本例中,uwsgi的日志是 /path/to/mywiki/uwsgi/uwsgi.log;nginx的日志是 /var/log/nginx/moin.error_log。 日志中出现权限问题,除了是文件本身权限问题,检查一下相关目录是否已经创建。比如本例中的 /run/uwsgi/, /path/to/mywiki/uwsgi等,请务必检查已经创建。 ","date":"2022-06-23","permalink":"https://kinneyzhang.github.io/post/deploy-of-moinwiki/","summary":"MoinMoin 是一个基于Python环境的wiki引擎程序。本篇主要简单记录部署流程和遇到的坑。 概念介绍 部署之前先来了解几个概念: WSGI: 全称是 Python Web Server Gateway Interface(Pyt","title":"moinwiki 部署总结"},{"content":"以前写代码总有一种担忧:担心逻辑不够规范、组织结构不够规范、命名不够规范… 这种对规范的洁癖,浪费了我许多不必要的精力。比如链表的数据结构,我看过好几种不同的写法,便会纠结究竟哪一种更好,哪一种更合理?\n当我决定用c系统的学习算法的时候,我终于想明白:对于数据结构和算法的实现,很多时候,并不存在所谓的最合理的规范,更多的是不同的思路和代码风格。在保证逻辑没有错误和遗漏的情况下,任何一种实现方法都是可以的。个人偏好形成风格,把握算法本质和实现的功能才是最重要的。在没有了这种心理负担后,我发现自己愈发能够看清数据结构和算法的本质,敲起代码来也顺畅了许多。\n代码 链表、队列和栈所有代码实现查看 github 。\n概述 我一直觉得,在学习具体内容前,对一块知识有个整体的认识是很有必要的。首先要了解的概念是数据结构。数据结构主要分为 逻辑结构 和 物理结构 。逻辑结构指 数据对象中数据元素的相互关系;物理结构指 数据逻辑结构在计算机中的存储方式。\n常见的逻辑结构有:集合、线性、树形、图形结构。常见的物理结构有:顺序、链式存储结构。数据结构在逻辑和存储结构上的差异决定了它们适合什么样的功能场景。一种结构不擅长的场景,可能另一种结构更擅长。开销大操作,换一种结构表示可能会有更低的时间和空间复杂度。因此,想搞清楚什么场景下使用什么数据结构,了解每种结构的本质和优缺点是第一步。\n当然了解是不够,也得会用计算机语言来实现并使用这些数据结构来实现具体功能,这便是算法。算法就是解决问题的思路。\n从直觉来说,我们用程序解决问题的过程和学习的过程是相反的。学习时,我们先掌握各种数据结构,然后再去了解复杂场景下的算法实现。而解决问题的过程是,脑子里先有一个步骤思路,这是算法;然后再考虑如何在程序中存储数据来实现算法,这是数据结构。\n用哪种程序语言实现都可以,并不影响概念的理解。我选择c,因为c在高级语言中更接近计算机底层,即你可以在使用c的过程中感知到内存的分配和释放。\n链表 第一篇,总结一下基础的数据结构:链表、队列和栈。链表、队列和栈都是线性的链式存储结构。我们可以把队列和栈理解为特殊的链表,因此核心的算法实现只有链表。\n无论是c的结构体,还是c++, java的类,链表的实现都要依赖两个基本结构:节点和链表结构本身。 链表由节点组成,节点包含数据域和指针域,数据域存放节点数据,指针域存储指向另一个节点的指针。链表包含两个指针,分别指向头尾节点。用c结构体表示如下:\ntypedef struct _node { void* data; struct _node* next; } node; typedef struct _linkedlist { node* head; node* tail; } linkedlist; 定义了数据的存储方式,接下来就是定义操作数据的方法。基础的方法无非就是:构建链表、查询、插入、删除节点等操作。定义的方法如下:\ntypedef void(*print)(void*); typedef int(*compare)(void*, void*); void initlist(linkedlist* list); // main int length(linkedlist* list); // main bool isempty(linkedlist* list); // main void printlinkedlist(linkedlist* list, print print); // main void addhead(linkedlist* list, void* data); void addtail(linkedlist* list, void* data); void buildlinkedlist(linkedlist* list, void* arr[], int n); node* getnode(linkedlist* list, compare compare, void* data); // main node* getnthnode(linkedlist* list, int nth); // main void insertnode(linkedlist* list, node* node, void* newdata, int flag); // main void insertnodebefore(linkedlist* list, node* node, void* data); void insertnodeafter(linkedlist* list, node* node, void* data); void insertbydata(linkedlist* list, compare compare, void* data, void* newdata, int flag); void insertbydatabefore(linkedlist* list, compare compare, void* data, void* newdata); void insertbydataafter(linkedlist* list, compare compare, void* data, void* newdata); void insertbynth(linkedlist* list, int nth, void* data); void* deletenode(linkedlist* list, node* node); // main void* deletebynth(linkedlist* list, int nth); void deletebydata(linkedlist* list, compare compare, void* data); void updatenode(node* node, void* data); // main void updatebynth(linkedlist* list, int nth, void* newdata); void updatebydata(linkedlist* list, compare compare, void* data, void* newdata); 开头定义了两个函数指针,用于数据比较和链表打印。注释了\u0026quot;main\u0026quot;的是核心方法,其余方法都可以通过复用这些核心方法来实现。\n其他特殊的链表,可以用于一些特殊的场景:\n双链表:每个节点都有前驱和后继两个指针。 循环链表:尾节点指向头节点。 注意点 对初学者来说,裸敲一遍链表代码通常会遇到很多bug。有bug不代表你对这个数据结构的原理和操作不熟悉,但能够说明缺少些代码实现的经验,这些经验就是在不断的敲代码和调试bug中积累的。所以,不要怕遇到bug,每一个bug都是进步的机会。下面我总结了自己的经验:\n1.每个节点(node)的数据和指针都要被赋值:这一点尤其是在双向链表的实现中,由于每个节点都有前驱和后继指针,在节点操作的过程中容易遗漏。\n2.边界条件考虑:链表算法实现最容易犯的错误是对头尾节点的考虑不全面。有的实现中会额外增加一个头部的哨兵节点来排除一些边界情况。我的实现中就用了最原始的链表结构,因此要考虑边界情况。其实总结起来也并不复杂:如果链表的头尾节点发生了变化,需要更新链表的头尾指针。\n一个典型的例子是删除节点的操作,我借此来讲解如何分析。首先我们简单的思考一下,可以得出结论:\n删除头节点,需要更新头指针;删除尾节点,需要更新尾指针;删除中间节点,不需要更新链表的头尾指针。 删除头节点,不需要更新相关节点的next指针;删除中间节点和尾节点都需要更新前一个节点的next指针,单链表需要遍历才能拿的这个前驱节点。 只有一个节点,且删除的就是该节点时,头尾指针要置空。 综合上面的考虑,合并一下逻辑,代码可以分为两块:删除的就是头节点 和 删除的是非头节点。其中删除头节点又分为 只有一个节点 和 多个节点的情况;删除非头节点分为:删除的是 中间节点 和 尾节点 两种情况。代码如下:\nvoid* deletenode(linkedlist* list, node* node) { if (node != null) { node* curr = list-\u0026gt;head; if (curr == node) { if (curr-\u0026gt;next == null) { list-\u0026gt;head = list-\u0026gt;tail = null; } else { list-\u0026gt;head = curr-\u0026gt;next; } } else { while (curr != null \u0026amp;\u0026amp; curr-\u0026gt;next != node) { curr = curr-\u0026gt;next; } if (curr != null) { if (node == list-\u0026gt;tail) { list-\u0026gt;tail = curr; } curr-\u0026gt;next = node-\u0026gt;next; } } void* data = node-\u0026gt;data; free(node); return data; } return null; } 同时,删除节点的最后要释放掉内存。\n3.插入节点时,避免丢失剩余节点:实现插入节点时,新手容易犯的错误就是提前断开链表节点的next指针,导致丢失了后面的节点。解决方法是:先将要插入的节点的next指针指到链表上,再将链表上的节点的next指向插入的节点。\n队列和栈 队列和栈的结构与链表一致,只是数据操作的方式有特殊的规定。因此可以复用链表的方法。\n队列:先进先出。入队列相当于从链尾插入一个节点,出队列相当于从链头删除一个节点。 栈:后进先出。入栈相当于从链尾插入一个节点,出栈相当于从链尾删除一个节点。 队列和链表的结构、方法定义如下:\n// 队列 #include \u0026quot;linkedlist.h\u0026quot; typedef linkedlist queue; void initqueue (queue* queue); bool isqueueempty (queue* queue); int queuelength (queue* queue); void enqueue (queue* queue, void* data); void* dequeue (queue* queue); void printqueue (queue* queue, print print); // 栈 #include \u0026quot;linkedlist.h\u0026quot; typedef linkedlist stack; void initstack (stack* stack); bool isstackempty (stack* stack); int stacklength (stack* stack); void push (stack* stack, void* data); void* pop (stack* stack); void* peek (stack* stack); void printstack (stack* stack, print print); ","date":"2022-04-08","permalink":"https://kinneyzhang.github.io/post/c-linkedlist-queue-stack/","summary":"以前写代码总有一种担忧:担心逻辑不够规范、组织结构不够规范、命名不够规范… 这种对规范的洁癖,浪费了我许多不必要的精力。比如链表的数据结构,我看过好几种不同的写法","title":"c-algorithm: 链表、队列和栈"},{"content":" 去大理是临时起意。\n从南京坐飞机到昆明,随后高铁到大理。13日晚上到时,已经是8点多。古城旁客栈的老板说:大理古城晚上逛逛就行了,看看夜景,白天没什么人。我放完行李后,便徒步到古城。\n夜晚的古城,各色店铺装饰着霓虹,氛围与南京夫子庙相差无几。不同的是,各具特色的小酒馆传来舒缓悦耳的民谣,门口有品种各异的狗狗慵懒惬意于歌声中,望着往来的游客。走在古城的街道上,能够感受到商业文明给城墙内的世界画上了浓墨重彩的一笔。这些变化,是游客想象中现代古城该有的模样,也是当地人们习以为常的生活方式,虽然这个时间可能并不长。逛着逛着累了,觉得些许无趣。\n回到客栈,已是晚上10点多。古城旁的客栈大多便宜,环境也不错。我住的这个颇有设计感。从门口进去,越过前台,映入眼帘的是鹅暖石铺满的地面,鹅暖石上是一个个圆形石阶,形成道路。正前方是两层落地玻璃的空间。一层有厨具餐桌,是老板和朋友喝酒吃饭的地方;二层摆上茶具茶几,可饮茶休息。我住的房间右转上楼。\n第二天,去古城吃午饭。从\"洱海东门\"穿过长长的拱道,看到的是与昨晚差别不大的各种店铺。只是太阳光下的房屋比霓虹光下的建筑要自然、壮观许多。抬眼,远处的苍山巍峨壮丽,似远又近;抬头,湛蓝的天空,干净明亮。古城,苍山,天空交相辉映,这是夜间所没有的景色。我一边拍着照,一边寻觅美食...\n下午去才村、龙龛湿地公园,湿地公园位于海西的生态长廊中。环海的长廊只允许自行车和行人进入,我租了一辆自行车,绕着洱海骑行。墨绿的浮萍,金黄的芦苇,枫红的水杉,深蓝的湖水与碧蓝的天空,还有成群的西伯利亚红嘴鸥漫天飞舞,眼前景色美好,感觉时光很慢。去湿地公园的车上,司机师傅和我说:“为什么不去网红s弯,才村那边就是一码头,没什么好玩的。”我说后面再去。我后面也没有再去。因为所谓的网红s弯,这里随处可见,随手一拍都是不输s弯的画面。好的风景从来不需要被人定义,好的风景可以赋予人以意义。回到古城已是傍晚。“再回首”是一家位于古城一隅的小吃店,我点了凉鸡米线、卤鸡皮和酸角汁,真的好吃!\n第三天的上午,又逛了逛古城,又有不一样的感受。中学门口,有穿着校服的学生,接送学生的家长;石板街上,有开三轮的送水工,骑电动车的外卖小哥;稍宽的道路上,有通勤的人们,有日常的生活。我突然意识到,大理古城在500年后的今天,仍兢兢业业的扮演着自己的角色:它承载着当地人的日常生活,只不过时间留下痕迹后,让它多了许多游客。想象几百年前,这里也有书塾学生,也有店铺客栈,或许也有送外卖的小二和各自的通勤劳作。古时人,穿着古时的衣服,过着古时的生活;现代人,穿着现代的衣服,也有现代的工作。这种想象,就像看一城的空间,在百年历史间的无数时刻的横截面,真是有趣!保护一座古城最好方式,不是让它束之高阁,仅是成为游客的聚集地;而是仍然发挥效用与价值,让时间继续在砖墙上雕琢\u0026hellip;\n走进一间书店,买了陈春城的《夜晚的潜水艇》,这本书的文字很符合古城的气质。我喜欢这种“古中带新”的气质。\n下午到了环海东路旁的客栈,准备呆一晚。客栈不便宜,是这几天最贵的。走到期待的海景阳台,看到期待的全景苍山洱海,楼顶有氛围满分的泳池和帐篷。全部期待的事物都有了,却没有了期待的心情。“环海东路”就是洱海东边的环海公路,在公路旁有着各种村庄、古镇和客栈。我住的地方,处在前不着村,后不着镇的位置。若是与人同行,这种偏安一隅的安静或许也是种惬意的体验。一个人,难免感到孤寂。眼前的景色越美,越是感到孤寂,只能用图片视频和好友分享,聊以慰藉。幸得三两好友。\n到双廊的时候,客栈小哥开电动三轮来接我。古镇内部道路崎岖复杂,来的时候看到三两游客拖着行李走在坑洼不平的小路上,很是费劲。更觉得自己幸运。\n在双廊住了两晚,这不是原来的计划,原本是打算第二天去挖色的。原因也是奇葩,只是因为我太喜欢这个客栈了。虽然没有海东客栈那么宽阔的苍山洱海的视野,但房间的设计布局我非常喜欢。两面的落地玻璃,洱海近在眼前,白天阳光透进来,暖洋洋的,可以躺在沙发上看书,很是惬意。事实上,第二天我正是这么度过的。\n双廊比古城安静,比海东热闹,这种恰到好处的氛围我也很是喜欢。这里有白族村落,可以吃白族特色菜,听白族歌曲;有许多设计地很好看的餐厅和小酒馆;可以步行在洱海边,可以坐邮轮到不远处的南诏风情岛玩(虽然我没去)。但我最喜欢的还是躺在沙发上看书。\n《夜晚的潜水艇》中有一篇《竹峰寺》。主人公要藏一把钥匙,钥匙是自己即将消失的老宅的钥匙,把钥匙藏起来仿佛将与老宅相关的周遭和记忆一起封存。\u0026ldquo;藏\u0026quot;的意境在这本短篇集中很多地方可以看到,而我来到这个地方又何尝不是因为\u0026quot;藏\u0026rdquo;。\u0026ldquo;藏\u0026quot;虽然不能解决实际问题,但给心灵暂时找到了休息之所,然后便可踏实的去面对现实。这大概便是这次旅行的意义。\n离开时,客栈小哥又开电动三轮送我到方便打车的路边。4天旅行,去的地方不多,但内心温暖富足。我还会再来的,大理。\n","date":"2021-12-22","permalink":"https://kinneyzhang.github.io/post/trip-to-dali/","summary":"\u003cstyle\u003e\n.gk-container {\n column-count: 2;\n column-gap: 20px;\n}\n@media screen and (min-width: 600px) {\n.gk-container2 {\n \u003c!-- column-count: 2; --\u003e\n \u003c!-- column-gap: .5em; --\u003e\n \u003c!-- padding-left: 1em; --\u003e\n \u003c!-- padding-right: 1em; --\u003e\n text-align: center;\n}\n\n.gk-img {\n \u003c!-- margin: 0 .2em 0 0; --\u003e\n border: 1px solid #aaa;\n border-radius: .2em;\n padding: .2em;\n display: inline;\n width: 45%;\n}}\n\n@media screen and (max-width: 600px) {\n.gk-container2 {\n \u003c!-- column-count: 0; --\u003e\n \u003c!-- column-gap: 0; --\u003e\n \u003c!-- padding-left: .6em; --\u003e\n \u003c!-- padding-right: 1.2em; --\u003e\n text-align: center;\n}\n.gk-img {\n \u003c!-- margin: .5em 1em 0 .5em; --\u003e\n border: 1px solid #aaa;\n border-radius: .2em;\n padding: .2em;\n \u003c!-- display: inline; --\u003e\n width: 100%;\n}}\n\n.gk-img1 {\n border:1px solid #ccc; \n padding: .2em;\n border-radius: .2em;\n}\n\u003c/style\u003e\n\u003cimg class=\"gk-img1\" src=\"/image/gucheng.jpg\" width=\"100%\"/\u003e\n\u003cp\u003e去大理是临时起意。\u003c/p\u003e","title":"大理之行简记"},{"content":" 文档目录结构\n1 介绍 1.1 什么是meteor? 1.2 快速入门 1.3 meteor资源 1.4 什么是meteor指南? 1.5 指南开发 2 代码风格 2.1 风格一致的好处 2.2 javascript风格指南 2.3 用eslint检查你的代码 2.4 meteor编码风格 3 应用程序结构 3.1 通用javascript 3.2 文件结构 3.3 默认的文件加载顺序 3.4 拆分成多个应用程序 4 迁移到 meteor 2.4 4.1 从2.3以前的版本迁移? 1 介绍 这是使用 meteor 的指南,它是一个开发现代网络和移动应用的全栈式 javascript 平台。\n1.1 什么是meteor? meteor 是一个用于开发现代网络和移动应用的全栈 javascript平台。meteor 包括一套用于构建连接客户端反应式应用程序的关键技术、一个构建工具、以及来自 node.js 和一般 javascript社区的一套精心策划的软件包。\nmeteor 允许你用一种语言,即 javascript,在所有环境中进行开发:应用服务器、网络浏览器和移动设备。 meteor 使用线上数据,这意味着服务器发送数据,而不是html,然后由客户端进行渲染。 meteor 拥抱生态系统,以一种谨慎和深思熟虑的方式将极其活跃的javascript社区的最佳部分带给你。 meteor 提供了全堆栈的反应性,允许你的用户界面以最小的开发工作量无缝地反映世界的真实状态。 1.2 快速入门 按照我们文档中的步骤安装最新的官方 meteor版本。\n一旦你安装了 meteor,打开一个新的终端窗口并创建一个项目:\nmeteor create myapp 在本地运行它:\ncd myapp meteor npm install meteor # meteor server running on: http://localhost:3000/ meteor 捆绑了 npm,所以你可以输入 meteor npm 而不用担心自己安装。如果你喜欢,你也可以使用全局安装的 npm 来管理你的软件包。\n1.3 meteor资源 从 教程页面 开始着手使用 meteor。 meteor examples 里有一系列使用 meteor 的例子。你也可以将你的例子提交上去。 一旦你熟悉了基础知识,meteor指南 涵盖了关于如何在更大规模的应用中使用 meteor 的中间材料。 访问 meteor论坛 来宣布项目,获得帮助,谈论社区,或者讨论对核心代码的修改。 meteor slack社区 是询问(和回答!)技术问题的最佳场所,同时也可以认识 meteor 开发者。 atmosphere 是专门为 meteor 设计的社区包的代码仓库。 1.4 什么是meteor指南? 这是一组文章,概述了使用 meteor 平台开发应用程序的最佳实践的意见。我们的目标是涵盖所有现代 web和移动应用开发中常见的模式,所以这里记录的许多概念不一定是针对 meteor 的,可以应用于任何以现代交互式用户界面为重点的应用程序。\nmeteor指南中的任何内容都不是建立 meteor应用的必要条件 \u0026ndash; 你当然可以用与指南中的原则和模式相矛盾的方式来使用这个平台。然而,该指南是一个记录最佳实践和社区惯例的尝试,所以我们希望 meteor社区的大多数人能够从采用这里记录的实践中受益。\nmeteor平台的api可以在 docs网站 上找到,你也可以在 atmosphere 上浏览社区包。\n1.目标受众\n本指南针对的是对 javascript、meteor平台和一般的web开发有一定了解的中级开发者。如果你刚开始接触 meteor,我们建议从教程开始。\n2.应用实例\n如果你想看一些例子,我们有一个专门的资源库,里面有几个由社区提供的例子,展示了许多概念,在用 meteor 实现你的应用程序时可以使用。要了解更多,你可以看这里。\n1.5 指南开发 1.贡献\n持续的 meteor指南开发是在 github 上公开进行的。我们鼓励 pull requests 和 issues 来讨论任何可以对内容进行修改的问题。我们希望保持我们的过程是公开和诚实的,这将使我们清楚地知道我们计划在指南中包括什么,以及在未来的 meteor 版本中会有什么变化。\n2.指南目标\n指南中做出的决定和实践必然是有取舍的。某些最佳实践将被强调,而其他有效的方法则被忽略。我们的目标是在重大决策上达成与社区的共识,但在开发你的应用程序时,总会有其他方法来解决问题。我们相信,重要的是,要知道解决一个问题的\u0026quot;标准\u0026quot;方法是什么,然后再进行其他选择。如果另一种方法被证明是优越的,那么它就应该进入本指南的未来版本。\n指南的一个重要功能是塑造 meteor 平台的未来发展。通过记录最佳实践,该指南指出了平台中可以更好、更容易或更高性能的领域,因此该指南将被用来关注许多未来的平台选择。\n同样地,指南中强调的平台中的差距往往可以由社区包来填补;我们希望,如果你看到了一个可以通过编写包来改善 meteor 工作流程的机会,你应该抓住它。如果你不确定如何最好地设计或架构你的包,请在论坛上展开讨论。\n2 代码风格 推荐的代码风格指南。\n读完这篇文章,你会知道:\n为什么拥有一致的代码风格是个好主意。 我们为 javascript 代码推荐哪种风格指南。 如何设置 eslint 来自动检查代码风格。 对 meteor 特定模式的风格建议,如方法、发布等。 2.1 风格一致的好处 多年来,开发者们花了无数的时间来争论单引号和双引号、在哪里放括号、要打多少空格、以及其他各种表面上的代码风格问题。这些问题充其量只是与代码质量有切身关系,但却很容易产生意见,因为它们很直观。\n虽然对字符串使用单引号还是双引号对你的代码库并不一定重要,但一旦做出决定,并在整个组织中保持一致,会有很大的好处。这些好处也适用于整个 meteor 和 javascript 开发社区。\n1.易于阅读的代码\n就像你不会一个字一个字地读英语句子一样,你也不会一个符号一个符号地读代码。大多数情况下,你只是看某个表达式的形状,或者它在编辑器中的突出显示方式,然后假设它是做什么的。如果每段代码的风格都是一致的,那就可以确保看起来相同的代码位实际上就是相同的 \u0026ndash; 没有任何隐藏的标点符号或你意想不到的麻烦,因此你可以专注于理解逻辑而不是符号。这方面的一个例子是缩进 \u0026ndash; 虽然在 javascript 中,缩进是没有意义的,但是让你所有的代码都有一致的缩进是很有帮助的,因为这样你就不需要详细地阅读所有的括号来了解它想表达什么。\n// this code is misleading because it looks like both statements // are inside the conditional. if (condition) firststatement(); secondstatement(); // much clearer! if (condition) { firststatement(); } secondstatement(); 2.自动检查错误\n拥有一致的风格意味着更容易采用标准工具进行错误检查。例如,如果你采用了一个惯例,即你必须始终使用 let 或 const 而不是 var,那么你现在就可以使用一个工具来确保你的所有变量都以你所期望的方式进行范围界定。这意味着你可以避免变量产生异常表现的bug。另外,通过强制要求所有的变量在使用前都要声明,你可以在运行任何代码前就发现打字错误。\n3.更深层次的理解\n要想一下子学会一门编程语言的所有知识是很难的。例如,刚接触到 javascript 的程序员经常会在 var 关键字和函数范围上纠结。使用社区推荐的具有自动提示功能的编码风格可以主动警告你这些陷阱。这意味着你可以直接进入编码,而不必提前了解 javascript 的所有边缘情况。\n当你写更多的代码并遇到推荐的风格规则时,你可以把它作为一个机会来学习更多关于你的编程语言和不同的人喜欢如何使用它。\n2.2 javascript风格指南 基于多方面的原因,我们坚信 javascript 是 meteor 中构建 web 应用的最佳语言。javascript 在不断地改进,而围绕 es2015 的标准确实将 javascript社区聚集在一起。下面是我们关于今天如何在你的应用程序中使用 es2015 javascript 的建议。\n从 javascript 重构到 es2015 的一个例子。\n1.使用 ecmascript 包\necmascript 是每个浏览器的 javascript 实现所依据的语言标准,已经转为每年发布一次标准。最新的完整标准是 es2015,它包括了一些期待已久的、对 javascript 语言非常重要的改进。meteor 的 ecmascript 包使用流行的 babel编译器 将这个标准编译成所有浏览器都能理解的普通 javascript。它完全向后兼容\u0026quot;常规\u0026quot;的 javascript,所以如果你不想使用任何新的功能,就不必使用。我们花了很多精力使先进的浏览器功能(如源码地图)与这个包很好得配合,这样你就可以用你喜欢的开发工具调试你的代码,而不必看到任何编译后的输出。\necmascript 包默认包含在所有新的应用程序和软件包中,并自动编译所有带有 .js 扩展名的文件。请看 ecmascript 包所支持的所有es2015功能的列表。\n为了获得完整的体验,你还应该使用 es5-shim 包,它默认包含在所有新的应用程序中。这意味着你可以依赖 array#foreach 等运行时特性,而不必担心哪些浏览器支持它们。\n本指南和未来的 meteor 教程中的所有代码样本将使用所有新的 es2015 特性。你也可以在 meteor 博客上阅读更多关于 es2015 以及如何开始使用它的信息:\n开始使用 es2015 和 meteor 为 es2015 设置 sublime text es2015 的开销如何? 2.遵循javascript风格指南\n我们建议选择并坚持使用一个 javascript 风格指南,并通过工具强制执行。我们推荐的一个流行的选择是 airbnb风格指南 与 es6扩展(以及可选的 react 扩展)。\n2.3 用eslint检查你的代码 \u0026ldquo;代码提示\u0026quot;是自动检查你的代码是否有常见错误或风格问题的过程。例如,eslint可以确定你是否在一个变量名称中出现了错别字,或者你的代码的某些部分由于if条件写得不好而无法执行。\n我们推荐使用 airbnb eslint 配置,它可以验证 airbnb 风格指南。\n下面,你可以找到在许多不同的开发阶段设置自动刷新的指示。一般来说,你希望尽可能频繁地运行 linter,因为它是一种自动识别错别字和小错误的方法。\n1.安装和运行eslint\n为了在你的应用程序中设置 eslint,你可以安装以下 npm 包:\nmeteor npm install --save-dev babel-eslint eslint-config-airbnb eslint-plugin-import eslint-plugin-meteor eslint-plugin-react eslint-plugin-jsx-a11y eslint-import-resolver-meteor eslint @meteorjs/eslint-config-meteor meteor 捆绑了 npm,所以你可以输入 meteor npm 而不用担心自己安装它。如果你喜欢,你也可以使用全局安装的 npm 命令。\n你也可以在 package.json 中添加 eslintconfig 部分,以指定你想使用的 airbnb 的配置,并启用 eslint-plugin-meteor。你也可以设置任何你想改变的额外规则,以及添加一个 lint npm 命令:\n{ ... \u0026quot;scripts\u0026quot;: { \u0026quot;lint\u0026quot;: \u0026quot;eslint .\u0026quot;, \u0026quot;pretest\u0026quot;: \u0026quot;npm run lint --silent\u0026quot; }, \u0026quot;eslintconfig\u0026quot;: { \u0026quot;extends\u0026quot;: \u0026quot;@meteorjs/eslint-config-meteor\u0026quot; } } 要运行 linter,你现在可以输入:\nmeteor npm run lint 更多细节,请阅读 eslint 网站上的 入门指南。\n2.与你的编辑器集成\nlinting 是发现你的代码中潜在错误的最快方法。运行 linter 通常比运行你的应用程序或单元测试要快,所以一直运行它是个好主意。在你的编辑器中设置 linting,一开始似乎很烦人,因为当你保存格式不好的代码时,它会经常抱怨。但随着时间的推移,你会对首先写出格式良好的代码形成肌肉记忆。这里有一些在不同编辑器中设置 eslint 的方法:\n1) sublime text : 略\n2) atom : 略\n3) webstorm : 略\n4) visual studio code :\n在 vscode 中使用 eslint 需要安装第三方的 eslint 扩展。为了安装该扩展,请遵循以下步骤:\n启动 vscode,通过输入 ctrl+p打开 快速打开 菜单。 在命令窗口中粘贴 ext install vscode-eslint,然后按回车键。 重新启动 vscode。 2.4 meteor编码风格 上面的部分谈到了一般的 javascript 代码 - 你可以在任何 javascript 应用程序中应用它,而不仅仅是 meteor 应用程序。然而,有一些是meteor特有的风格问题,特别是如何命名和组织你的应用程序的所有不同组件。\n1.集合\n集合应该被命名为复数名词,用 pascalcase。数据库中的集合名称(集合构造函数的第一个参数)应该与 javascript 符号的名称相同。\n// defining a collection lists = new mongo.collection('lists'); 数据库中的字段应该像你的javascript变量名一样使用camelcased。\n// inserting a document with camelcased field names widgets.insert({ myfieldname: 'hello, world!', otherfieldname: 'goodbye.' }); 2.方法和发布 方法和发布的名称应该用camelcase,并与它们所在的模块命名间隔:\n// in imports/api/todos/methods.js updatetext = new validatedmethod({ name: 'todos.updatetext', // ... }); 注意,这个代码样本使用了 methods 文章中推荐的 validatedmethod 包。如果你没有使用那个包,你可以使用这个名字作为传递给 meteor.methods 的属性。\n下面是这个命名规则应用于一个 publication 时的样子:\n// naming a publication meteor.publish('lists.public', function listspublic() { // ... }); 3.文件、导出和包\n你应该使用es2015的导入和导出功能来管理你的代码。这将让你更好地了解你的代码的不同部分之间的依赖关系,并且能够帮助你导航到依赖关系的源代码。\n你的应用程序中的每个文件应该代表一个逻辑模块。避免拥有导出各种不相关的函数和符号的万能工具模块。通常,这可能意味着每个文件有一个类、ui组件或集合是好的,但也有些例外,例如:你有在该文件之外不会使用的ui组件和小的子组件。\n当一个文件代表一个单一的类或ui组件时,该文件应该与它所定义的内容命名相同,并使用相同的首字母。因此,如果你有一个文件导出了类:\nexport default class clickcounter { ... } 这个类应该被定义在一个叫做 clickcounter.js 的文件中。当你导入它时,它看起来会像这样:\nimport clickcounter from './clickcounter.js'; 注意,导入使用相对路径,并在文件名的末尾包括文件扩展名。\n对于 atmosphere 包来说,由于旧的 pre-1.3 api.export 语法允许每个包有一个以上的 export,你往往会看到符号使用非默认的 export。例如:\n// you'll need to destructure here, as meteor could export more symbols import { meteor } from 'meteor/meteor'; // this will not work import meteor from 'meteor/meteor'; 4.模板和组件\n由于 spacebars 模板总是全局性的,不能作为模块导入和导出,而且需要在整个应用程序中拥有完全独特的名称,我们建议用命名空间的完整路径来命名您的 blaze 模板,并用下划线隔开。在这种情况下,下划线是一个很好的选择,因为这样你就可以在 javascript 中把模板的名字输入为一个符号。\n\u0026lt;template name=\u0026quot;lists_show\u0026quot;\u0026gt; ... \u0026lt;/template\u0026gt; 如果这个模板是一个加载服务器数据和访问路由器的\u0026quot;智能\u0026quot;组件,请在名称后加上 _page:\n\u0026lt;template name=\u0026quot;lists_show_page\u0026quot;\u0026gt; ... \u0026lt;/template\u0026gt; 通常,当你处理模板或ui组件时,你会有几个紧密配合的文件需要管理。它们可能是两个或多个html、css和javascript文件。在这种情况下,我们建议将这些文件放在同一目录下,并使用相同的名称:\n# the lists_show template from the todos example app has 3 files: show.html show.js show.less 整个目录或路径应表明这些模板与 lists 模块有关,所以没有必要在文件名中重现这一信息。请阅读下面关于目录结构的更多信息。\n如果你在react中编写你的ui,你不需要使用下划线分割的名字,因为你可以使用 javascript 模块系统导入和导出你的组件。\n3 应用程序结构 如何用 es2015 模块构造你的 meteor 应用,将代码运送到客户端和服务器,并将你的代码分成多个应用。\n读完这篇文章,你会知道:\n在文件结构方面,meteor应用程序与其他类型的应用程序相比是怎样的。 如何组织你的应用程序,包括小型和大型的应用程序。 如何以一致和可维护的方式格式化你的代码和命名你的应用程序的各个部分。 3.1 通用javascript meteor 是一个用于构建 javascript 应用程序的全栈框架。这意味着 meteor 应用程序与大多数应用程序不同,因为它包括在客户端、web浏览器 或 cordova 移动应用程序内运行的代码,还有在服务器、node.js 容器,以及两种环境中同时运行的通用代码。meteor构建工具 允许你指定哪些 javascript 代码(包括任何支持的ui模板、css规则和静态文件)使用 es2015 导入和导出功能 及 meteor构建系统 默认文件的加载顺序 规则的组合,来在每个环境中运行。\n1.es2015模块\n从1.3版本开始,meteor 已经完全支持 es2015模块 了。es2015 模块标准是对 commonjs 和 amd 的替代,它们是通用的 javascript 模块格式和加载系统。\n在es2015中,你可以使用 export 关键字使变量在文件之外可用。要在其他地方使用这些变量,你必须通过源文件的路径来导入它们。输出一些变量的文件被称为\u0026quot;模块\u0026rdquo;,因为它们代表了一个可重用的代码单元。明确导入你使用的模块和包有助于你以模块化的方式编写代码,避免引入全局符号和\u0026quot;远距离操作\u0026quot;。\n由于这是在 meteor 1.3 中引入的新功能,你会发现网上有很多代码使用旧的、更集中的围绕包和应用程序声明全局符号的例子。这个旧系统仍然有效,所以要选择加入新的模块系统,代码必须放在你的应用程序的 imports/ 目录内。我们希望未来的 meteor 版本将默认开启所有代码的模块,因为这与更广泛的 javascript 社区的开发者编写代码的方式更一致。\n你可以在模块包 readme 中详细了解模块系统。这个包作为 ecmascript 元包 的一部分,自动包含在了每个新的 meteor 应用程序中,所以大多数应用程序不需要做任何事情就可以立即开始使用模块。\n2.使用 import 和 export 的介绍\nmeteor不仅允许你在应用程序中导入 javascript,还可以导入 css 和 html 来控制加载顺序:\nimport '../../api/lists/methods.js'; // import from relative path import '/imports/startup/client'; // import module with index.js from absolute path import './loading.html'; // import blaze compiled html from relative path import '/imports/ui/style.css'; // import css from absolute path 关于导入样式的更多方法,请看 build system 一文。\nmeteor 也支持标准的 es2015 模块导出语法:\nexport const listrenderhold = launchscreen.hold(); // named export export { todos }; // named export export default lists; // default export export default new collection('lists'); // default export 3.从包中导入\n在 meteor 中,使用 import 语法在客户端或服务器上加载 npm 包,并像使用其他模块一样访问包的导出符号,也是简单明了的。你也可以从 meteor atmosphere 包中导入,但导入路径必须以 meteor/ 为前缀,以避免与 npm 包命名空间冲突。例如,要从 npm 导入 moment,从 atmosphere 导入 http。\nimport moment from 'moment'; // default import from npm import { http } from 'meteor/http'; // named import from atmosphere 关于使用导入包的更多细节,请看 meteor 指南中的 using packages。\n4.使用 require\n在 meteor 中,import 语句可以编译成 commonjs 的 require 语法。然而,作为一种惯例,我们鼓励你使用 import。\n也就是说,在某些情况下,你可能需要直接调用 require。一个值得注意的例子是:当需要从一个公共文件中获得客户端或服务器上的代码时。由于导入必须在顶层范围内,你不能把它们放在 if 语句中,所以你需要写这样的代码:\nif (meteor.isclient) { require('./client-only-file.js'); } 请注意,对 require() 的动态调用(被 require 的名称在运行时可能会改变)不能被正确分析,并可能导致客户端捆绑的损坏。\n如果你需要从具有默认导出的 es2015 模块中执行 require,你可以用 require(\u0026quot;package\u0026quot;).default 来获取导出。\n5.使用 coffeescript\n详见文档:modules » syntax » coffeescript\n// lists.coffee export lists = new collection 'lists' import { lists } from './lists.coffee' 3.2 文件结构 为了充分使用模块系统,并确保我们的代码只在我们要求的时候运行,我们建议所有的应用程序代码都应该放在 import/ 目录内。这意味着 meteor 构建系统只会在该文件被另一个文件使用导入的方式引用时才会捆绑和包含该文件(也称为 \u0026ldquo;懒执行或加载\u0026rdquo;)。\nmeteor 将使用默认的文件加载顺序规则加载应用程序中任何 import/ 目录之外的所有文件(也称为 \u0026ldquo;紧急执行或加载\u0026rdquo;)。建议你创建两个需要紧急加载的文件:client/main.js 和 server/main.js,以便为客户端和服务器定义明确的入口点。meteor确保任何名为 server/ 的目录中的文件都只能在服务器上使用,同理任何名为 client/ 的目录。这也排除了试图从任何名为 client/ 的目录中导入在服务器上使用的文件,即使它被嵌套在 import/ 目录中,反之亦然,从 server/ 中导入客户端文件。\n这些 main.js 文件本身不会做任何事情,但它们应该在应用程序加载时导入一些启动模块,这些模块将分别在客户端和服务器上立即运行。这些模块应该为应用程序中使用的包做任何必要的配置,并导入应用程序的其他代码。\n1.目录布局示例\n首先,让我们看看我们的 todos示例应用程序,它是一个很好的例子,在构建你的应用程序时可以借鉴。下面是其目录结构的概述。你可以使用 meteor create appname --full 命令生成一个具有这种结构的新应用。\nimports/ startup/ client/ index.js # import client startup through a single index entry point routes.js # set up all routes in the app useraccounts-configuration.js # configure login templates server/ fixtures.js # fill the db with example data on startup index.js # import server startup through a single index entry point api/ lists/ # a unit of domain logic server/ publications.js # all list-related publications publications.tests.js # tests for the list publications lists.js # definition of the lists collection lists.tests.js # tests for the behavior of that collection methods.js # methods related to lists methods.tests.js # tests for those methods ui/ components/ # all reusable components in the application # can be split by domain if there are many layouts/ # wrapper components for behaviour and visuals pages/ # entry points for rendering used by the router client/ main.js # client entry point, imports all client code server/ main.js # server entry point, imports all server code 2.结构化的导入\n现在我们已经把所有文件放在了 import/ 目录下,让我们考虑一下如何用模块来组织我们的代码。把所有在应用程序启动时运行的代码放在 import/startup 目录下是有意义的。另一个好主意是将数据和业务逻辑与 ui 渲染代码分开。我们建议使用名为imports/api 和 imports/ui 的目录来进行这种逻辑区分。\n在 imports/api 目录下,根据代码所提供的api的域将代码分在几个目录是很明智的 - 通常这与你在应用中定义的集合相对应。例如,在todos示例应用程序中,我们有 imports/api/lists 和 imports/api/todos 域。在每个目录中,我们定义了用于操作相关域数据的集合、发布和方法。\n注意:在一个更大的应用程序中,考虑到 todos 本身是列表的一部分,把这两个域归入一个更大的\u0026quot;列表\u0026quot;模块可能是有意义的。todo 的例子足够小,我们需要把这些分开,只是为了证明模块化。\n在 imports/ui 目录下,通常有必要根据它们所定义的ui端代码的类型将文件分组,即顶层页面、包装布局或可重用组件。\n对于上面定义的每个模块,将各种辅助文件与基本的javascript文件放在一起是有意义的。例如,一个 blaze ui 组件应该将其模板 html、javascript逻辑 和 css规则 放在同一个目录下。一个有业务逻辑的 javascript 模块应该与该模块的单元测试放在一起。\n3.启动文件\n代码中有一部分不是业务逻辑或用户界面的单元,而是一些设置或配置代码,它们需要在应用程序启动时在其上下文中运行。在 todos示例应用程序中,imports/startup/client/useraccounts-configuration.js 文件配置了 useraccounts 登录模板(关于 useraccounts 的更多信息,请参考 accounts 文章)。imports/startup/client/routes.js 文件配置了所有的路由,然后导入客户端所需的所有其他代码。\nimport { flowrouter } from 'meteor/ostrio:flow-router-extra'; import { blazelayout } from 'meteor/kadira:blaze-layout'; import { accountstemplates } from 'meteor/useraccounts:core'; // import to load these templates import '../../ui/layouts/app-body.js'; import '../../ui/pages/root-redirector.js'; import '../../ui/pages/lists-show-page.js'; import '../../ui/pages/app-not-found.js'; // import to override accounts templates import '../../ui/accounts/accounts-templates.js'; // below here are the route definitions 然后我们在 imports/startup/client/index.js 中导入这两个文件:\nimport './useraccounts-configuration.js'; import './routes.js'; 这使我们可以很容易从主要的急加载的客户端入口 client/main.js 文件中导入所有客户端启动代码。只需要一个导入模块:\nimport '/imports/startup/client'; 我们在服务器上使用了同样的技术,在 import/startup/server/index.js 中导入所有启动代码:\n// this defines a starting set of data to be loaded if the app is loaded with an empty db. import '../imports/startup/server/fixtures.js'; // this file configures the accounts package to define the ui of the reset password email. import '../imports/startup/server/reset-password-email.js'; // set up some rate limiting and other important security settings. import '../imports/startup/server/security.js'; // this defines all the collections, publications and methods that the application provides // as an api to the client. import '../imports/api/api.js'; 然后在主服务器入口 server/main.js 导入这个启动模块。你可以看到,这里我们实际上并没有从这些文件中导入任何变量 - 我们导入它们是为了按照这个顺序执行。\n4.meteor 伪全局导入\n为了向后兼容,meteor 1.3 仍然为 meteor 核心包以及应用程序中包含的其他 meteor 包提供全局命名空间。你也仍然可以像以前版本的 meteor 一样直接调用 meteor.publish 等函数,而不需要先导入它们。然而,推荐的最佳做法是,在使用 meteor \u0026ldquo;伪全局\u0026quot;之前,首先使用 import { name } from 'meteor/package’ 语法加载它们。比如说:\nimport { meteor } from 'meteor/meteor'; import { ejson } from 'meteor/ejson'; 3.3 默认的文件加载顺序 尽管建议你在编写应用程序时使用 es2015 模块和 import/ 目录,但 meteor 1.3 继续支持使用这些默认的加载顺序规则对文件急执行,以便向后兼容为 meteor 1.2 及以前编写的应用程序。关于急执行、懒执行和懒加载之间的区别的,请参考 stack overflow 的这篇文章。\n在应用程序中使用 import 时,你可以结合急执行和懒加载。当使用这些规则加载和执行一个文件时,任何导入语句都会按照它们在该文件中列出的顺序执行。\n有几个加载顺序规则。它们按照下面给出的优先级,依次应用于应用程序中所有适用的文件:\nhtml模板文件总是在其他内容之前加载 以 main. 开头的文件最后加载 任何 lib/ 目录内的文件都是接下来被加载 具有更深路径的文件接下来被加载 按整个路径的字母顺序加载文件 示例:\nnav.html main.html client/lib/methods.js client/lib/styles.js lib/feature/styles.js lib/collections.js client/feature-y.js feature-x.js client/main.js 例如,上面的文件是按照正确的加载顺序排列的。main.html 被加载在第二位,因为html模板总是先被加载,即使它以 main. 开头,因为规则1比规则2优先级高。然而,它将在 nav.html 之后被加载,因为规则2比规则5优先级高。\nclient/lib/styles.js 和 lib/feature/styles.js 在规则4之前有相同的加载顺序;然而,由于 client 按字母顺序排在 lib 之前,它将首先被加载。\n你也可以使用 meteor.startup 来控制代码在服务器和客户端的运行时间。\n1.特殊目录\n默认情况下,meteor 应用程序目录中任何 javascript 文件都会被捆绑并同时在客户端和服务器上加载。然而,项目中文件和目录的名称会影响它们的加载顺序,加载的位置,以及其他一些特性。下面是一个被 meteor 特别处理的文件和目录名称的列表:\nimports\n任何名为 imports/ 的目录在任何地方都不会被加载,文件必须使用 import 导入。\nnode_modules\n任何名为 node_modules/ 的目录在任何地方都不会被加载。安装到 node_modules 目录中的 node.js 包必须使用 import 或在 package.js 中使用 npm.dependence 导入。\nclient\n任何名为 client/ 的目录都不会在服务器上加载。这类似于将你的代码包装在 if (meteor.isclient) { … }。 在生产模式下,所有在客户端加载的文件都会自动合并后被删除。在开发模式下,为了便于调试,javascript 和 css 文件不会被删除。为了保证生产和开发的一致性,css 文件仍然会被合并成一个文件,因为改变 css 文件的 url 会影响文件中 url 的处理方式。\nmeteor 应用程序中的html文件的处理方式与服务端的框架有很大的不同。meteor 会扫描你目录中的所有html文件,寻找三个顶级元素:\u0026lt;head\u0026gt;, \u0026lt;body\u0026gt; 和 \u0026lt;template\u0026gt;。head 和 body 部分被分别合并为一个单独的 head 和 body,并在初始页面加载时被传送到客户端。\nserver\n任何名为 server/ 的目录都不会在客户端加载。类似于用 if (meteor.isserver){ …} 来包裹你的代,客户端永远不会收到这些代码。任何你不希望提供给客户端的敏感代码,例如包含密码或认证机制的代码,都应该保存在 server/ 目录下。\nmeteor 收集你所有的 javascript 文件,除了 client、public 和 private 子目录下的任何文件,并将它们加载到一个 node.js 服务器实例中。在meteor 中,你的服务器代码在每个请求中以单线程运行,而不是node 中典型的异步回调风格。\npublic\npublic/ 顶层目录内的所有文件都以原样提供给客户端。当引用这些静态文件时,不要在url中包含 public/,要把url写成它们都在顶层的格式。例如,引用 public/bg.png 为 \u0026lt;img src='/bg.png' /\u0026gt;。这是存放 favicon.ico、robots.txt 和其它类似文件最佳的地方。\nprivate\nprivate/ 顶层目录内的所有文件只能从服务器代码中访问,并且可以通过静态api加载。这可以用于存放 私人数据文件 和 任何你不希望从外部访问的项目中的文件。\nclient/compatibility\n这个文件夹是为了让那些依赖 var 声明的变量在被全局导出时与 javascript 库兼容。这个目录中的文件在执行时不会被包裹在一个新的变量范围内。这些文件会在其他客户端的 javascript 文件之前执行。\n建议使用 npm 来获取第三方的 javascript 库,并使用 import 来控制文件何时被加载。\ntests\ntest/ 目录不会被加载到任何地方。使用这个目录存放任何使用meteor 内置测试工具之外的测试运行器来运行的 测试代码。\n以下目录也不会作为你的应用程序代码的一部分被加载:\n以点开头的文件或目录,如 .meteor 和 .git packages/:用于本地软件包 cordova-build-override/:用于高级的移动端 build 定制 programs:历史遗留原因 2.特殊目录以外的文件\n所有在特殊目录之外的 javascript 文件都会同时在客户端和服务器上加载。meteor 提供了 meteor.isclient 和 meteor.isserver 变量,用来让你的代码判断是在客户端还是在服务器上运行从而改变其行为。\n特殊目录之外的 css 和 html 文件只在客户端加载,不能从服务器代码中使用。\n3.4 拆分成多个应用程序 如果你正在编写一个足够复杂的系统,把代码分割成多个应用程序是有意义的。例如,你可能想为 管理用户的ui 创建一个单独的应用程序(而不是全部通过网站管理的代码 检查权限,你可以检查一次),或者为应用程序的移动和桌面版本分离代码。\n另一个非常常见的用例是将一个工作进程从你的主应用程序中分离出来,这样开销大的工作就不会因为被锁住一个网络服务器中而影响访问体验。\n以这种方式拆分你的应用程序有一些好处:\n如果你把特定类型的用户始终不会使用的代码分离出来,客户端 javascript 捆绑的代码就会明显变小。\n你可以用不同的扩展设置来部署不同的应用程序,并以不同的方式保护它们(例如,可以只允许防火墙后面的用户访问 admin 应用程序)。\n你可以允许你的组织中的不同团队独立处理不同的应用程序。\n然而,以这种方式拆分你的代码有一些在使用之前就应该考虑的困难。\n1.代码共享\n主要的挑战是在不同的应用程序之间恰当地共享代码。处理这个问题的最简单方法是在不同的网络服务器上部署相同的应用程序,通过不同的设置控制行为。这种方法允许你部署具有不同扩展行为的不同版本的代码,但不能享受上面所说的大多数其他优势。\n如果你想用独立的代码创建 meteor 应用程序,你会有一些模块想在它们之间共享。如果这些模块是更广的范围可以使用的,你应该考虑将它们发布到一个包系统中,可以是 npm,也可以是 atmosphere,这取决于代码是否是 meteor 特有的。\n如果这些代码是私有的,或者其他人不感兴趣,那么在两个应用程序中包含相同的模块通常是有意义的(你可以用私有的 npm 模块来做这个)。有几种方法可以做到这一点:\n一个直接的方法是将公共代码作为两个应用程序的 git 子模块。 或者,如果你将两个应用程序都包含在一个仓库中,你可以使用符号链接将公共模块包含在两个应用程序中。 2.数据共享\n另一个重要的考虑是如何在不同的应用程序之间共享数据。\n最简单的方法是将两个应用程序都指向同一个 mongo_url,并允许两个应用程序直接从数据库中读取和写入。这种方法可行要感谢 meteor 支持反应式数据库。当应用程序改变 mongodb 中的一些数据时,由于 meteor 的 livequery,连接到数据库的任何其他应用程序的用户将立即看到这些变化。\n然而,在某些情况下,最好让一个应用程序成为主程序,并通过api控制其他应用程序对数据的访问。如果你想在不同的时间部署不同的应用程序,并且需要对数据的变化保守,那么这可以帮助你。\n提供 server-server api 的最简单方法是直接使用 meteor 内置的 ddp 协议。这与你的 meteor 客户端从服务器获取数据的方式相同,但也可以用它来在不同的应用程序之间进行通信。你可以使用ddp.connect() 从一个\u0026quot;客户\u0026quot;服务器连接到主服务器,然后使用返回的连接对象来进行方法调用和从发布中读取。\n3.账号共享\n如果你有两个访问同一数据库的服务器,并且想让认证的用户在这两个服务器上进行 ddp 调用,可以使用一个连接上设置的令牌来登录另一个连接。\n如果你的用户已经连接到服务器a,那么你可以使用 ddp.connect() 打开一个到服务器b的连接,并传入服务器a的令牌来认证服务器b。认证的代码看起来像这样:\n// this is server a's token as the default `accounts` points at our server const token = accounts._storedlogintoken(); // we create a *second* accounts client pointing at server b const app2 = ddp.connect('url://of.server.b'); const accounts2 = new accountsclient({ connection: app2 }); // now we can login with the token. further calls to `accounts2` will be authenticated accounts2.loginwithtoken(token); 你可以在一个 例子仓库 中看到这个架构的概念证明。\n4 迁移到 meteor 2.4 如何将你的应用程序迁移到 meteor 2.4。\nmeteor 2.4 中的大部分新功能都是直接在幕后应用的(以向后兼容的方式),或者是可选的。完整的变化,请参考 changelog。\n综上所述,有一些事情需要先做,以便给将来节省更多的时间。\ncreateindex\n以前没有记录的 _ensureindex 已经与 mongodb 在命名上的重大变化相一致,现在可以作为 createindex 使用。_ensureindex 已被弃用,在开发中使用时会抛出一个警告。\nemail 2.2\n电子邮件包有一个功能更新。你现在可以用email.customtransport 完全覆盖发送功能,或者 如果你使用了已知的服务,现在可以抛弃 mail_url 环境变量,通过在 settings.json 文件中这样设置:\n{ \u0026quot;packages\u0026quot;: { \u0026quot;email\u0026quot;: { \u0026quot;service\u0026quot;: \u0026quot;mailgun\u0026quot;, \u0026quot;user\u0026quot;: \u0026quot;postmaster@meteor.com\u0026quot;, \u0026quot;password\u0026quot;: \u0026quot;superduperpassword\u0026quot; } } } 4.1 从2.3以前的版本迁移? 如果你要从比 meteor 2.3 更早的版本迁移,可能会有一些重要的注意事项没有在本指南中列出(本指南特别涵盖了从 2.2 到 2.3)。请查看更早的迁移指南以了解细节:\nmigrating to meteor 2.3(from 2.2) migrating to meteor 2.2(from 2.0) migrating to meteor 2.0(from 1.12) ","date":"2021-10-17","permalink":"https://kinneyzhang.github.io/post/meteor-guide/","summary":"文档目录结构 1 介绍 1.1 什么是Meteor? 1.2 快速入门 1.3 Meteor资源 1.4 什么是Meteor指南? 1.5 指南开发 2 代码风格 2.1 风格一致的好处 2.2 JavaScript风格指南","title":"meteor 指南"},{"content":" 文档目录结构\n1 创建app 1.1 安装 meteor 1.2 创建 meteor 项目 1.3 创建任务组件 1.4 创建任务示例 1.5 渲染任务示例 1.6 移动端外观 1.7 更换热模块 2 集合 2.1 创建任务集合 2.2 初始化任务集 2.3 渲染任务集合 3 表单和事件 3.1 创建任务表单 3.2 更新app组件 3.3 更新样式表 3.4 添加提交处理程序 3.5 首先显示最新的任务 4 更新和删除 4.1 添加复选框 4.2 切换复选框 4.3 删除任务 5 样式 5.1 css 5.2 应用样式 6 筛选任务 6.1 usestate 6.2 按钮样式 6.3 过滤任务 6.4 meteor开发工具插件 6.5 待办任务 7 添加用户账户 7.1 密码认证 7.2 创建用户账户 7.3 登录表单 7.4 要求认证 7.5 登录表单样式 7.6 服务器启动 7.7 任务所有者 7.8 退出登录 8 方法 8.1 禁用快速原型技术 8.2 添加任务方法 8.3 实现方法调用 8.4 api和db文件夹 9 发布 9.1 自动发布 9.2 任务发布 9.3 任务订阅 9.4 加载状态 9.5 检查用户权限 10 在移动端运行 10.1 ios 模拟器 10.2 安卓模拟器 10.3 安卓设备 10.4 iphone or ipad 11 测试 11.1 安装依赖 11.2 脚手架测试 11.3 数据准备 11.4 测试任务删除 11.5 更多的测试 12 部署 12.1 创建账号 12.2 部署应用 12.3 访问并享受吧 13 后续 本文档翻译自 meteor react tutorial\n在本教程中,我们将使用 react 和 meteor 平台建立一个简单的待办事项应用。meteor 与其他一些框架如 blaze、angular 和 vue 都可以开箱即用。\n我们推荐你在这里查看我们所有的教程。现在,让我们开始用 meteor 构建你的 react to-do应用程序吧!\n1 创建app 1.1 安装 meteor 首先我们需要安装 meteor。按照接下来文档中的步骤安装最新的 meteor 官方发布版本。\n1.2 创建 meteor 项目 用 react 设置 meteor 的最简单的方法是使用 meteor create 命令,并加上选项 --react 和你的项目名称(你也可以省略 --react 选项,因为它是默认的)。\nmeteor create simple-todos-react meteor 将为你创建所有必要的文件。\n位于客户端目录下的文件正在设置你的客户端(web),你可以看到例如 client/main.jsx,meteor 正在将你的应用程序的主要组件渲染成 html。\n另外,检查服务器目录,meteor 正在设置服务器端(node.js),你可以看到 server/main.js 正在用一些数据初始化你的 mongodb 数据库。你不需要安装 mongodb,因为 meteor 提供了它的嵌入式版本供你使用。\n现在你可以用以下方式运行你的 meteor 应用程序。\nmeteor run 别担心,从现在开始,meteor会让你的应用程序与你的所有变化保持同步。\n你的react代码将位于 import/ui 目录下,而 app.jsx 文件是你的 react to-do应用的根组件。\n快速浏览一下 meteor 创建的所有文件,你现在不需要理解它们,但知道它们在哪里是很好的。\n1.3 创建任务组件 现在你将进行第一次修改。在你的 ui 文件夹中创建一个名为 task.jsx 的新文件。\n这个文件将导出一个名为 task 的 react组件,它将代表你的待办事项列表中的一个任务。\nimports/ui/task.jsx\nimport react from 'react'; export const task = ({ task }) =\u0026gt; { return \u0026lt;li\u0026gt;{task.text}\u0026lt;/li\u0026gt; }; 由于这个组件将在一个列表中,你将返回一个li元素。\n1.4 创建任务示例 由于你还没有连接到你的服务器和数据库,让我们定义一些样本数据,这些数据很快就会被用来呈现一个任务列表。它将是一个数组,你可以称它为tasks。\nimports/ui/app.jsx\nimport react from 'react'; const tasks = [ {_id: 1, text: 'first task'}, {_id: 2, text: 'second task'}, {_id: 3, text: 'third task'}, ]; export const app = () =\u0026gt; ... 你可以将任何内容作为每个任务上的text属性。要有创造性!\n1.5 渲染任务示例 现在我们可以用react实现一些简单的渲染逻辑。我们现在可以使用我们之前的任务组件来渲染我们的列表项。\n在react中,你可以使用{ }来在它们之间编写javascript代码。\n请看下面,你将使用数组对象的.map函数来迭代你的样本任务。\nimports/ui/app.jsx\nimport react from 'react'; import { task } from './task'; const tasks = ..; export const app = () =\u0026gt; ( \u0026lt;div\u0026gt; \u0026lt;h1\u0026gt;welcome to meteor!\u0026lt;/h1\u0026gt; \u0026lt;ul\u0026gt; { tasks.map(task =\u0026gt; \u0026lt;task key={ task._id } task={ task }/\u0026gt;) } \u0026lt;/ul\u0026gt; \u0026lt;/div\u0026gt; ); 记得给你的任务添加 key 属性,否则react会发出警告,因为它将看到许多相同类型的组件作为兄弟节点。如果没有键,react将很难在必要时重新渲染其中一个。\n你可以在这里阅读更多关于react和键的信息。\n从你的app组件中删除 hello 和 info,记得也要删除文件顶部的导入。同时删除 hello.jsx 和 info.jsx 文件。\n1.6 移动端外观 让我们来看看你的应用程序在移动端上的表现如何。你可以通过在浏览器中右击你的应用程序来模拟移动环境(我们假设你使用的是谷歌浏览器,因为它是当今最流行的浏览器),然后检查,这将在你的浏览器中打开一个名为 \u0026ldquo;开发工具 \u0026ldquo;的新窗口。在开发工具中,你有一个小图标,显示一个移动设备和一个平板电脑:\n点击它,然后在顶部栏中选择你想模拟的手机。\n你也可以在你的手机上检查你的应用程序。要做到这一点,在你的手机浏览器的导航浏览器中使用你的本地ip连接到你的应用程序。 在unix系统中,这个命令至少可以为你打印出你的本地ip: ifconfig | grep \u0026quot;inet \u0026quot; | grep -fv 127.0.0.1 | awk '{print $2}'\n你会看到类似这样的内容。\n正如你所看到的,一切都很小,因为我们没有为移动设备调整视图端口。你可以通过在你的 client/main.html 文件中的 head标签内、title标签后添加下面这几行来解决这个问题和其他类似问题。\nclient/main.html\n\u0026lt;meta charset=\u0026quot;utf-8\u0026quot;/\u0026gt; \u0026lt;meta http-equiv=\u0026quot;x-ua-compatible\u0026quot; content=\u0026quot;ie=edge\u0026quot;/\u0026gt; \u0026lt;meta name=\u0026quot;viewport\u0026quot; content=\u0026quot;width=device-width, height=device-height, viewport-fit=cover, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no\u0026quot; /\u0026gt; \u0026lt;meta name=\u0026quot;mobile-web-app-capable\u0026quot; content=\u0026quot;yes\u0026quot;/\u0026gt; \u0026lt;meta name=\u0026quot;apple-mobile-web-app-capable\u0026quot; content=\u0026quot;yes\u0026quot;/\u0026gt; 现在你的应用程序应该看起来像这样:\n1.7 更换热模块 meteor默认在使用react时已经为你添加了一个叫做 hot-module-replacement的包。这个包会更新正在运行的应用程序中在重建过程中被修改的javascript模块。缩短了开发时的反馈周期,所以你可以更快地查看和测试变化(它甚至会在构建完成之前更新应用)。你也不会丢失状态,你的应用代码会被更新,你的状态也会是一样的。\n你还应该在这时添加 dev-error-overlay包,这样你就可以在你的网络浏览器中看到错误。\nmeteor add dev-error-overlay 你可以试着犯一些错误,然后你就会在浏览器中看到错误,而不仅仅是在控制台中。\n回顾:你可以在这里检查你的代码在当前步骤结束时应该是怎样的。\n下一步,我们将与 mongodb数据库合作,以存储我们的任务。\n2 集合 meteor已经为你设置了 mongodb。为了使用数据库,我们需要创建一个集合,这就是存储文件的地方,在我们的例子中就是 tasks。\n你可以在这里阅读更多关于集合的信息。\n在这一步中,我们将实现所有必要的代码,使用react钩子(hooks)为我们的任务建立和运行一个基本集合。\n2.1 创建任务集合 我们可以通过在 import/api/taskscollection.js 创建一个新文件来存储我们的任务,该文件实例化了一个新的 mongo集合并将其导出。\nimports/api/taskscollection.js\nimport { mongo } from 'meteor/mongo'; export const taskscollection = new mongo.collection('tasks'); 注意,我们把文件存放在 imports/api目录下,这是一个存放api相关代码的地方,比如出版物和方法。你可以随心所欲地命名这个文件夹,这只是一种选择而已。\n你可以删除这个文件夹中的 link.js文件,因为我们不打算使用这个集合。\n你可以在这里阅读更多关于应用程序结构和 imports/exports 的信息。\n2.2 初始化任务集 为了让我们的集合发挥作用,你需要在服务器中导入它,以便它设置一些管道。\n你可以使用 import \u0026quot;/imports/api/taskscollection\u0026quot; 或者 import { taskscollection } from \u0026quot;/imports/api/taskscollection\u0026quot; 如果你要在同一个文件上使用,但要确保它被导入。\n现在很容易检查我们的集合中是否有数据,否则我们也可以轻松地插入一些样本数据。\n你不需要保留 server/main.js的旧内容。\nserver/main.js\nimport { meteor } from 'meteor/meteor'; import { taskscollection } from '/imports/api/taskscollection'; const inserttask = tasktext =\u0026gt; taskscollection.insert({ text: tasktext }); meteor.startup(() =\u0026gt; { if (taskscollection.find().count() === 0) { [ 'first task', 'second task', 'third task', 'fourth task', 'fifth task', 'sixth task', 'seventh task' ].foreach(inserttask) } }); 因此,你正在导入 taskscollection,并在其上添加一些任务:迭代一个字符串数组,对每个字符串调用一个函数,在我们的任务文件中插入这个字符串作为我们的文本字段。\n2.3 渲染任务集合 现在有趣的部分来了,你将使用一个react函数组件和一个叫做 usetracker的hook从一个叫做 react-meteor-data的包中渲染任务。\nmeteor与 meteor包和 npm包一起工作,通常 meteor包是使用 meteor的内部程序或其他 meteor包。\n这个包已经包含在react的骨架(meteor create yourproject),所以你不需要添加它,但你可以随时运行 meteor add package-name 添加 meteor包。\nmeteor add react-meteor-data 现在你已经准备好从这个包中导入代码了,当从 meteor包中导入代码时,与npm模块唯一不同的是,你需要在导入的 from部分前加上 meteor/。\nreact-meteor-data 导出的 usetracker函数是一个 react hook,允许你的react组件作出反应。每当数据发生变化时,你的组件将重新渲染。很酷,对吗?\n关于react hooks的更多信息,请阅读这里。\nimports/ui/app.jsx\nimport react from 'react'; import { usetracker } from 'meteor/react-meteor-data'; import { taskscollection } from '/imports/api/taskscollection'; import { task } from './task'; export const app = () =\u0026gt; { const tasks = usetracker(() =\u0026gt; taskscollection.find({}).fetch()); return ( \u0026lt;div\u0026gt; \u0026lt;h1\u0026gt;welcome to meteor!\u0026lt;/h1\u0026gt; \u0026lt;ul\u0026gt; { tasks.map(task =\u0026gt; \u0026lt;task key={ task._id } task={ task }/\u0026gt;) } \u0026lt;/ul\u0026gt; \u0026lt;/div\u0026gt; ); }; 看看你的应用程序现在应该是什么样子:\n你可以在服务器上改变你的 mongodb的数据,你的应用程序将为你做出反应并重新渲染。\n你可以从你的应用程序文件夹或使用 mongo ui客户端,如 nosqlbooster,在终端通过运行 meteor mongo 连接到的mongodb。你的嵌入式 mongodb在 3001端口运行。\n请看如何连接:\n看看数据库:\n你可以双击你的集合,以查看存储在其中的文件:\n但是,等等,我的任务是如何从服务器传到客户端的?我们将在后面关于出版物和订阅的步骤中解释这个问题。你现在需要知道的是,你正在把数据库中的所有数据发布到客户端。这一点以后会被删除,因为我们不想一直发布所有的数据。\n回顾:你可以在这里检查你的代码在这一步骤结束时应该是怎样的\n在下一步,我们将使用一个表单来创建任务。\n3 表单和事件 所有的应用程序都需要允许用户与存储的数据进行某些类型的交互。在我们的案例中,第一种类型的交互是插入新的任务。没有它,我们的 to-do应用就不会有什么帮助。\n用户在网站上插入或编辑数据的主要方式之一是通过表单。在大多数情况下,使用 \u0026lt;form\u0026gt; 标签是个好主意,因为它为里面的元素赋予了语义。\n3.1 创建任务表单 首先,我们需要创建一个简单的表单组件来封装我们的逻辑。如你所见,我们设置了 usestate react hook。\n请注意数组的解构[text, settext],其中text是我们要使用的存储值,在这里是一个字符串;而settext是一个用来更新该值的函数。\n在你的ui文件夹中创建一个新文件 taskform.jsx。\nimports/ui/taskform.jsx\nimport react, { usestate } from 'react'; export const taskform = () =\u0026gt; { const [text, settext] = usestate(\u0026quot;\u0026quot;); return ( \u0026lt;form classname=\u0026quot;task-form\u0026quot;\u0026gt; \u0026lt;input type=\u0026quot;text\u0026quot; placeholder=\u0026quot;type to add new tasks\u0026quot; /\u0026gt; \u0026lt;button type=\u0026quot;submit\u0026quot;\u0026gt;add task\u0026lt;/button\u0026gt; \u0026lt;/form\u0026gt; ); }; 3.2 更新app组件 然后,我们可以简单地将其添加到你的任务列表上方的app组件中。\nimports/ui/app.jsx\nimport { usetracker } from 'meteor/react-meteor-data'; import { task } from './task'; import { taskscollection } from '/imports/api/taskscollection'; import { taskform } from './taskform'; export const app = () =\u0026gt; { const tasks = usetracker(() =\u0026gt; taskscollection.find({}).fetch()); return ( \u0026lt;div\u0026gt; \u0026lt;h1\u0026gt;welcome to meteor!\u0026lt;/h1\u0026gt; \u0026lt;taskform/\u0026gt; \u0026lt;ul\u0026gt; { tasks.map(task =\u0026gt; \u0026lt;task key={ task._id } task={ task }/\u0026gt;) } \u0026lt;/ul\u0026gt; \u0026lt;/div\u0026gt; ); }; 3.3 更新样式表 你也可以按照你的意愿来设计它的样式。现在,我们只需要在顶部留出一些空白,这样表格就不会显得不伦不类。添加css类.task-form,这需要与表单组件中的 classname属性的名称相同。\nclient/main.css\n.task-form { margin-top: 1rem; } 3.4 添加提交处理程序 现在你可以使用onsubmit事件给你的表单附加一个提交处理程序;同时将你的react钩子插入输入元素中的onchange事件。\n正如你所看到的,你正在使用usestate react钩子来存储你的\u0026lt;input\u0026gt;元素的值。注意,你还需要将你的value属性设置为text常量,这将使input元素与我们的钩子保持同步。\n在更复杂的应用中,如果在潜在的频繁事件(如onchange)之间有许多计算发生,你可能想实现一些去噪或节流逻辑。有一些库可以帮助你做到这一点,比如说 lodash。\nimports/ui/taskform.jsx\nimport react, { usestate } from 'react'; import { taskscollection } from '/imports/api/taskscollection'; export const taskform = () =\u0026gt; { const [text, settext] = usestate(\u0026quot;\u0026quot;); const handlesubmit = e =\u0026gt; { e.preventdefault(); if (!text) return; taskscollection.insert({ text: text.trim(), createdat: new date() }); settext(\u0026quot;\u0026quot;); }; return ( \u0026lt;form classname=\u0026quot;task-form\u0026quot; onsubmit={handlesubmit}\u0026gt; \u0026lt;input type=\u0026quot;text\u0026quot; placeholder=\u0026quot;type to add new tasks\u0026quot; value={text} onchange={(e) =\u0026gt; settext(e.target.value)} /\u0026gt; \u0026lt;button type=\u0026quot;submit\u0026quot;\u0026gt;add task\u0026lt;/button\u0026gt; \u0026lt;/form\u0026gt; ); }; 同时在你的task文件中插入日期createdat,这样你就能知道每个任务是什么时候创建的。\n3.5 首先显示最新的任务 现在你只需要做一个能让用户满意的改变:我们需要先显示最新的任务。我们可以通过对我们的mongo查询进行排序来快速完成。\nimports/ui/app.jsx\n.. export const app = () =\u0026gt; { const tasks = usetracker(() =\u0026gt; taskscollection.find({}, { sort: { createdat: -1 } }).fetch()); .. 你的app应该看起来像这样:\n回顾:你可以在这里检查你的代码在这一步骤结束时应该是怎样的。\n在下一步,我们将更新你的任务状态,并为用户提供一个删除任务的方法。\n4 更新和删除 到目前为止,你只是在我们的集合中插入了文件。让我们来看看如何通过与用户界面的交互来更新和删除它们。\n4.1 添加复选框 首先,你需要给你的任务组件添加一个复选框元素。\n请确保添加了 readonly 属性,因为我们不使用 onchange 来更新状态。\n我们还必须将我们的 checked 属性强制设为布尔值,因为react理解 undefined 的值是不存在的,因此会导致组件从不受控的状态切换到受控状态。\n我们还邀请你做更多的实践,看看这个app是如何表现的,以便学习。\n你还需要接收一个回调,一个当复选框被点击时将被调用的函数。\nimports/ui/task.jsx\nimport react from 'react'; export const task = ({ task, oncheckboxclick }) =\u0026gt; { return ( \u0026lt;li\u0026gt; \u0026lt;input type=\u0026quot;checkbox\u0026quot; checked={!!task.ischecked} onclick={() =\u0026gt; oncheckboxclick(task)} readonly /\u0026gt; \u0026lt;span\u0026gt;{task.text}\u0026lt;/span\u0026gt; \u0026lt;/li\u0026gt; ); }; 4.2 切换复选框 现在你可以更新你的任务文件,切换其ischecked字段。\n创建一个函数来改变你的文档,并将其传递给你的任务组件。\nimports/ui/app.jsx\nconst togglechecked = ({ _id, ischecked }) =\u0026gt; { taskscollection.update(_id, { $set: { ischecked: !ischecked } }) }; export const app = () =\u0026gt; { .. \u0026lt;ul\u0026gt; { tasks.map(task =\u0026gt; \u0026lt;task key={ task._id } task={ task } oncheckboxclick={togglechecked} /\u0026gt;) } \u0026lt;/ul\u0026gt; .. 你的app应该看起来像这样:\n4.3 删除任务 你只需要几行代码就可以删除任务。\n首先在你的任务组件中的文本后添加一个按钮,并接收一个回调函数。\nimports/ui/task.jsx\nimport react from \u0026lsquo;react\u0026rsquo;;\nexport const task = ({ task, oncheckboxclick, ondeleteclick }) =\u0026gt; { return ( .. \u0026lt;span\u0026gt;{task.text}\u0026lt;/span\u0026gt; \u0026lt;button onclick={ () =\u0026gt; ondeleteclick(task) }\u0026gt;\u0026amp;times;\u0026lt;/button\u0026gt; .. 现在在app中添加删除逻辑,你需要有一个删除任务的函数,并在任务组件的回调属性中提供这个函数。\nimports/ui/app.jsx\nconst deletetask = ({ _id }) =\u0026gt; taskscollection.remove(_id); export const app = () =\u0026gt; { .. \u0026lt;ul\u0026gt; { tasks.map(task =\u0026gt; \u0026lt;task key={ task._id } task={ task } oncheckboxclick={togglechecked} ondeleteclick={deletetask} /\u0026gt;) } \u0026lt;/ul\u0026gt; .. } 你的app应该看起来像这样:\n回顾:你可以在这里检查你的代码在这一步骤结束时应该是怎样的\n在下一步,我们将使用 css 和 flexbox 来改善你的app的外观。\n5 样式 5.1 css 到目前为止,我们的用户界面看起来相当难看。让我们添加一些基本的样式,这将作为一个更专业的app的基础。\n用下面的文件替换 client/main.css文件的内容,我们的想法是在顶部有一个应用栏,和一个可滚动的内容,包括:\n添加新任务的表单 任务列表 client/main.css\nbody { font-family: sans-serif; background-color: #315481; background-image: linear-gradient(to bottom, #315481, #918e82 100%); background-attachment: fixed; position: absolute; top: 0; bottom: 0; left: 0; right: 0; padding: 0; margin: 0; font-size: 14px; } button { font-weight: bold; font-size: 1em; border: none; color: white; box-shadow: 0 3px 3px rgba(34, 25, 25, 0.4); padding: 5px; cursor: pointer; } button:focus { outline: 0; } .app { display: flex; flex-direction: column; height: 100vh; } .app-header { flex-grow: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .main { display: flex; flex-direction: column; flex-grow: 1; overflow: auto; background: white; } .main::-webkit-scrollbar { width: 0; height: 0; background: inherit; } header { background: #d2edf4; background-image: linear-gradient(to bottom, #d0edf5, #e1e5f0 100%); padding: 20px 15px 15px 15px; position: relative; box-shadow: 0 3px 3px rgba(34, 25, 25, 0.4); } .app-bar { display: flex; justify-content: space-between; } .app-bar h1 { font-size: 1.5em; margin: 0; display: inline-block; margin-right: 1em; } .task-form { display: flex; margin: 16px; } .task-form \u0026gt; input { flex-grow: 1; box-sizing: border-box; padding: 10px 6px; background: transparent; border: 1px solid #aaa; width: 100%; font-size: 1em; margin-right: 16px; } .task-form \u0026gt; input:focus { outline: 0; } .task-form \u0026gt; button { min-width: 100px; height: 95%; background-color: #315481; } .tasks { list-style-type: none; padding-inline-start: 0; padding-left: 16px; padding-right: 16px; margin-block-start: 0; margin-block-end: 0; } .tasks \u0026gt; li { display: flex; padding: 16px; border-bottom: #eee solid 1px; } .tasks \u0026gt; li \u0026gt; span { flex-grow: 1; } .tasks \u0026gt; li \u0026gt; button { justify-self: flex-end; background-color: #ff3046; } 如果你想了解更多关于这个样式表的信息,请查看这篇关于flexbox的文章,以及 wes bos 关于它的免费视频教程。\nflexbox是一个很好的工具,可以在你的用户界面中分配和对齐元素。\n5.2 应用样式 现在你需要在你的组件周围添加一些元素。你要在 app中的主div上添加一个 classname,还要在 h1周围添加一个带有几个 div的 header元素,并在表单和列表周围添加一个主div。看看下面应该是怎样添加的,注意类的名称,它们需要与css文件中的相同:\nimports/ui/app.jsx\n.. return ( \u0026lt;div classname=\u0026quot;app\u0026quot;\u0026gt; \u0026lt;header\u0026gt; \u0026lt;div classname=\u0026quot;app-bar\u0026quot;\u0026gt; \u0026lt;div classname=\u0026quot;app-header\u0026quot;\u0026gt; \u0026lt;h1\u0026gt;welcome to meteor!\u0026lt;/h1\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/header\u0026gt; \u0026lt;div classname=\u0026quot;main\u0026quot;\u0026gt; \u0026lt;taskform /\u0026gt; \u0026lt;ul classname=\u0026quot;tasks\u0026quot;\u0026gt; {tasks.map(task =\u0026gt; ( \u0026lt;task key={task._id} task={task} oncheckboxclick={togglechecked} ondeleteclick={deletetask} /\u0026gt; ))} \u0026lt;/ul\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; ); 在react中,我们使用 classname 而不是 class,因为 react使用 javascript 来定义用户界面,而 class是 javascript中的一个保留词。\n另外,为你的app选择一个更好的标题,meteor很了不起,但你不希望在你的应用程序顶部栏中一直看到 welcome to meteor!。\n你可以选择类似下面的内容:\nimports/ui/app.jsx\n.. \u0026lt;h1\u0026gt;📝️ to do list\u0026lt;/h1\u0026gt; .. 你的app应该看起来像这样:\n回顾:你可以在这里检查你的代码在这一步骤结束时应该是怎样的\n在下一步,我们将使这个任务列表更具交互性,例如,提供一种过滤任务的方法。\n6 筛选任务 在这一步,你将按状态过滤你的任务,并显示待办任务的数量。\n6.1 usestate 首先,你要添加一个按钮来显示或隐藏列表中的已完成任务。\n来自 react 的 usestate函数是保持这个按钮状态的最好方法。它返回一个有两个元素的数组,其中第一个元素是状态的值,第二个是一个setter函数,是你要更新状态的方式。你可以使用数组析构来获得这两项的返回,并且已经为它们声明了一个变量。\n请记住,用于常量的名字不属于react api,你可以随心所欲地命名它们。\n同时在任务表单下面添加一个按钮,它将根据当前状态显示不同的文本。\nimports/ui/app.jsx\nimport react, { usestate } from 'react'; .. export const app = () =\u0026gt; { const [hidecompleted, sethidecompleted] = usestate(false); .. \u0026lt;div classname=\u0026quot;main\u0026quot;\u0026gt; \u0026lt;taskform /\u0026gt; \u0026lt;div classname=\u0026quot;filter\u0026quot;\u0026gt; \u0026lt;button onclick={() =\u0026gt; sethidecompleted(!hidecompleted)}\u0026gt; {hidecompleted ? 'show all' : 'hide completed'} \u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; .. 你可以在这里阅读更多关于 usestate hook 的信息。\n我们建议你把钩子总是添加在组件的顶部,这样会更容易避免一些问题,比如总是以相同的顺序运行它们。\n6.2 按钮样式 你应该给按钮添加一些样式,这样它就不会显得灰暗和没有良好的对比度。你可以使用下面的样式作为参考:\nclient/main.css\n.filter { display: flex; justify-content: center; } .filter \u0026gt; button { background-color: #62807e; } 6.3 过滤任务 现在,如果用户只想看到待处理的任务,你可以在 mini mongo 查询中给你的选择器添加一个过滤器,你需要得到所有不是 ischecked=true 的任务。\nimports/ui/app.jsx\n.. const hidecompletedfilter = { ischecked: { $ne: true } }; const tasks = usetracker(() =\u0026gt; taskscollection.find(hidecompleted ? hidecompletedfilter : {}, { sort: { createdat: -1 }, }).fetch() ); .. 6.4 meteor开发工具插件 你可以安装一个扩展来可视化你在 mini mongo 中的数据。\nmeteor devtools evolved 将帮助你调试你的app,因为你可以看到 mini mongo 上有哪些数据。\n你还可以看到 meteor 从服务器上发送和接收的所有信息,这对你了解 meteor 的工作方式很有帮助。\n使用此链接在你的谷歌浏览器中安装它。\n6.5 待办任务 更新app组件,以便在应用栏中显示待办任务的数量。\n当没有待办任务时,你应该避免在你的应用栏中添加零。\nimports/ui/app.jsx\n.. const pendingtaskscount = usetracker(() =\u0026gt; taskscollection.find(hidecompletedfilter).count() ); const pendingtaskstitle = `${ pendingtaskscount ? ` (${pendingtaskscount})` : '' }`; .. \u0026lt;h1\u0026gt; 📝️ to do list {pendingtaskstitle} \u0026lt;/h1\u0026gt; .. 你可以在同一个 usetracker 中进行这两种查找,然后返回一个具有这两种属性的对象,但是为了有一个更容易理解的代码,我们在这里创建了两个不同的跟踪器。\n你的app应该看起来像这样:\n回顾:你可以在这里检查你的代码在这一步骤结束时应该是怎样的\n在下一步,我们将在你的app中包含用户访问。\n7 添加用户账户 7.1 密码认证 meteor 已经配备了一个基本的认证和账户管理系统,所以你只需要添加 accounts-password 来启用用户名和密码认证。\nmeteor add accounts-password 支持的认证方法还有很多。你可以在这里阅读更多关于账户系统的信息。\n我们还建议你安装 bcrypt node模块,否则你会看到一个警告,说你正在使用它的纯 javascript 实现。\nmeteor npm install --save bcrypt 你应该总是使用 meteor npm,而不是只使用 npm,所以你总是使用 meteor 绑定的 npm 版本,这有助于你避免由于不同版本的npm安装不同的模块而产生的问题。\n7.2 创建用户账户 现在你可以为我们的app创建一个默认用户,使用 meteorite 作为用户名,如果我们没有在数据库中找到它,就在服务器启动时创建一个新用户。\nserver/main.js\nimport { meteor } from 'meteor/meteor'; import { accounts } from 'meteor/accounts-base'; import { taskscollection } from '/imports/api/taskscollection'; .. const seed_username = 'meteorite'; const seed_password = 'password'; meteor.startup(() =\u0026gt; { if (!accounts.finduserbyusername(seed_username)) { accounts.createuser({ username: seed_username, password: seed_password, }); } .. }); 在你的app用户界面中,你应该还没有看到任何变化。\n7.3 登录表单 你需要为用户提供一种方法来输入密钥并进行认证,为此我们需要一个表单。\n我们可以使用 usestate 钩子实现它。创建一个名为 loginform.jsx 的新文件并在其中添加一个表单。你应该使用 meteor.loginwithpassword(username, password); 来用所提供的输入来验证你的用户。\nimports/ui/loginform.jsx\nimport { meteor } from 'meteor/meteor'; import react, { usestate } from 'react'; export const loginform = () =\u0026gt; { const [username, setusername] = usestate(''); const [password, setpassword] = usestate(''); const submit = e =\u0026gt; { e.preventdefault(); meteor.loginwithpassword(username, password); }; return ( \u0026lt;form onsubmit={submit} classname=\u0026quot;login-form\u0026quot;\u0026gt; \u0026lt;label htmlfor=\u0026quot;username\u0026quot;\u0026gt;username\u0026lt;/label\u0026gt; \u0026lt;input type=\u0026quot;text\u0026quot; placeholder=\u0026quot;username\u0026quot; name=\u0026quot;username\u0026quot; required onchange={e =\u0026gt; setusername(e.target.value)} /\u0026gt; \u0026lt;label htmlfor=\u0026quot;password\u0026quot;\u0026gt;password\u0026lt;/label\u0026gt; \u0026lt;input type=\u0026quot;password\u0026quot; placeholder=\u0026quot;password\u0026quot; name=\u0026quot;password\u0026quot; required onchange={e =\u0026gt; setpassword(e.target.value)} /\u0026gt; \u0026lt;button type=\u0026quot;submit\u0026quot;\u0026gt;log in\u0026lt;/button\u0026gt; \u0026lt;/form\u0026gt; ); }; 好了,现在你有了一个表单,让我们来使用它。\n7.4 要求认证 我们的app应该只允许认证的用户访问其任务管理功能。\n当我们没有认证的用户时,我们可以实现返回 loginform 组件,否则我们返回表单、过滤器和列表组件。\n你应该首先将3个组件(表单、过滤器和列表)包裹在一个 \u0026lt;fragment\u0026gt; 中,fragment 是 react 中的一个特殊组件,你可以用它将组件组合在一起而不影响你的最终dom,这意味着不影响你的ui,因为它不会在html中引入其它元素。\n在这里阅读更多关于 fragments 的信息\n所以你可以从 meteor.user() 中获得你的认证用户或null,你应该把它包在 usetracker 钩子里,这样才是反应式的。然后你可以根据用户是否在会话中,返回带有任务和其他内容的 fragment 或 loginform。\nimports/ui/app.jsx\nimport { meteor } from 'meteor/meteor'; import react, { usestate, fragment } from 'react'; import { usetracker } from 'meteor/react-meteor-data'; import { taskscollection } from '/imports/api/taskscollection'; import { task } from './task'; import { taskform } from './taskform'; import { loginform } from './loginform'; .. export const app = () =\u0026gt; { const user = usetracker(() =\u0026gt; meteor.user()); .. return ( .. \u0026lt;div classname=\u0026quot;main\u0026quot;\u0026gt; {user ? ( \u0026lt;fragment\u0026gt; \u0026lt;taskform /\u0026gt; \u0026lt;div classname=\u0026quot;filter\u0026quot;\u0026gt; \u0026lt;button onclick={() =\u0026gt; sethidecompleted(!hidecompleted)}\u0026gt; {hidecompleted ? 'show all' : 'hide completed'} \u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;ul classname=\u0026quot;tasks\u0026quot;\u0026gt; {tasks.map(task =\u0026gt; ( \u0026lt;task key={task._id} task={task} oncheckboxclick={togglechecked} ondeleteclick={deletetask} /\u0026gt; ))} \u0026lt;/ul\u0026gt; \u0026lt;/fragment\u0026gt; ) : ( \u0026lt;loginform /\u0026gt; )} \u0026lt;/div\u0026gt; .. 7.5 登录表单样式 好了,现在让我们来设计登录表单。\n把你的标签和输入都包在 div 里,这样会更容易用 css 来控制它。\nclient/main.css\n.login-form { display: flex; flex-direction: column; height: 100%; justify-content: center; align-items: center; } .login-form \u0026gt; div { margin: 8px; } .login-form \u0026gt; div \u0026gt; label { font-weight: bold; } .login-form \u0026gt; div \u0026gt; input { flex-grow: 1; box-sizing: border-box; padding: 10px 6px; background: transparent; border: 1px solid #aaa; width: 100%; font-size: 1em; margin-right: 16px; margin-top: 4px; } .login-form \u0026gt; div \u0026gt; input:focus { outline: 0; } .login-form \u0026gt; div \u0026gt; button { background-color: #62807e; } 现在你的登录表单应该是集中的、漂亮的了。\n7.6 服务器启动 从现在起,每个任务都应该有一个拥有者。所以去你的数据库,就像你之前学的那样,从那里删除所有的任务: db.tasks.remove({})。\n修改你的 server/main.js,用你的 meteorite 用户作为所有者添加种子任务。\n确保在这次修改后重启服务器,这样 meteor.startup 部分将再次运行。这可能会以任何方式自动发生,因为要在服务器端代码中进行修改。\nserver/main.js\nimport { meteor } from 'meteor/meteor'; import { accounts } from 'meteor/accounts-base'; import { taskscollection } from '/imports/api/taskscollection'; const inserttask = (tasktext, user) =\u0026gt; taskscollection.insert({ text: tasktext, userid: user._id, createdat: new date(), }); const seed_username = 'meteorite'; const seed_password = 'password'; meteor.startup(() =\u0026gt; { if (!accounts.finduserbyusername(seed_username)) { accounts.createuser({ username: seed_username, password: seed_password, }); } const user = accounts.finduserbyusername(seed_username); if (taskscollection.find().count() === 0) { [ 'first task', 'second task', 'third task', 'fourth task', 'fifth task', 'sixth task', 'seventh task', ].foreach(tasktext =\u0026gt; inserttask(tasktext, user)); } }); 可以看到,我们正在使用一个新的字段,叫做 userid,值是用户的 _id 字段,同时也设置了 credateat 字段。\n7.7 任务所有者 现在你可以在用户界面中通过认证的用户过滤任务。当从 mini mongo 获取任务时,使用用户的 _id 将字段 userid 添加到你的 mongo 选择器。\nimports/ui/app.jsx\n.. const hidecompletedfilter = { ischecked: { $ne: true } }; const userfilter = user ? { userid: user._id } : {}; const pendingonlyfilter = { ...hidecompletedfilter, ...userfilter }; const tasks = usetracker(() =\u0026gt; { if (!user) { return []; } return taskscollection.find( hidecompleted ? pendingonlyfilter : userfilter, { sort: { createdat: -1 }, } ).fetch(); }); const pendingtaskscount = usetracker(() =\u0026gt; { if (!user) { return 0; } return taskscollection.find(pendingonlyfilter).count(); }); .. \u0026lt;taskform user={user} /\u0026gt; .. 同时更新插入的调用,在 taskform 中包括 userid 字段。你应该把用户从 app 组件中传递到 taskform 中。\nimports/ui/taskform.jsx\n.. export const taskform = ({ user }) =\u0026gt; { const [text, settext] = usestate(''); const handlesubmit = e =\u0026gt; { e.preventdefault(); if (!text) return; taskscollection.insert({ text: text.trim(), createdat: new date(), userid: user._id }); settext(''); }; .. 7.8 退出登录 我们还可以通过在应用栏下面显示所有者的用户名来更好地组织我们的任务。你可以在我们的 fragment 开始标签之后加入一个新的 div。\n在这里你可以添加一个 onclick 处理程序来注销用户。这是非常简单的,只要调用 meteor.logout() 就可以了。\nimports/ui/app.jsx\n.. const logout = () =\u0026gt; meteor.logout(); return ( .. \u0026lt;fragment\u0026gt; \u0026lt;div classname=\u0026quot;user\u0026quot; onclick={logout}\u0026gt; {user.username} 🚪 \u0026lt;/div\u0026gt; .. 记得也要给你的用户名加上样式。\nclient/main.css\n.user { display: flex; align-self: flex-end; margin: 8px 16px 0; font-weight: bold; } 唷! 在这一步,你已经做了很多事情:认证了用户,在任务中设置了用户,并为用户提供了一种注销方式。\n你的app应该看起来像这样:\n回顾:你可以在这里检查你的代码在这一步骤结束时应该是怎样的。\n在下一步,我们将开始使用方法,只在检查一些条件后改变数据。\n8 方法 在这一步之前,app的任何用户都可以编辑数据库的任何部分,直接在客户端进行更改。这对于快速的原型设计来说可能不错,但真正的应用需要控制对其数据的访问。\n在 meteor 中,安全地在服务器中进行修改的最简单办法是声明方法,而不是在客户端直接调用 insert、update 或 remove。\n通过方法,你可以验证用户是否经过认证,是否被授权执行某些操作,然后相应地改变数据库。\nmeteor 方法是使用函数 meteor.call 与你的服务器通信的一种方式,你需要提供你的方法的名称和参数。\n你可以在这里阅读更多关于方法的信息。\n8.1 禁用快速原型技术 每个新创建的 meteor 项目都默认安装了不安全的包。\n这个包允许我们从客户端编辑数据库,就像我们上面说的,这对快速制作原型很有用。\n我们需要删除它,因为顾名思义它是不安全的。\nmeteor remove insecure 现在你的app的改变不再起作用了,因为你已经撤销了所有客户端数据库的权限。例如,尝试插入一个新的任务,你会看到 insert failed: access denied 出现在你的浏览器控制台中。\n8.2 添加任务方法 现在你需要定义方法。\n你需要为我们想在客户端执行的每个数据库操作定义一个方法。\n方法应该被定义在客户端和服务器上执行的代码中,以支持优化的用户界面。\n优化的用户界面\n当我们使用 meteor.call 在客户端调用一个方法时,有两件事是并行发生的。\n客户端向服务器发送请求,以便在安全环境下运行该方法。 该方法的模拟直接在客户端运行,试图预测调用的结果。 这意味着,在服务器返回结果之前,一个新创建的任务实际出现在了屏幕上。\n如果结果与服务器的结果一致,一切都保持原样,否则用户界面会被修改以反映服务器的实际状态。\nmeteor 为你做了所有这些工作,你不需要担心这些,但了解正在发生的事情很重要。你可以在这里阅读更多关于 optimistic ui 的内容。\n现在你应该在你的 imports/api 文件夹中添加一个名为 tasksmethods 的新文件。在这个文件中,对于你在客户端进行的每个操作,接下来我们将从客户端调用这些方法,而不是直接使用 mini mongo 操作。\n在 methods 里面,你有一些特殊的属性准备在这个对象上使用,例如,认证用户的 userid。\nimports/api/tasksmethods.js\nimport { check } from 'meteor/check'; import { taskscollection } from './taskscollection'; meteor.methods({ 'tasks.insert'(text) { check(text, string); if (!this.userid) { throw new meteor.error('not authorized.'); } taskscollection.insert({ text, createdat: new date, userid: this.userid, }) }, 'tasks.remove'(taskid) { check(taskid, string); if (!this.userid) { throw new meteor.error('not authorized.'); } taskscollection.remove(taskid); }, 'tasks.setischecked'(taskid, ischecked) { check(taskid, string); check(ischecked, boolean); if (!this.userid) { throw new meteor.error('not authorized.'); } taskscollection.update(taskid, { $set: { ischecked } }); } }); 正如你在代码中所看到的,我们也在使用检查包来确保我们收到了预期的输入类型,这对于确保你确切地知道你在数据库中插入或更新什么是很重要的。\n最后一部分是确保你的服务器正在注册这些方法,你可以在 server/main.js 中导入这个文件,以强制评估。\nserver/main.js\nimport { meteor } from 'meteor/meteor'; import { accounts } from 'meteor/accounts-base'; import { taskscollection } from '/imports/db/taskscollection'; import '/imports/api/tasksmethods'; 看,你不需要从导入中得到任何对象,你只需要要求你的服务器导入文件,然后 meteor.methods 将被评估,并在服务器启动时注册你的方法。\n8.3 实现方法调用 由于你已经定义了方法,你需要将操作集合的代码替换成它们。\n在 taskform 文件中,你应该调用 meteor.call('tasks.insert', text); 而不是 taskscollection.insert。记住,也要修正你导入的方法。\nimports/ui/taskform.jsx\nimport { meteor } from 'meteor/meteor'; import react, { usestate } from 'react'; export const taskform = () =\u0026gt; { const [text, settext] = usestate(''); const handlesubmit = e =\u0026gt; { e.preventdefault(); if (!text) return; meteor.call('tasks.insert', text); settext(''); }; .. }; 可以看到,你的 taskform 组件不需要再接收用户,因为我们在服务器中得到了 userid。\n在 app 文件中,你应该调用 meteor.call('tasks.setischecked', _id, !ischecked); 而不是 taskscollection.update,以及 meteor.call('tasks.remove', _id) 而不是 taskscollection.remove。\n记住也要从你的 \u0026lt;taskform /\u0026gt; 中删除用户属性。\nimports/ui/app.jsx\nimport { meteor } from 'meteor/meteor'; import react, { usestate, fragment } from 'react'; import { usetracker } from 'meteor/react-meteor-data'; import { taskscollection } from '/imports/db/taskscollection'; import { task } from './task'; import { taskform } from './taskform'; import { loginform } from './loginform'; const togglechecked = ({ _id, ischecked }) =\u0026gt; meteor.call('tasks.setischecked', _id, !ischecked); const deletetask = ({ _id }) =\u0026gt; meteor.call('tasks.remove', _id); .. \u0026lt;taskform /\u0026gt; .. 现在你的输入和按钮将重新开始工作。你获得了什么?\n当我们向数据库插入任务时,我们可以安全地验证用户是经过认证的;createdat 字段是正确的;userid 是合法的。 如果我们愿意,我们可以在以后的方法中添加额外的验证逻辑。 我们的客户端代码与数据库逻辑更加隔离。没有在事件处理程序中发生大量的处理逻辑,而是在任何地方都有可调用的方法。 8.4 api和db文件夹 我们想在这里花点时间思考一下,集合文件所在的文件夹是api,但api在你的项目中意味着服务器和客户端之间的通信层,但集合不再执行这个角色了。所以你应该把你的 taskscollection 文件移到一个叫db的新文件夹里。\n这个改变不是必须的,但建议保持我们的命名与实际情况一致。\n记住要修复你的导入,你有4个导入到 taskscollection 的文件,在以下文件中:\nimports/api/tasksmethods.js imports/ui/taskform.jsx imports/ui/app.jsx server/main.js 它们应该从 import { taskscollection } from '/imports/api/taskscollection'; 改为 import { taskscollection } from '/imports/db/taskscollection';。\n你的app看起来应该和以前一样,因为在这一步中,我们没有改变任何用户可见的内容。你可以使用 meteor devtools 来查看送往服务器的信息和返回的结果,这些信息可以在ddp标签页中找到。\nddp 是 meteor 通信层背后的协议,你可以在这里了解更多关于它的信息。\n我们建议你改变对错误类型的检查调用,以产生一些错误,这样你就可以理解在这些情况下会发生什么。\n回顾:你可以在这里检查你的代码在这一步骤结束时应该是怎样的。\n在下一步中,我们将开始使用 publication,只发布每个案例中需要的数据。\n9 发布 现在我们已经把所有的app的敏感代码移到了方法中,我们需要了解 meteor 的另一半涉及安全内容。到目前为止,我们一直假设整个数据库存在于客户端,这意味着如果我们调用 tasks.find(),我们将得到集合中的每个任务。如果我们app的用户想要存储隐私敏感的数据,这就不好了。我们需要一种方法来控制 meteor 将哪些数据发送到客户端的数据库。\n9.1 自动发布 就像上一步中的 insecure 一样,所有新的 meteor 应用程序开始时都会自动发布包,它会自动将所有数据库内容同步到客户端。所以你应该删除它:\nmeteor remove autopublish 当应用程序刷新时,任务列表将是空的。如果没有自动发布包,我们将不得不明确指定服务器向客户端发送的内容。meteor 中实现这个的函数是 meteor.publish 和 meteor.subscribe。\nmeteor.publish:允许将数据从服务器发布到客户端。 meteor.subscribe:允许客户端的代码向客户端索取数据。 9.2 任务发布 你需要首先在你的服务器上添加一个 publication,这个 publication 应该发布来自认证用户的所有任务。在方法中,你也可以在发布功能中使用 this.userid 来获得认证用户的身份。\n在 api 文件夹中创建一个名为 taskspublications.js 的新文件。\nimports/api/taskspublications.js\nimport { meteor } from 'meteor/meteor'; import { taskscollection } from '/imports/db/taskscollection'; meteor.publish('tasks', function publishtasks() { return taskscollection.find({ userid: this.userid }); }); 由于你是在这个函数里面使用的,所以不应该使用箭头函数(=\u0026gt;),因为箭头函数不提供上下文。你需要以传统的方式,通过函数关键字使用这个函数。\n最后一部分是确保你的服务器注册这个 publication。你可以这样做,在 server/main.js 中导入这个文件,以强制评估。\nserver/main.js\nimport { meteor } from 'meteor/meteor'; import { accounts } from 'meteor/accounts-base'; import { taskscollection } from '/imports/db/taskscollection'; import '/imports/api/tasksmethods'; import '/imports/api/taskspublications'; 9.3 任务订阅 然后我们可以在客户端订阅该发布。\n由于我们想要接收来自该发布的变化,我们将在一个 usetracker 钩子中订阅它。\n这也是我们重构代码的好时机,使用一个 usetracker 从 taskscollection 获取数据。\nimports/ui/app.jsx\n.. const { tasks, pendingtaskscount, isloading } = usetracker(() =\u0026gt; { const nodataavailable = { tasks: [], pendingtaskscount: 0 }; if (!meteor.user()) { return nodataavailable; } const handler = meteor.subscribe('tasks'); if (!handler.ready()) { return { ...nodataavailable, isloading: true }; } const tasks = taskscollection.find( hidecompleted ? pendingonlyfilter : userfilter, { sort: { createdat: -1 }, } ).fetch(); const pendingtaskscount = taskscollection.find(pendingonlyfilter).count(); return { tasks, pendingtaskscount }; }); .. 9.4 加载状态 你也应该为你的app添加一个加载状态。也就是说,当订阅数据还没有准备好的时候,你应该向你的用户告知这一点。要发现订阅是否准备好了,你应该得到订阅调用的返回值,它是一个包含订阅状态的对象,包括将返回一个布尔值的ready函数。\nimports/ui/app.jsx\n.. \u0026lt;div classname=\u0026quot;filter\u0026quot;\u0026gt; \u0026lt;button onclick={() =\u0026gt; sethidecompleted(!hidecompleted)}\u0026gt; {hidecompleted ? 'show all' : 'hide completed'} \u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; {isloading \u0026amp;\u0026amp; \u0026lt;div classname=\u0026quot;loading\u0026quot;\u0026gt;loading...\u0026lt;/div\u0026gt;} \u0026lt;ul classname=\u0026quot;tasks\u0026quot;\u0026gt; .. 让我们也调整一下这种加载的样式。\n.loading { display: flex; flex-direction: column; height: 100%; justify-content: center; align-items: center; font-weight: bold; } 一旦你这样做了,所有的任务将重新出现。\n在服务器上调用 meteor.publish 会注册一个名为 tasks 的发布。当 meteor.subscribe 在客户端被调用时,客户端就会订阅该发布的所有数据。在这个例子中,它是数据库中的所有任务,用于认证用户。\n9.5 检查用户权限 只有任务的所有者才能改变某些内容。你应该改变你的方法,以检查被验证的用户是否是创建任务的同一个用户。\nimports/api/tasksmethods.js\n.. 'tasks.remove'(taskid) { check(taskid, string); if (!this.userid) { throw new meteor.error('not authorized.'); } const task = taskscollection.findone({ _id: taskid, userid: this.userid }); if (!task) { throw new meteor.error('access denied.'); } taskscollection.remove(taskid); }, 'tasks.setischecked'(taskid, ischecked) { check(taskid, string); check(ischecked, boolean); if (!this.userid) { throw new meteor.error('not authorized.'); } const task = taskscollection.findone({ _id: taskid, userid: this.userid }); if (!task) { throw new meteor.error('access denied.'); } taskscollection.update(taskid, { $set: { ischecked, }, }); }, .. 为什么不在客户端返回其他用户的任务很重要呢?\n这很重要,因为任何人都可以使用浏览器控制台调用 meteor 方法。你可以使用你的 devtools 控制台标签来测试,然后输入并点击回车:meteor.call('tasks.remove', 'xtptsnecc3kpumndu'); 。如果你从 remove 方法中移除验证,并从数据库中传递一个有效的任务 _id,你将能够移除它。\n回顾:你可以在这里检查你的代码在这一步骤结束时应该是怎样的。\n在下一步,我们将在移动环境中以本地应用程序的形式运行该应用程序。\n10 在移动端运行 目前,windows 上的 meteor 不支持在移动端构建。如果你在 windows 上使用 meteor,你应该跳过这个步骤。\n到目前为止,我们只在网页浏览器中构建我们的app并进行测试,但 meteor 的设计是为了在不同的平台上工作 \u0026ndash; 你的简单的todo列表网站只需几个命令就可以变成一个 ios 或 android app。\nmeteor 使建立移动应用所需的所有工具变得简单,但下载所有程序可能需要一段时间 \u0026ndash; 对于android来说,下载量约为300mb;对于ios来说,你需要安装xcode,这大约是2gb。如果你不想等待下载这些工具,请随时跳到下一步。\n重要提示:手机的设置和设定是非常动态的。如果你发现下面的任何步骤没有按照规定工作,或者任何链接的文件不是最新的,请打开一个issue,我们将更新它。如果你知道应该如何改变,你也可以开prs。\n10.1 ios 模拟器 如果你有一台mac,你可以在ios模拟器内运行你的应用程序。\n按照这个指南来安装ios的所有开发先决条件。\n当你完成后,输入:\nmeteor add-platform ios meteor run ios 你会看到ios模拟器弹出,里面运行着你的应用程序。\n10.2 安卓模拟器 按照这个指南来安装安卓的所有开发先决条件。\n当你完成所有安装后,输入:\nmeteor add-platform android 在你同意许可条款后,输入:\nmeteor run android 在一些初始化之后,你会看到一个安卓模拟器弹出,在一个本地安卓包装内运行你的应用程序。该模拟器可能有点慢,所以如果你想看看使用你的app的真实情况,你应该在实际设备上运行它。\n10.3 安卓设备 首先,完成上述所有步骤,在你的系统上设置安卓工具。然后,确保你在手机上启用了usb调试功能,并且用usb线将其插入电脑。另外,在设备上运行之前,你必须退出安卓模拟器。\n然后,运行以下命令:\nmeteor run android-device 该app将被构建并安装在你的设备上\n10.4 iphone or ipad 这需要一个苹果开发者账号。\n如果你有一个苹果开发者账号,你也可以在ios设备上运行你的app。运行以下命令:\nmeteor run ios-device 这将为你的ios应用程序打开xcode的项目。你可以使用xcode在任何设备或xcode支持的模拟器上启动该应用程序。\n回顾:你可以在这里检查你的代码在这一步骤结束时应该是怎样的。\n在下一步,我们将添加自动测试。\n11 测试 现在我们已经为app创建了一些功能,让我们添加一个测试,以确保我们不会倒退和以我们期望的方式工作。\n我们将写一个测试,执行一个方法,并验证它是否正常工作。\n11.1 安装依赖 我们将为 mocha javascript 测试框架添加一个测试驱动,以及一个测试断言库。\nmeteor add meteortesting:mocha meteor npm install --save-dev chai 现在我们可以在\u0026quot;测试模式\u0026quot;下运行我们的应用程序,方法是运行 meteor test 并指定一个测试驱动包(你需要停止常规应用程序的运行,或者用 -port xyz 指定一个备用端口)。\ntest_watch=1 meteor test --driver-package meteortesting:mocha 它应该输出类似这样的内容:\nsimple-todos-react ✓ package.json has correct name ✓ server is not client 2 passing (10ms) 这两个测试是从哪里来的?每一个新的 meteor 应用程序都包括一个 test/main.js 模块,其中包含了几个使用描述、断言风格的测试例子,这些风格在 mocha 等测试框架中很流行。\nmeteor mocha 的集成是由社区维护的,你可以在这里阅读更多信息。\n当你用这些选项运行时,你也可以在浏览器中的应用程序url中看到测试的结果:\n11.2 脚手架测试 然而,如果你希望将你的测试分成多个模块,你也可以这样做:添加一个新的测试模块,名为 imports/api/tasksmethods.test.js。\nimports/api/tasksmethods.tests.js\nimport { meteor } from 'meteor/meteor'; if (meteor.isserver) { describe('tasks', () =\u0026gt; { describe('methods', () =\u0026gt; { it('can delete owned task', () =\u0026gt; {}); }); }); } 并在 test/main.js 中导入它,比如导入 '/imports/api/tasksmethods.test.js'; 并删除该文件中的其他所有内容,因为我们不需要这些测试。\ntests/main.js\nimport '/imports/api/tasksmethods.tests.js'; 11.3 数据准备 在任何测试中,你需要确保数据库在开始之前处于我们期望的状态。你可以使用 mocha 的 beforeeach 结构来轻松做到这一点。\nimports/api/tasksmethods.tests.js\nimport { meteor } from 'meteor/meteor'; import { random } from 'meteor/random'; import { taskscollection } from '/imports/db/taskscollection'; if (meteor.isserver) { describe('tasks', () =\u0026gt; { describe('methods', () =\u0026gt; { const userid = random.id(); let taskid; beforeeach(() =\u0026gt; { taskscollection.remove({}); taskid = taskscollection.insert({ text: 'test task', createdat: new date(), userid, }); }); }); }); } 在这里,你正在创建一个单一的任务,它与一个随机的 userid 相关联,这个 userid 在每次测试运行中都会不同。\n11.4 测试任务删除 现在你可以编写测试,以该用户的身份调用 tasks.remove 方法,并验证任务是否被删除,因为你要测试一个方法并模拟认证的用户。你可以安装这个实用程序包,使你的生活更轻松:\nmeteor add quave:testing imports/api/tasks.tests.js\nimport { meteor } from 'meteor/meteor'; import { random } from 'meteor/random'; import { mockmethodcall } from 'meteor/quave:testing'; import { assert } from 'chai'; import { taskscollection } from '/imports/db/taskscollection'; import '/imports/api/tasksmethods'; if (meteor.isserver) { describe('tasks', () =\u0026gt; { describe('methods', () =\u0026gt; { const userid = random.id(); let taskid; beforeeach(() =\u0026gt; { taskscollection.remove({}); taskid = taskscollection.insert({ text: 'test task', createdat: new date(), userid, }); }); it('can delete owned task', () =\u0026gt; { mockmethodcall('tasks.remove', taskid, { context: { userid } }); assert.equal(taskscollection.find().count(), 0); }); }); }); } 记得从 chai 导入 assert (import { assert } from \u0026lsquo;chai\u0026rsquo;;)\n11.5 更多的测试 你可以添加你想要的测试。下面的代码提供了一些其他的测试,可以帮助你更多的了解测试什么及如何测试:\nimports/api/tasks.tests.js\nimport { meteor } from 'meteor/meteor'; import { random } from 'meteor/random'; import { mockmethodcall } from 'meteor/quave:testing'; import { assert } from 'chai'; import { taskscollection } from '/imports/db/taskscollection'; import '/imports/api/tasksmethods'; if (meteor.isserver) { describe('tasks', () =\u0026gt; { describe('methods', () =\u0026gt; { const userid = random.id(); let taskid; beforeeach(() =\u0026gt; { taskscollection.remove({}); taskid = taskscollection.insert({ text: 'test task', createdat: new date(), userid, }); }); it('can delete owned task', () =\u0026gt; { mockmethodcall('tasks.remove', taskid, { context: { userid } }); assert.equal(taskscollection.find().count(), 0); }); it(`can't delete task without an user authenticated`, () =\u0026gt; { const fn = () =\u0026gt; mockmethodcall('tasks.remove', taskid); assert.throw(fn, /not authorized/); assert.equal(taskscollection.find().count(), 1); }); it(`can't delete task from another owner`, () =\u0026gt; { const fn = () =\u0026gt; mockmethodcall('tasks.remove', taskid, { context: { userid: 'somebody-else-id' }, }); assert.throw(fn, /access denied/); assert.equal(taskscollection.find().count(), 1); }); it('can change the status of a task', () =\u0026gt; { const originaltask = taskscollection.findone(taskid); mockmethodcall('tasks.setischecked', taskid, !originaltask.ischecked, { context: { userid }, }); const updatedtask = taskscollection.findone(taskid); assert.notequal(updatedtask.ischecked, originaltask.ischecked); }); it('can insert new tasks', () =\u0026gt; { const text = 'new task'; mockmethodcall('tasks.insert', text, { context: { userid }, }); const tasks = taskscollection.find({}).fetch(); assert.equal(tasks.length, 2); assert.istrue(tasks.some(task =\u0026gt; task.text === text)); }); }); }); } 如果你再次运行测试命令或之前让它在观察模式下运行,你应该看到以下输出:\ntasks methods ✓ can delete owned task ✓ can't delete task without an user authenticated ✓ can't delete task from another owner ✓ can change the status of a task ✓ can insert new tasks 5 passing (70ms) 为了方便输入测试命令,你可能想在package.json文件的scripts部分添加一个速记。\n事实上,新的meteor应用程序带有一些预先配置好的npm脚本,欢迎你使用或修改它们。\n标准的meteor npm test命令运行以下命令:\nmeteor test --once --driver-package meteortesting:mocha 这个命令适合在持续集成(ci)环境中运行,如 travis ci 或 circleci,因为它只运行你的服务器端测试。如果所有的测试都通过了,则以0退出。\n如果你想在开发应用程序时运行测试(并在开发服务器重新启动时重新运行),考虑使用 meteor npm run test-app,这相当于:\ntest_watch=1 meteor test --full-app --driver-package meteortesting:mocha 这和前面的命令几乎一样,只是它也会正常加载你的应用程序代码(由于--full-app),允许你在浏览器中与你的应用程序交互,同时运行客户端和服务器测试。\n你可以用 meteor 测试做更多的事情! 你可以在 meteor 指南关于测试的文章中阅读更多内容。\n回顾:你可以在这里检查你的代码在这一步骤结束时应该是怎样的。\n在下一步,我们将把你的应用程序部署到 galaxy,这是 meteor 应用程序的最佳托管,由 meteor 背后的同一个团队开发。\n12 部署 现在你的应用已经测试完毕,准备发布,以便任何人都可以使用它。\n运行你的 meteor 应用程序的最好地方是 galaxy。galaxy 提供免费的部署。很酷,对吗?\n如果你在这一步有任何问题,应该给 galaxy 支持部门发邮件,他们会帮助你。把你的信息发到 support@meteor.com,尽量详细解释问题是什么,你将会尽快得到帮助。同时确保在主题中包括 react tutorial,这样对方就知道你是从哪里来的了。\n12.1 创建账号 你有一个 meteor cloud 账号吗?没有吗?好吧,让我们来解决这个问题。\n进入cloud.meteor.com,你将会看到一个像这样的表单:\n在github上注册,然后从那里开始。它只要求你提供用户名和密码,你将需要这些来部署你的应用程序。\n完成后,你的账户就创建好了。你可以用这个账户访问 atmospherejs.com、论坛和更多的内容,包括 galaxy 的免费部署。\n12.2 部署应用 现在你已经准备好部署了,确保在部署前运行 meteor npm install,以确保所有的依赖项都已安装。\n你还需要选择一个子域名来发布你的应用程序。我们将使用主域名 meteorapp.com,这是免费的,包含在任何 galaxy 计划中。\n在这个例子中,我们将使用 react-tutorial.meteorapp.com,但请确保你选择了不同的,否则你将收到一条错误信息,说它已经被使用了。\n你可以在这里学习如何在 galaxy 上使用自定义域名。自定义域名从 essentials 计划开始可用。\n运行部署命令:\nmeteor deploy react-tutorial.meteorapp.com --free --mongo 确保你用一个想作为子域名的自定义名称来替换 react-tutorial。\n你将会看到一个类似这样的日志:\nmeteor deploy react-tutorial.meteorapp.com --free --mongo talking to galaxy servers at https://us-east-1.galaxy-deploy.meteor.com preparing to build your app... preparing to upload your app... uploaded app bundle for new app at react-tutorial.meteorapp.com. galaxy is building the app into a native image. waiting for deployment updates from galaxy... building app image... deploying app... you have successfully deployed the first version of your app. *** your mongodb shared instance database uri will be here as well *** for details, visit https://galaxy.meteor.com/app/react-tutorial.meteorapp.com 这个过程通常需要5分钟左右,但这取决于你的网速,因为它要把应用程序包发送到 galaxy 服务器。\ngalaxy 构建了一个新的 docker 镜像,其中包含了你的应用包,然后用它来部署容器,阅读更多。\n你可以在 galaxy 上查看日志,包括 galaxy 正在构建的 docker 镜像和部署的部分。\n12.3 访问并享受吧 现在你应该能够访问你的 galaxy 仪表板,网址是 https://galaxy.meteor.com/app/react-tutorial.meteorapp.com (用你的子域替换 react-tutorial)。\n当然,也能够在你选择的域中访问和使用你的应用程序,在我们的例子中是 react-tutorial.meteorapp.com。祝贺你!\n我们部署了在美国运行的 galaxy 系统(us-east-1),我们也有在世界其他地区运行的 galaxy 系统,请查看这里的列表。\n这很了不起,你的 meteor 应用程序运行在 galaxy 上,准备好被世界上的任何人使用吧!\n回顾:你可以在这里检查你的代码在这一步骤结束时应该是怎样的。\n在下一步,我们将为你提供一些继续开发你的应用程序的想法,以及接下来要看到的更多内容。\n13 后续 祝贺你新建立了 meteor 应用程序,它正在 galaxy 上运行! 哇!\n你的应用程序目前支持为认证的用户添加私人任务。\n对新功能的想法:\n将已完成的任务样式化,使其状态更加明显 在登录表单中也添加创建新用户的选项 与他人分享任务 在 galaxy 上要做的事情:\n在 galaxy 上检查你的日志,看这里或读这里 设置你的免费ssl证书,这样你就可以使用https了,阅读这里 设置你的通知,阅读这里 根据需求自动扩展你的应用程序,观看这里或阅读这里 在apm上进行专业操作并观察你的指标,阅读这里 查看所有的 galaxy 指南,了解更多 这里有一些关于你下一步可以去哪里的选择:\n阅读meteor指南,了解最佳实践和有用的社区包 查阅完整的文档 ","date":"2021-10-15","permalink":"https://kinneyzhang.github.io/post/meteor-react-docs/","summary":"文档目录结构 1 创建app 1.1 安装 Meteor 1.2 创建 Meteor 项目 1.3 创建任务组件 1.4 创建任务示例 1.5 渲染任务示例 1.6 移动端外观 1.7 更换热模块 2 集合 2.1 创建任务集合 2.2 初始化任务集 2.3 渲染任务集合 3","title":"meteor react 文档"},{"content":"我读过的书虽然不多,但与阅读相处的经验还是挺丰富的。从小就想当个作家,借个这个理由,小学的时候倒是看过一些书。虽然现在,在记忆中只留下些故事的情节片段,但至少让我体验到了阅读的一些滋味:感动的,有趣的,揪心的,平静的感觉。稍微大了些,课业重了,能够分散的精力不多。虽然自己的时间是有的,但却很难静下心来看书。\n无论是出于附庸风雅,还是当作家的理由,我多次尝试重拾阅读,看各种up主的阅读视频,学习如何作笔记。每次我以为自己准备好进入阅读的殿堂时,最后都在大门口撞个头破血流。头破血流后自然会打击自信心,但也顺便得到了些经验:\n让阅读成为生活的一部分 习惯培养的初期,越是简单越好。对于没有阅读习惯的人来说,忘记阅读本身是重要的。不要想着阅读中能够收获什么,如何作笔记等等。如果没有阅读的习惯,其余都是空谈。我们可以精心的作一次、两次的笔记,但不会长久。对于新手,做笔记会成为负担,不利于阅读习惯的形成。最好的状态是:随手拿起一本书就读,不去考虑得失。养成阅读的习惯后,再对自己提更高的要求:做笔记,思考和总结。\n学会做标记 无论是阅读故事还是严肃文学,过程中总会遇到有共鸣的文字和触发自己的思考,不要放过这些文字!这时候,我们或许会纠结要不要停下阅读,记录这些文字和思考,还是做个标记后继续阅读?\n我个人的建议是后者。因为自己思考过的东西是很难忘记的,标记下这些触发思考的文字可以不打断阅读的心流。任何你觉得有共鸣的文字都可以标记,别在这种问题上纠结。读完后,再去考虑标记的部分值不值得作笔记和进一步延伸思考。\n学会做笔记 做读书笔记的形式多样,但一定要有自己的想法。可以摘录句子,写上对此的思考;也可以从全篇出发,写些总结性的文字。这要根据自己的文字功底决定了,写不成文章,就写些只言片语。思考的内容该写些什么?如果你有这个问题,那不妨想到什么就写什么。写多了,便会有自己的总结:1.联想到的类似的(相反的)观点;2.让自己产生共鸣的经历;3.进一步的发散思考,对问题的探究\u0026hellip;\n阅读,最好不要纸上谈兵,从阅读中得到的思考最好能够回归到现实生活中的实践。所以第三点的发散思考尤为重要,这就是为什么我们总会听到诸如:一本书,一句话改变了人的一生\u0026hellip;\n暂时就写这三点。随着阅读经验的丰富,一定会有更多的补充。\n","date":"2021-08-29","permalink":"https://kinneyzhang.github.io/post/my-view-of-reading/","summary":"我读过的书虽然不多,但与阅读相处的经验还是挺丰富的。从小就想当个作家,借个这个理由,小学的时候倒是看过一些书。虽然现在,在记忆中只留下些故事的情节片段,但至少让","title":"关于阅读"},{"content":"介绍 ewoc 是 emacs\u0026rsquo;s widget for object collections的简写,它可以根据lisp对象的结构绘制和更新buffer的文本,就像mvc设计模式中的视图层。其中,生成的buffer的文本分为三个部分:特定的头部文本,lisp对象代表的数据元素的文本,特定的底部文本。一个ewoc对象的信息包含在以下内容中:\n用于产生文本的buffer buffer中文本的起始位置 头部和底部文本的内容 一个双向链接的结点链,每一个结点包含: 一个数据元素(单个lisp对象) 结点链中上一个和下一个结点的链接 一个将数据元素的文本插入到buffer中的打印函数 使用 ewoc-create 来定义一个ewoc对象,然后用其他的ewoc函数构建结点的结构并在buffer中显示。一旦在buffer中显示了文本,便可使用其他函数负责buffer的光标位置和结点间的数据通信。\n让结点包含数据元素就像给变量设置值。通常结点包含数据元素的行为发生在将结点加入到ewoc对象过程中。可以通过下面两个函数获取和设置数据元素的值:\n(ewoc-data node) ⇒ value (ewoc-set-data node new-value) ⇒ new-value 也可以使用lisp对象(list or vector)或其他结构的索引作为数据元素的值。\n当数据改变时,buffer中文本会相应的更新。使用 ewoc-update 函数更新所有结点,或者用 ewoc-invalidate ,使用 ewoc-map 函数给所有的结点增加条件判断。同时,可以使用 ewoc-delete 或 ewoc-filter 函数删除无效的结点并设置新的结点。删除一个结点的同时会删掉该结点在buffer中展示的文本。\n函数 这一部分的术语中, ewoc 和 node 分别代表前面提到的两种结构,而 data 代表作为数据元素的lisp对象。三者的关系是:ewoc包含node,node包含data。下面详细介绍ewoc的每个函数。\n— ewoc-create pretty-printer \u0026amp;optional header footer nosep\n创建并返回一个不包含结点的ewoc对象。 pretty-printer 是包含一个数据元素参数的函数,该数据元素的文本值将被插入到buffer中。(使用 insert 函数插入,不要用 insert-before-markers ,因为它对ewoc包的内部机制有干扰)\n通常,该函数会自动在头部文本,底部文本和每一个node的文本后插入新的一行。设置 nosep 参数为 non-nil 可以不插入新行,这在需要将整个ewoc的文本显示在一行时很有用。ewoc创建时会在当前buffer下维护文本,所以在调用创建函数前要先切换到目标buffer。\n— ewoc-buffer ewoc\n返回维护ewoc文本的buffer。\n— ewoc-get-hf ewoc\n返回一个由头部文本和底部文本构成的 cons cell (header . footer)。\n— ewoc-enter-first ewoc data\n— ewoc-enter-last ewoc data\n分别在ewoc的结点链的开头和结尾添加包含数据元素的新结点。\n— ewoc-enter-before ewoc node data\n— ewoc-enter-after ewoc node data\n分别在指定结点的前面和后面添加一个包含数据元素的新结点。\n— ewoc-prev ewoc node\n— ewoc-next ewoc node\n分别返回指定结点的前一个和后一个结点。\n— ewoc-nth ewoc n\n返回以0开始的索引n的结点,n为负值时从最后开始索引。n超出范围时返回nil。\n— ewoc-data node\n获取并返回node包含的数据元素。\n— ewoc-set-data node\n设置node包含的数据元素值为data。\n— ewoc-locate ewoc \u0026amp;optional pos guess\n确定并返回包含光标位置的结点。如果ewoc没有结点,返回nil。如果 pos 在第一个结点之前,返回第一个结点;在最后一个结点之后,返回最后一个结点。可选参数 guess 是有可能在pos附近的结点,它不会改变函数的结果但会让函数执行的更快。\n— ewoc-location node\n返回结点的起始位置。\n— ewoc-goto-prev ewoc arg\n— ewoc-goto-next ewoc arg\n分别移动光标到上arg个或下arg个结点。如果已经在第一个结点或ewoc为空, ewoc-goto-prev 不移动光标并返回nil,同样 ewoc-goto-next 不超过最后一个结点。除了以上特殊情况,函数返回移动到的结点。\n— ewoc-goto-node ewoc node\n移动光标到结点的起始位置。\n— ewoc-refresh ewoc\n重新生成ewoc的文本。执行过程是:删除header和footer之间的文本,然后为每一个结点依次调用 pretty-printer 函数生成文本。\n— ewoc-invalidate ewoc \u0026amp;rest nodes\n和 ewoc-refresh 类似,但只更新 nodes 列表内的结点而不是所有结点。\n— ewoc-delete ewoc \u0026amp;rest nodes\n删除所有 nodes 列表内的结点。\n— ewoc-filter ewoc predicate \u0026amp;rest args\n为ewoc中的每一个数据元素调用 predicate 函数,删除断言为nil的结点。 args 是传递给断言函数的参数。\n— ewoc-collect ewoc predicate \u0026amp;rest args\n为ewoc中的每一个数据元素调用 predicate 函数,返回断言为non-nil的元素列表。元素在列表中的顺序和buffer中一致。 args 是传递给断言函数的参数。\n— ewoc-map map-function ewoc \u0026amp;rest args\n为ewoc中的每一个数据元素调用 map-function , 更新map函数返回为non-nil的结点。 args 是传递给map函数的参数。\n例子 下面是使用ewoc实现的显示颜色组成的例子,颜色组成由buffer中的三个整数组成的向量表示。\n(setq colorcomp-ewoc nil colorcomp-data nil colorcomp-mode-map nil colorcomp-labels [\u0026quot;red\u0026quot; \u0026quot;green\u0026quot; \u0026quot;blue\u0026quot;]) (defun colorcomp-pp (data) (if data (let ((comp (aref colorcomp-data data))) (insert (aref colorcomp-labels data) \u0026quot;\\t: #x\u0026quot; (format \u0026quot;%02x\u0026quot; comp) \u0026quot; \u0026quot; (make-string (ash comp -2) ?#) \u0026quot;\\n\u0026quot;)) (let ((cstr (format \u0026quot;#%02x%02x%02x\u0026quot; (aref colorcomp-data 0) (aref colorcomp-data 1) (aref colorcomp-data 2))) (samp \u0026quot; (sample text) \u0026quot;)) (insert \u0026quot;color\\t: \u0026quot; (propertize samp 'face `(foreground-color . ,cstr)) (propertize samp 'face `(background-color . ,cstr)) \u0026quot;\\n\u0026quot;)))) (defun colorcomp (color) \u0026quot;allow fiddling with color in a new buffer. the buffer is in color components mode.\u0026quot; (interactive \u0026quot;scolor (name or #rgb or #rrggbb): \u0026quot;) (when (string= \u0026quot;\u0026quot; color) (setq color \u0026quot;green\u0026quot;)) (unless (color-values color) (error \u0026quot;no such color: %s\u0026quot; color)) (switch-to-buffer (generate-new-buffer (format \u0026quot;originally: %s\u0026quot; color))) (kill-all-local-variables) (setq major-mode 'colorcomp-mode mode-name \u0026quot;color components\u0026quot;) (use-local-map colorcomp-mode-map) (erase-buffer) (buffer-disable-undo) (let ((data (apply 'vector (mapcar (lambda (n) (ash n -8)) (color-values color)))) (ewoc (ewoc-create 'colorcomp-pp \u0026quot;\\ncolor components\\n\\n\u0026quot; (substitute-command-keys \u0026quot;\\n\\\\{colorcomp-mode-map}\u0026quot;)))) (set (make-local-variable 'colorcomp-data) data) (set (make-local-variable 'colorcomp-ewoc) ewoc) (ewoc-enter-last ewoc 0) (ewoc-enter-last ewoc 1) (ewoc-enter-last ewoc 2) (ewoc-enter-last ewoc nil))) 通过定义改变 colorcomp-data 的值,完成选择过程和按键绑定,这个例子可以拓展成一个颜色选择的组件(mvc模式中的模型)。\n(defun colorcomp-mod (index limit delta) (let ((cur (aref colorcomp-data index))) (unless (= limit cur) (aset colorcomp-data index (+ cur delta))) (ewoc-invalidate colorcomp-ewoc (ewoc-nth colorcomp-ewoc index) (ewoc-nth colorcomp-ewoc -1)))) (defun colorcomp-r-more () (interactive) (colorcomp-mod 0 255 1)) (defun colorcomp-g-more () (interactive) (colorcomp-mod 1 255 1)) (defun colorcomp-b-more () (interactive) (colorcomp-mod 2 255 1)) (defun colorcomp-r-less () (interactive) (colorcomp-mod 0 0 -1)) (defun colorcomp-g-less () (interactive) (colorcomp-mod 1 0 -1)) (defun colorcomp-b-less () (interactive) (colorcomp-mod 2 0 -1)) (defun colorcomp-copy-as-kill-and-exit () \u0026quot;copy the color components into the kill ring and kill the buffer. the string is formatted #rrggbb (hash followed by six hex digits).\u0026quot; (interactive) (kill-new (format \u0026quot;#%02x%02x%02x\u0026quot; (aref colorcomp-data 0) (aref colorcomp-data 1) (aref colorcomp-data 2))) (kill-buffer nil)) (setq colorcomp-mode-map (let ((m (make-sparse-keymap))) (suppress-keymap m) (define-key m \u0026quot;i\u0026quot; 'colorcomp-r-less) (define-key m \u0026quot;o\u0026quot; 'colorcomp-r-more) (define-key m \u0026quot;k\u0026quot; 'colorcomp-g-less) (define-key m \u0026quot;l\u0026quot; 'colorcomp-g-more) (define-key m \u0026quot;,\u0026quot; 'colorcomp-b-less) (define-key m \u0026quot;.\u0026quot; 'colorcomp-b-more) (define-key m \u0026quot; \u0026quot; 'colorcomp-copy-as-kill-and-exit) m)) 参考 gnu emacs lisp reference manual - abstract-display\n","date":"2020-08-27","permalink":"https://kinneyzhang.github.io/post/emacs-lisp-ewoc/","summary":"介绍 Ewoc 是 Emacs\u0026rsquo;s Widget for Object Collections的简写,它可以根据lisp对象的结构绘制和更新buffer的文本,就像MVC设计模式中的视图层。其中,生成的buffer的","title":"emacs lisp - ewoc使用介绍"},{"content":"geekinney 说: \u0026ldquo;文件管理\u0026quot;是每个操作系统最基本的功能。当我们使用计算机时,不可避免的要和文件打交道,比如:文件的创建、重命名、拷贝、移动、删除、搜索… 通常,我们通过鼠标来完成这些操作。但你有没有想过,这种鼠标点击和拖动的操作实际是非常低效的,我们在鼠标上浪费了大量的时间!\n此刻,linux用户加入了会话:“我们不用鼠标,我们用命令行。” 诚然,linux命令行确实比鼠标高效很多。但你有没有想过存在一种更加便捷的方式, 结合了gui的直观和tui的高效,将每种命令换成一个或简单的几个按键来完成 ?这就是我要介绍的emacs文件管理系统。\n在阅读下文之前,请先在 .emacs.d 目录下的 elisp 文件夹中创建 init-filemanage.el 文件, 我们将用这个文件来存储所有与文件管理相关的配置。\n基础 文件目录结构 emacs中浏览文件目录结构主要使用内置的 dired-mode 及其拓展 dired-x 。\n由于是内置的package,无需额外安装。在emacs中直接通过按键 c-x d (调用 dired 命令)后输入要打开的目录后回车,即可打开对应的目录结构。默认情况下,emacs将当前buffer的所在的目录作为打开的目录。在改变目录路径时可以使用 tab 键补全。\n或者,通过按键 c-x c-j (调用 dired-jump 命令,这是dired-x的功能)直接打开当前buffer文件所在的目录。dired目录结构如下图:\n文件导航 打开目录结构后,使用 n/p 或 c-n/c-p 或方向键都可以在文件和目录间移动。在目录上按回车键会打开新的目录,在文件上按回车键会打开该文件。在 .. 上按回车键可以跳转到上级目录。除了这种方式,还可以按 c-x c-j 回到上级目录。其他的按键操作:\nspc: 向下移动一行 ^: 跳转到上一目录级 \u0026lt;: 移到上一个目录行,跳过文件行 \u0026gt;: 移到下一个目录行,跳过文件行 j: 移到指定文件行 文件浏览 a: 按照正则搜索文件,列出搜索结果 v: 以view-mode(只读)浏览文件内容 o: 在另一个窗口打开文件或目录 i: 在当前窗口插入子目录 s: 对列表按名字或日期排序 一些文件在emacs中的浏览体验不是很好,可以使用系统默认程序浏览文件,将下面这段代码(来自xah-emacs)加入 init-filemanage.el 文件中:\n(defun xah-open-in-external-app (\u0026amp;optional @fname) \u0026quot;open the current file or dired marked files in external app. the app is chosen from your os's preference. when called in emacs lisp, if @fname is given, open that. url `http://ergoemacs.org/emacs/emacs_dired_open_file_in_ext_apps.html' version 2019-11-04\u0026quot; (interactive) (let* (($file-list (if @fname (progn (list @fname)) (if (string-equal major-mode \u0026quot;dired-mode\u0026quot;) (dired-get-marked-files) (list (buffer-file-name))))) ($do-it-p (if (\u0026lt;= (length $file-list) 5) t (y-or-n-p \u0026quot;open more than 5 files? \u0026quot;)))) (when $do-it-p (cond ((string-equal system-type \u0026quot;windows-nt\u0026quot;) (mapc (lambda ($fpath) (w32-shell-execute \u0026quot;open\u0026quot; $fpath)) $file-list)) ((string-equal system-type \u0026quot;darwin\u0026quot;) (mapc (lambda ($fpath) (shell-command (concat \u0026quot;open \u0026quot; (shell-quote-argument $fpath)))) $file-list)) ((string-equal system-type \u0026quot;gnu/linux\u0026quot;) (mapc (lambda ($fpath) (let ((process-connection-type nil)) (start-process \u0026quot;\u0026quot; nil \u0026quot;xdg-open\u0026quot; $fpath))) $file-list)))))) (define-key dired-mode-map (kbd \u0026quot;\u0026lt;c-return\u0026gt;\u0026quot;) 'xah-open-in-external-app) 使用方法:按键 c-return 调用这个命令可以使用系统默认应用打开dired中光标所在文件或被标记的多个文件。\n文件选择 选择多个文件的目的是批量操作,比如批量移动、批量删除等。选择文件的操作我们称为标记(mark),相关按键如下:\nm: 标记光标所在的文件或目录,并将光标下移一行 del: 删除上一行标记,并将光标上移一行 u: 取消光标所在文件或目录的标记 u: 取消所有文件或目录的标记 t: 反选所有文件或目录的标记 #: 标记所有以 # 结尾的emacs临时文件 ~: 标记所有以 ~ 结尾的emacs自动备份文件 d: 给文件或目录加“待删除”标记(按\u0026rsquo;x\u0026rsquo;执行删除) 文件操作 按下快捷键时,dired优先操作有mark标记的文件,多个标记则为批量操作,没有标记则只对当前光标下的文件操作。常用的操作有:\n+: 创建子目录 c: 拷贝文件或目录 r: 重命名/移动 文件或目录 d: 直接删除文件或目录 x: 删除带有“待删除”标记(d)的文件或目录 c: 压缩文件,默认可使用的后缀有 .zip, .tar.gz, .tar.bz2, .tar.xz, .tar.zst z: 使用gzip压缩或解压缩文件 g: 刷新dired buffer 文件侧边栏 emacs中浏览文件侧边栏目录树的插件有好几个,比较常用的有 内置的speedbar、neotree、treemacs 等。这里我们介绍neotree。neotree侧边栏效果如下图:\n安装 将如下安装代码粘贴到 init-filemanage.el 文件中:\n(use-package neotree :ensure t :init (setq neo-window-fixed-size nil neo-theme (if (display-graphic-p) 'icons 'arrow)) :bind ((\u0026quot;\u0026lt;f8\u0026gt;\u0026quot; . neotree-toggle))) 使用 \u0026lt;f8\u0026gt;: 打开neotree p, n: 文件目录间上下移动 spc/ret/tab: 这三个快捷键都可以打开文件或展开目录 u: 跳转到上一级目录 g: 刷新 h: 显示或隐藏 隐藏文件(dotfiles) o: 打开目录下的所有目录结构 a: 最大化/最小化neotree窗口 c-c c-n: 创建文件或目录(以\u0026rdquo;/\u0026ldquo;结尾) c-c c-d: 删除文件或目录 c-c c-r: 重命名文件后目录 c-c c-c: 设置当前目录为展示的根目录 c-c c-p: 复制文件或目录 文件tab栏 文件的tab栏用于快速切换最近打开的文件,我们介绍 centaur-tabs 。centaur-tabs效果如下图:\n安装 将如下安装代码粘贴到 init-filemanage.el 文件中:\n(use-package centaur-tabs :ensure t :config (setq centaur-tabs-style \u0026quot;bar\u0026quot; centaur-tabs-height 22 centaur-tabs-set-icons t centaur-tabs-plain-icons t centaur-tabs-gray-out-icons t centaur-tabs-set-close-button t centaur-tabs-set-modified-marker t centaur-tabs-show-navigation-buttons t centaur-tabs-set-bar 'left centaur-tabs-cycle-scope 'tabs x-underline-at-descent-line nil) (centaur-tabs-headline-match) ;; (setq centaur-tabs-gray-out-icons 'buffer) ;; (centaur-tabs-enable-buffer-reordering) ;; (setq centaur-tabs-adjust-buffer-order t) (centaur-tabs-mode t) (setq uniquify-separator \u0026quot;/\u0026quot;) (setq uniquify-buffer-name-style 'forward) (defun centaur-tabs-buffer-groups () \u0026quot;`centaur-tabs-buffer-groups' control buffers' group rules. group centaur-tabs with mode if buffer is derived from `eshell-mode' `emacs-lisp-mode' `dired-mode' `org-mode' `magit-mode'. all buffer name start with * will group to \\\u0026quot;emacs\\\u0026quot;. other buffer group by `centaur-tabs-get-group-name' with project name.\u0026quot; (list (cond ((ignore-errors (and (string= \u0026quot;*xwidget\u0026quot; (substring (buffer-name) 0 8)) (not (string= \u0026quot;*xwidget-log*\u0026quot; (buffer-name))))) \u0026quot;xwidget\u0026quot;) ((or (string-equal \u0026quot;*\u0026quot; (substring (buffer-name) 0 1)) (memq major-mode '(magit-process-mode magit-status-mode magit-diff-mode magit-log-mode magit-file-mode magit-blob-mode magit-blame-mode ))) \u0026quot;emacs\u0026quot;) ((derived-mode-p 'prog-mode) \u0026quot;editing\u0026quot;) ((derived-mode-p 'dired-mode) \u0026quot;dired\u0026quot;) ((memq major-mode '(helpful-mode help-mode)) \u0026quot;help\u0026quot;) ((memq major-mode '(org-mode org-agenda-clockreport-mode org-src-mode org-agenda-mode org-beamer-mode org-indent-mode org-bullets-mode org-cdlatex-mode org-agenda-log-mode diary-mode)) \u0026quot;orgmode\u0026quot;) (t (centaur-tabs-get-group-name (current-buffer)))))) :hook (dashboard-mode . centaur-tabs-local-mode) (term-mode . centaur-tabs-local-mode) (calendar-mode . centaur-tabs-local-mode) (org-agenda-mode . centaur-tabs-local-mode) (helpful-mode . centaur-tabs-local-mode) :bind (\u0026quot;c-c b\u0026quot; . centaur-tabs-backward) (\u0026quot;c-c n\u0026quot; . centaur-tabs-forward) (\u0026quot;c-c m\u0026quot; . centaur-tabs-forward-group) (\u0026quot;c-c v\u0026quot; . centaur-tabs-backward-group)) 使用 c-c n: 切换到下一个tab c-c b: 切换到上一个tab c-c v: 切换到上一个分组 c-c m: 切换到下一个分组 文件(内容)搜索 搜索当前文件内容 在当前文件中快速搜索内容,使用 swiper ,将下面代码粘贴到 init-filemanage.el 中:\n(use-package swiper ;; 快捷搜索 :ensure nil :bind ((\u0026quot;c-s\u0026quot; . swiper))) 按键 c-s 就可以搜索内容啦。\n搜索最近访问的文件 定位最近访问的文件最快的方法就是切换buffer,使用 ivy-switch-buffer 或 counsel-switch-buffer 。区别是,后者在buffer选项间移动时会实时的显示buffer的内容。读者可以尝试一下这两个命令,然后选择自己喜欢的方式绑定到快捷键。我用 ivy-switch-buffer 。将下面的代码粘贴到init-filemanage.el文件中:\n(global-set-key (kbd \u0026quot;c-x b\u0026quot;) 'ivy-switch-buffer) 这段按键绑定的代码应该不难理解吧,相信你也应该知道怎么修改来使用另一个命令。哈哈,其实emacs-lisp也没有那么难啦~\n当然也可以在之前ivy的配置中使用bind参数来绑定快捷键,就像前面的centaur-tabs。我们后面也将使用这种方式。\n搜索经常访问的文件 对于经常需要访问的文件,使用 bookmark ,下次访问时直接从bookmark列表打开。将下面的代码粘贴到 init-filemanage.el 文件中:\n(use-package bookmark :ensure nil :bind ((\u0026quot;c-x r m\u0026quot; . bookmark-set) (\u0026quot;c-x r d\u0026quot; . bookmark-delete) (\u0026quot;c-x r j\u0026quot; . bookmark-jump))) 按键 c-x r m 将当前文件加入bookmark,默认名称为文件名,也可以自己重命名。按键 c-x r d 选择一个bookmark删除。按键 c-x r j 选择一个bookmark打开。或者,使用 counsel-bookmark ,结合了创建和跳转bookmark两个功能,代码见下文。\n搜索当前目录下文件内容 在当前目录内按照文件的内容查找,使用 counsel-rg 。由于该命令通过 rg 来实现搜索,所以在使用前需要先安装命令行工具 rg(ripgrep) 。macos下直接使用homebrew安装(其他系统请使用各自的包管理器安装):\n$ brew install ripgrep 搜索当前目录下文件 在当前目录下按照文件名称查找文件,使用 counsel-fzf 。由于该命令通过 fzf 来实现搜索,所以在使用前需要先安装命令行工具 fzf 。\n$ brew install fzf 搜索当前git仓库下文件 在当前git代码仓库中查找文件,使用 counsel-git 。当前文件若不在git仓库中则无法使用该命令。\n以上三种搜索方式,基本可以满足在emacs中快速定位和查找文件的需求。将下面的代码粘贴到 init-filemanage.el 文件中,相应快捷键的使用不再赘述。\n(use-package counsel :ensure t :bind ((\u0026quot;m-x\u0026quot; . counsel-m-x) (\u0026quot;c-x c-f\u0026quot; . counsel-find-file) (\u0026quot;c-c c t\u0026quot; . counsel-load-theme) (\u0026quot;c-c c b\u0026quot; . counsel-bookmark) (\u0026quot;c-c c r\u0026quot; . counsel-rg) (\u0026quot;c-c c f\u0026quot; . counsel-fzf) (\u0026quot;c-c c g\u0026quot; . counsel-git))) 额外地, c-c c t 用于切换内置主题。\n附加 dired额外功能 与文件系统相关的操作:\nh: 建立硬链接 s: 建立软链接 g: 改变文件group o: 改变文件owner m: 改变文件权限 p: 打印 dired中除了使用标记来批量操作文件外,还可以使用正则表达式。正则操作的快捷键一般以 % 开头。\n% m: 标记正则匹配的文件 % d: 给正则匹配的文件添加“待删除标记”(按键\u0026rsquo;x\u0026rsquo;执行删除) % g: 根据正则表达式,搜索所有文件的内容,标记内容中有正则匹配的文件 % u: 所有标记的文件名称转化为大写 % l: 所有标记的文件名称转化为小写 对于已经标记的文件,我们可以不用打开文件,而对多个文件的内容进行搜索或替换操作。\na: 根据正则表达式搜索已标记的文件的内容,并列出所有匹配行 q: 对标记的文件逐一进行正则替换,按键 \u0026ldquo;y\u0026rdquo; 替换,按键 \u0026ldquo;n\u0026rdquo; 跳过 dired还可以通过调用外部命令来操作文件。\n!: 以同步的方式调用shell命令来操作文件,命令运行的工作目录就是dired的当前目录 \u0026amp;: 以异步的方式调用shell命令来操作文件,命令运行的工作目录就是dired的当前目录 dired美化 嫌dired-mode颜色太单调?下面我们就给dired变个妆:\n(use-package diredfl :ensure t :config (diredfl-global-mode t)) (use-package all-the-icons-dired :ensure t :config (add-hook 'dired-mode-hook 'all-the-icons-dired-mode)) all-the-icons-dired 会自动安装其依赖的 all-the-icons package,安装成功后通过按键 m-x all-the-icons-install-fonts 安装必要的字体(此操作需要科学上网)。 没办法科学上网的同学可以直接将 字体 下载到本地后添加到系统的字体目录中,如何添加自行搜索。\n美化后的效果:\n结语 断断续续,花了一周的时间梳理文件管理的工作流。希望大家能够积极留言,说说有哪些对于新手难以理解的地方,有哪些其他的文件管理方面的需求。有不合理的地方也敬请指出,我将根据大家的反馈不断完善文章的内容。\nat last, happy hacking emacs!\n","date":"2020-08-27","permalink":"https://kinneyzhang.github.io/post/emacs-workflow-file-management/","summary":"Geekinney 说: \u0026ldquo;文件管理\u0026quot;是每个操作系统最基本的功能。当我们使用计算机时,不可避免的要和文件打交道,比如:文件的创建、重命名、拷贝、移动、删除、搜","title":"emacs workflow - 文件管理"},{"content":"问题描述 orgmode内置的创建表格的函数是 org-table-create , 传入\u0026quot;列数x行数\u0026quot;参数即可生成特定行数、列数的表格。这种交互函数在编写org文档时很实用,但在代码中却显得鸡肋。因为在代码中,我们通常希望表格和数据可以一起生成,而不是手动添加数据。\n我在折腾 gk-habit.el 时,就产生了这样的需求:生成习惯的月度打卡视图。就像下面这个样子:\nsun mon tue wed thu fri sat \u0026#xa0; \u0026#xa0; \u0026#xa0; \u0026#xa0; \u0026#xa0; \u0026#xa0; 1 \u0026#xa0; \u0026#xa0; \u0026#xa0; \u0026#xa0; \u0026#xa0; \u0026#xa0; -- 2 3 4 5 6 7 8 -- -- -- -- -- -- ✔ 9 10 11 12 13 14 15 ✘ ✔ ✔ ✘ ✔ ✔ ✘ 16 17 18 19 20 21 22 ✔ ✔ ✔ ✔ ○ ○ ○ 23 24 25 26 27 28 29 ○ ○ ○ ○ ○ ○ ○ 30 31 \u0026#xa0; \u0026#xa0; \u0026#xa0; \u0026#xa0; \u0026#xa0; ○ ○ \u0026#xa0; \u0026#xa0; \u0026#xa0; \u0026#xa0; \u0026#xa0; 思路分析 解决这个问题的关键是把握几个操作org表格的函数 org-create-table, org-table-next-field, org-table-insert-hline, org-table-kill-row… 然后就是在表格创建的过程中依次插入数据。用于创建表格的每一行的数据用列表表示,分隔线用 hl 对象表示。\n首先创建一个一行n列的表格,因为 org table 的函数只有在表格内才能使用。其中n为每行元素的个数。 在数据列表中循环,如果元素是一个list,表示是数据。继续在该list中循环,插入数据后跳到下一个单元格(注意数字要转为字符串)。 如果元素是 hl 对象,表示是分隔线,直接插入一行分割线 (org-table-insert-hline 1) 。 每一行插入最后一个数据后会执行“跳到下一个单元格”的操作,当右边没有单元格时会自动插入新的一行。 因此,最后会多出一行,用 org-table-kill-row 函数删掉。 代码实现 (defun gk-org-table-create (list) \u0026quot;create org table from a list form at point.\u0026quot; (let ((column (catch 'break (dolist (row-data list) (when (listp row-data) (throw 'break (length row-data)))))) (beg (point))) (org-table-create (concat (number-to-string column) \u0026quot;x1\u0026quot;)) (goto-char beg) (when (org-at-table-p) (org-table-next-field) (dotimes (i (length list)) (let ((row-data (nth i list))) (if (listp row-data) (dolist (data row-data) (cond ((numberp data) (insert (number-to-string data))) ((null data) (insert \u0026quot;\u0026quot;)) (t (insert data))) (org-table-next-field)) (when (equal 'hl row-data) (org-table-insert-hline 1))) (when (= i (1- (length list))) (org-table-kill-row)))))) (forward-line)) 使用案例 (gk-org-table-create '((\u0026quot;n1\u0026quot; \u0026quot;n2\u0026quot; \u0026quot;n3\u0026quot; \u0026quot;n4\u0026quot; \u0026quot;n5\u0026quot;) hl (1 2 3 4 5) (6 7 8 9 10) hl (\u0026quot;c1\u0026quot; \u0026quot;c2\u0026quot; \u0026quot;c3\u0026quot; \u0026quot;c4\u0026quot; \u0026quot;c5\u0026quot;) hl (\u0026quot;a\u0026quot; \u0026quot;b\u0026quot; \u0026quot;c\u0026quot; \u0026quot;d\u0026quot; \u0026quot;e\u0026quot;) (\u0026quot;f\u0026quot; \u0026quot;g\u0026quot; \u0026quot;h\u0026quot; \u0026quot;i\u0026quot; \u0026quot;j\u0026quot;))) | n1 | n2 | n3 | n4 | n5 | |----+----+----+----+----| | 1 | 2 | 3 | 4 | 5 | | 6 | 7 | 8 | 9 | 10 | |----+----+----+----+----| | c1 | c2 | c3 | c4 | c5 | |----+----+----+----+----| | a | b | c | d | e | | f | g | h | i | j | 实现开篇提出的习惯打卡的视图是个更复杂的问题,这里涉及到了不同月份的天数不同,起始星期不同,以及每天对应的打卡状态不同等问题。解决了这些问题后,将得到的数据整合成 gk-org-table-create 合法的数据列表形式即可生成相应的表格。相关代码在这里。\n如果你有更简单、漂亮的实现,欢迎留言探讨~\n","date":"2020-08-19","permalink":"https://kinneyzhang.github.io/post/emacs-hack-create-table-from-data-list/","summary":"问题描述 OrgMode内置的创建表格的函数是 org-table-create , 传入\u0026quot;列数x行数\u0026quot;参数即可生成特定行数、列数的表格。这种交互函数在编写org文档时很实用,但","title":"emacs hack - 通过列表数据创建表格"},{"content":"想要培养一些好的习惯,想要不那么费力的变得更自律。\n读了一些关于时间管理和习惯培养的书籍和文章,我意识到“仪式感”和“记录反思”的重要性。事实上,以自己喜欢的方式做记录是件很开心的事情,我已经连续80天写晨间日记和晚间总结。现在, 写日志已经变成了习惯。但我希望培养更多好的习惯,用我自己喜欢的方式(在emacs中),所以有了gk-habit这个demo。\n效果 详见 https://github.com/kinneyzhang/gkhabit\n思路 gk-habit使用emacsql-sqlite创建数据库,有两个表 \u0026ldquo;habit\u0026rdquo; 和 \u0026ldquo;record\u0026rdquo;。前者记录习惯及其相关参数,后者记录习惯打卡的数据。\nhabit表\ncreate-time 习惯创建的时间 name 习惯名称 frequency-type 习惯打卡频率类型,包括“每天”、“每周几重复”,“一周几次”,“一月几次” frequency-param 频率类型的参数,每天重复则为nil、每周几重复则用数字表示(134表示每周一三四)、一周或一月的打卡的次数。 period 习惯发生的时间时间段 remind-time 提醒打卡的时间 remind-string 提醒打卡时的文本 status 习惯的状态,active或archive,archive表示不再跟踪该习惯 archive-time 归档的时间 record表\ncreate-time 记录创建的时间 habit 记录的习惯 status 习惯完成状态(done完成,miss未完成) comment 未完成的原因 代码 ;;; habit record - gk-habit (require 'cl-lib) (require 'emacsql-sqlite) (defconst gkh-buffer \u0026quot;*gk-habit*\u0026quot;) (defvar gkh-db (emacsql-sqlite3 \u0026quot;~/.emacs.d/gk-habit/habit.db\u0026quot;)) (defvar gkh-file \u0026quot;~/.emacs.d/gk-habit/habit.org\u0026quot;) (defvar gkh-record-status '(\u0026quot;done\u0026quot; \u0026quot;miss\u0026quot;)) (defvar gkh-frequency-type '(\u0026quot;everyday\u0026quot; \u0026quot;repeat\u0026quot; \u0026quot;by-week\u0026quot; \u0026quot;by-month\u0026quot;)) (defvar gkh-period '(\u0026quot;get-up\u0026quot; \u0026quot;morning\u0026quot; \u0026quot;noon\u0026quot; \u0026quot;afternoon\u0026quot; \u0026quot;evening\u0026quot; \u0026quot;before-sleep\u0026quot;)) (defvar gkh-table-habit-column '(\u0026quot;create-time\u0026quot; \u0026quot;name\u0026quot; \u0026quot;frequency\u0026quot; \u0026quot;frequency-param\u0026quot; \u0026quot;peroid\u0026quot; \u0026quot;remind-time\u0026quot; \u0026quot;remind-string\u0026quot; \u0026quot;status\u0026quot; \u0026quot;archive-time\u0026quot;)) (defvar gkh-table-record-column '(\u0026quot;create-time\u0026quot; \u0026quot;habit\u0026quot; \u0026quot;status\u0026quot; \u0026quot;comment\u0026quot;)) (defun gkh-db-create-tables () (interactive) \u0026quot;create database tables of gk-habit.\u0026quot; (emacsql gkh-db [:create-table habit ([(create-time string :primary-key :not-null) (name string :not-null :unique) (frequency-type string :not-null) (frequency-param) (period string :not-null) (remind-time) (remind-string) (status string :not-null) (archive-time string)])]) (emacsql gkh-db [:create-table record ([(create-time string :primary-key :not-null) (habit string :not-null) (status string :not-null) (comment string)] (:foreign-key [habit] :references habit [name] :on-delete :cascade))])) (defun gkh-db-drop-tables () \u0026quot;drop database tables of gk-habit.\u0026quot; (interactive) (let* ((tables (emacsql gkh-db [:select name :from sqlite_master :where (= type 'table)])) (table (completing-read \u0026quot;choose a table: \u0026quot; tables nil t))) (emacsql gkh-db `[:drop-table ,(intern table)]))) (defun gkh--frequency-params (frequency-type) \u0026quot;get the habit frequency parameters\u0026quot; (let (param) (cond ((string= frequency-type \u0026quot;everyday\u0026quot;) (setq param nil)) ((string= frequency-type \u0026quot;repeat\u0026quot;) (setq param (completing-read \u0026quot;repeated day (exam. \\\u0026quot;135\\\u0026quot; means habit repeat on monday, wensday and friday in every week.): \u0026quot; nil))) ((string= frequency-type \u0026quot;by-week\u0026quot;) (setq param (completing-read \u0026quot;how many times a week: \u0026quot; nil))) ((string= frequency-type \u0026quot;by-month\u0026quot;) (setq param (completing-read \u0026quot;how many times a month: \u0026quot; nil)))) param)) (defun gkh-init () \u0026quot;gk-habit initialize, create database and org tables.\u0026quot; (interactive) (ignore-errors (gkh-db-create-tables)) (gkh-org-table-draw)) (defun gkh-new () \u0026quot;add a new habit\u0026quot; (interactive) (cl-block 'return (let* ((create-time (format-time-string \u0026quot;%y-%m-%d %t\u0026quot;)) (habit (let ((temp (completing-read \u0026quot;name of habit: \u0026quot; nil)) (habits (mapcar 'car (emacsql gkh-db [:select name :from habit :where (= status \u0026quot;active\u0026quot;)])))) (if (member temp habits) (cl-return-from 'return (message \u0026quot;the habit '%s' already exist!\u0026quot; temp)) temp))) (frequency-type (completing-read \u0026quot;frequency of habit: \u0026quot; gkh-frequency-type nil t)) (frequency-param (gkh--frequency-params frequency-type)) (period (completing-read \u0026quot;period of habit: \u0026quot; gkh-period nil t)) (remind-time (let ((temp (completing-read \u0026quot;remind this habit at: \u0026quot; nil))) (if (string= \u0026quot;\u0026quot; temp) nil temp))) (remind-string (let ((temp (completing-read \u0026quot;habit remind sentence: \u0026quot; nil))) (if (string= \u0026quot;\u0026quot; temp) nil temp)))) (emacsql gkh-db `[:insert :into habit :values ([,create-time ,habit ,frequency-type ,frequency-param ,period ,remind-time ,remind-string \u0026quot;active\u0026quot; nil])]) (gkh-org-table-draw) (message \u0026quot;habit '%s' is added!\u0026quot; habit)))) (defun gkh-record () \u0026quot;insert a habit redord in table.\u0026quot; (interactive) (let* ((create-time (format-time-string \u0026quot;%y-%m-%d %t\u0026quot;)) (habit (completing-read \u0026quot;choose a habit: \u0026quot; (emacsql gkh-db [:select [name] :from habit :where (= status \u0026quot;active\u0026quot;)]))) (status (completing-read \u0026quot;is the habit done?\u0026quot; gkh-record-status nil t)) (comment (when (string= \u0026quot;miss\u0026quot; status) (completing-read \u0026quot;reason why missed: \u0026quot; nil)))) (emacsql gkh-db `[:insert-into record :values ([,create-time ,habit ,status ,comment])]) (gkh-org-table-draw) (message \u0026quot;habit '%s' is %s, record on %s, %s\u0026quot; habit status create-time comment))) (defun gkh-archive () \u0026quot;archive a habit\u0026quot; (interactive) (let* ((habits (emacsql gkh-db [:select name :from habit :where (= status \u0026quot;active\u0026quot;)])) (habit (completing-read \u0026quot;choose a habit: \u0026quot; habits nil t))) (emacsql gkh-db `[:update habit :set [(= status \u0026quot;archive\u0026quot;) (= archive-time ,(format-time-string \u0026quot;%y-%m-%d %t\u0026quot;))] :where (= name ,habit)]) (gkh-org-table-draw) (message \u0026quot;habit %s has been archived!\u0026quot; habit))) (defun gkh-org-table-draw () \u0026quot;draw gk-habit database in org table.\u0026quot; (interactive) (let* ((table-alist '((\u0026quot;habit\u0026quot; . gkh-table-habit-column) (\u0026quot;record\u0026quot; . gkh-table-record-column)))) (with-temp-file gkh-file (goto-char (point-min)) (dotimes (i (length table-alist)) (let* ((headline (car (nth i table-alist))) (column-list (eval (cdr (nth i table-alist)))) (column-num (length column-list))) (insert (concat \u0026quot;* \u0026quot; headline \u0026quot; table\\n\u0026quot;)) (org-table-create (concat (format \u0026quot;%s\u0026quot; column-num) \u0026quot;x2\u0026quot;)) (dotimes (j column-num) (org-table-next-field) (insert (nth j column-list))) (let ((items (emacsql gkh-db `[:select * :from ,(intern headline)]))) (dotimes (m (length items)) (dotimes (n column-num) (org-table-next-field) (insert (format \u0026quot;%s\u0026quot; (nth n (nth m items))))))) (org-table-align) (forward-line 2) (end-of-line) (newline 2)))))) (defun gkh-org-table-display () \u0026quot;display gk-habit org table in a bottom buffer.\u0026quot; (interactive) (gkh-org-table-draw) (if (string= (buffer-name) gkh-buffer) (message \u0026quot;already in the gk habit buffer.\u0026quot;)) (select-window (or (get-buffer-window gkh-buffer) (selected-window))) (with-current-buffer (get-buffer-create gkh-buffer) (org-mode) (read-only-mode -1) (erase-buffer) (insert (file-contents gkh-file)) (valign-mode) (goto-char (point-min)) (read-only-mode 1)) (view-buffer gkh-buffer 'kill-buffer)) (provide 'gk-habit) 计划 设置习惯打卡时间提醒 使用matplotlib库绘制习惯打卡统计图 ","date":"2020-08-04","permalink":"https://kinneyzhang.github.io/post/record-habit-demo/","summary":"想要培养一些好的习惯,想要不那么费力的变得更自律。 读了一些关于时间管理和习惯培养的书籍和文章,我意识到“仪式感”和“记录反思”的重要性。事实上,以自己喜欢的方式","title":"emacs hack - 习惯记录与打卡的demo"},{"content":"实际问题 将一个文件中的相似块分割成若干个文件。比如下面这个例子:\n第一回: 灵根育孕源流出 心性修持大道生 这是第一段内容..... 这是第二段内容..... 这是第三段内容..... 更多... 第二回: 悟彻菩提真妙理 断魔归本合元神 这是第一段内容..... 这是第二段内容..... 这是第三段内容..... 更多... 第三回: 四海千山皆拱伏 九幽十类尽除名 这是第一段内容..... 这是第二段内容..... 这是第三段内容..... 更多... 假设有一个长篇小说txt文件,每一回有相似的结构。要解决的问题是:如何将每一回分割成单独的文件?文件名是每一回的标题,文件的内容就是每一回的内容。\n解决思路 解决这种实际的问题,我的思路一般是:用人的思维找到直观的解决办法。然后,将人的思维转换成对应的代码逻辑,比如:运用什么控制语句?使用什么数据结构?涉及什么特殊算法?等等。\n首先我们从人的思维出发考虑如何解决这个问题。最直接的方法是:根据开头“第一回:”这几个字确定这一回的标题,然后确定接下来直到“第二回:”之间的部分为第一回的内容。然后将第一回的内容写到以标题命名的文件中。接下里,以此类推,直到最后没有内容。\n接下来,思考如何将这个过程转化成代码的逻辑。\n显然,这里是循环判断的逻辑,循环的范围是从第一行到最后一行。分析过程如下:\n搜索第一行,是标题行,记录标题并移到下一行。 搜索这一行,不是标题行,记录行首的位置start。判断下一行是否是标题行,不是则移到下一行。 重复过程2,直到当前行的下一行是标题行时,记录本行行首的位置end。 将start和end之间的内容写到以标题命名的文件中。移到下一行。 重复上面的过程1,2,3,4。直到光标到达最后。 代码实现 根据上面的分析过程,有如下的代码:\n(defun my-split-file (file) (interactive \u0026quot;fchoose a file: \u0026quot;) (let ((filedir \u0026quot;~/test-dir/\u0026quot;) filename start end content) (with-current-buffer (get-buffer-create \u0026quot;*split-file*\u0026quot;) (insert-file-contents file) (goto-char (point-min)) (while (\u0026lt; (point) (point-max)) (if (search-forward-regexp \u0026quot;^第.+回:\u0026quot; (line-end-position) t) (progn (setq filename (string-trim (thing-at-point 'line))) (next-line) (beginning-of-line) (setq start (point))) ;; 获取内容的起始位置start (save-excursion ;; 保存光标的位置:只判断下一行的情况,不改变实际要操作的光标。 (next-line) (beginning-of-line) (when (or (search-forward-regexp \u0026quot;^第.+回:\u0026quot; (line-end-position) t) (= (point) (point-max))) (previous-line) (end-of-line) (setq end (point)) ;; 获取内容的结尾位置end (setq content (buffer-substring-no-properties start end)) ;; 获取start和end之间的内容。 (with-temp-file (concat filedir filename \u0026quot;.txt\u0026quot;) (insert content)) ;; 写文件 )) (next-line) (beginning-of-line)))))) ","date":"2020-06-28","permalink":"https://kinneyzhang.github.io/post/emacs-hack-split-similar-blocks/","summary":"实际问题 将一个文件中的相似块分割成若干个文件。比如下面这个例子: 第一回: 灵根育孕源流出 心性修持大道生 这是第一段内容..... 这是第二段内容..... 这是第三段内","title":"emacs hack - 分割文件相似内容"},{"content":"众所周知,emacs以学习曲线陡峭著称,所以在开始介绍workflow前,得做些准备工作。这篇文章主要介绍emacs的安装、基础概念、基础配置和使用emacs的基础知识。\n安装 emacs的安装非常简单,各个平台的安装方法详见官网。我使用的是macos,通过homebrew安装 emacs-plus 27,支持xwidget webkit。安装代码如下:\n$ brew tap d12frosted/emacs-plus $ brew install emacs-plus@27 --with-xwidgets macos homebrew 的安装方法见 brew.sh 。\n快捷键 emacs和其他编辑器的相比一个重要的优势就是全键盘操作。所以在使用emacs之前,先通过按键 c-h t 查看内置的快捷键教程是一个较好的开端。下面对基本快捷键做简要介绍:\nemacs的快捷键都是组合键,由前缀键加上字母或数字组成。常见的前缀键有:\nm 对应 alt 键 c 对应 control 键 s 对应 shift 键 形如 c-x c-f 的快捷键表示按住 ctrl 键时按下字母 x , 再按住 ctrl 键同时按下字母 f 。\n下面是一些常用快捷键:\n快捷键 功能 快捷键 功能 c-x c-c 关闭emacs 编辑 \u0026#xa0; c-g 取消当前按键输入 c-@ 或 c-spc 设置mark \u0026#xa0; \u0026#xa0; m-w 复制 移动 \u0026#xa0; c-w 剪切 c-p 光标上移 c-y 粘贴 c-n 光标下移 c-x h 全选 c-b 光标左移 c-x c-q 切换只读/编辑模式 c-f 光标右移 c-/ 撤销上一步操作 c-a 光标移到行首 \u0026#xa0; \u0026#xa0; c-e 光标移到行尾 缓冲区 \u0026#xa0; m-b 光标前移一个单词 c-x b 切换buffer m-f 光标后移一个单词 c-x k 关闭buffer c-v 向下翻页 c-x c-b 查看所有打开的缓冲区 m-v 向上翻页 \u0026#xa0; \u0026#xa0; c-l 光标移到屏幕上/中/下部 窗口 \u0026#xa0; c-x [ 光标跳到文首 c-x 2 在下面分割一个窗口 c-x ] 光标跳到文末 c-x 3 在右边分割一个窗口 \u0026#xa0; \u0026#xa0; c-x 0 关闭当前窗口 文件 \u0026#xa0; c-x 1 关闭其它窗口 c-x c-f 打开(创建)文件 c-x o 依次切换窗口 c-x c-s 保存文件 \u0026#xa0; \u0026#xa0; 最小配置 emacs使用emacs-lisp作为配置语言,通过添加配置可以使emacs符合个人的使用习惯和实现各种功能。通常,emacs的配置文件放置在用户根目录的 .emacs.d 文件夹中。其中的 init.el 作为配置的入口文件。\n下面提供了一段最小配置(参考 better-defaults ),直接将它复制粘贴到 init.el 文件中后,重启emacs。\n;;; better defaults (unless (or (fboundp 'helm-mode) (fboundp 'ivy-mode)) (ido-mode t) (setq ido-enable-flex-matching t)) ;; 使用ido补全 (unless (eq window-system 'ns) (menu-bar-mode -1)) ;; 禁用菜单栏 (when (fboundp 'tool-bar-mode) (tool-bar-mode -1)) ;; 禁用工具栏 (when (fboundp 'scroll-bar-mode) (scroll-bar-mode -1)) ;; 禁用垂直滚动条 (when (fboundp 'horizontal-scroll-bar-mode) (horizontal-scroll-bar-mode -1)) ;; 禁用水平滚动条 (require 'uniquify) (setq uniquify-buffer-name-style 'forward) ;; https://www.emacswiki.org/emacs/saveplace (save-place-mode 1) ;; 保存光标文位置 (global-set-key (kbd \u0026quot;c-s\u0026quot;) 'isearch-forward) (global-set-key (kbd \u0026quot;c-r\u0026quot;) 'isearch-backward) (global-set-key (kbd \u0026quot;c-c c-/\u0026quot;) 'comment-or-uncomment-region) ;; 代码块注释和反注释 (show-paren-mode 1) ;; 显示括号匹配 (setq-default indent-tabs-mode nil) (setq save-interprogram-paste-before-kill t apropos-do-all t mouse-yank-at-point t require-final-newline t load-prefer-newer t ediff-window-setup-function 'ediff-setup-windows-plain) (require 'dired-x) (delete-selection-mode 1) ;; 选择后插入,删除原字符。 (recentf-mode 1) ;; 保存最近访问 (global-auto-revert-mode 1) ;; 自动加载更新内容 (fset 'yes-or-no-p 'y-or-n-p) ;; 使用 'y/n' 代替 'yes/no' (setq custom-file (concat user-emacs-directory \u0026quot;custom.el\u0026quot;)) (setq inhibit-startup-message t) ;; 禁止启动信息 (setq ring-bell-function 'ignore) ;; 禁止发出声音警告 (setq make-backup-files nil) ;; 不允许备份 (setq auto-save-default nil ;; 不允许默认自动保存 auto-save-silent t)) ;; 自动保存时不显示消息 (setq scroll-step 1 scroll-margin 3 scroll-conservatively 10000) ;; 连续滚动 (setq confirm-kill-emacs (lambda (prompt) (y-or-n-p-with-timeout \u0026quot;whether to quit emacs:\u0026quot; 10 \u0026quot;y\u0026quot;))) ;; 防误操作退出 (setq dired-recursive-deletes 'always dired-recursive-copies 'always) ;; 全部递归拷贝、删除文件夹中的文件 包管理 emacs的package(也就是我们通常说的\u0026quot;包\u0026quot;或\u0026quot;插件\u0026quot;)可以为emacs拓展丰富多样的功能。为了能够使用这些package,需要配置获取package的源。在init.el的最后加上以下代码:\n(setq package-enable-at-startup nil) (setq package-archives '((\u0026quot;gnu\u0026quot; . \u0026quot;http://mirrors.cloud.tencent.com/elpa/gnu/\u0026quot;) (\u0026quot;melpa\u0026quot; . \u0026quot;http://mirrors.cloud.tencent.com/elpa/melpa/\u0026quot;))) 配置好源后,按键 m-x list-packages 可以查看所有已发布的package。按键 m-x package-install 后输入package名字可以直接安装,同理使用 package-delete 删除。使用package,需要先在配置文件中写入 (require '\u0026lt;package-name\u0026gt;) 这个过程相当于导入(import)。再加上必要的自定义配置便可使用该package所有的功能。\n以上的包管理方案由emacs内置的 package.el 提供。但内置的不一定是最好的。因此,有一些package专门提供了更加灵活、自动化的包管理方案。常用的有 use-package quelpa straight el-get 等,我使用的是 use-package 结合 git submodule 。下面的代码用于初始化 use-package ,加入init.el结尾。\n(unless (package-installed-p 'use-package) (package-refresh-contents) (package-install 'use-package)) 写完配置代码后,在最后一个括号后面按键 c-x c-e (eval-last-sexp)即可执行配置,安装package。也可以重启emacs,再次打开时emacs会自动加载所有配置。\n配置管理 值得注意的是,我们将上面的配置代码统统写入了init.el文件中。可以预见,当安装许多package时,配置代码将会增多,init.el的内容会变得复杂无比,难以阅读和维护。我们需要一种合理的组织配置文件的方式。\n解决方法是将每一种workflow的配置代码写在单独的文件中,然后在init.el中引入该文件。操作如下:\n在.emacs.d文件夹下创建elisp文件夹。 在init.el中添加代码 (add-to-list 'load-path (concat user-emacs-directory \u0026quot;elisp\u0026quot;)) 。 在elisp文件夹下创建 init-better.el ,将“最小配置”的代码粘贴进去。 在 init-better.el 最后加上代码 (provide 'init-better) 。 在 init.el 最后加上代码 (require 'init-better) 根据字面意思也不难理解:步骤2的代码将elisp文件夹下的所有文件加入配置加载路径;步骤4的provide提供文件名,使其可以被引入;步骤5的require引入了该文件。这样我们就将最小配置的代码引入到init.el中了。以后的各种workflow我们也将使用这种方式来组织配置文件。\n实用package 介绍一些对于新手实用的package,直接将下面的配置粘贴到elisp文件夹下的 init-utils.el 文件中。\n(use-package super-save ;; 自动保存,用于替换默认的自动保存 :ensure t :config (super-save-mode +1) (setq super-save-auto-save-when-idle t)) (use-package which-key ;; emacs按键提示 :ensure t :config (which-key-mode)) (use-package ivy ;; emacs补全框架 :ensure t :init (setq ivy-use-virtual-buffers t enable-recursive-minibuffers t) :config (ivy-mode 1)) 同理,在配置init-utils.el文件结尾加上 (provide 'init-utils) ,然后在init.el中引入 (require 'init-utils) 。\nemacs主题 选择一个简洁、美观的主题不仅可以缓解眼睛疲劳,还可以提高使用emacs的效率。emacs的主题分为亮色和暗色两种,我的使用习惯是白天使用亮色主题,晚上使用暗色主题。也可以选择喜欢的第三方主题安装。我最喜欢的亮色主题是leuven(内置),暗色主题是dracula(第三方)。\n全部配置代码 我建议你按照教程的步骤,一步步拷贝、粘贴、执行代码。这个过程中,你会了解到如何从零配置一个功能强大的emacs编辑器,如何像搭积木一样通过添加配置文件使emacs充满无限的可能性。所有的配置代码我也会放在 emacs-workflow-config 这个代码仓库,读者可以直接把它克隆到 .emacs.d 文件夹下使用。\n结语 如何配置一个舒适易用的emacs环境是一个大话题,有很多非常nice的package,但考虑到这大多数是与“提高编程的体验”相关,并不是emacs workflow的重点。所以,这一篇中我只介绍一个最小配置和部分实用(必要)的package,更多的优化配置不多讲解。好啦,以上就是使用emacs前的准备工作,接下来就可以愉快的学习各种工作流啦!\n","date":"2020-06-25","permalink":"https://kinneyzhang.github.io/post/emacs-workflow-preparatory-work/","summary":"众所周知,emacs以学习曲线陡峭著称,所以在开始介绍workflow前,得做些准备工作。这篇文章主要介绍emacs的安装、基础概念、基础配置和使用emacs的","title":"emacs workflow - 准备工作"},{"content":"实际问题 不使用interactive function(m-%)如何批量替换文件中的字符串?应用场景为文字校验。\n解决思路 字符串替换分三种情况:\n在当前buffer替换 在指定文件中替换 为指定目录下所有文件替换。 用于替换的pair有两种来源:\nrepl-string-list 和 repl-regexp-list 变量 repl-file 对应的文件。 字符串查找的方式有两种,分别对应两个函数:\n纯字符串 (replall-string) 正则表达式 (replall-regexp) 优先使用列表变量的值,如果该变量值为nil则使用repl-file对应的文件中的值。其中文件中的pair只能用于 replall-string 函数。两个变量列表的值分别对应两个函数。repl-file的格式为每行一个pair,分别为被替换的字符串和用于替换的字符串,用空格隔开。\n代码实现 主函数 replall-string 和 replall-regexp 可以选择替换类型。对于具体的情况也可以直接使用具体的替换函数。代码如下:\n(setq repl-string-list '((\u0026quot;old\u0026quot; \u0026quot;new\u0026quot;) (\u0026quot;test\u0026quot; \u0026quot;测试\u0026quot;) (\u0026quot;错误\u0026quot; \u0026quot;right\u0026quot;) (\u0026quot;隔开你\u0026quot; \u0026quot;戈楷旎\u0026quot;))) (setq repl-regexp-list '((\u0026quot;\\\\.\u0026quot; \u0026quot;。\u0026quot;))) (setq repl-file \u0026quot;~/replace.txt\u0026quot;) (defun replall--read-pair-from-file () (let ((repl-list '())) (with-temp-buffer (insert-file-contents repl-file) (goto-char (point-min)) (while (\u0026lt; (point) (point-max)) (setq repl-pair (split-string (thing-at-point 'line) \u0026quot;[ \\f\\t\\n\\r\\v]+\u0026quot; t \u0026quot;[ \\f\\t\\n\\r\\v]+\u0026quot;)) (if (null repl-pair) (next-line) (next-line) (setq repl-list (append repl-list (list repl-pair)))))) repl-list)) (defun replall--get-repl-string-list () (if (bound-and-true-p repl-string-list) repl-string-list (replall--read-pair-from-file))) (defun replall--get-repl-regexp-list () (if (bound-and-true-p repl-regexp-list) repl-regexp-list (message \u0026quot;please set variable 'repl-regexp-list'!\u0026quot;))) (defun replall--string (file lst) (with-temp-buffer (insert-file-contents file) (goto-char (point-min)) (dolist (pair lst) (while (search-forward (car pair) nil t) (replace-match (cadr pair))) (goto-char (point-min))) (write-file file))) (defun replall--regexp (file lst) (with-temp-buffer (insert-file-contents file) (goto-char (point-min)) (dolist (pair lst) (while (re-search-forward (car pair) nil t) (replace-match (cadr pair))) (goto-char (point-min))) (write-file file))) (defun replall-string-in-curr-buffer () (interactive) (let ((curr-file (buffer-file-name (current-buffer))) (repl-list (replall--get-repl-string-list))) (replall--string curr-file repl-list))) (defun replall-regexp-in-curr-buffer () (interactive) (let ((curr-file (buffer-file-name (current-buffer))) (repl-list (replall--get-repl-regexp-list))) (replall--regexp curr-file repl-list))) (defun replall-string-in-file (file repl) (interactive \u0026quot;fchoose a file to be processed: \u0026quot;) (let ((repl-list (replall--get-repl-string-list))) (replall--string file repl-list))) (defun replall-regexp-in-file (file repl) (interactive \u0026quot;fchoose a file to be processed: \u0026quot;) (let ((repl-list (replall--get-repl-regexp-list))) (replall--regexp file repl-list))) (defun replall--get-real-files-in-dir (dir) (let ((real-files) (files (directory-files dir))) (dolist (file files) (when (not (or (string= \u0026quot;.\u0026quot; (substring file 0 1)) (string= \u0026quot;#\u0026quot; (substring file 0 1)) (string= \u0026quot;~\u0026quot; (substring file -1)))) (push file real-files))) real-files)) (defun replall-string-in-directory (dir) (interactive \u0026quot;dchoose a directory to be processed: \u0026quot;) (let* ((repl-list (replall--get-repl-string-list)) (real-files (replall--get-real-files-in-dir dir))) (dolist (file real-files) (replall--string (concat dir file) repl-list)))) (defun replall-regexp-in-directory (dir) (interactive \u0026quot;dchoose a directory to be processed: \u0026quot;) (let* ((repl-list (replall--get-repl-regexp-list)) (real-files (replall--get-real-files-in-dir dir))) (dolist (file real-files) (replall--regexp (concat dir file) repl-list)))) (defun replall-string (type) (interactive \u0026quot;sreplace string: 1.in current buffer 2.in a file 3.in a directory (input 1~3): \u0026quot;) (cond ((string= type \u0026quot;1\u0026quot;) (replall-string-in-curr-buffer)) ((string= type \u0026quot;2\u0026quot;) (call-interactively #'replall-string-in-file)) ((string= type \u0026quot;3\u0026quot;) (call-interactively #'replall-string-in-directory)) (t (message \u0026quot;please input 1~3!\u0026quot;)))) (defun replall-regexp (type) (interactive \u0026quot;sreplace regexp: 1.in current buffer 2.in a file 3.in a directory (input 1~3): \u0026quot;) (cond ((string= type \u0026quot;1\u0026quot;) (replall-regexp-in-curr-buffer)) ((string= type \u0026quot;2\u0026quot;) (call-interactively #'replall-regexp-in-file)) ((string= type \u0026quot;3\u0026quot;) (call-interactively #'replall-regexp-in-directory)) (t (message \u0026quot;please input 1~3!\u0026quot;)))) ","date":"2020-06-16","permalink":"https://kinneyzhang.github.io/post/emacs-hack-string-batch-replacement/","summary":"实际问题 不使用interactive function(M-%)如何批量替换文件中的字符串?应用场景为文字校验。 解决思路 字符串替换分三种情况: 在当前buffer替","title":"emacs hack - 批量替换字符串"},{"content":"emacs workflow 顾名思义 “emacs工作流”。这一系列的文章主要介绍emacs中的常用的工作流,比如:文件管理、日程管理、笔记管理、听音乐、网页浏览、收发邮件、版本控制、字典查阅、快捷搜索、代数计算、telegram… 每一种工作流都可以替代一种额外的app或系统应用。最终,我们逐渐向 \u0026ldquo;live in emacs\u0026rdquo; 的目标靠近。\n在介绍具体的emacs workflow之前,我觉得有必要先搞清楚下面几个问题。\n什么是emacs? 官网的介绍是 \u0026ldquo;an extensible, customizable, free/libre text editor — and more\u0026rdquo;。这里有5个关键词:可拓展、可定制、自由、编辑器、更多。\nemacs本质上是一个 编辑器 ,从这个意义上来说,它和办公中经常使用的 ms word,程序员使用的vim, notepad别无二致。 可拓展 意味着我们可以通过程序来实现新的功能、特性。 可定制 意味着我们可以通过配置使emacs符合个人使用习惯。 自由 说的是emacs是一个自由软件(非商业)。 更多 包含了不计其数的可能性。\n上面的解释可以总结为一句话: \u0026ldquo;if related data, nothing is impossible in emacs.\u0026quot;。你可能觉得这有点夸张,但我可以解释给你听:\n这是个物质的世界,也是个数据的世界。物质的联系产生了数据,数据的改变塑造了物质。数据的种类有很多:简单的阿拉伯数字、数学公式、化学方程式、计算机程序、历史资料、软件密码、基因序列、社会信息…这些都是数据。因此,我们可以简单的得出一个结论,改变数据就可以改变一切。\n编辑器的作用是编辑数据,处理文本。普通的编辑器只能对数据进行简单的处理,比如插入、删除、改变样式、公式计算等等。而emacs的高度可拓展性为数据处理提供了无限的可能。使用elisp,调用外部程序,emacs可以为数据之间创造复杂的逻辑,实现各种功能,满足各种需求,从而无所不能。\n我们可以随便举一个看似不可能在emacs中完成的例子,看看emacs如何把不可能变成可能。比如:用emacs来煮咖啡。值得注意的是,使用emacs的前提是 \u0026ldquo;data related\u0026rdquo;,即能够与emacs交换数据信息。所以,假设我们有一个智能咖啡机,可以通过外部指令控制。现在我们可以编写一个 make-coffee 函数执行煮咖啡的指令,然后 m-x make-coffee ,大功告成!这是一个看似很简单的例子,但却揭示了emacs的本质: emacs是一个使用程序来处理数据的万能前端 。这一点和操作系统很像。\n以上就是我对emacs的理解。\n什么样的人适合使用emacs? 很多人认为emacs是程序员、hacker的专利,我不认同。我觉得emacs可以是大众的编辑器,可以服务于我们每一个普通人。无论你的职业为何,只要生活和工作离不开计算机,emacs都可以成为你提升效率的大杀器。从这个以上意义上来说, emacs适合每一个经常和计算机打交道的人 。\n为什么我推荐你使用emacs? 我曾经向很多熟悉的,不熟悉的人安利过emacs,但成功的情况很少。原因在于,我总是会像上面\u0026quot;什么是emacs?\u0026ldquo;中一样,把它介绍的很牛逼,因此让人望而生畏。事实上,emacs确实很牛逼,但牛逼的东西也是由简单的事物构成的,就像一个复杂的计算机系统是由简单的 0、1 组成。\n不同职业、不同身份的人使用计算机的需求和程度不一样,学习的计算机知识的难度也不同。用计算机办公、处理邮件的人只需要学习office办公软件,学会收发邮件;用计算机制作和处理图片、音视频的人只需要学习诸如ps、pr、ae等专业软件;普通程序员需要了解计算机运行的原理,学习如何通过特定的程序语言与计算机交互;高级黑客需要学习计算机底层架构和原理,了解程序语言背后的逻辑 ……\n很少人因为计算机是个复杂的东西就放弃使用它,因为你不需要了解它的全部,只需学习你需要使用的部分;因为只要你用它,就会给工作和生活带来极大的便利。这句话用在emacs上也再合适不过了!\n这就是我推荐你使用emacs的全部理由。\n这是怎样的emacs教程? emacs workflow 系列的文章会更关注新手,因此概念解释会比较详细。一个完整的工作流包含与之相关的方方面面,我将在我知识所及的情况下尽可能的介绍全面。工作流的不同方面可能会使用不同的emacs package或我自己编写的elisp代码,对于package或代码的内容,新手无需理解,拷贝粘贴后掌握如何使用即可。工作流的介绍分为“基础”和“附加”两部分。附加部分介绍不常用的或与程序相关的功能,这部分新手可以直接跳过。实现某一特定功能的package可能不只一个,我的解决方案可能不是最优解,欢迎留言补充。\n最后,希望我的工作对你有所帮助,希望emacs伴你走过每一个春夏秋冬~\n","date":"2020-06-14","permalink":"https://kinneyzhang.github.io/post/emacs-workflow-get-started/","summary":"Emacs Workflow 顾名思义 “emacs工作流”。这一系列的文章主要介绍emacs中的常用的工作流,比如:文件管理、日程管理、笔记管理、听音乐、网页浏览、收发邮件、版本控制、字","title":"emacs workflow - 开门见山"},{"content":"english document\n介绍 pp-html 是使用elisp开发的html模版语言。它借鉴了部分 liquid template language 的设计思路,包括对象,标签和过滤器三部分。通过书写elisp的s表达式,读者可以快速便捷地构建简单html片段或复杂的html页面。其中 :include 和 :extend (包含和继承) 标签使得模块化构建html成为可能,提高了代码重用率。\n安装 克隆此代码仓库:\n$ git clone https://github.com/kinneyzhang/pp-html.git \u0026lt;path-to-pp-html\u0026gt; 安装相关依赖: dash, s 和 web-mode。\n然后在emacs配置中添加如下两行:\n(add-to-list 'load-path \u0026quot;\u0026lt;path-to-pp-html\u0026gt;\u0026quot;) (require 'pp-html) 使用 基础 pp-html使用elisp的s表达式来构建html代码,使用 pp-html 函数对s表达式求值。下面有一些简单的例子,便于理解基本的使用方法。\n单个s表达式 单个s表达式的构成为: (html标签 多个属性键值对 内容) 。\n其中html标签是必须的,其余可缺省。属性键值对的书写语法为plist形式。特别地是,对css选择器设置了语法糖,用 \u0026ldquo;.\u0026rdquo; 表示 \u0026ldquo;class\u0026rdquo;,\u0026quot;@\u0026quot; 表示 \u0026ldquo;id\u0026rdquo;,其值为符号后面的内容。对于无值属性(例如 async)有两种写法:1. (:async nil) 2. 直接写:async,要求不能是最后一个属性。\n(pp-html '(a \u0026quot;content\u0026quot;)) (pp-html '(a @id .class)) (pp-html '(a :id \u0026quot;id\u0026quot; :class \u0026quot;class\u0026quot;)) (pp-html '(a .test :href \u0026quot;url\u0026quot; :target \u0026quot;_blank\u0026quot; \u0026quot;content\u0026quot;)) (pp-html '(link :async :rel \u0026quot;stylesheet\u0026quot; :href \u0026quot;url\u0026quot; :async nil)) \u0026lt;a\u0026gt;content\u0026lt;/a\u0026gt; \u0026lt;a id=\u0026quot;id\u0026quot; class=\u0026quot;class\u0026quot;\u0026gt;\u0026lt;/a\u0026gt; \u0026lt;a id=\u0026quot;id\u0026quot; class=\u0026quot;class\u0026quot;\u0026gt;\u0026lt;/a\u0026gt; \u0026lt;a class=\u0026quot;test\u0026quot; href=\u0026quot;url\u0026quot; target=\u0026quot;_blank\u0026quot;\u0026gt;content\u0026lt;/a\u0026gt; \u0026lt;link async rel=\u0026quot;stylesheet\u0026quot; href \u0026quot;url\u0026quot; async/\u0026gt; 并列s表达式 并列的多个s表达式直接用括号包含。\n(pp-html '((div .div1 \u0026quot;div-content\u0026quot;) (p \u0026quot;paragraph\u0026quot;) (a :href \u0026quot;url\u0026quot; \u0026quot;a-content\u0026quot;) (img :src \u0026quot;path\u0026quot;) (ul .org-ul (li \u0026quot;1\u0026quot;) (li \u0026quot;2\u0026quot;) (li \u0026quot;3\u0026quot;)))) \u0026lt;div class=\u0026quot;div1\u0026quot;\u0026gt;div-content\u0026lt;/div\u0026gt; \u0026lt;p\u0026gt;paragraph\u0026lt;/p\u0026gt; \u0026lt;a href=\u0026quot;url\u0026quot;\u0026gt;a-content\u0026lt;/a\u0026gt; \u0026lt;img src=\u0026quot;path\u0026quot;/\u0026gt; \u0026lt;ul class=\u0026quot;org-ul\u0026quot;\u0026gt; \u0026lt;li\u0026gt;1\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;2\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;3\u0026lt;/li\u0026gt; \u0026lt;/ul\u0026gt; 嵌套s表达式 在同一html标签的s表达式内代表该元素的子元素,否则为兄弟元素。\n(pp-html '(div .container (div .row (div .col-8 (p \u0026quot;paragraph 1\u0026quot;)) (div .col-4 (p \u0026quot;paragraph 2\u0026quot;))))) \u0026lt;div class=\u0026quot;container\u0026quot;\u0026gt; \u0026lt;div class=\u0026quot;row\u0026quot;\u0026gt; \u0026lt;div class=\u0026quot;col-8\u0026quot;\u0026gt; \u0026lt;p\u0026gt;paragraph 1\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026quot;col-4\u0026quot;\u0026gt; \u0026lt;p\u0026gt;paragraph 2\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; 对象 对象告诉pp-html在页面何处显示对象的值。它包括三类:变量求值、对象属性求值和函数求值。可以使用 pp-html-eval 函数获取对象的值。\n变量求值 变量求值的基本语法是在变量前加上\u0026quot;$\u0026ldquo;符号。\n(let ((var1 \u0026quot;happy hacking emacs\u0026quot;)) (pp-html-eval '$var1)) happy hacking emacs 变量可应用于s表达式的任何部分。\n(let ((url \u0026quot;https://geekinney.com/\u0026quot;) (name \u0026quot;戈楷旎\u0026quot;)) (pp-html '(a :href $url $name))) \u0026lt;a href=\u0026quot;https://geekinney.com/\u0026quot;\u0026gt;戈楷旎\u0026lt;/a\u0026gt; 对象属性求值 特别地,对于plist对象使用\u0026rdquo;.\u0026ldquo;来获取属性值。\n(let ((site '(:name \u0026quot;戈楷旎\u0026quot; :domain \u0026quot;geekinney.com\u0026quot; :author \u0026quot;geekinney\u0026quot;))) (pp-html '(div .site-info (p $site.name) (p $site.domain) (p $site.author)))) \u0026lt;div class=\u0026quot;site-info\u0026quot;\u0026gt; \u0026lt;p\u0026gt;戈楷旎\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;geekinney.com\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;geekinney\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; 函数求值 函数求值的s表达式语法为 ($ \u0026lt;args…\u0026gt;), 函数的参数也可写成变量形式。\n(let ((var1 \u0026quot;happy\u0026quot;) (var2 \u0026quot; hacking\u0026quot;)) (pp-html-eval '($ concat $var1 $var2 \u0026quot; emacs\u0026quot;))) happy hacking emacs 函数可嵌套调用,或直接写,两种写法等价。\n(let ((var1 \u0026quot;now\u0026quot;) (var2 \u0026quot; is \u0026quot;) (now '(current-time))) (pp-html-eval '($ concat ($ upcase $var1) $var2 ($ format-time-string \u0026quot;%y-%m-%d\u0026quot; $now))) (pp-html-eval '($ concat (upcase $var1) $var2 (format-time-string \u0026quot;%y-%m-%d\u0026quot; $now)))) now is 2020-05-10 now is 2020-05-10 同理,函数也可用于s表达式的任何部分,这样pp-html就可以任意使用elisp丰富强大的函数库了。\n标签 标签为模版创造了逻辑和流程控制,它用冒号表示并且放在s表达式的第一个位置: (:tag …)。标签分为5类:\n变量定义 流程控制 迭代 代码块 变量定义 assign\n定义变量,相当于elisp的let或setq。\n(pp-html '((:assign str1 \u0026quot;happy\u0026quot; str2 \u0026quot;hacking\u0026quot; str3 \u0026quot;emacs\u0026quot;) (p ($ concat $str1 \u0026quot; \u0026quot; $str2 \u0026quot; \u0026quot; $str3)))) \u0026lt;p\u0026gt;happy hacking emacs\u0026lt;/p\u0026gt; 流程控制 if\n如果条件为真执行第一个代码块,否则执行第二个\n(pp-html '((:assign bool nil) (:if $bool (p \u0026quot;true\u0026quot;) (p \u0026quot;false\u0026quot;)))) \u0026lt;p\u0026gt;false\u0026lt;/p\u0026gt; unless\n和if相反,如果条件为假,执行第一个代码块,否则执行第二个。\n(pp-html '((:assign bool nil) (:unless $bool (p \u0026quot;true\u0026quot;) (p \u0026quot;false\u0026quot;)))) \u0026lt;p\u0026gt;true\u0026lt;/p\u0026gt; cond\n执行每一个分支,直到条件满足,执行满足条件的代码块。\n(pp-html '((:assign case \u0026quot;case3\u0026quot;) (:cond ($ string= $case \u0026quot;case1\u0026quot;) (p \u0026quot;case1 branch\u0026quot;) ($ string= $case \u0026quot;case2\u0026quot;) (p \u0026quot;case2 branch\u0026quot;) ($ string= $case \u0026quot;case3\u0026quot;) (p \u0026quot;case3 branch\u0026quot;) t (p \u0026quot;default branch\u0026quot;)))) \u0026lt;p\u0026gt;case3 branch\u0026lt;/p\u0026gt; 迭代 for\nfor循环\n(pp-html '((:assign editors (\u0026quot;vim\u0026quot; \u0026quot;emacs\u0026quot; \u0026quot;vscode\u0026quot;)) (ul (:for editor in $editors (li :id $editor $editor))))) \u0026lt;ul\u0026gt; \u0026lt;li id=\u0026quot;vim\u0026quot;\u0026gt;vim\u0026lt;/li\u0026gt; \u0026lt;li id=\u0026quot;emacs\u0026quot;\u0026gt;emacs\u0026lt;/li\u0026gt; \u0026lt;li id=\u0026quot;vscode\u0026quot;\u0026gt;vscode\u0026lt;/li\u0026gt; \u0026lt;/ul\u0026gt; 代码块 include\n在一个代码块中包含另一个代码块。\n(setq block1 '(p \u0026quot;block1 content\u0026quot; (a :href \u0026quot;url\u0026quot; \u0026quot;content\u0026quot;))) (setq block2 '(div .block2 (p \u0026quot;block2 content\u0026quot;) (:include $block1))) (pp-html block2) \u0026lt;div class=\u0026quot;block2\u0026quot;\u0026gt; \u0026lt;p\u0026gt;block2 content\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt; block1 content \u0026lt;a href=\u0026quot;url\u0026quot;\u0026gt;content\u0026lt;/a\u0026gt; \u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; extend 和 block\n代码块继承。如果新代码块重写了 :block 标签之间的内容,覆盖原代码块对应的部分,其余保持不变。\n(setq base-block '(p .base (:block block-name (span \u0026quot;base content\u0026quot;))) extend-block1 '(:extend $base-block (:block block-name)) extend-block2 '(:extend $base-block (:block block-name (span \u0026quot;extended content\u0026quot;)))) (pp-html '((div \u0026quot;extend the default\u0026quot; (:include $extend-block1)) (div \u0026quot;extend with new\u0026quot; (:include $extend-block2)))) \u0026lt;div\u0026gt; extend the default \u0026lt;p class=\u0026quot;base\u0026quot;\u0026gt; \u0026lt;span\u0026gt;base content\u0026lt;/span\u0026gt; \u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div\u0026gt; extend with new \u0026lt;p class=\u0026quot;base\u0026quot;\u0026gt; \u0026lt;span\u0026gt;extended content\u0026lt;/span\u0026gt; \u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; 过滤器 过滤器的语法形式为 (/ \u0026lt;:filter args\u0026gt; …)。过滤器作用于,可以有参数,也可以没有。\n自定义过滤器 pp-html支持自定义过滤器,使用 pp-html-define-filter 函数,它有两个参数:过滤器名称和过滤函数。例:\n(pp-html-define-filter :add 'pp-html-filter-add) (defun pp-html-filter-add (value arg) \u0026quot;add a value to a number\u0026quot; (let ((arg (if (stringp arg) (string-to-number arg) arg))) (+ value arg))) 内置过滤器 abs: 取绝对值\n(pp-html-eval '(/ -5 :abs)) ;; =\u0026gt; 5 add: 加上一个数\n(pp-html-eval '(/ 4 :add 5)) ;; =\u0026gt; 9 append: 结合两个列表\n(let ((list1 '(1 2 3)) (list2 '(5 6 7))) (pp-html-eval '(/ $list1 :append $list2))) ;; =\u0026gt; (1 2 3 5 6 7) capitalize: 第一个单词首字母大写\n(pp-html-eval '(/ \u0026quot;happy hacking emacs!\u0026quot; :capitalize)) ;; =\u0026gt; happy hacking emacs! compact: 删除列表中所有的nil\n(let ((lst '(nil 1 2 nil 3 4 nil))) (pp-html-eval '(/ $lst :compact))) ;; =\u0026gt; (1 2 3 4) concat: 字符串连接\n(let ((str1 \u0026quot;happy hacking \u0026quot;) (str2 \u0026quot;emacs\u0026quot;)) (pp-html-eval '(/ $str1 :concat $str2))) ;; =\u0026gt; happy hacking emacs default: 不是nil或空字符串,设为默认值\n(let ((str1 \u0026quot;\u0026quot;) (str2 \u0026quot;new value\u0026quot;) (lst1 '(1 2 3)) (lst2 nil)) (pp-html-eval '(/ $str1 :default \u0026quot;default value\u0026quot;)) ;; =\u0026gt; default value (pp-html-eval '(/ $str2 :default \u0026quot;default value\u0026quot;)) ;; =\u0026gt; new value (pp-html-eval '(/ $lst1 :default (4 5 6))) ;; =\u0026gt; (1 2 3) (pp-html-eval '(/ $lst2 :default (4 5 6))) ;; =\u0026gt; (4 5 6) ) escape: html特殊字符转义\n(pp-html-eval '(/ \u0026quot;have you read 'james \u0026amp; the giant peach'?\u0026quot; :escape)) ;; =\u0026gt; have you read \u0026amp;apos;james \u0026amp;amp; the giant peach\u0026amp;apos;? join: 使用分隔符连接列表中字符串\n(let ((lst '(\u0026quot;happy\u0026quot; \u0026quot;hacking\u0026quot; \u0026quot;emacs\u0026quot;))) (pp-html-eval '(/ $lst :join \u0026quot;-\u0026quot;))) ;; =\u0026gt; happy-hacking-emacs … more useful filters are on the way!\n综合 综合以上语法的例子:\n(setq assign-vars '(:assign name \u0026quot;geekinney blog\u0026quot; description \u0026quot;emacs is a lifestyle :-) and happy hacking emacs!\u0026quot; menus ((:path \u0026quot;/\u0026quot; :name \u0026quot;index\u0026quot;) (:path \u0026quot;/archive\u0026quot; :name \u0026quot;archive\u0026quot;) (:path \u0026quot;/category\u0026quot; :name \u0026quot;category\u0026quot;) (:path \u0026quot;/about\u0026quot; :name \u0026quot;about\u0026quot;)) comment-p t comment-type \u0026quot;disqus\u0026quot; valine-block (p \u0026quot;this is valine block\u0026quot;) disqus-block (p \u0026quot;this is disqus block\u0026quot;))) (setq header-block '(header @topheader (a @logo :href \u0026quot;/\u0026quot; $name) (p .description $description))) (setq menu-block '(nav @topmenu (:for menu in $menus (a :href $menu.path $menu.name)))) (setq article-block '(article (p ($ concat \u0026quot;function: the site name is \u0026quot; ($ upcase $name))) (p (/ \u0026quot;filter: the site name is \u0026quot; :concat (/ $name :capitalize))) (p (/ (\u0026quot;happy\u0026quot; \u0026quot;hacking\u0026quot; \u0026quot;emacs\u0026quot;) :join \u0026quot; \u0026quot; :capitalize :concat \u0026quot;!\u0026quot;)))) (setq comment-block '(div @comment (:if comment-p (:cond ($ string= $comment-type \u0026quot;valine\u0026quot;) (:include $valine-block) ($ string= $comment-type \u0026quot;disqus\u0026quot;) (:include $disqus-block) t nil) (p \u0026quot;the comment is closed!\u0026quot;)))) (setq side-block '(aside @sidebar (:block side-block (p \u0026quot;this is base sidebar\u0026quot;)))) (setq footer-block '(:block footer-block (footer (p \u0026quot;this is base footer.\u0026quot;)))) (setq base-block '((:include $assign-vars) (body (div .container (div .row (div .col-12 (:include $header-block))) (div .row (div .col-12 (:include $menu-block))) (div .row (div .col-12 .col-sm-12 .col-md-8 .col-lg-8 (:include $article-block) (:include $comment-block)) (div .col-md-4 .col-lg-4 (:include $side-block))) (div .row (div .col-12 (:include $footer-block))))))) (pp-html '(:extend $base-block (:block side-block (p \u0026quot;this is extended sidebar\u0026quot;)) (:block footer-block))) \u0026lt;body\u0026gt; \u0026lt;div class=\u0026quot;container\u0026quot;\u0026gt; \u0026lt;div class=\u0026quot;row\u0026quot;\u0026gt; \u0026lt;div class=\u0026quot;col-12\u0026quot;\u0026gt; \u0026lt;header id=\u0026quot;topheader\u0026quot;\u0026gt; \u0026lt;a id=\u0026quot;logo\u0026quot; href=\u0026quot;/\u0026quot;\u0026gt;geekinney blog\u0026lt;/a\u0026gt; \u0026lt;p class=\u0026quot;description\u0026quot;\u0026gt;emacs is a lifestyle :-) and happy hacking emacs!\u0026lt;/p\u0026gt; \u0026lt;/header\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026quot;row\u0026quot;\u0026gt; \u0026lt;div class=\u0026quot;col-12\u0026quot;\u0026gt; \u0026lt;nav id=\u0026quot;topmenu\u0026quot;\u0026gt; \u0026lt;a href=\u0026quot;/\u0026quot;\u0026gt;index\u0026lt;/a\u0026gt; \u0026lt;a href=\u0026quot;/archive\u0026quot;\u0026gt;archive\u0026lt;/a\u0026gt; \u0026lt;a href=\u0026quot;/category\u0026quot;\u0026gt;category\u0026lt;/a\u0026gt; \u0026lt;a href=\u0026quot;/about\u0026quot;\u0026gt;about\u0026lt;/a\u0026gt; \u0026lt;/nav\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026quot;row\u0026quot;\u0026gt; \u0026lt;div class=\u0026quot;col-12 col-sm-12 col-md-8 col-lg-8\u0026quot;\u0026gt; \u0026lt;article\u0026gt; \u0026lt;p\u0026gt;function: the site name is geekinney blog\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;filter: the site name is geekinney blog\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;happy hacking emacs!\u0026lt;/p\u0026gt; \u0026lt;/article\u0026gt; \u0026lt;div id=\u0026quot;comment\u0026quot;\u0026gt; \u0026lt;p\u0026gt;this is disqus block\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026quot;col-md-4 col-lg-4\u0026quot;\u0026gt; \u0026lt;aside id=\u0026quot;sidebar\u0026quot;\u0026gt; \u0026lt;p\u0026gt;this is extended sidebar\u0026lt;/p\u0026gt; \u0026lt;/aside\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026quot;row\u0026quot;\u0026gt; \u0026lt;div class=\u0026quot;col-12\u0026quot;\u0026gt; \u0026lt;footer\u0026gt; \u0026lt;p\u0026gt;this is base footer.\u0026lt;/p\u0026gt; \u0026lt;/footer\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; 说明 预览调试 pp-html-test 函数可以在view buffer中预览生成的格式化html。 pp-html-parse 函数可以查看解析完所有逻辑标签后的s表达式。这两个函数便于调试代码。\nxml支持 pp-html还额外支持生成xml。与html不同,xml没有单元素(img,link…),所以更简单。使用方法为设置 pp-html 函数的第二个参数为t。\n结合orgmode 在org文件中,使用带参数的emacs-lisp代码块可以在org或html中生成elisp代码对应的html。例如:\n1.当导出org文件时,生成一个有红色背景div的html页面。\n#+begin_src emacs-lisp :results value html :exports results (pp-html '(div :style \u0026quot;background-color:red;\u0026quot; \u0026quot;content\u0026quot;)) #+end_src #+results: #+begin_export html \u0026lt;div style=\u0026quot;background-color:red;\u0026quot;\u0026gt;content\u0026lt;/div\u0026gt; #+end_export 2.当导出org文件时,生成包含 \u0026lt;div style=\u0026quot;background-color:red;\u0026quot;\u0026gt;content\u0026lt;/div\u0026gt; 代码的html页面。\n#+begin_src emacs-lisp :wrap src html :exports results (pp-html '(div :style \u0026quot;background-color:red;\u0026quot; \u0026quot;content\u0026quot;)) #+end_src #+results: #+begin_src html \u0026lt;div style=\u0026quot;background-color:red;\u0026quot;\u0026gt;content\u0026lt;/div\u0026gt; #+end_src 关于orgmode导出代码块的参数设置参考 working-with-source-code 。\n构建博客 我的 个人博客 就是基于 pp-html 构建的,我将构建博客的代码组织成了emacs包: geekblog ,目前处理代码优化整理阶段,敬请关注 我的github 或博客。\n鸣谢 pp-html是我写的第一个emacs包。由于是新手,开发过程断断续续持续了一个多月的时间,其间遇到了许多的技术难题。特别感谢 emacs-china社区 的同学们答疑解惑。\n此package可能有不成熟的地方,希望读者诸君、emacs大牛批评指正。关于package功能的拓展和集成,也可以给我提建议(issue或博客留言)。\n如果你觉得我的工作对你有所帮助,欢迎 star 此代码仓库!\n","date":"2020-05-10","permalink":"https://kinneyzhang.github.io/post/html-template-language-in-emacs/","summary":"English Document 介绍 pp-html 是使用elisp开发的html模版语言。它借鉴了部分 Liquid template language 的设计思路,包括对象,标签和过滤器三部分。通过书写elisp的S表达式,读者可以快速便捷地构","title":"基于emacs-lisp的html模版库"},{"content":"有三类事情:第一类只要花时间就能完美解决;第二类不仅要花时间,还得费点脑筋才能大体完成;第三类即使花了时间、耗了精力仍感觉遥遥无期。\n把大量的时间花在第一类事情,就是在做些无意义的重复劳动。死磕第三类事情,发现自己的预期与现状遥不可及,便会忧虑。唯有第二类事情,在力所能及的范围内又不能轻易达到。完成它有成就感,再次碰到想追求更完美,也有挑战欲。\n当第二类事情轻车熟路,就变成了第一类事情;而曾经遥不可及的第三类事情也随着我们能力的增长,变成了现在的第二类事情。当然,最终也会变为第一类事情。三二一、三二一的转变,不变的是事情本身,变化的是我们处理事情的能力。\n私以为,焦虑的人把焦点放在了第三类事情,期盼着更加明朗和美好的未来,前路一旦不明朗便倍感焦虑。殊不知,未来是由脚下的砖石铺砌起来的,如果连脚下的路都走不稳,那何谈未来?\n我的经验是,别去想那些有的没的,抓紧时间把第二类事情做好,同时把第三类事情放入未来的规划。这样脚踏实地,前路会越来越明亮,能够解决的问题会越来越多,能力也便水涨船高。\n小的时候,我们不知”焦虑“为何物,所以不开心的时候会自发的排遣不快,或找亲人朋友聊天,或听音乐打游戏,然后开开心心的开始新的一天。现在长大了,我们在焦虑的时候便敏感的意识到:”哦,我现在很焦虑,怎么办,怎么办?“,然后想着想着就陷入了思维的怪圈。一天两天,一周半月,时间在焦虑中荒废。孩子解决不开心的思维几乎是本能的,而大人面对焦虑多数时候显得无能为力。不是大人的烦恼更多,而是我们内心习惯了焦虑和消极是生活必不可少的状态,我们习惯地以为可以通过思考解决问题。但其实,最好的解决问题的方法不是胡思乱想,而是立刻行动!做力所能力的第二类事情吧。\n","date":"2020-03-17","permalink":"https://kinneyzhang.github.io/post/thinking-about-anxiety/","summary":"有三类事情:第一类只要花时间就能完美解决;第二类不仅要花时间,还得费点脑筋才能大体完成;第三类即使花了时间、耗了精力仍感觉遥遥无期。 把大量的时间花在第一类事情,","title":"关于焦虑"},{"content":"lisp介绍 lisp(历史上拼写为lisp)是具有悠久历史的计算机编程语言家族,有独特和完全括号的前缀符号表示法。起源于公元1958年,是现今第二悠久而仍广泛使用的高端编程语言。只有fortran编程语言比它更早一年。lisp编程语族已经演变出许多种方言。现代最著名的通用编程语种是clojure、common lisp和scheme。\nlisp最初创建时受到阿隆佐·邱奇的lambda演算的影响,用来作为计算机程序实用的数学表达。因为是早期的高端编程语言之一,它很快成为人工智能研究中最受欢迎的编程语言。在计算机科学领域,lisp开创了许多先驱概念,包括:树结构、自动存储器管理、动态类型、条件表达式、高端函数、递归、自主(self-hosting)编译器、读取﹣求值﹣输出循环(英语:read-eval-print loop,repl)。\n\u0026ldquo;lisp\u0026quot;名称源自“列表处理器”(英语:list processor)的缩写。列表是lisp的主要数据结构之一,lisp编程代码也同样由列表组成。因此,lisp程序可以把源代码当作数据结构进行操作,而使用其中的宏系统,开发人员可将自己定义的新语法或领域专用的语言,嵌入在lisp编程中。\n代码和数据的可互换性为lisp提供了立即可识别的语法。所有的lisp程序代码都写为s-表达式或以括号表示的列表。函数调用或语义形式也同样写成列表,首先是函数或操作符的名称,然后接着是一或多个参数:例如,取三个参数的函数f即为(f arg1 arg2 arg3)。\nlisp语言的主要现代版本包括common lisp, scheme,racket以及clojure。1980年代盖伊·史提尔二世编写了common lisp试图进行标准化,这个标准被大多数解释器和编译器所接受。还有一种是编辑器emacs所派生出来的emacs lisp(而emacs正是用lisp作为扩展语言进行功能扩展)非常流行,并创建了自己的标准。\nelisp 概览 运行emacs-lisp的几种方式 key command description `c-x c-e` `eval-last-sexp` 在s表达式结尾运行,在minibuffer显示结果 `c-j` `eval-print-last-sexp` 在s表达式结尾运行,打印运行结果 `m-:` `eval-expression` 在minibuffer输入命令并执行 `m-x ielm` `ielm` 使用ielm解释器运行代码 创建命令(interactive函数) ;; example (defun buffer/insert-filename () \u0026quot;insert file path of current buffer at current point\u0026quot; (interactive) (insert (buffer-file-name (current-buffer)))) emacs探索 key command description `c-h k` `describe-key` 运行命令后,继续按键,查看此时按键绑定的函数 `c-h b` `describe-bindings` 在\\*help\\*界面搜索 `major mode bindings:` 可以查看所有与当前major mode相关的按键。 `c-h f` `describe-function` 查看函数文档及详细代码 elisp编程的基本设置 三个有用的pcakage:\nrainbow-delimiters: 不同颜色区分不同层级的括号 paredit: 检查括号匹配 company: elisp代码补全 基本运算 算术 elisp\u0026gt; (+ 20 30) 50 elisp\u0026gt; (- 100 80) 20 elisp\u0026gt; (+ 1 2 3 4 5 6) 21 elisp\u0026gt; (* 1 2 3 4 5 6) 720 elisp\u0026gt; (/ 1 100) 0 elisp\u0026gt; (\u0026gt; 10 1) ;; ?? 10 \u0026gt; 1 t elisp\u0026gt; (\u0026lt; 2 8) ;; ?? 2 \u0026lt; 8 t elisp\u0026gt; (\u0026lt; 8 2) ;; ?? 8 \u0026lt; 2 nil elisp\u0026gt; (= 2 2) t elisp\u0026gt; (= 2 4) nil elisp\u0026gt; (/= 2 2) nil elisp\u0026gt; (exp -1) 0.36787944117144233 elisp\u0026gt; (log 10) 2.302585092994046 elisp\u0026gt; (sin pi) 1.2246467991473532e-16 elisp\u0026gt; (cos pi) -1.0 elisp\u0026gt; (tan (/ pi 2)) 1.633123935319537e+16 elisp\u0026gt; 比较 ;;;; compare numbers elisp\u0026gt; (= 2 (+ 1 1)) t ;;; compare symbols and numbers elisp\u0026gt; (eq 1 1) t elisp\u0026gt; (eq 1 2) nil elisp\u0026gt; elisp\u0026gt; (eq 'x 'x) t elisp\u0026gt; ;;; compare elements of a list elisp\u0026gt; (equal (list 1 2 3 4) (list 1 2 3 4)) t ;;; compare strings elisp\u0026gt; (string= \u0026quot;hello\u0026quot; \u0026quot;hello\u0026quot;) t 列表 elisp\u0026gt; '(10 20 30 40) (10 20 30 40) elisp\u0026gt; '(10 203 40 \u0026quot;hello\u0026quot; () (\u0026quot;empty\u0026quot; 65)) (10 203 40 \u0026quot;hello\u0026quot; nil (\u0026quot;empty\u0026quot; 65)) 类型判断和literals emacs literals ;;; numbers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; elisp\u0026gt; 1e3 1000.0 ;;; string ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; elisp\u0026gt; \u0026quot;hello world emacs literals\u0026quot; \u0026quot;hello world emacs literals\u0026quot; elisp\u0026gt; ;;; symbol ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; elisp\u0026gt; 'this-a-symbol this-a-symbol elisp\u0026gt; 'vector-\u0026gt;list vector-\u0026gt;list elisp\u0026gt; 'symbol? symbol\\? elisp\u0026gt; ;; boolean t and nil ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; elisp\u0026gt; t t elisp\u0026gt; nil nil elisp\u0026gt; ;;; everything that is not \u0026quot;nil\u0026quot; is true: ;;----------------------------------------- elisp\u0026gt; (if t \u0026quot;it is true (not nil)\u0026quot; \u0026quot;it is false (it is nil)\u0026quot;) \u0026quot;it is true (not nil)\u0026quot; elisp\u0026gt; elisp\u0026gt; (if 100e3 \u0026quot;it is true (not nil)\u0026quot; \u0026quot;it is false (it is nil)\u0026quot;) \u0026quot;it is true (not nil)\u0026quot; elisp\u0026gt; (if '(a b c d) \u0026quot;it is true (not nil)\u0026quot; \u0026quot;it is false (it is nil)\u0026quot;) \u0026quot;it is true (not nil)\u0026quot; elisp\u0026gt; elisp\u0026gt; (if nil \u0026quot;it is true (not nil)\u0026quot; \u0026quot;it is false (it is nil)\u0026quot;) \u0026quot;it is false (it is nil)\u0026quot; elisp\u0026gt; ;;; pair / cons cell ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; elisp\u0026gt; '(a . b) (a . b) elisp\u0026gt; '(a . 2999) (a . 2999) ;;; list ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; elisp\u0026gt; '(1 2 3 (3 4) (5 6 (+ 3 4)) 10 'a 'b \u0026quot;hello\u0026quot; ) (1 2 3 (3 4) (5 6 (+ 3 4)) 10 'a 'b \u0026quot;hello\u0026quot;) elisp\u0026gt; '(+ 1 2 3 4 5) (+ 1 2 3 4 5) elisp\u0026gt; '(cos 10) (cos 10) ;;; vectors ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; elisp\u0026gt; [1 2 3 4 (+ 1 2 3 54)] [1 2 3 4 (+ 1 2 3 54)] 基本类型判断 type predicate literal description nil null nil '() test if argument is nil numbers numberp 100, 200e3 test if it is number. string stringp \"hello\" test if it is string symbol symbolp 'sym :keyworkd test if it is a symbol. \u0026#xa0; \u0026#xa0; \u0026#xa0; \u0026#xa0; atom atom 'x \"h\" :key 200 everything that is not a list or pair is an atom. list listp '(1 2 x y) test if it is a list pair consp '(a . 200) test if it is a pair (cons cell) vector vectorp [1 200 'sym] test if it is a vector object predicate buffer bufferp window windowp frame framep process processp elisp\u0026gt; (null nil) t elisp\u0026gt; elisp\u0026gt; (null '()) t elisp\u0026gt; (null 10) nil elisp\u0026gt; (atom 10) t elisp\u0026gt; (atom '(a . b)) nil elisp\u0026gt; (atom \u0026quot;hello world\u0026quot;) t elisp\u0026gt; elisp\u0026gt; (bufferp (current-buffer)) t elisp\u0026gt; (bufferp (selected-window)) nil elisp\u0026gt; (windowp (selected-window)) t elisp\u0026gt; 获取对象类型 elisp\u0026gt; (type-of (current-buffer)) buffer elisp\u0026gt; elisp\u0026gt; (type-of (selected-window)) window elisp\u0026gt; elisp\u0026gt; (equal 'buffer (type-of (current-buffer))) t elisp\u0026gt; (equal 'buffer (type-of (selected-window))) nil elisp\u0026gt; 变量定义 ;;; constants ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; elisp\u0026gt; (defconst zsh-shell \u0026quot;/usr/bin/zsh\u0026quot;) zsh-shell elisp\u0026gt; zsh-shell \u0026quot;/usr/bin/zsh\u0026quot; elisp\u0026gt; ;;; define a variable ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; set is not used very much ;; elisp\u0026gt; (set 'avar \u0026quot;hello world\u0026quot;) \u0026quot;hello world\u0026quot; elisp\u0026gt; avar \u0026quot;hello world\u0026quot; elisp\u0026gt; ;;;;; the most used command for assignment is setq ;; elisp\u0026gt; (setq x 10) 10 elisp\u0026gt; (setq avar \u0026quot;hello world\u0026quot;) \u0026quot;hello world\u0026quot; elisp\u0026gt; x 10 elisp\u0026gt; avar \u0026quot;hello world\u0026quot; elisp\u0026gt; elisp\u0026gt; (setq my-list '(10 20 30 40)) (10 20 30 40) elisp\u0026gt; my-list (10 20 30 40) ;;; multiple assignment ;; elisp\u0026gt; (setq a 10 b 20 c \u0026quot;emacs\u0026quot;) \u0026quot;emacs\u0026quot; elisp\u0026gt; a 10 elisp\u0026gt; b 20 elisp\u0026gt; c \u0026quot;emacs\u0026quot; elisp\u0026gt; ;; dynamic scoping (local variables) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; elisp\u0026gt; (let ((x 1) (y 10)) (+ (* 4 x) (* 5 y)) ) 54 elisp\u0026gt; x ** eval error ** symbol's value as variable is void: x elisp\u0026gt; y ** eval error ** symbol's value as variable is void: y elisp\u0026gt; 函数定义 定义简单函数 语法: (defun () ())\nelisp\u0026gt; (defun afunction (a b c) (+ a b c)) afunction elisp\u0026gt; (afunction 10 20 30) 60 elisp\u0026gt; (defun myfun () (message \u0026quot;hello emacs\u0026quot;)) myfun elisp\u0026gt; (myfun) \u0026quot;hello emacs\u0026quot; elisp\u0026gt; elisp\u0026gt; elisp\u0026gt; (defun signum (n) (cond ((\u0026gt; n 0) 1 ) ((\u0026lt; n 0) -1) (0))) signum elisp\u0026gt; (signum 10) 1 elisp\u0026gt; (signum 0) 0 elisp\u0026gt; (signum -23) -1 elisp\u0026gt; elisp\u0026gt; (defun factorial (n) (if (= n 0) 1 (* n (factorial (- n 1))))) factorial elisp\u0026gt; (factorial 5) 120 elisp 匿名函数/lambda函数 语法: (lambda () ())\nelisp\u0026gt; (lambda (x) (+ x 3)) (lambda (x) (+ x 3)) ;;; applying lambda functions ;; elisp\u0026gt; ((lambda (x) (+ x 3)) 4) 7 elisp\u0026gt; (funcall (lambda (x) (+ x 3)) 4) 7 elisp\u0026gt; ;;; storing lambda function in variable ;; ;; elisp\u0026gt; (defvar add3 (lambda (x) (+ x 3))) add3 elisp\u0026gt; add3 (lambda (x) (+ x 3)) elisp\u0026gt; (funcall add3 10) 13 elisp\u0026gt; (add3 10) ** eval error ** symbol's function definition is void: add3 elisp\u0026gt; (funcall #'add3 10) ** eval error ** symbol's function definition is void: add3 elisp\u0026gt; ;;; passing lambda function to functions ;; elisp\u0026gt; (mapcar (lambda (x) (+ x 3)) '(1 2 3 4 5)) (4 5 6 7 8) 函数作为参数 语法: (caller-function #\u0026rsquo; #\u0026rsquo; arg1 arg2 …)\n在函数内部,使用 funcall 调用函数作为参数\nelisp\u0026gt; (mapcar log '(1 10 100 1000)) ** eval error ** symbol's value as variable is void: log elisp\u0026gt; (mapcar #'log10 '(1 10 100 1000)) (0.0 1.0 2.0 3.0) (defun sum-fun (f1 f2 x) (+ (funcall f1 x) (funcall f2 x))) elisp\u0026gt; (sum-fun #'log #'exp 3) 21.18414921185578 elisp\u0026gt; elisp\u0026gt; (+ (log 3) (exp 3)) 21.18414921185578 elisp\u0026gt; elisp\u0026gt; (sum-fun (lambda (x) (* 3 x)) (lambda (x) (* 4 x)) 5) 35 elisp\u0026gt; elisp\u0026gt; (defun 1+ (x) (+ 1 x)) 1+ elisp\u0026gt; (defun 3* (x) (* 3 x)) 3* elisp\u0026gt; (sum-fun #'1+ #'3* 4) 17 elisp\u0026gt; elisp\u0026gt; (sum-fun #'1+ (lambda (x) (* 3 x)) 4) 17 elisp\u0026gt; 多参函数 (defun sum (\u0026amp;rest numbers) (apply #'+ numbers)) elisp\u0026gt; (sum 1 2 3 4 5 6) 21 elisp\u0026gt; (apply #'sum '(1 2 3 5 6)) 17 elisp\u0026gt; (apply #'sum (list 1 2 3 5 (+ 6 5 2))) 24 elisp\u0026gt; (apply #'sum '()) 0 elisp\u0026gt; (apply #'sum nil) 0 elisp\u0026gt; (sum nil) ** eval error ** wrong type argument: number-or-marker-p, ni ;;---------------------------------- (defun sum-prod (a \u0026amp;rest xs) (* a (apply #'+ xs))) elisp\u0026gt; (sum-prod 3 1 2 3 4 5) 45 elisp\u0026gt; (sum-prod 1 1 2 3 4 5) 15 可选参数函数 (defun test-optional (a \u0026amp;optional b) (list a b)) elisp\u0026gt; (test-optional 10 20) (10 20) elisp\u0026gt; (test-optional 10 ) (10 nil) ;--------------------------------; (defun test-optional2 (a b \u0026amp;optional b c d e) (list :a a :b b :c c :d d :e e)) elisp\u0026gt; (test-optional2 0 1 2 3 4 5 ) (:a 0 :b 2 :c 3 :d 4 :e 5) elisp\u0026gt; (test-optional2 0 1 2 3 4 ) (:a 0 :b 2 :c 3 :d 4 :e nil) elisp\u0026gt; (test-optional2 0 1 2 3 ) (:a 0 :b 2 :c 3 :d nil :e nil) elisp\u0026gt; (test-optional2 0 1 2 ) (:a 0 :b 2 :c nil :d nil :e nil) elisp\u0026gt; (test-optional2 0 1 ) (:a 0 :b nil :c nil :d nil :e nil) elisp\u0026gt; (test-optional2 0 1) (:a 0 :b nil :c nil :d nil :e nil) ;--------------------------------; (defun test-optional-default-b (a \u0026amp;optional b) (if b (list a b) (list a \u0026quot;b is null\u0026quot;))) elisp\u0026gt; (test-optional-default-b 1 2) (1 2) elisp\u0026gt; (test-optional-default-b 1) (1 \u0026quot;b is null\u0026quot;) elisp\u0026gt; (test-optional-default-b 1 nil) (1 \u0026quot;b is null\u0026quot;) 含属性列表参数函数 (defun make-shell-interface (\u0026amp;rest params) \u0026quot; create a shell interface. possible parameters: :name name of shell :type ['sh, 'bash, ...] :path path to program :buffer name of buffer \u0026quot; (let ((name (plist-get params :name )) (type (plist-get params :type)) (path (plist-get params :path)) (buffer (plist-get params :buffer))) (list (cons 'name buffer) (cons 'type type) (cons 'path path) (cons 'buffer buffer)))) elisp\u0026gt; (make-shell-interface :name \u0026quot;pylaucher\u0026quot; :path \u0026quot;/usr/bin/python\u0026quot; :type 'sh :buffer \u0026quot;pyshell\u0026quot;) ((name . \u0026quot;pyshell\u0026quot;) (type . sh) (path . \u0026quot;/usr/bin/python\u0026quot;) (buffer . \u0026quot;pyshell\u0026quot;)) elisp\u0026gt; (make-shell-interface :name \u0026quot;pylaucher\u0026quot; :path \u0026quot;/usr/bin/python\u0026quot; :type 'sh) ((name) (type . sh) (path . \u0026quot;/usr/bin/python\u0026quot;) (buffer)) elisp\u0026gt; (make-shell-interface :name \u0026quot;pylaucher\u0026quot; :path \u0026quot;/usr/bin/python\u0026quot; :type 'bash) ((name) (type . bash) (path . \u0026quot;/usr/bin/python\u0026quot;) (buffer)) elisp\u0026gt; (make-shell-interface :name \u0026quot;pylaucher\u0026quot; :path \u0026quot;/usr/bin/python\u0026quot;) ((name) (type) (path . \u0026quot;/usr/bin/python\u0026quot;) (buffer)) elisp\u0026gt; (make-shell-interface :name \u0026quot;pylaucher\u0026quot; ) ((name) (type) (path) (buffer)) elisp\u0026gt; (make-shell-interface ) ((name) (type) (path) (buffer)) elisp\u0026gt; (make-shell-interface :buffer \u0026quot;pyshell\u0026quot; :path \u0026quot;/usr/bin/python\u0026quot; :type 'sh :name \u0026quot;pylaucher\u0026quot;) ((name . \u0026quot;pyshell\u0026quot;) (type . sh) (path . \u0026quot;/usr/bin/python\u0026quot;) (buffer . \u0026quot;pyshell\u0026quot;)) closures elisp方言默认不支持closure,所以下面的代码不会像scheme或common lisp一样执行。\n参考:\nemacswiki: lexical binding\nemacswiki: dynamic binding vs lexical binding\nemacs lisp readable closures « null program\nhttps://www.jamesporter.me/2013/06/14/emacs-lisp-closures-exposed.html\ntechnical dresese: a brief demonstration of emacs new lexical bindings\n(defun make-adder (x) (lambda (y) (+ x y))) elisp\u0026gt; elisp\u0026gt; (make-adder 3) (lambda (y) (+ x y)) elisp\u0026gt; ((make-adder 3) 4) ** eval error ** invalid function: (make-adder 3) elisp\u0026gt; (funcall (make-adder 3) 4) ** eval error ** symbol's value as variable is void: x elisp\u0026gt; (map (make-adder 3) '(1 2 3 4 5)) ** eval error ** symbol's value as variable is void: x elisp\u0026gt; 支持closure的代码:\n(setq lexical-binding t) (defun make-adder (x) (lambda (y) (+ x y))) elisp\u0026gt; (make-adder 3) (closure ((x . 3) t) (y) (+ x y)) elisp\u0026gt; ((make-adder 3) 4) ** eval error ** invalid function: (make-adder 3) elisp\u0026gt; elisp\u0026gt; (funcall (make-adder 3) 4) 7 elisp\u0026gt; elisp\u0026gt; (mapcar (make-adder 3) '(1 2 3 4 5)) (4 5 6 7 8) ;;;; sometimes is better to create macro rather than a higher order function (defmacro make-sum-fun (f1 f2) `(lambda (x) (+ (,f1 x) (,f2 x)))) elisp\u0026gt; elisp\u0026gt; (funcall (make-sum-fun sin cos) 3) -0.8488724885405782 elisp\u0026gt; elisp\u0026gt; (make-sum-fun sin cos) (closure (t) (x) (+ (sin x) (cos x))) elisp\u0026gt; (map (make-sum-fun sin cos) '(1 2 3 4 5)) (1.3817732906760363 0.4931505902785393 -0.8488724885405782 -1.4104461161715403 -0.6752620891999122) 在 ~/.emacs.d/init.el 中添加如下配置以支持closure.\n(setq lexical-binding t) 列表操作 参考:\nhttps://www.fincher.org/tips/languages/emacs.shtml\n;; defining a list ;; ;; an emacs list can contain elements of almost any type. ;; elisp\u0026gt; '( \u0026quot;a\u0026quot; 2323 \u0026quot;b\u0026quot; 21.2323 \u0026quot;hello\u0026quot; \u0026quot;emacs\u0026quot; nil () (34 134) '(+ 2 3 5)) (\u0026quot;a\u0026quot; 2323 \u0026quot;b\u0026quot; 21.2323 \u0026quot;hello\u0026quot; \u0026quot;emacs\u0026quot; nil nil (34 134) '(+ 2 3 5)) elisp\u0026gt; (quote (1 3 3 4 5)) (1 3 3 4 5) ;;;;; empty list ;; elisp\u0026gt; nil nil elisp\u0026gt; '() nil elisp\u0026gt; ;; length of a list elisp\u0026gt; (length '(1 2 3 4 5 6)) 6 elisp\u0026gt; ;; nth element of a list ;; elisp\u0026gt; (nth 0 '(0 1 2 3 4 5)) 0 elisp\u0026gt; (nth 2 '(0 1 2 3 4 5)) 2 elisp\u0026gt; (nth 5 '(0 1 2 3 4 5)) 5 elisp\u0026gt; (nth 10 '(0 1 2 3 4 5)) nil elisp\u0026gt; ;; membership test ;; member returns null if the element is not member of the list ;; elisp\u0026gt; (member 2 '(0 1 2 3 4 5)) (2 3 4 5) elisp\u0026gt; (member 10 '(0 1 2 3 4 5)) nil elisp\u0026gt; ;; position of list element (prior to emacs 24.4) ;; elisp\u0026gt; (position 7 '(5 6 7 8)) 2 elisp\u0026gt; (position 17 '(5 6 7 8)) nil elisp\u0026gt; ;; position of list element (emacs 24.4 or later) ;; elisp\u0026gt; (cl-position 7 '(5 6 7 8)) 2 elisp\u0026gt; (cl-position 17 '(5 6 7 8)) nil elisp\u0026gt; ;; cdr ;; ;; removes first element of the list, returns the list tail. ;; elisp\u0026gt; (cdr '(1 2 3 4 5)) (2 3 4 5) ;; car ;; ;; returns the first list element ;; elisp\u0026gt; (car '(1 2 3 4 5)) 1 elisp\u0026gt; ;; cons ;; ;; list constructor ;; elisp\u0026gt; (cons 10 '(1 2 3 4)) (10 1 2 3 4) elisp\u0026gt; (cons 1 (cons 2 (cons 3 (cons 4 (cons 5 '()))))) (1 2 3 4 5) ;; last element of a list ;; ;; elisp\u0026gt; (car (last '(1 2 3 4 5))) 5 elisp\u0026gt; ;; reverse a list ;; elisp\u0026gt; (reverse '(1 2 3 4 5)) (5 4 3 2 1) ;; append lists ;; ;; note: nil also means an empty list ;; elisp\u0026gt; (append '(1 2) '( \u0026quot;a\u0026quot; \u0026quot;b\u0026quot; \u0026quot;c\u0026quot; \u0026quot;d\u0026quot;)) (1 2 \u0026quot;a\u0026quot; \u0026quot;b\u0026quot; \u0026quot;c\u0026quot; \u0026quot;d\u0026quot;) elisp\u0026gt; (append '(1 2) nil '( \u0026quot;a\u0026quot; \u0026quot;b\u0026quot; \u0026quot;c\u0026quot; \u0026quot;d\u0026quot;) nil) (1 2 \u0026quot;a\u0026quot; \u0026quot;b\u0026quot; \u0026quot;c\u0026quot; \u0026quot;d\u0026quot;) ;; filter list elements given a predicate function ;; ;; elisp\u0026gt; (remove-if-not (lambda (x) (\u0026gt; x 2)) '(1 2 3 4 5 6 7 8 9 10)) (3 4 5 6 7 8 9 10) ;; test if list is empty ;; elisp\u0026gt; (null '(1 2 3 4 5)) nil elisp\u0026gt; (null '()) t elisp\u0026gt; (null nil) t elisp\u0026gt; ;; drop the firsts n elements of a list ;; ;; elisp\u0026gt; (nthcdr 2 '(1 2 3 4)) (3 4) elisp\u0026gt; (nthcdr 3 '(1 2 3 4)) (4) elisp\u0026gt; (nthcdr 13 '(1 2 3 4)) nil elisp\u0026gt; ;; delete an element of a list ;; ;; elisp\u0026gt; (delq 1 '(1 2 3 4)) (2 3 4) elisp\u0026gt; (delq 10 '(1 2 3 4)) (1 2 3 4) ;; it doesn't work to delete sublists ;; elisp\u0026gt; (delq (5) '(1 2 (5) 3 4)) ** eval error ** invalid function: 5 elisp\u0026gt; (delq '(5) '(1 2 (5) 3 4)) (1 2 (5) 3 4) elisp\u0026gt; (delete '(5) '(1 2 (5) 3 4)) (1 2 3 4) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; convert vector to list ;; ;; elisp\u0026gt; (coerce [1 2 3] 'list) (1 2 3) ;; convert list to vector ;; elisp\u0026gt; (coerce '(1 2 3) 'vector) [1 2 3] elisp\u0026gt; (number-sequence 0 10 2) (0 2 4 6 8 10) elisp\u0026gt; (number-sequence 9 4 -1) (9 8 7 6 5 4) ;; modify list variables. ;; elisp\u0026gt; alist (a b c d e) elisp\u0026gt; (push 'f alist) (f a b c d e) elisp\u0026gt; alist (f a b c d e) elisp\u0026gt; (pop alist) f elisp\u0026gt; alist (a b c d e) elisp\u0026gt; (pop alist) a elisp\u0026gt; alist (b c d e) elisp\u0026gt; 关联列表和属性列表 概览 关联列表是一系列cons对,这里我可以称作 clist 或者 由两个元素组成的列表的集合,可以称为 alist\n关联列表类型:clist\n键: a, x, 2 and 4 值: b, y, 3 and (1 2 3 4 5)\nelisp\u0026gt; '((a . b) (x . y) (2 . 3) (4 . (1 2 3 4 5))) ((a . b) (x . y) (2 . 3) (4 1 2 3 4 5) elisp\u0026gt; (cons 'a 'b) (a . b) elisp\u0026gt; (cons 'a (cons 'b (cons 'c nil))) (a b c) 关联列表类型:alist\nelisp\u0026gt; '((a b) (x y) (2 3) (4 (1 2 3 4 5))) ((a b) (x y) (2 3) (4 (1 2 3 4 5))) elisp\u0026gt; (list (list 'a 'b) (list 'x 'y) (list 2 3) (list 2 '(1 2 3 4 5))) ((a b) (x y) (2 3) (2 (1 2 3 4 5))) alist 不像 clist 有歧义。\n属性列表:plist\n属性列表是连续的键值对集合,它的优势是括号少和可读性高。\n'(:key1 value1 :key2 value2 :key3 1002.23 :key4 (a b c d e)) elisp\u0026gt; '(:key1 value1 :key2 value2 :key3 1002.23 :key4 (a b c d e)) (:key1 value1 :key2 value2 :key3 1002.23 :key4 (a b c d e)) ;;; it is more useful in configuration files ( :key1 value1 :key2 value2 :key3 value3 :key4 (a b c d e ) ) 关联列表/alist elisp\u0026gt; (setq dict '((pine . cones) (oak . acorns) (maple . seeds))) ((pine . cones) (oak . acorns) (maple . seeds)) elisp\u0026gt; dict ((pine . cones) (oak . acorns) (maple . seeds)) ;; get a cell associated with a key ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; elisp\u0026gt; elisp\u0026gt; (assoc 'oak dict) (oak . acorns) elisp\u0026gt; (assoc 'wrong dict) nil ;; get a key ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; elisp\u0026gt; (car (assoc 'oak dict)) oak elisp\u0026gt; (cdr (assoc 'oak dict)) acorns elisp\u0026gt; elisp\u0026gt; (car (assoc 'oak dict)) oak elisp\u0026gt; ;; get all keys ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; elisp\u0026gt; (mapcar #'car dict) (pine oak maple) ;; get all values ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; elisp\u0026gt; (mapcar #'cdr dict) (cones acorns seeds) 例:过滤多个键\nelisp\u0026gt; (defvar language-list '( (\u0026quot;io\u0026quot; . ((:command . \u0026quot;io\u0026quot;) (:description . \u0026quot;run io language script\u0026quot;))) (\u0026quot;lua\u0026quot; . ((:command . \u0026quot;lua\u0026quot;) (:description . \u0026quot;run lua script\u0026quot;))) (\u0026quot;groovy\u0026quot; . ((:command . \u0026quot;groovy\u0026quot;) (:description . \u0026quot;run groovy\u0026quot;))) (\u0026quot;scala\u0026quot; . ((:command . \u0026quot;scala\u0026quot;) (:cmdopt . \u0026quot;-dfile.encoding=utf-8\u0026quot;) (:description . \u0026quot;run scala file with scala command\u0026quot;))) (\u0026quot;haml\u0026quot; . ((:command . \u0026quot;haml\u0026quot;) (:exec . \u0026quot;%c %o %s\u0026quot;) (:description . \u0026quot;convert haml to html\u0026quot;))) (\u0026quot;sass\u0026quot; . ((:command . \u0026quot;sass\u0026quot;) (:exec . \u0026quot;%c %o --no-cac\u0026quot;))) )) language-list elisp\u0026gt; (assoc \u0026quot;scala\u0026quot; language-list ) (\u0026quot;scala\u0026quot; (:command . \u0026quot;scala\u0026quot;) (:cmdopt . \u0026quot;-dfile.encoding=utf-8\u0026quot;) (:description . \u0026quot;run scala file with scala command\u0026quot;)) elisp\u0026gt; (assoc \u0026quot;lua\u0026quot; language-list ) (\u0026quot;lua\u0026quot; (:command . \u0026quot;lua\u0026quot;) (:description . \u0026quot;run lua script\u0026quot;)) elisp\u0026gt; (assoc \u0026quot;wrong\u0026quot; language-list ) nil elisp\u0026gt; (assoc ':command (assoc \u0026quot;scala\u0026quot; language-list )) (:command . \u0026quot;scala\u0026quot;) elisp\u0026gt; (cdr (assoc ':command (assoc \u0026quot;scala\u0026quot; language-list ))) \u0026quot;scala\u0026quot; elisp\u0026gt; elisp\u0026gt; (assoc ':description (assoc \u0026quot;scala\u0026quot; language-list )) (:description . \u0026quot;run scala file with scala command\u0026quot;) elisp\u0026gt; (cdr (assoc ':description (assoc \u0026quot;scala\u0026quot; language-list ))) \u0026quot;run scala file with scala command\u0026quot; elisp\u0026gt; elisp\u0026gt; (mapcar 'car language-list) (\u0026quot;io\u0026quot; \u0026quot;lua\u0026quot; \u0026quot;groovy\u0026quot; \u0026quot;scala\u0026quot; \u0026quot;haml\u0026quot; \u0026quot;sass\u0026quot;) elisp\u0026gt; (mapcar 'cdr language-list) (((:command . \u0026quot;io\u0026quot;) (:description . \u0026quot;run io language script\u0026quot;)) ((:command . \u0026quot;lua\u0026quot;) (:description . \u0026quot;run lua script\u0026quot;)) ((:command . \u0026quot;groovy\u0026quot;) (:description . \u0026quot;run groovy\u0026quot;)) ((:command . \u0026quot;scala\u0026quot;) (:cmdopt . \u0026quot;-dfile.encoding=utf-8\u0026quot;) (:description . \u0026quot;run scala file with scala command\u0026quot;)) ((:command . \u0026quot;haml\u0026quot;) (:exec . \u0026quot;%c %o %s\u0026quot;) (:description . \u0026quot;convert haml to html\u0026quot;)) ((:command . \u0026quot;sass\u0026quot;) (:exec . \u0026quot;%c %o --no-cac\u0026quot;))) elisp\u0026gt; elisp\u0026gt; (mapcar (lambda (x) ( list (car x) (cdr x) )) language-list) ((\u0026quot;io\u0026quot; ((:command . \u0026quot;io\u0026quot;) (:description . \u0026quot;run io language script\u0026quot;))) (\u0026quot;lua\u0026quot; ((:command . \u0026quot;lua\u0026quot;) (:description . \u0026quot;run lua script\u0026quot;))) (\u0026quot;groovy\u0026quot; ((:command . \u0026quot;groovy\u0026quot;) (:description . \u0026quot;run groovy\u0026quot;))) (\u0026quot;scala\u0026quot; ((:command . \u0026quot;scala\u0026quot;) (:cmdopt . \u0026quot;-dfile.encoding=utf-8\u0026quot;) (:description . \u0026quot;run scala file with scala command\u0026quot;))) (\u0026quot;haml\u0026quot; ((:command . \u0026quot;haml\u0026quot;) (:exec . \u0026quot;%c %o %s\u0026quot;) (:description . \u0026quot;convert haml to html\u0026quot;))) (\u0026quot;sass\u0026quot; ((:command . \u0026quot;sass\u0026quot;) (:exec . \u0026quot;%c %o --no-cac\u0026quot;)))) elisp\u0026gt; elisp\u0026gt; (mapcar (lambda (x) ( list (car x) (assoc ':command (cdr x)) (assoc ':cmdopt (cdr x)) (assoc ':description (cdr x)) )) language-list) ((\u0026quot;io\u0026quot; (:command . \u0026quot;io\u0026quot;) nil (:description . \u0026quot;run io language script\u0026quot;)) (\u0026quot;lua\u0026quot; (:command . \u0026quot;lua\u0026quot;) nil (:description . \u0026quot;run lua script\u0026quot;)) (\u0026quot;groovy\u0026quot; (:command . \u0026quot;groovy\u0026quot;) nil (:description . \u0026quot;run groovy\u0026quot;)) (\u0026quot;scala\u0026quot; (:command . \u0026quot;scala\u0026quot;) (:cmdopt . \u0026quot;-dfile.encoding=utf-8\u0026quot;) (:description . \u0026quot;run scala file with scala command\u0026quot;)) (\u0026quot;haml\u0026quot; (:command . \u0026quot;haml\u0026quot;) nil (:description . \u0026quot;convert haml to html\u0026quot;)) (\u0026quot;sass\u0026quot; (:command . \u0026quot;sass\u0026quot;) nil nil)) elisp\u0026gt; elisp\u0026gt; (mapcar (lambda (x) ( list (car x) (cdr (assoc ':command (cdr x))) (cdr (assoc ':cmdopt (cdr x))) (cdr (assoc ':description (cdr x))) )) language-list) ((\u0026quot;io\u0026quot; \u0026quot;io\u0026quot; nil \u0026quot;run io language script\u0026quot;) (\u0026quot;lua\u0026quot; \u0026quot;lua\u0026quot; nil \u0026quot;run lua script\u0026quot;) (\u0026quot;groovy\u0026quot; \u0026quot;groovy\u0026quot; nil \u0026quot;run groovy\u0026quot;) (\u0026quot;scala\u0026quot; \u0026quot;scala\u0026quot; \u0026quot;-dfile.encoding=utf-8\u0026quot; \u0026quot;run scala file with scala command\u0026quot;) (\u0026quot;haml\u0026quot; \u0026quot;haml\u0026quot; nil \u0026quot;convert haml to html\u0026quot;) (\u0026quot;sass\u0026quot; \u0026quot;sass\u0026quot; nil nil)) elisp\u0026gt; elisp\u0026gt; (defun get-value (alist key) (cdr (assoc key alist))) get-value elisp\u0026gt; (get-value language-list \u0026quot;scala\u0026quot;) ((:command . \u0026quot;scala\u0026quot;) (:cmdopt . \u0026quot;-dfile.encoding=utf-8\u0026quot;) (:description . \u0026quot;run scala file with scala command\u0026quot;)) elisp\u0026gt; (get-value language-list \u0026quot;lua\u0026quot;) ((:command . \u0026quot;lua\u0026quot;) (:description . \u0026quot;run lua script\u0026quot;)) elisp\u0026gt; elisp\u0026gt; (get-value language-list \u0026quot;0\u0026quot;) nil elisp\u0026gt; elisp\u0026gt; (defun get-key-value (alist key field) (cdr (assoc field (cdr (assoc key alist)) ))) get-key-value elisp\u0026gt; elisp\u0026gt; (get-key-value language-list \u0026quot;scala\u0026quot; ':description) \u0026quot;run scala file with scala command\u0026quot; elisp\u0026gt; elisp\u0026gt; (get-key-value language-list \u0026quot;scala\u0026quot; ':command) \u0026quot;scala\u0026quot; elisp\u0026gt; 属性列表 elisp\u0026gt; (defvar plst (list :buffer (current-buffer) :line 10 :pos 2000)) plst elisp\u0026gt; elisp\u0026gt; (plist-get plst :line) 10 elisp\u0026gt; (plist-get plst :pos) 2000 elisp\u0026gt; (plist-get plst :buffer) #\u0026lt;buffer *ielm*\u0026gt; elisp\u0026gt; elisp\u0026gt; elisp\u0026gt; (plist-get plst :buffdfds) nil elisp\u0026gt; elisp\u0026gt; (plist-member plst :buffer) (:buffer #\u0026lt;buffer *ielm*\u0026gt; :line 10 :pos 2000) elisp\u0026gt; (plist-member plst :bufferasd) nil elisp\u0026gt; elisp\u0026gt; (plist-put plst :winconf (current-window-configuration)) (:buffer #\u0026lt;buffer *ielm*\u0026gt; :line 10 :pos 2000 :winconf #\u0026lt;window-configuration\u0026gt;) elisp\u0026gt; plst (:buffer #\u0026lt;buffer *ielm*\u0026gt; :line 10 :pos 2000 :winconf #\u0026lt;window-configuration\u0026gt;) elisp\u0026gt; 转换alist成plist和vice-versa ;; alist to plist (defun plist-\u0026gt;alist (plist) (if (null plist) '() (cons (list (car plist) (cadr plist)) (plist-\u0026gt;alist (cddr plist))))) elisp\u0026gt; (plist-\u0026gt;alist (list :x 10 :y 20 :name \u0026quot;point\u0026quot;)) ((:x 10) (:y 20) (:name \u0026quot;point\u0026quot;)) ;;; converts association list to plist (defun alist-\u0026gt;plist (assocl) (if (null assocl) '() (let ((hd (car assocl)) (tl (cdr assocl))) (cons (car hd) (cons (cadr hd) (alist-\u0026gt;plist tl)))))) ;;; converts plist to clist (list of cons pairs) (defun plist-\u0026gt;clist (plist) (if (null plist) '() (cons (cons (car plist) (cadr plist)) (plist-\u0026gt;clist (cddr plist))))) elisp\u0026gt; (plist-\u0026gt;clist (list :x 10 :y 20 :name \u0026quot;point\u0026quot;)) ((:x . 10) (:y . 20) (:name . \u0026quot;point\u0026quot;)) ;; separates a property list into two lists of keys and values. ;; (defun plist-\u0026gt;kv (plist) (let ((alist (plist-\u0026gt;alist plist))) (cons (mapcar #'car alist) (mapcar #'cdr alist)))) elisp\u0026gt; (setq al (plist-\u0026gt;alist (list :x 10 :y 20 :name \u0026quot;point\u0026quot;))) ((:x 10) (:y 20) (:name \u0026quot;point\u0026quot;)) elisp\u0026gt; (alist-\u0026gt;plist al) (:x 10 :y 20 :name \u0026quot;point\u0026quot;) elisp\u0026gt; (setq keylist '(\u0026quot;m-i\u0026quot; 'previous-line \u0026quot;m-j\u0026quot; 'backward-char \u0026quot;m-k\u0026quot; 'next-line \u0026quot;m-l\u0026quot; 'forward-char)) elisp\u0026gt; (setq kv (plist-\u0026gt;kv keylist)) ((\u0026quot;m-i\u0026quot; \u0026quot;m-j\u0026quot; \u0026quot;m-k\u0026quot; \u0026quot;m-l\u0026quot;) ('previous-line) ('backward-char) ('next-line) ('forward-char)) elisp\u0026gt; (car kv) (\u0026quot;m-i\u0026quot; \u0026quot;m-j\u0026quot; \u0026quot;m-k\u0026quot; \u0026quot;m-l\u0026quot;) elisp\u0026gt; (cdr kv) (('previous-line) ('backward-char) ('next-line) ('forward-char)) elisp\u0026gt; 字符串 ;; split string elisp\u0026gt; (split-string \u0026quot; two words \u0026quot;) (\u0026quot;two\u0026quot; \u0026quot;words\u0026quot;) elisp\u0026gt; elisp\u0026gt; (split-string \u0026quot;o\\no\\no\u0026quot; \u0026quot;\\n\u0026quot; t) (\u0026quot;o\u0026quot; \u0026quot;o\u0026quot; \u0026quot;o\u0026quot;) elisp\u0026gt; (split-string \u0026quot;soup is good food\u0026quot; \u0026quot;o*\u0026quot; t) (\u0026quot;s\u0026quot; \u0026quot;u\u0026quot; \u0026quot;p\u0026quot; \u0026quot; \u0026quot; \u0026quot;i\u0026quot; \u0026quot;s\u0026quot; \u0026quot; \u0026quot; \u0026quot;g\u0026quot; \u0026quot;d\u0026quot; \u0026quot; \u0026quot; \u0026quot;f\u0026quot; \u0026quot;d\u0026quot;) elisp\u0026gt; ;; format string elisp\u0026gt; (format-time-string \u0026quot;%y/%m/%d %h:%m:%s\u0026quot; (current-time)) \u0026quot;2015/06/26 06:10:04\u0026quot; elisp\u0026gt; elisp\u0026gt; ;; concatenate strings elisp\u0026gt; (concat \u0026quot;the \u0026quot; \u0026quot;quick brown \u0026quot; \u0026quot;fox.\u0026quot;) \u0026quot;the quick brown fox.\u0026quot; elisp\u0026gt; elisp\u0026gt; (mapconcat 'identity '(\u0026quot;aaa\u0026quot; \u0026quot;bbb\u0026quot; \u0026quot;ccc\u0026quot;) \u0026quot;,\u0026quot;) \u0026quot;aaa,bbb,ccc\u0026quot; elisp\u0026gt; (split-string \u0026quot;aaa,bbb,ccc\u0026quot; \u0026quot;,\u0026quot;) elisp\u0026gt; (split-string \u0026quot;aaa,bbb,ccc\u0026quot; \u0026quot;,\u0026quot;) (\u0026quot;aaa\u0026quot; \u0026quot;bbb\u0026quot; \u0026quot;ccc\u0026quot;) ;; string width elisp\u0026gt; (string-width \u0026quot;hello world\u0026quot;) 11 elisp\u0026gt; elisp\u0026gt; (substring \u0026quot;freedom land\u0026quot; 0 5) \u0026quot;freed\u0026quot; elisp\u0026gt; elisp\u0026gt; (string-match \u0026quot;ce\u0026quot; \u0026quot;central park\u0026quot;) 0 elisp\u0026gt; (string-match \u0026quot;gt\u0026quot; \u0026quot;central park\u0026quot;) nil elisp\u0026gt; ;;;;; misc elisp\u0026gt; (make-string 5 ?x) \u0026quot;xxxxx\u0026quot; elisp\u0026gt; (make-string 5 ?a) \u0026quot;aaaaa\u0026quot; elisp\u0026gt; (make-string 5 ?r) \u0026quot;rrrrr\u0026quot; elisp\u0026gt; (make-string 15 ?r) \u0026quot;rrrrrrrrrrrrrrr\u0026quot; elisp\u0026gt; elisp符号/字符串转换\n; convert a symbol to string elisp\u0026gt; (symbol-name 'wombat) \u0026quot;wombat\u0026quot; ; convert a string to symbol elisp\u0026gt; (intern \u0026quot;wombat\u0026quot;) wombat 读取字符串中的s表达式\nelisp\u0026gt; (read-from-string \u0026quot;( (point1 (x 10.2323) (y 20.2323)) (point2 (x 0.2) (y 923.23)) (point3 (x -10.5) (y 78,23)) )\u0026quot;) (((point1 (x 10.2323) (y 20.2323)) (point2 (x 0.2) (y 923.23)) (point3 (x -10.5) (y 78 (\\, 23)))) . 174) elisp\u0026gt; 符号 ;;; convert a string to symbol elisp\u0026gt; (intern \u0026quot;a-symbol\u0026quot;) a-synmbol elisp\u0026gt; (symbolp (intern \u0026quot;a-symbol\u0026quot;)) t elisp\u0026gt; ;;; convert a symbol to a string elisp\u0026gt; (symbol-name 'symbol) \u0026quot;symbol\u0026quot; elisp\u0026gt; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; elisp\u0026gt; (setq sym '(1 2 3 4 5)) (1 2 3 4 5) elisp\u0026gt; sym (1 2 3 4 5) ;;; test if variable is defined elisp\u0026gt; (boundp 'sym) t elisp\u0026gt; ;;; test if variable sym is a symbol elisp\u0026gt; (symbolp sym) nil ;;; test if the symbol sym is a symbol elisp\u0026gt; (symbolp 'sym) t elisp\u0026gt; ;; get symbol as string ;; elisp\u0026gt; (symbol-name 'sym) \u0026quot;sym\u0026quot; ;; get value from a symbol ;; elisp\u0026gt; (symbol-value 'sym) (1 2 3 4 5) elisp\u0026gt; (symbol-function 'sym) nil elisp\u0026gt; (symbol-plist 'sym) nil ;;-------------------------;; elisp\u0026gt; (defun func (x y) (+ (* 3 x) (* 4 y))) func elisp\u0026gt; (func 10 2) 38 elisp\u0026gt; ;;; check if function is defined ;; elisp\u0026gt; (fboundp 'func) t elisp\u0026gt; (fboundp 'sym) nil elisp\u0026gt; elisp\u0026gt; (symbol-name 'func) \u0026quot;func\u0026quot; elisp\u0026gt; (symbol-value 'func) ** eval error ** symbol's value as variable is void: func elisp\u0026gt; (symbol-function 'func) (lambda (x y) (+ (* 3 x) (* 4 y))) elisp\u0026gt; (symbol-plist 'func) nil elisp\u0026gt; ;;; function source code elisp\u0026gt; (symbol-function #'func) (lambda (x y) (+ (* 3 x) (* 4 y))) ;; test if function is an elisp primitive elisp\u0026gt; (subrp (symbol-function 'goto-char)) t elisp\u0026gt; 类型转换 类型查询\nelisp\u0026gt; (type-of 1000) integer elisp\u0026gt; (type-of 1000.3434) float elisp\u0026gt; elisp\u0026gt; (type-of \u0026quot;lisp\u0026quot;) string elisp\u0026gt; (type-of '(1 2 3 4 5)) cons elisp\u0026gt; (type-of (list 'cos 'sin 1 2 3 4 5)) cons elisp\u0026gt; elisp\u0026gt; (type-of [1 2 3 4]) vector elisp\u0026gt; (type-of 'elisp-mode-map) symbol elisp\u0026gt; elisp\u0026gt; (type-of #'cos) symbol elisp\u0026gt; 类型判断\n;; test if it is a number ;;----------------------------------- elisp\u0026gt; (numberp 1000) t elisp\u0026gt; (numberp 10e4) t elisp\u0026gt; (numberp '(1 2 3 4)) nil elisp\u0026gt; (numberp \u0026quot;hello world\u0026quot;) nil elisp\u0026gt; ;; test if it is a string ;;----------------------------------- elisp\u0026gt; (stringp \u0026quot;emacs\u0026quot;) t elisp\u0026gt; (stringp '(1 2 3 4)) nil elisp\u0026gt; ;; test if ti is a symbol ;;------------------------------------ elisp\u0026gt; (symbolp 'emacs) t elisp\u0026gt; (symbolp #'emacs) t elisp\u0026gt; (symbolp \u0026quot;something\u0026quot;) nil elisp\u0026gt; (symbolp 10000) nil elisp\u0026gt; ;; test if it is a list ;;----------------------------------- elisp\u0026gt; (listp '(1 2 3 4)) t elisp\u0026gt; (listp [1 2 3 4]) nil elisp\u0026gt; (listp \u0026quot;hello world\u0026quot;) nil elisp\u0026gt; ;; test if it is a vector ;;----------------------------------- elisp\u0026gt; (vectorp [\u0026quot;lisp\u0026quot; \u0026quot;emacs\u0026quot; \u0026quot;scheme\u0026quot; \u0026quot;clojure\u0026quot;]) t elisp\u0026gt; elisp\u0026gt; (vectorp '(1 2 3)) nil elisp\u0026gt; (vectorp \u0026quot;lisp\u0026quot;) nil elisp\u0026gt; 数字/字符串转换\nelisp\u0026gt; elisp\u0026gt; (number-to-string 1000) \u0026quot;1000\u0026quot; elisp\u0026gt; (string-to-number \u0026quot;200\u0026quot;) 200 elisp\u0026gt; elisp\u0026gt; 符号/字符串转换\nelisp\u0026gt; (symbol-name 'my-symbol) \u0026quot;my-symbol\u0026quot; elisp\u0026gt; (symbol-name :my-symbol) \u0026quot;:my-symbol\u0026quot; elisp\u0026gt; elisp\u0026gt; (intern \u0026quot;some-symbol\u0026quot;) some-symbol s表达式/字符串转换\nread: 解析s表达式\nelisp\u0026gt; elisp\u0026gt; (setq raw \u0026quot;(:x 10 :y 20 :z 30 :w \\\u0026quot;hello world\\\u0026quot;)\u0026quot;) \u0026quot;(:x 10 :y 20 :z 30 :w \\\u0026quot;hello world\\\u0026quot;)\u0026quot; elisp\u0026gt; elisp\u0026gt; (read raw) (:x 10 :y 20 :z 30 :w \u0026quot;hello world\u0026quot;) elisp\u0026gt; (plist-get (read raw) :x) 10 elisp\u0026gt; (plist-get (read raw) :w) \u0026quot;hello world\u0026quot; elisp\u0026gt; - prin1-to-string: 序列化s表达式 elisp\u0026gt; (setq sexp '(:x 10 :y 20 :z 30 :w \u0026quot;hello world\u0026quot;)) (:x 10 :y 20 :z 30 :w \u0026quot;hello world\u0026quot;) elisp\u0026gt; sexp (:x 10 :y 20 :z 30 :w \u0026quot;hello world\u0026quot;) elisp\u0026gt; (prin1-to-string sexp) \u0026quot;(:x 10 :y 20 :z 30 :w \\\u0026quot;hello world\\\u0026quot;)\u0026quot; elisp\u0026gt; 求值 s表达式求值\nelisp\u0026gt; (eval '(+ 1 2 3 4 5)) 15 elisp\u0026gt; elisp\u0026gt; '(defun func1(x)(* 10 x)) (defun func1 (x) (* 10 x)) elisp\u0026gt; elisp\u0026gt; '((+ 1 3) (* 4 5) (- 8 9)) ((+ 1 3) (* 4 5) (- 8 9)) elisp\u0026gt; (eval '(defun func1(x)(* 10 x))) func1 elisp\u0026gt; (func1 5) 50 elisp\u0026gt; elisp\u0026gt; (mapcar 'eval '((+ 1 3) (* 4 5) (- 8 9))) (4 20 -1) 字符串求值\nelisp\u0026gt; (defun eval-string (str) (eval (read str))) eval-string elisp\u0026gt; (eval-string \u0026quot;(+ 1 2 3 4 5 6)\u0026quot;) 21 elisp\u0026gt; elisp\u0026gt; (eval-string \u0026quot;(defun func2(x)(* 10 x)))\u0026quot;) func2 elisp\u0026gt; (func2 6) 60 elisp\u0026gt; s表达式格式化为字符串\nelisp\u0026gt; (setq sexp1 '(+ 1 (* 2 3))) (+ 1 (* 2 3)) elisp\u0026gt; (eval sexp1) 7 elisp\u0026gt; (format \u0026quot;%s\u0026quot; sexp1) \u0026quot;(+ 1 (* 2 3))\u0026quot; elisp\u0026gt; elisp中的求值命令\n命令 功能 m-x eval-defun 函数求值 m-x eval-region 区域内表达式求值 m-x eval-buffer buffer内表达式求值 m-x eval-expression 输入框输入求值 m-x load-file 文件加载 defalias 内置宏 defalias 可以为emaca函数定义简短的名字。\n参考:emacs: use alias for fast m-x\nelisp\u0026gt; (require 'cl) cl elisp\u0026gt; elisp\u0026gt; (defalias 'map 'mapcar) map elisp\u0026gt; (map (lambda (x) (* 3 x)) (list 1 2 3 4 5 6)) (3 6 9 12 15 18) elisp\u0026gt; (defalias 'filter 'remove-if-not) ;; remove-if-not comes from \u0026quot;cl\u0026quot; library filter ;;; filter all buffers bounded to a file ;; elisp\u0026gt; (filter #'buffer-file-name (buffer-list)) (#\u0026lt;buffer readme.org\u0026gt; #\u0026lt;buffer projects.wiki.org\u0026gt; #\u0026lt;buffer index.wiki.org\u0026gt; #\u0026lt;buffer settings.org\u0026gt; #\u0026lt;buffer project.org\u0026gt;) ;;; reject all buffers which are not bounded to a file elisp\u0026gt; (reject #'buffer-file-name (buffer-list)) (#\u0026lt;buffer *ielm*\u0026gt; #\u0026lt;buffer *help*\u0026gt; #\u0026lt;buffer *minibuf-1*\u0026gt; #\u0026lt;buffer emacs\u0026gt; #\u0026lt;buffer *scratch*\u0026gt; ..) ;;; the command m-x org-html-export-to-htm will export this document (readme.org) to html ;; the command m-x org2html will do so too. ;; (defalias #'org2html #'org-html-export-to-html) ;; ;; it is also useful to create more convenient names for emacs api ;; in a namsepace-like fashion that makes easier to find functions and ;; autocomplete functions, for instance: ;; (defalias 'file/extension 'file-name-extension) (defalias 'file/extension-sans 'file-name-sans-extension) (defalias 'file/path-expand 'expand-file-name) (defalias 'file/filename 'file-name-nondirectory) (defalias 'file/path-relative 'file-relative-name) (defalias 'file/rename 'rename-file) (defalias 'file/delete 'delete-file) (defalias 'file/copy 'copy-file) ;;; to find the documentation of a function group defined in this fashion ;; enter m-x apropos and then type file/ (apropos \u0026quot;file/\u0026quot;) elisp\u0026gt; (set-buffer \u0026quot;readme.org\u0026quot;) #\u0026lt;buffer readme.org\u0026gt; elisp\u0026gt; (buffer-file-name) \u0026quot;/home/tux/pycharmprojects/emacs/readme.org\u0026quot; elisp\u0026gt; (file/basename (buffer-file-name)) \u0026quot;readme\u0026quot; elisp\u0026gt; (file/extension (buffer-file-name)) \u0026quot;org\u0026quot; elisp\u0026gt; (file/filename (buffer-file-name)) \u0026quot;readme.org\u0026quot; elisp\u0026gt; 控制结构 conditional statement if else 语句\n;; ;; any value different from nil or '() is true, otherwise false. ;; ;; true ;; elisp\u0026gt; (if t 5 6) 5 elisp\u0026gt; (if 10 5 6) 5 elisp\u0026gt; (if 0 5 6) 5 ;; false elisp\u0026gt; (if nil 5 6) 6 elisp\u0026gt; (if '() 5 6) 6 ;; inverting predicate ;; elisp\u0026gt; (if (not t) 5 6) 6 elisp\u0026gt; (if (not nil) 5 6) 5 elisp\u0026gt; (if (\u0026lt; 5 10) (message \u0026quot;less than 10\u0026quot;) (message \u0026quot;greater or equal to 10\u0026quot;) ) \u0026quot;less than 10\u0026quot; elisp\u0026gt; (if (\u0026lt; 30 10) (message \u0026quot;less than 10\u0026quot;) (message \u0026quot;greater or equal to 10\u0026quot;) ) \u0026quot;greater or equal to 10\u0026quot; elisp\u0026gt; ;;; if else with multiple statements elisp\u0026gt; (setq x 10) 10 elisp\u0026gt; (if (\u0026gt; x 5) ;; then statement (progn (message \u0026quot;positive number\u0026quot;) (print \u0026quot;greater than five\u0026quot;) (split-window-vertically) 78 ;; return value ) ;; else statement (progn (print \u0026quot;less than five\u0026quot;) (split-window-horizontally) 12 ;; return value )) \u0026quot;greater than five\u0026quot; 78 elisp\u0026gt; when语句\nelisp\u0026gt; (when t 3) 3 elisp\u0026gt; (when nil 3) nil elisp\u0026gt; (setq x 5) 5 elisp\u0026gt; (when (\u0026gt; x 3) (message \u0026quot;less than 3\u0026quot;)) \u0026quot;less than 3\u0026quot; elisp\u0026gt; elisp\u0026gt; (setq x 1) 1 elisp\u0026gt; (when (\u0026gt; x 3) (message \u0026quot;less than 3\u0026quot;)) nil elisp\u0026gt; ;;;;; when with multiple statements elisp\u0026gt; (setq x 10) 10 elisp\u0026gt; (when (\u0026gt; x 7) (progn (message \u0026quot;greater than 7 ok.\u0026quot;) (message \u0026quot;print message 2\u0026quot;) (split-window-horizontally) )) #\u0026lt;window 8 on *ielm*\u0026gt; elisp\u0026gt; cond - case switch elisp\u0026gt; (setq a 3) ;; a = 3 3 elisp\u0026gt; elisp\u0026gt; (cond ((evenp a) a) ;; if (a % 2 == 0) ==\u0026gt; a ((\u0026gt; a 7) (/ a 2)) ;; elif (a \u0026gt; 7) ==\u0026gt; a/2 ((\u0026lt; a 5) (- a 1)) ;; elif (a \u0026lt; 5) ==\u0026gt; a-1 (t 7) ;; else ==\u0026gt; 7 ) 2 elisp\u0026gt; cl-case - case switch (defun test-cl-case (operation x y) (cl-case operation (:mul (* x y)) (:add (+ x y)) (:sub (- x y)) (:div (/ x y)) (otherwise nil))) elisp\u0026gt; (test-cl-case :mul 2 10) 20 elisp\u0026gt; (test-cl-case :sub 10 2) 8 elisp\u0026gt; (test-cl-case :add 10 2) 12 elisp\u0026gt; (test-cl-case :div 10 2) 5 elisp\u0026gt; (test-cl-case 'dummy 20 10) nil 循环 dolist\nelisp\u0026gt; (dolist (h '(a b c)) (print h)) a b c nil elisp\u0026gt; (dolist (x '(1 2 3)) (print (* 2 x))) 2 4 6 nil elisp\u0026gt; elisp\u0026gt; (dolist (x '(1 2 3)) (dolist (y '(a b)) (print (list x y)))) (1 a) (1 b) (2 a) (2 b) (3 a) (3 b) nil elisp\u0026gt; dotimes\nelisp\u0026gt; (dotimes (i 3) (print i)) 0 1 2 nil elisp elisp\u0026gt; (dotimes (i 3) (print (* 2 i))) 0 2 4 nil elisp\u0026gt; loop\n最好使用 map 和 filter 代替 loops , 详见 functional programming\nelisp\u0026gt; (setq a 4) 4 elisp\u0026gt; (loop (setq a (+ a 1)) (when (\u0026gt; a 7) (return a))) 8 elisp\u0026gt; a 8 elisp\u0026gt; elisp\u0026gt; (loop (setq a (- a 1)) (when (\u0026lt; a 3) (return))) nil elisp\u0026gt; a 2 elisp\u0026gt; loop collecting / summing / for\nelisp\u0026gt; (loop for i from 1 to 10 collecting i) (1 2 3 4 5 6 7 8 9 10) elisp\u0026gt; (loop for i from 1 to 10 collecting (* 3 i)) (3 6 9 12 15 18 21 24 27 30) elisp\u0026gt; (loop for x from 1 to 10 summing (expt x 2)) 385 elisp\u0026gt; (loop for x from 1 to 10 collecting (* 2 x)) (2 4 6 8 10 12 14 16 18 20) elisp\u0026gt; (loop for x from 1 to 10 summing (* 2 x)) 110 elisp\u0026gt; elisp\u0026gt; (apply #'+ '(2 4 6 8 10 12 14 16 18 20)) 110 elisp\u0026gt; (loop for i below 10 collecting i) (0 1 2 3 4 5 6 7 8 9) elisp\u0026gt; (loop for x in '(1 2 3) do (print x) ) 1 2 3 nil (loop for x in '(a b c) for y in '(1 2 3 4 5 6) collect (list x y)) ((a 1) (b 2) (c 3)) elisp\u0026gt; (loop for (a b) in '((x 1) (y 2) (z 3)) collect (list b a)) ((1 x) (2 y) (3 z)) elisp\u0026gt; (loop for i upto 20 if (oddp i) collect i into odds else collect i into evens finally (return (values evens odds))) ((0 2 4 6 8 10 12 14 16 18 20) (1 3 5 7 9 11 13 15 17 19)) do loop\n(do (variable-definition*) (end-test-form result-form*) statement*) (do ;; variables definitions ((i 0 (1+ i))) ;; test form ((\u0026gt;= i 4)) ;; statement form (print i)) 0 1 2 3 nil ;; fibbonaci computing loop ;; (do ((n 0 (1+ n)) (cur 0 next) (next 1 (+ cur next))) ((= 10 n) cur)) 55 函数式编程 dash 是emacs经常使用的函数式编程库。\nmap and filter mapcar / equivalent to map\nelisp\u0026gt; (defun my-fun (x) (* x 10)) my-fun elisp\u0026gt; elisp\u0026gt; (mapcar 'my-fun '(1 2 3 5 6)) (10 20 30 50 60) elisp\u0026gt; (mapcar 'capitalize '(\u0026quot;hello\u0026quot; \u0026quot;world\u0026quot; \u0026quot;emacs\u0026quot;)) (\u0026quot;hello\u0026quot; \u0026quot;world\u0026quot; \u0026quot;emacs\u0026quot;) ;; anonymous functions ;; elisp\u0026gt; (mapcar (lambda (x) (* x x)) '(1 2 3 4 5 6)) (1 4 9 16 25 36) elisp\u0026gt; (setq anon (lambda (x) (* x x))) (lambda (x) (* x x)) elisp\u0026gt; (mapcar anon '(1 2 3 4 5 6)) (1 4 9 16 25 36) filter\nelisp\u0026gt; (null nil) t elisp\u0026gt; (null 23) nil elisp\u0026gt; ;; equivalent to haskell idiom: ;; ;; \u0026gt; filter predicate list ;; elisp\u0026gt; (remove-if-not 'null '(1 2 3 nil 5 6 nil nil )) (nil nil nil) ;; equivalent to haskell idiom: ;; ;; \u0026gt; filter (\\x -\u0026gt; not (predicate x)) list ;; ;; a more apropriate name would be reject ;; elisp\u0026gt; (remove-if 'null '(1 2 3 nil 5 6 nil nil )) (1 2 3 5 6) elisp\u0026gt; (defun range (step start stop) (if (\u0026gt; start stop) nil (cons start (range step (+ step start) stop)) );; end if );; end range elisp\u0026gt; (range 1 0 10) (0 1 2 3 4 5 6 7 8 9 10) elisp\u0026gt; (range 2 0 20) (0 2 4 6 8 10 12 14 16 18 20) elisp\u0026gt; (remove-if (lambda (x) (= (% x 2) 0)) (range 1 0 20)) (1 3 5 7 9 11 13 15 17 19) elisp\u0026gt; (remove-if-not (lambda (x) (= (% x 2) 0)) (range 1 0 20)) (0 2 4 6 8 10 12 14 16 18 20) elisp\u0026gt; (remove-if (lambda (x) (= (% x 3) 0)) (range 1 0 20)) (1 2 4 5 7 8 10 11 13 14 16 17 19 20) elisp\u0026gt; (remove-if-not (lambda (x) (= (% x 3) 0)) (range 1 0 20)) (0 3 6 9 12 15 18) elisp\u0026gt; 匿名函数/lambda函数\nelisp\u0026gt; (lambda (x)(* x 10)) (lambda (x) (* x 10)) elisp\u0026gt; elisp\u0026gt; (funcall (lambda (x)(* x 10)) 5) 50 elisp\u0026gt; elisp\u0026gt; (setq my-lambda (lambda (x) (+ (* x 10) 5))) ;; 10 * x + 5 (lambda (x) (+ (* x 10) 5)) elisp\u0026gt; (funcall my-lambda 10) 105 elisp\u0026gt; (mapcar my-lambda '(1 2 3 4 5)) (15 25 35 45 55) elisp\u0026gt; (setq double (function (lambda (x) (+ x x)) )) (lambda (x) (+ x x)) elisp\u0026gt; (funcall double 22) 44 elisp\u0026gt; ;; ;; apply a function to a list of arguments ;; ;;;;;;;;;;; elisp\u0026gt; (apply #'+ '(1 2 3 4 5)) 15 elisp\u0026gt; elisp\u0026gt; elisp\u0026gt; (defun f (x y z) (+ (* 10 x) (* -4 y) (* 5 z))) f elisp\u0026gt; (f 2 3 5) 33 elisp\u0026gt; (apply 'f '(2 3 5)) 33 elisp\u0026gt; (mapcar (lambda (x) (apply 'f x)) '( (2 3 5) (4 5 6) (8 9 5))) (33 50 69) ;; create higher order functions ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; function composition ????\nelisp\u0026gt; ;; id: f0c736a9-afec-3e3f-455c-40997023e130 (defun compose (\u0026amp;rest funs) \u0026quot;return function composed of funs.\u0026quot; (lexical-let ((lex-funs funs)) (lambda (\u0026amp;rest args) (reduce 'funcall (butlast lex-funs) :from-end t :initial-value (apply (car (last lex-funs)) args))))) compose elisp\u0026gt; (funcall (compose 'prin1-to-string 'random* 'exp) 10) \u0026quot;4757.245739507558\u0026quot; elisp\u0026gt; interactive functions\n(defun some-interactive-function () \u0026quot;documentation\u0026quot; (interactive) ...) list recursive functions\nmap\n(defun map (fun xs) (if (null xs) '() (cons (funcall fun (car xs)) (map fun (cdr xs))))) elisp\u0026gt; (map #'buffer-name (buffer-list)) (\u0026quot;*ielm*\u0026quot; \u0026quot;*scratch*\u0026quot; \u0026quot; *minibuf-1*\u0026quot; \u0026quot;*backtrace*\u0026quot; \u0026quot;*eshell*\u0026quot; \u0026quot;sclj.import.scm\u0026quot; \u0026quot;*messages*\u0026quot; \u0026quot;*gnu emacs*\u0026quot; \u0026quot; *minibuf-0*\u0026quot; \u0026quot; *code-conversion-work*\u0026quot; \u0026quot; *echo area 0*\u0026quot; \u0026quot; *echo area 1*\u0026quot; \u0026quot;*shell command output*\u0026quot; \u0026quot;*completions*\u0026quot;) elisp\u0026gt; filter\n(defun filter (fun xs) (if (null xs) '() (let ((hd (car xs)) (tl (cdr xs))) (if (funcall fun hd) (cons hd (filter fun tl)) (filter fun tl))))) (defun odd? (x) (zerop (% x 2))) elisp\u0026gt; (filter #'odd? '(1 2 3 4 5 6)) (2 4 6) take\n(defun take (n xs) (if (or (null xs) (zerop n)) '() (cons (car xs) (take (- n 1) (cdr xs))))) elisp\u0026gt; (take 5 '(a b c d e f g h i j)) (a b c d e) elisp\u0026gt; (take 10 '(a b c d e f g h i j)) (a b c d e f g h i j) elisp\u0026gt; (take 200 '(a b c d e f g h i j)) (a b c d e f g h i j) elisp\u0026gt; (take 0 '(a b c d e f g h i j)) nil elisp\u0026gt; (take 10 '()) nil elisp\u0026gt; drop\n(defun drop (n xs) (if (or (null xs) (zerop n)) xs (drop (- n 1) (cdr xs)))) elisp\u0026gt; (drop 3 '(a b c d e f g h i j)) (d e f g h i j) elisp\u0026gt; (drop 4 '(a b c d e f g h i j)) (e f g h i j) elisp\u0026gt; (drop 25 '(a b c d e f g h i j)) nil elisp\u0026gt; map-apply\n(defun map-apply (fun xss) (mapcar (lambda (xs) (apply fun xs)) xss)) elisp\u0026gt; (map-apply #'fxyz '((1 2 3) (3 4 5) (2 3 1))) (17 35 20) elisp\u0026gt; (fxyz 1 2 3) 17 elisp\u0026gt; (fxyz 3 4 5) 35 elisp\u0026gt; (fxyz 2 3 1) 20 elisp\u0026gt; zip\n(defun zip (\u0026amp;rest xss) (if (null (car xss)) '() (cons (mapcar #'car xss) (apply #'zip (mapcar #'cdr xss))))) elisp\u0026gt; (zip (list 1 2 3 4) '(a b c d) '(x y z w)) ((1 a x) (2 b y) (3 c z) (4 d w)) zipwith\n(defun zipwith (f \u0026amp;rest xss) (map-apply f (apply #'zip xss))) elisp\u0026gt; (zipwith #'f '(1 2 3) '(4 5 6) '(3 6 8)) (23 40 53) elisp\u0026gt; (f 1 4 3) 23 elisp\u0026gt; (f 2 5 6) 40 elisp\u0026gt; (f 3 6 8) 53 elisp\u0026gt; foldr\n;; f :: x -\u0026gt; acc -\u0026gt; acc ;; foldr :: (a -\u0026gt; b -\u0026gt; b) -\u0026gt; b -\u0026gt; [a] -\u0026gt; b ;; foldr :: (x -\u0026gt; acc -\u0026gt; acc) -\u0026gt; acc -\u0026gt; [x] -\u0026gt; acc ;; foldr f z [] = z ;; foldr f z (x:xs) = f x (foldr f z xs) ;; ;; x = (car xss) , xs = (cdr xss) (defun foldr (f acc xss) (if (null xss) ;; foldr f z [] = z acc ;; foldr f z (x:xs) = f x (foldr f z xs) (funcall f (car xss) (foldr f acc (cdr xss))))) elisp\u0026gt; (foldr (lambda (a b) (+ (* 10 b) a)) 0 '(1 2 3 4 5)) 54321 elisp\u0026gt; elisp\u0026gt; (foldr #'+ 0 '(1 2 3 4 5)) 15 elisp\u0026gt; foldl\n;; foldl :: (b -\u0026gt; a -\u0026gt; b) -\u0026gt; b -\u0026gt; [a] -\u0026gt; b ;; foldl f z [] = z ;; foldl f z (x:xs) = foldl f (f z x) xs (defun foldl (f acc xss) (if (null xss) acc (foldl f (funcall f acc (car xss)) (cdr xss)))) elisp\u0026gt; (foldl (lambda (a b) (+ (* 10 a) b)) 0 '(1 2 3 4 5)) 12345 elisp\u0026gt; map pairs\n(defun map-pair (func xs) (mapcar (lambda (x) (cons x (funcall func x))) xs)) elisp\u0026gt; (map-pair #'1+ '(1 2 3 4)) ((1 . 2) (2 . 3) (3 . 4) (4 . 5)) elisp\u0026gt; (map-pair #'log10 '(1 10 100 1000 10000)) ((1 . 0.0) (10 . 1.0) (100 . 2.0) (1000 . 3.0) (10000 . 4.0)) (defun buffer-mode (buffer-or-string) \u0026quot;returns the major mode associated with a buffer.\u0026quot; (with-current-buffer buffer-or-string major-mode)) elisp\u0026gt; (map-pair #'buffer-mode (buffer-list)) ((#\u0026lt;buffer *ielm*\u0026gt; . inferior-emacs-lisp-mode) (#\u0026lt;buffer *scratch*\u0026gt; . lisp-interaction-mode) (#\u0026lt;buffer *backtrace*\u0026gt; . debugger-mode) (#\u0026lt;buffer *gnu emacs*\u0026gt; . fundamental-mode) (#\u0026lt;buffer *minibuf-1*\u0026gt; . minibuffer-inactive-mode) (#\u0026lt;buffer *minibuf-0*\u0026gt; . minibuffer-inactive-mode) (#\u0026lt;buffer *messages*\u0026gt; . messages-buffer-mode) map pairs xy\n(defun map-xypair (func-x func-y xs) (mapcar (lambda (x) (cons (funcall func-x x) (funcall func-y x))) xs)) elisp\u0026gt; (map-xypair #'buffer-name #'buffer-mode (buffer-list)) ((\u0026quot;*ielm*\u0026quot; . inferior-emacs-lisp-mode) (\u0026quot;*scratch*\u0026quot; . lisp-interaction-mode) (\u0026quot;*backtrace*\u0026quot; . debugger-mode) (\u0026quot;*gnu emacs*\u0026quot; . fundamental-mode) (\u0026quot; *minibuf-1*\u0026quot; . minibuffer-inactive-mode) (\u0026quot; *minibuf-0*\u0026quot; . minibuffer-inactive-mode) (\u0026quot;*messages*\u0026quot; . messages-buffer-mode) (\u0026quot; *code-conversion-work*\u0026quot; . fundamental-mode) (\u0026quot; *echo area 0*\u0026quot; . fundamental-mode) (\u0026quot; *echo area 1*\u0026quot; . fundamental-mode) (\u0026quot; *http www.httpbin.org:80*\u0026quot; . fundamental-mode) (\u0026quot; *http www.httpbin.org:80*-820734\u0026quot; . fundamental-mode) (\u0026quot; *http www.httpbin.org:80*-914099\u0026quot; . fundamental-mode) (\u0026quot; *http www.httpbin.org:80*-945998\u0026quot; . fundamental-mode) (\u0026quot;*help*\u0026quot; . help-mode) (\u0026quot;*completions*\u0026quot; . completion-list-mode)) juxt\nelisp\u0026gt; (juxt #'buffer-name #'buffer-mode) (lambda (x) (list ((funcall #'buffer-name x) (funcall #'buffer-mode x)))) elisp\u0026gt; (funcall (juxt #'buffer-file-name #'buffer-name #'buffer-mode) (current-buffer)) (nil \u0026quot;*ielm*\u0026quot; inferior-emacs-lisp-mode) elisp\u0026gt; (mapcar (juxt #'buffer-name #'buffer-file-name #'buffer-mode) (buffer-list)) ((\u0026quot;*ielm*\u0026quot; nil inferior-emacs-lisp-mode) (\u0026quot;*scratch*\u0026quot; nil lisp-interaction-mode) (\u0026quot;passgen.py\u0026quot; \u0026quot;/home/tux/bin/passgen.py\u0026quot; python-mode) (\u0026quot;.bashrc\u0026quot; \u0026quot;/home/tux/.bashrc\u0026quot; sh-mode) (\u0026quot; *minibuf-1*\u0026quot; nil minibuffer-inactive-mode) (\u0026quot;init.el\u0026quot; \u0026quot;/home/tux/.emacs.d/init.el\u0026quot; emacs-lisp-mode) (\u0026quot;*backtrace*\u0026quot; nil debugger-mode) (\u0026quot;*gnu emacs*\u0026quot; nil fundamental-mode) (\u0026quot; *minibuf-0*\u0026quot; nil minibuffer-inactive-mode) (\u0026quot;*messages*\u0026quot; nil messages-buffer-mode) (\u0026quot; *code-conversion-work*\u0026quot; nil fundamental-mode) (\u0026quot; *echo area 0*\u0026quot; nil fundamental-mode) (\u0026quot; *echo area 1*\u0026quot; nil fundamental-mode) (\u0026quot; *http www.httpbin.org:80*\u0026quot; nil fundamental-mode) (\u0026quot; *http www.httpbin.org:80*-820734\u0026quot; nil fundamental-mode) (\u0026quot; *http www.httpbin.org:80*-914099\u0026quot; nil fundamental-mode) (\u0026quot; *http www.httpbin.org:80*-945998\u0026quot; nil fundamental-mode) (\u0026quot;*help*\u0026quot; nil help-mode) (\u0026quot;*completions*\u0026quot; nil completion-list-mode)) map juxt\n(defmacro map-juxt (xs_f xs) `(mapcar (juxt ,@xs_f) ,xs)) elisp\u0026gt; (map-juxt (#'buffer-name #'buffer-file-name #'buffer-mode) (buffer-list)) ((\u0026quot;*ielm*\u0026quot; nil inferior-emacs-lisp-mode) (\u0026quot;*scratch*\u0026quot; nil lisp-interaction-mode) (\u0026quot;passgen.py\u0026quot; \u0026quot;/home/tux/bin/passgen.py\u0026quot; python-mode) (\u0026quot;.bashrc\u0026quot; \u0026quot;/home/tux/.bashrc\u0026quot; sh-mode) (\u0026quot; *minibuf-1*\u0026quot; nil minibuffer-inactive-mode) (\u0026quot;init.el\u0026quot; \u0026quot;/home/tux/.emacs.d/init.el\u0026quot; emacs-lisp-mode) (\u0026quot;*backtrace*\u0026quot; nil debugger-mode) (\u0026quot;*gnu emacs*\u0026quot; nil fundamental-mode) (\u0026quot; *minibuf-0*\u0026quot; nil minibuffer-inactive-mode) (\u0026quot;*messages*\u0026quot; nil messages-buffer-mode) ... lambda function macro\n(defmacro $f (f \u0026amp;rest params) `(lambda ($) (,f ,@params))) elisp\u0026gt; ($f - 10 $) (lambda ($) (- 10 $)) elisp\u0026gt; ($f * (+ 3 $) 5) (lambda ($) (* (+ 3 $) 5)) elisp\u0026gt; (funcall ($f * (+ 3 $) 5) 10) 65 elisp\u0026gt; (mapcar ($f * (+ 3 $) 5) '(1 2 3 4 5)) (20 25 30 35 40) elisp\u0026gt; elisp\u0026gt; (mapcar ($f list (1+ $) (1- $) (log10 $)) '(1 10 100 1000)) ((2 0 0.0) (11 9 1.0) (101 99 2.0) (1001 999 3.0)) partial application\n(defmacro $c (f \u0026amp;rest params) `(lambda (__x) (,f ,@params __x))) elisp\u0026gt; (defun f (x y z) (+ (* 3 x) (* 2 y) (* 4 z))) f elisp\u0026gt; (f 1 2 3) 19 elisp\u0026gt; ($c f 1 2) (lambda (__x) (f 1 2 __x)) elisp\u0026gt; (mapcar ($c f 1 2) '(1 2 3 4 5)) (11 15 19 23 27) elisp\u0026gt; (mapcar ($c + 1 2) '(1 2 3 4 5)) (4 5 6 7 8) elisp\u0026gt; structures elisp\u0026gt; (defstruct account id name balance) account elisp\u0026gt; (make-account :id 3434 :name \u0026quot;john\u0026quot; :balance 1000.34) [cl-struct-account 3434 \u0026quot;john\u0026quot; 1000.34] elisp\u0026gt; (setq user1 (make-account :id 3434 :name \u0026quot;john\u0026quot; :balance 1000.34)) [cl-struct-account 3434 \u0026quot;john\u0026quot; 1000.34] elisp\u0026gt; (account-name user1) \u0026quot;john\u0026quot; elisp\u0026gt; (account-id user1) 3434 elisp\u0026gt; (account-balance user1) 1000.34 ;; test if input is an account object ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; elisp\u0026gt; (account-p user1) t elisp\u0026gt; ;; change field ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; elisp\u0026gt; (defun withdraw (accc amount) (setf (account-balance acc) (- (account-balance acc) amount))) withdraw elisp\u0026gt; (withdraw user1 300) 700.34 elisp\u0026gt; user1 [cl-struct-account 3434 \u0026quot;john\u0026quot; 700.34] elisp\u0026gt; (withdraw user1 500) 200.34000000000003 elisp\u0026gt; user1 [cl-struct-account 3434 \u0026quot;john\u0026quot; 200.34000000000003] elisp\u0026gt; ;; build structure from a list of parameters ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; elisp\u0026gt; (defun build-account (id name balance) (make-account :id id :name name :balance balance)) build-account elisp\u0026gt; (build-account 3434 \u0026quot;o' neil\u0026quot; 35434.23) [cl-struct-account 3434 \u0026quot;o' neil\u0026quot; 35434.23] elisp\u0026gt; (apply 'build-account '(3434 \u0026quot;o' neil\u0026quot; 35434.23)) [cl-struct-account 3434 \u0026quot;o' neil\u0026quot; 35434.23] elisp\u0026gt; elisp\u0026gt; (mapcar (lambda (params) (apply 'build-account params)) '( (34423 \u0026quot;o' neil\u0026quot; 23.2323) (1023 \u0026quot;john edwards\u0026quot; 1002323.23) (92323 \u0026quot;mr. dummy\u0026quot; 2323241.2323) (8723 \u0026quot;john oliver\u0026quot; 9823) )) ([cl-struct-account 34423 \u0026quot;o' neil\u0026quot; 23.2323] [cl-struct-account 1023 \u0026quot;john edwards\u0026quot; 1002323.23] [cl-struct-account 92323 \u0026quot;mr. dummy\u0026quot; 2323241.2323] [cl-struct-account 8723 \u0026quot;john oliver\u0026quot; 9823]) elisp\u0026gt; elisp\u0026gt; (defun build-accounts-from-list (list-of-params) (mapcar (lambda (params) (apply 'build-account params)) list-of-params)) build-accounts-from-list elisp\u0026gt; elisp\u0026gt; (setq accounts (build-accounts-from-list '( (34423 \u0026quot;o' neil\u0026quot; 23.2323) (1023 \u0026quot;john edwards\u0026quot; 1002323.23) (92323 \u0026quot;mr. dummy\u0026quot; 2323241.2323) (8723 \u0026quot;john oliver\u0026quot; 9823) ))) ([cl-struct-account 34423 \u0026quot;o' neil\u0026quot; 23.2323] [cl-struct-account 1023 \u0026quot;john edwards\u0026quot; 1002323.23] [cl-struct-account 92323 \u0026quot;mr. dummy\u0026quot; 2323241.2323] [cl-struct-account 8723 \u0026quot;john oliver\u0026quot; 9823]) elisp\u0026gt; accounts ([cl-struct-account 34423 \u0026quot;o' neil\u0026quot; 23.2323] [cl-struct-account 1023 \u0026quot;john edwards\u0026quot; 1002323.23] [cl-struct-account 92323 \u0026quot;mr. dummy\u0026quot; 2323241.2323] [cl-struct-account 8723 \u0026quot;john oliver\u0026quot; 9823]) elisp\u0026gt; (mapcar #'account-id accounts) (34423 1023 92323 8723) elisp\u0026gt; elisp\u0026gt; elisp\u0026gt; (mapcar #'account-name accounts) (\u0026quot;o' neil\u0026quot; \u0026quot;john edwards\u0026quot; \u0026quot;mr. dummy\u0026quot; \u0026quot;john oliver\u0026quot;) elisp\u0026gt; elisp\u0026gt; (mapcar #'account-balance accounts) (23.2323 1002323.23 2323241.2323 9823) elisp\u0026gt; 宏和元编程 quasi-quote ;;;; quasiquote \u0026gt; `(the product of 3 and 4 is ,(* 3 4)) (the product of 3 and 4 is 12) \u0026gt; `(\u0026quot;the product of 3 and 4 is\u0026quot; ,(* 3 4)) (\u0026quot;the product of 3 and 4 is\u0026quot; 12) \u0026gt; `(\u0026quot;the value of (exp 3) is \u0026quot; ,(exp 3) \u0026quot;the value of (sqrt 100) is\u0026quot; ,(sqrt 100)) (\u0026quot;the value of (exp 3) is \u0026quot; 20.085536923187668 \u0026quot;the value of (sqrt 100) is\u0026quot; 10.0) \u0026gt; `(a ,a b ,b c ,c d ,d) (a 10 b 20 c my-symbol d \u0026quot;a string\u0026quot;) \u0026gt; `((a . ,a) (b . ,b) (c . ,c) (d . ,d)) ((a . 10) (b . 20) (c . my-symbol) (d . \u0026quot;a string\u0026quot;)) \u0026gt; (setq xs '(sym1 sym2 sym3)) (sym1 sym2 sym3) \u0026gt; xs (sym1 sym2 sym3) \u0026gt; `(xs ,xs) (xs (sym1 sym2 sym3)) \u0026gt; `(xs ,@xs) (xs sym1 sym2 sym3) \u0026gt; `(if (\u0026lt; ,a ,b) ,(+ a 4) ,d) (if (\u0026lt; 10 20) 14 \u0026quot;a string\u0026quot;) \u0026gt; (eval `(if (\u0026lt; ,a ,b) ,(+ a 4) ,d)) 14 \u0026gt; \u0026gt; (eval `(if (\u0026gt; ,a ,b) ,(+ a 4) ,d)) \u0026quot;a string\u0026quot; ;;------------------ \u0026gt; (setq xlist '(1 2 3 4)) (1 2 3 4) \u0026gt; (setq ylist '(a b c d e)) (a b c d e) \u0026gt; `(xs ,xlist ys ,ylist) (xs (1 2 3 4) ys (a b c d e)) \u0026gt; `(xs ,@xlist ys ,@ylist) (xs 1 2 3 4 ys a b c d e) 宏 定义lambda函数语法糖:λ\n(defmacro λ (args body) `(lambda ,args ,body)) elisp\u0026gt; (λ (x) (+ x 3)) (lambda (x) (+ x 3)) elisp\u0026gt; (mapcar (λ (x) (+ x 3)) '(1 2 3 4 5 6)) (4 5 6 7 8 9) set variable to nil\n(defmacro nil! (var) `(setq ,var nil)) elisp\u0026gt; (setq x 10) 10 elisp\u0026gt; x 10 elisp\u0026gt; elisp\u0026gt; (nil! x) nil elisp\u0026gt; x nil elisp\u0026gt; elisp\u0026gt; (nil! z) nil elisp\u0026gt; z nil elisp\u0026gt; create clojure def, defn and fn special forms\n(defmacro fn (args body) `(lambda ,args ,body)) (defmacro def (name value) `(setq ,name ,value)) (defmacro defn (name args body) `(defun ,name ,args ,body)) elisp\u0026gt; (fn (x) (* x x)) (lambda (x) (* x x)) elisp\u0026gt; (mapcar (fn (x) (* x x)) '(1 2 3 4 5)) (1 4 9 16 25) elisp\u0026gt; (def x 1000) 1000 elisp\u0026gt; x 1000 elisp\u0026gt; elisp\u0026gt; (defn f (x y z) (+ (* 3 x) (* -4 y) (* 5 z))) f elisp\u0026gt; (f 4 5 6) 22 elisp\u0026gt; ……\nemacs api emacs术语 emacs terminology description point cursor position, number of characters from beggining of the buffer to current cursor position. buffer place where the user edit something. not all buffers are bound to a file. mark beginning of the selected area. region selected area/ text frame the current window of emacs windows each frame can be split in sections that emacs documentation calls windows fill word wrap yank copy kill region cut kill ring clipboard kill buffer close buffer mode line status bar font locking syntax coloring ben\u0026rsquo;s journal: 11 concepts the emacs newbie should master\nemacs api api对象\nbuffer temporary buffer modes mode hooks mode map window frame point process network process minibuffers buffers buffer attributes (buffer-list) (current-buffer) (mapcar #'buffer-name (buffer-list)) (mapcar #'buffer-file-name (buffer-list)) (kill-buffer \u0026quot;init.el\u0026quot;) (get-buffer \u0026quot;*scratch*\u0026quot;) 列出打开文件\n(defun opened-files () \u0026quot;list all opened file in current session\u0026quot; (interactive) (remove-if 'null (mapcar 'buffer-file-name (buffer-list)))) (opened-files) 创建新buffer\n;; ;; ;; this function returns a buffer named buffer-or-name. ;; the buffer returned does not become the current ;; buffer—this function does not change which buffer is current. ;; elisp\u0026gt; (get-buffer-create \u0026quot;foobar\u0026quot;) #\u0026lt;buffer foobar\u0026gt; elisp\u0026gt; ;; ;; divide the screen in two windows, and switch to the new buffer ;; window ;; elisp\u0026gt; (switch-to-buffer-other-window \u0026quot;foobar\u0026quot;) #\u0026lt;buffer foobar\u0026gt; elisp\u0026gt; ;; clean current buffer ;; elisp\u0026gt; (erase-buffer) nil elisp\u0026gt; ;; edit another buffer and go back to the old buffer ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; elisp\u0026gt; (defun within-buffer (name function) (let (curbuff (current-buffer)) (switch-to-buffer name) (funcall function) (switch-to-buffer current-buffer) )) elisp\u0026gt; (within-buffer \u0026quot;foobar\u0026quot; (lambda () (insert \u0026quot;dummy\u0026quot;))) #\u0026lt;buffer *ielm*\u0026gt; elisp\u0026gt; elisp\u0026gt; (lambda (x)(* x 10)) (lambda (x) (* x 10)) ;;;; translated from: http://d.hatena.ne.jp/rubikitch/20100201/elispsyntax ;; elisp\u0026gt; ;; test-buffer create a buffer named, to write a variety of content (with-current-buffer (get-buffer-create \u0026quot;test-buffer\u0026quot;) ;; empty the contents of the buffer (erase-buffer) ;; /tmp/foo.txt make the contents inserted (insert-file-contents \u0026quot;/etc/fstab\u0026quot;) ;; insert a string (insert \u0026quot;end\\n\u0026quot;) ;; write the contents of a buffer to a file (write-region (point-min) (point-max) \u0026quot;/tmp/bar.txt\u0026quot;)) nil elisp\u0026gt; buffer mode show buffers mode\nelisp\u0026gt; (defun buffer-mode (buffer-or-string) \u0026quot;returns the major mode associated with a buffer.\u0026quot; (with-current-buffer buffer-or-string major-mode)) buffer-mode elisp\u0026gt; (mapcar (lambda (b)( let ( (name (buffer-name b)) (type (buffer-mode (buffer-name b))) ) (list name type) )) (buffer-list)) ((\u0026quot;*ielm*\u0026quot; inferior-emacs-lisp-mode) (\u0026quot;*speedbar*\u0026quot; speedbar-mode) (\u0026quot; *minibuf-1*\u0026quot; minibuffer-inactive-mode) (\u0026quot;*scratch*\u0026quot; emacs-lisp-mode) (\u0026quot;test3.ml\u0026quot; tuareg-mode) (\u0026quot;*help*\u0026quot; help-mode) (\u0026quot;*messages*\u0026quot; messages-buffer-mode) (\u0026quot;sbet.ml\u0026quot; tuareg-mode) (\u0026quot; *minibuf-0*\u0026quot; minibuffer-inactive-mode) (\u0026quot;test.el\u0026quot; emacs-lisp-mode) ... get buffer contents / selection / line get buffer content as string\nelisp\u0026gt; (defun buffer-content (name) (with-current-buffer name (buffer-substring-no-properties (point-min) (point-max)))) buffer-content elisp\u0026gt; elisp\u0026gt; (buffer-content \u0026quot;test3.ml\u0026quot;) \u0026quot;\\n\\nlet rec prodlist = function \\n | [] ... \u0026quot; get selected text in current buffer as string\n(defun get-selection () \u0026quot;get the text selected in current buffer as string\u0026quot; (interactive) (buffer-substring-no-properties (region-beginning) (region-end)) ) get current line in current buffer\n(defun get-current-line () (interactive) \u0026quot;get current line, where the cursor lies in the current buffer\u0026quot; (replace-regexp-in-string \u0026quot;[\\n|\\s\\t]+$\u0026quot; \u0026quot;\u0026quot; (thing-at-point 'line t)) ) search and replace in the entire buffer (defun replace-regexp-entire-buffer (pattern replacement) \u0026quot;perform regular-expression replacement throughout buffer.\u0026quot; (interactive (let ((args (query-replace-read-args \u0026quot;replace\u0026quot; t))) (setcdr (cdr args) nil) ; remove third value returned from query---args args)) (save-excursion (goto-char (point-min)) (while (re-search-forward pattern nil t) (replace-match replacement)))) point, region, line and buffer point point\nfunction description (point) current cursor position (point-min) minimum cursor position in current buffer. (always returns 1) (point-max) maximum cursor position in current buffer. \u0026#xa0; \u0026#xa0; (line-beginning-position) point of the beginning of current line. (line-end-position) point of the end of current line. \u0026#xa0; \u0026#xa0; (region-beginning) position of the beginning current region (selected text). (region-end) position of the end current region. \u0026#xa0; \u0026#xa0; (bounds-of-thing-at-point ) returns the cons pair '(beginning . end) position of thing at point. point interface functions\nfunction description (goto-char ) move the cursor to a given point. (insert ) insert text at current point. (buffer-substring [pmin] [pmax]) returns the text with properties between the points and . (buffer-substring-no-properties [pmin] pmax]) returns the text without properties between the points. (delete-region [pmin] [pmax]) deletes the text between and . \u0026gt; (point) 99696 \u0026gt; (point-min) 1 \u0026gt; (point-max) 185623 \u0026gt; (line-beginning-position) 99774 \u0026gt; (line-end-position) 99804 \u0026gt; (buffer-substring-no-properties (line-beginning-position) (line-end-position)) (defun delete-line () (interactive) (delete-region (line-beginning-position) (line-end-position))) (defun delete-region () (interactive) (delete-region (region-beginning) (region-end))) (defun insert-end-of-buffer () (interactive) ;; save current cursor position ;; and go back to initial positon when ;; finish this block (save-excursion (goto-char (point-max)) ;;; go to end of buffer (insert \u0026quot;testing insert end of buffer\u0026quot;) )) thing at point api ???\nmessage / output (message \u0026quot;hello world\u0026quot;) (message-box \u0026quot;time for a break.\\ndrink some coffee\u0026quot;) files, directories and path basic functions ;; get and set current directory elisp\u0026gt; (pwd) \u0026quot;directory /home/tux/tmp/\u0026quot; elisp\u0026gt; (cd \u0026quot;/etc/\u0026quot;) \u0026quot;/etc/\u0026quot; elisp\u0026gt; (pwd) \u0026quot;directory /etc/\u0026quot; elisp\u0026gt; elisp\u0026gt; (file-name-directory \u0026quot;/etc/hosts\u0026quot;) \u0026quot;/etc/\u0026quot; ;; expand file name ;; elisp\u0026gt; (expand-file-name \u0026quot;~/\u0026quot;) \u0026quot;/home/tux/\u0026quot; elisp\u0026gt; (expand-file-name \u0026quot;.\u0026quot;) \u0026quot;/home/tux/tmp\u0026quot; elisp\u0026gt; (expand-file-name \u0026quot;..\u0026quot;) \u0026quot;/home/tux\u0026quot; elisp\u0026gt; ;;;;; create a directory ;;; elisp\u0026gt; (mkdir \u0026quot;dummy\u0026quot;) nil elisp\u0026gt; (mkdir \u0026quot;dummy\u0026quot;) ** eval error ** file exists: /home/tux/dummy elisp\u0026gt; ;;; list directory ;;;; ;;; elisp\u0026gt; (directory-files \u0026quot;/home/tux/pycharmprojects/haskell/\u0026quot;) (\u0026quot;.\u0026quot; \u0026quot;..\u0026quot; \u0026quot;.git\u0026quot; \u0026quot;.gitignore\u0026quot; \u0026quot;.idea\u0026quot; \u0026quot;license\u0026quot; \u0026quot;make\u0026quot; \u0026quot;makefile\u0026quot; \u0026quot;readme.back.md\u0026quot; \u0026quot;readme.html\u0026quot; \u0026quot;readme.md\u0026quot; \u0026quot;test.html\u0026quot; \u0026quot;build.sh\u0026quot; \u0026quot;clean.sh\u0026quot; \u0026quot;codes\u0026quot; \u0026quot;dict.sh\u0026quot; \u0026quot;haskell\u0026quot; \u0026quot;ocaml\u0026quot; \u0026quot;papers\u0026quot; \u0026quot;tags\u0026quot; \u0026quot;tmp\u0026quot;) file name components elisp\u0026gt; (file-name-directory \u0026quot;/usr/bin/env\u0026quot;) \u0026quot;/usr/bin/\u0026quot; elisp\u0026gt; elisp\u0026gt; (file-name-nondirectory \u0026quot;/usr/bin/env\u0026quot;) \u0026quot;env\u0026quot; elisp\u0026gt; elisp\u0026gt; (file-name-base \u0026quot;/home/foo/zoo1.c\u0026quot;) \u0026quot;zoo1\u0026quot; elisp\u0026gt; (file-name-base \u0026quot;/home/foo/zoo1.c.back\u0026quot;) \u0026quot;zoo1.c\u0026quot; read / write file to a string read file\nelisp\u0026gt; (defun file-contents (filename) (interactive \u0026quot;ffind file: \u0026quot;) (with-temp-buffer (insert-file-contents filename) ;; 先将文件内容插入临时buffer,再读取内容 (buffer-substring-no-properties (point-min) (point-max)))) elisp\u0026gt; (file-contents \u0026quot;/proc/filesystems\u0026quot;) \u0026quot;nodev sysfs\\nnodev rootfs\\nnodev ramfs\\nnodev bdev\\nnodev proc\\nnodev cgroup\\nnode ... write to file\nelisp\u0026gt; (append-to-file \u0026quot;hello world\u0026quot; nil \u0026quot;/tmp/hello.txt\u0026quot;) nil elisp\u0026gt; (file-contents \u0026quot;/tmp/hello.txt\u0026quot;) \u0026quot;hello world\u0026quot; elisp\u0026gt; window functions basic window functions (split-window-horizontally) (split-window-vertically) (delete-other-windows) (switch-to-buffer-other-window \u0026quot;init.el\u0026quot;) (delete-window) (make-frame) (frame-list) (delete-frame) manipulate buffer in another window http://caiorss.github.io/emacs-elisp-programming/elisp_programming.html#sec-3-9-2\nwindow configuration (current-window-configuration) (setq w (current-window-configuration)) w (set-window-configuration w) ;; screen resolution elisp\u0026gt; (x-display-pixel-width) 1366 elisp\u0026gt; (x-display-pixel-height) 768 elisp\u0026gt; elisp\u0026gt; ;; resize and set emacs windows position ;; ;; from: http://uce.uniovi.es/tips/emacs/mydotemacs.html#sec-41 ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; elisp\u0026gt; (defun resize-frame () \u0026quot;set size\u0026quot; (interactive) (set-frame-width (selected-frame) 100) (set-frame-height (selected-frame) 28) (set-frame-position (selected-frame) 0 1)) resize-frame elisp\u0026gt; elisp\u0026gt; (resize-frame) t elisp\u0026gt; os interface find the current operating system value description gnu gnu hurd system. gnu/linux gnu/linux system. gnu/kfreebsd gnu system with a freebsd kernel. darwin darwin (gnu-darwin, mac os x) ms-dos ms-dos application. windows-nt native w32 application. cygwin compiled using the cygwin library system-type system-configuration date and time (current-time) (insert (format-time-string \u0026quot;%y-%m-%d\u0026quot;)) ;; 2019-12-15 (insert (format-time-string \u0026quot;%h:%m:%s\u0026quot;)) ;; 16:11:04 (format-time-string \u0026quot;%d/%m/%y %h:%m:%s\u0026quot; (current-time)) call external commands or apps (call-process \u0026quot;mpd\u0026quot;) (shell-command-to-string \u0026quot;pwd\u0026quot;) environment variables (getenv \u0026quot;path\u0026quot;) (split-string (getenv \u0026quot;path\u0026quot;) \u0026quot;:\u0026quot;) (dolist (e (split-string (getenv \u0026quot;path\u0026quot;) \u0026quot;:\u0026quot;)) (princ (format \u0026quot;%s\\n\u0026quot; e))) exec-path (getenv \u0026quot;home\u0026quot;) (setenv \u0026quot;java_home\u0026quot; \u0026quot;/usr/local/java\u0026quot;) system-type (eq system-type 'gnu/linux)() (dolist (e process-environment) (princ (format \u0026quot;%s\\n\u0026quot; e))) process management (process-list) (get-process \u0026quot;merlin\u0026quot;) (mapcar 'process-name (process-list)) ;;;; buffer process (process-command (get-process \u0026quot;vterm\u0026quot;)) (process-id (get-process \u0026quot;vterm\u0026quot;)) (process-buffer (get-process \u0026quot;vterm\u0026quot;)) (buffer-name (process-buffer (get-process \u0026quot;vterm\u0026quot;))) (mapcar (lambda (p) (buffer-name (process-buffer p))) (process-list)) (display-buffer (process-buffer (get-process \u0026quot;vterm\u0026quot;))) ;;;; start asyncronous process ;; start the process named py, with the buffer named pybff ;; using the command python, /usr/bin/python (on linux) (start-process \u0026quot;py\u0026quot; \u0026quot;pybff\u0026quot; \u0026quot;python\u0026quot;) ;; end the process named py (process-send-eof \u0026quot;py\u0026quot;) (process-send-string \u0026quot;py\u0026quot; \u0026quot;print 'hello world'\\n\u0026quot;) ;;;; get multiple fields (mapcar (lambda (p)(list p (process-name p) (process-command p) (list (process-buffer p) (buffer-name (process-buffer p))) (process-id p) (process-status p) )) (process-list)) interfaces creating quick access menu (require 'easymenu) (easy-menu-define djcb-menu global-map \u0026quot;utils\u0026quot; '(\u0026quot;utils\u0026quot; (\u0026quot;shells\u0026quot; ;; submenu [\u0026quot;ielm - emacs lisp shell\u0026quot; (ielm)] [\u0026quot;eshell - emacs buitin shell\u0026quot; (eshell)] [\u0026quot;native shell \u0026quot; (shell)] [\u0026quot;---------------------\u0026quot; nil] [\u0026quot;edit .bashrc\u0026quot; (find-file \u0026quot;~/.bashrc\u0026quot;)] [\u0026quot;edit .profile\u0026quot; (find-file \u0026quot;~/.profile\u0026quot;)] [\u0026quot;edit .xresources\u0026quot; (find-file \u0026quot;~/.xresources\u0026quot;)] [\u0026quot;edit .xsessin\u0026quot; (find-file \u0026quot;~/.xsession\u0026quot;)] [\u0026quot;see all gnu man pages\u0026quot; ( info)] [\u0026quot;see a specific man page\u0026quot; (woman)] );; end of shells menu (\u0026quot;emacs /elisp\u0026quot; ;; submenu [\u0026quot;ielm - emacs lisp shell\u0026quot; (ielm)] [\u0026quot;eval buffer\u0026quot; (eval-buffer) ] [\u0026quot;---------------------\u0026quot; nil] [\u0026quot;edit init.el\u0026quot; (find-file user-init-file)] [\u0026quot;reload init.el\u0026quot; (load-file user-init-file)] [\u0026quot;open .emac.d dir\u0026quot; (find-file \u0026quot;~/.emacs.d\u0026quot;)] [\u0026quot;list packages\u0026quot; (list-packages)] [\u0026quot;install package\u0026quot; (package-install)] ) ;; end of emacs / elisp submenu )) ;; end of custom menu add icons to toolbar http://caiorss.github.io/emacs-elisp-programming/elisp_programming.html#sec-3-11-2\ntimer run-with-timer ;;; (run-with-timer secs repeat function \u0026amp;rest args) (run-with-timer 5 nil (lambda () (message-box \u0026quot;happy hacking emacs!\u0026quot;))) (defun cofee-wait () (interactive) (let ((minutes 3)) (run-with-timer (* 60 minutes) nil (lambda () (message-box \u0026quot;coffee done\u0026quot;)) ) (message \u0026quot;waiting for the cofee\u0026quot;) )) emacs modes mode association with files ;; 列出所有和拓展名相关的mode auto-mode-alist ;; 列出与一个mode相关的所有拓展名 (remove-if-not (lambda (al) (equal (cdr al) 'web-mode)) auto-mode-alist) ;; 为一个mode关联拓展名 (add-to-list 'auto-mode-alist '(\u0026quot;\\\\.markdown\\\\'\u0026quot; . markdown-mode)) lisp routines to introspect modes?? (defun show-doc (function) (princ (documentation function))) (defun mode/show () \u0026quot; returns all modes associated with files to query the file extesions associated with a mode use: \u0026gt; (mode/ftypes 'markdown-mode) for example. \u0026quot; (dolist (m (remove-if #'listp (mapcar #'cdr auto-mode-alist))) (print m))) (defun mode/ftypes (mode) \u0026quot; get all file extension associated with a mode. usage: elisp\u0026gt; (get-mode-ftypes 'markdown-mode) ((\\\u0026quot;\\\\.md\\\\'\\\u0026quot; . markdown-mode) (\\\u0026quot;\\\\.text\\\\'\\\u0026quot; . markdown-mode) (\\\u0026quot;\\\\.markdown\\\\'\\\u0026quot; . markdown-mode) \u0026quot; (remove-if-not (lambda (al) (equal (cdr al) mode)) auto-mode-alist)) elisp\u0026gt; (mode/ftypes 'clojure-mode) ((\u0026quot;\\\\(?:build\\\\|profile\\\\)\\\\.boot\\\\'\u0026quot; . clojure-mode) (\u0026quot;\\\\.\\\\(clj\\\\|dtm\\\\|edn\\\\)\\\\'\u0026quot; . clojure-mode)) elisp\u0026gt; (mode/ftypes 'scheme-mode) ((\u0026quot;\\\\.\\\\(scm\\\\|stk\\\\|ss\\\\|sch\\\\)\\\\'\u0026quot; . scheme-mode) (\u0026quot;\\\\.scm\\\\.[0-9]*\\\\'\u0026quot; . scheme-mode) (\u0026quot;\\\\.oak\\\\'\u0026quot; . scheme-mode)) elisp\u0026gt; (show-doc #'mode/ftypes) get all file extension associated with a mode. usage: elisp\u0026gt; (get-mode-ftypes 'markdown-mode) ((\u0026quot;\\.md\\'\u0026quot; . markdown-mode) (\u0026quot;\\.text\\'\u0026quot; . markdown-mode) (\u0026quot;\\.markdown\\'\u0026quot; . markdown-mode)) mode specific key bindings (define-key emacs-lisp-mode-map (kbd \u0026quot;\u0026lt;f5\u0026gt;\u0026quot;) (lambda () (interactive) (message \u0026quot;hello world\u0026quot;))) (add-hook 'lisp-interaction-mode-hook 'turn-on-eldoc-mode) special variables emacs-major-version load-path window-system system-type system-configuration shell-file-name user-full-name user-mail-address user-init-file user-emacs-directory exec-directory network http://caiorss.github.io/emacs-elisp-programming/elisp_programming.html#sec-3-15\n正则表达式 emacs regex special characters\n. any character (but newline) \\* previous character or group, repeated 0 or more time + previous character or group, repeated 1 or more time ? previous character or group, repeated 0 or 1 time ^ start of line $ end of line […] any character between brackets [^..] any character not in the brackets [a-z] any character between a and z \\\\ prevents interpretation of following special char \\\\ or \\w word constituent \\b word boundary \\sc character with c syntax (e.g. \\s- for whitespace char) \u0026#xa0; start\\end of group \\\\\u003c \\\\\u003e start\\end of word \\\\\\` \\\\' start\\end of buffer \\\\1 string matched by the first group \\n string matched by the nth group \\\\{3\\\\} previous character or group, repeated 3 times \\\\{3,\\\\} previous character or group, repeated 3 or more times \\\\{3,6\\\\} previous character or group, repeated 3 to 6 times posix character classes\n[:digit:] digit, same as [0-9] [:upper:] letter in uppercase [:space:] whitespace character, as defined by the syntax table [:xdigit:] hexadecimal digit [:cntrl:] control character [:ascii:] ascii character syntax classes\n\\s- whitespace character \\s/ character quote character \\sw word constituent \\s$ paired delimiter \\s\\_ symbol constituent \\s' expression prefix \\s. punctuation character \\s\u003c comment starter \\s( open delimiter character \\s\u003e comment starter \\s) close delimiter character \\s! generic comment delimiter \\s\" string quote character \\s generic string delimiter \\s\\\\ escape character \u0026#xa0; \u0026#xa0; emacs x perl regex\nemacs regex perl regex description \u0026#xa0; ( ) capture group \\\\{ \\\\} { } \u0026#xa0; \\s- \\s white space \\\\1, \\\\2, \\\\3, \\\\4 $1, $2, $3 result of capture: search, replace. [ ] [ ] character class [0-9] or [:digit:] \\d digit from 0 to 9 \\b \\b word boundary \\w \\w word character regex commands c-m-s incremental forward search matching regexp c-m-r incremental backward search matching regexp buffer commands\nm-x replace-regexp replace string matching regexp m-x query-replace-regexp same, but query before each replacement m-x align-regexp align, using strings matching regexp as delimiters m-x highlight-regexp highlight strings matching regexp m-x grep call unix grep command and put result in a buffer m-x lgrep user-friendly interface to the grep command m-x rgrep recursive grep m-x dired-do-copy-regexp copy files with names matching regexp m-x dired-do-rename-regexp rename files matching regexp m-x find-grep-dired display files containing matches for regexp with dired line commands\ncommand (m-x command) alias description keep-lines delete-non-matching-lines delete all lines except those containing matches flush-lines delete-matching-lines delete lines containing matches highlight-lines-matching-regexp hi-lock-line-face-buffer highlight lines matching regexp occur list-matching-lines show lines containing a match multi-occur \u0026#xa0; show lines in all buffers containing a match how-many count-matches count the number of strings matching regexp regex functions match-string match-end match-beginning re-search re-search-forward replace-string-in-regexp replace-string build regex interactively m-x re-builder\nm-x query-replace-regexp\nemacs regex rx-notation (require 'rx) ;; (rx \u0026lt;patterns\u0026gt;) elisp\u0026gt; (rx digit) \u0026quot;[[:digit:]]\u0026quot; elisp\u0026gt; (rx-to-string '(or \u0026quot;foo\u0026quot; \u0026quot;bar\u0026quot;)) \u0026quot;\\\\(?:\\\\(?:bar\\\\|foo\\\\)\\\\)\u0026quot; description rx notation emacs regex beginning of line bol ^ end of line eol $ \u0026#xa0; \u0026#xa0; \u0026#xa0; begining of string bos \\\\\\\\\\` end of string eos `\\\\'` \u0026#xa0; \u0026#xa0; \u0026#xa0; beginning of word bow \\\\\\\\\u003c end of word eow \\\\\\\\\u003e \u0026#xa0; \u0026#xa0; \u0026#xa0; digit 0 to 9 digit \\lbr\\lbr:digit:\\rbr\\rbr hexadecimal digit hex \\lbr\\lbr:xdigit:\\rbr\\rbr match ascii character \u0026#xa0; \u0026#xa0; match anything lower case lower \\lbr\\lbr:lower:\\rbr\\rbr match anything upper case upper \\lbr\\lbr:upper:\\rbr\\rbr \u0026#xa0; \u0026#xa0; \u0026#xa0; word \u0026#xa0; \\sw example\nelisp\u0026gt; (require 'rx) rx elisp\u0026gt; (rx (+ digit)) \u0026quot;[[:digit:]]+\u0026quot; elisp\u0026gt; (rx digit (+ digit)) \u0026quot;[[:digit:]][[:digit:]]+\u0026quot; elisp\u0026gt; (rx bol (+ digit) eol) \u0026quot;^[[:digit:]]+$\u0026quot; elisp\u0026gt; (rx (zero-or-more digit)) \u0026quot;[[:digit:]]*\u0026quot; elisp\u0026gt; (rx (one-or-more digit)) \u0026quot;[[:digit:]]+\u0026quot; elisp\u0026gt; (rx (or \u0026quot;cat\u0026quot; \u0026quot;rat\u0026quot; \u0026quot;dog\u0026quot;)) \u0026quot;\\\\(?:cat\\\\|dog\\\\|rat\\\\)\u0026quot; ;; (replace-regexp-in-string regexp rep string ;; \u0026amp;optional fixedcase literal subexp start) elisp\u0026gt; (replace-regexp-in-string (rx (or \u0026quot;cat\u0026quot; \u0026quot;rat\u0026quot; \u0026quot;dog\u0026quot;)) \u0026quot;\u0026quot; \u0026quot;cat cata rat rat dograt dog cat2334 23rat2\u0026quot;) \u0026quot; a 2334 232\u0026quot; ;; replaces only in the beggining of line ;; elisp\u0026gt; (replace-regexp-in-string (rx bol (or \u0026quot;cat\u0026quot; \u0026quot;rat\u0026quot; \u0026quot;dog\u0026quot;)) \u0026quot;\u0026quot; \u0026quot;cat cata rat rat dograt dog cat2334 23rat2\u0026quot;) \u0026quot; cata rat rat dograt dog cat2334 23rat2\u0026quot; elisp\u0026gt; (replace-regexp-in-string (rx bow (or \u0026quot;cat\u0026quot; \u0026quot;rat\u0026quot; \u0026quot;dog\u0026quot;) eow) \u0026quot;\u0026quot; \u0026quot;cat cata rat rat dograt dog cat2334 23rat2\u0026quot;) \u0026quot; cata dograt cat2334 23rat2\u0026quot; elisp\u0026gt; (rx bow (or \u0026quot;cat\u0026quot; \u0026quot;rat\u0026quot; \u0026quot;dog\u0026quot;) eow) \u0026quot;\\\\\u0026lt;\\\\(?:cat\\\\|dog\\\\|rat\\\\)\\\\\u0026gt;\u0026quot; ;; removes all whitespaces ;; elisp\u0026gt; (replace-regexp-in-string (rx (* whitespace)) \u0026quot;\u0026quot; \u0026quot;cat cata rat rat dograt dog cat2334 23rat2\u0026quot;) \u0026quot;catcataratratdogratdogcat233423rat\u0026quot; elisp\u0026gt; (replace-regexp-in-string (rx (* whitespace)) \u0026quot;\u0026quot; \u0026quot;cat cata rat rat dograt dog cat2334 23rat2\u0026quot;) \u0026quot;catcataratratdogratdogcat233423rat\u0026quot; ;; capture group ;; elisp\u0026gt; (replace-regexp-in-string (rx (submatch bow (or \u0026quot;cat\u0026quot; \u0026quot;rat\u0026quot; \u0026quot;dog\u0026quot;) eow)) \u0026quot;(\\\\1)\u0026quot; \u0026quot;cat cata rat rat dograt dog cat2334 23rat2\u0026quot;) \u0026quot;(cat) cata (rat) (rat) dograt (dog) cat2334 23rat2\u0026quot; elisp\u0026gt; (rx (submatch bow (or \u0026quot;cat\u0026quot; \u0026quot;rat\u0026quot; \u0026quot;dog\u0026quot;) eow)) \u0026quot;\\\\(\\\\\u0026lt;\\\\(?:cat\\\\|dog\\\\|rat\\\\)\\\\\u0026gt;\\\\)\u0026quot; color scheme http://caiorss.github.io/emacs-elisp-programming/elisp_programming.html#sec-5\nkey bindings http://caiorss.github.io/emacs-elisp-programming/elisp_programming.html#sec-6\n","date":"2019-11-12","permalink":"https://kinneyzhang.github.io/post/emacs-lisp-learning-note/","summary":"lisp介绍 Lisp(历史上拼写为LISP)是具有悠久历史的计算机编程语言家族,有独特和完全括号的前缀符号表示法。起源于公元1958年,是现今第二悠久而仍广泛使","title":"emacs lisp 编程总结"},{"content":"收集阶段:归拢材料 捕获工作和生活中的所有未竟之事,这个阶段通常要花费1~6个小时。很多事情不是那么重要或紧急,但是它们会控制你,无谓消耗你的大量精力,始终让你觉得“还有什么事情要办”。所以收集的过程必须事无巨细。\n收集过程中的要点:\n收集时养成标注时间的习惯 收集的过程不做细枝末节的整理 收集过程中遇到已经列在任务清单的事情,照样收集 遇到紧急需要处理或不想忘记的事情,可以立即处理就处理掉,否则做上标记。 建立自己的“未竟事宜的引子”的清单 收集的内容要定期清理 理清阶段:清空工作篮 处理原则 首先处理最上面的事务 一次一事 永远不要把事务再次放回工作篮 处理流程 需要付诸行动则确定“下一步行动”是什么 如果不需要付诸行动,分为三类:垃圾(舍弃)、待酝酿的事务(加入“将来/也许”清单或写入日程表,备忘录)、参考资料(加入归档系统) 确定了“下一步行动”后面临三种选择:立刻执行(两分钟内可完成);委派他人(自己不是完成任务的最佳人选);推迟执行,有待进一步组织 开阔视野,确定项目 组织整理:建立好清单 基本分类 “项目”清单 项目的辅助资料 记录在日程表中的行动和信息 “下一步行动”清单 “等待”清单 参考资料 “将来/也许”清单 保持不同类别间界线明确,至关重要。组织整理需要的仅仅是“清单”和“文件夹”。\n组织整理行动提示信息 日程表应该只记录对时间有刚性需求的行动\n剩下的就是对时间没有苛刻要求的asap行动。\n根据情境分类 情境就是完成行动所需要的工具、地点或情形。将同一情境下的行动统一执行可以大大提高效率。 常见的一些情境分类例子:电话;在电脑旁;外出事宜;在办公室时;在家时;议事日程;阅读/回顾… “等待”清单 那些等待别人完成的任务需要清单来跟踪。”等待”清单上的内容是那些你不关心过程只关心结果的。 组织整理项目提示信息 项目是那些需要多个步骤才能完成的事情。项目清单不应该包含执行的具体细节或方案,也不应该按照重要性、大小、紧急程度来排序。事实上,我们每天不需要理睬“项目”清单,而应将更多的精力放在日程表、行动清单和突发事件上。既然这样,维护“项目”清单的意义是什么呢?项目让我们对自己要做的事情有宏观上的掌控感以及对工作量有一个客观的认识,这在很大程度上减缓了大脑的负担。\n如何寻找待发现项目? 当前活动:从目前的日程表、行动清单和工作区中需要事件之间的关联性\n较高视野的兴趣和项目:从职责、目标、愿景及核心价值观这些更长远、更重要的角度思考会发现一些“探究”性质的项目。这些项目一旦得到确认,会让人产生一种掌控自己世界的满足感\n当前的问题和机会:分为三类,当前存在的项目问题,流程改进,创意和能力建设\n项目细分:个人/职业;委派给他人的项目;特定类型的项目\n对项目分类是为了保证一目了然的回顾,对项目做到心中有数。不要将“项目的辅助信息”作为提示信息。关于项目的想法或念头应该被保存起来,作为项目的辅助信息。\n组织整理非行动性资料 不要将可执行的行动和不可执行的事务混杂在一起,这样会严重影响个人管理系统的运行。和可执行的行动一样,这些非行动的资料也应该被妥善的保存。\n非行动性资料非为三类:参考资料;“将来/也许”类行动的提示信息;不需要的东西。\n参考资料需要建立个人的资料归档系统。\n“将来/也许”清单保存目前不需要行动的提示信息。它可能源于你创造性的想象力,记录那些当你有了时间、金钱和机会,你希望做的事情;它让你重新评估当前进行中的项目,将没有进展的项目放回“将来/也许”清单;也可能是你感兴趣的一些类别,如想读的一些书、想看的一些电影、周末的外出旅行以及五花八门的想法…\n核查清单 核查清单的目的是帮助我们关注项目,工作流程和程序、事件、爱好、职责等领域中可能出现的潜在问题。这里有一些例子帮助读者理解:\n旅行核查清单 每周回顾 要保持联系的人 工作职责范围 回顾阶段:保障系统的有效运行 回顾的主要目的是保证系统实时更新,紧跟事态发展,这样在行动的时候才能作出恰当的选择。明白了为什么要回顾,接下来需要考虑两个问题。\n要回顾那部分内容?在什么时候进行?\n查看最频繁的应该是你的日程表,接下来浏览当前情境的行动清单, …..\n执行阶段:选择合适的行动 如何在繁忙而辛劳的工作日中,根据各种情况选择合适的行动执行?相信自己的直觉。事实上,我们大多数时候都是在按照直觉决定接下来要做什么,但这并不意味着随心所欲。我们得用一系列的方法增强自己对直觉的信任程度。有三个评估方法对行动的决策过程很有帮助。\n确定某一时刻行动的四标准法 当确定下一步行动时,依据下面四重标准进行:*情境;有多少时间;有多少精力;重要性* 。\n情境:根据不同的情境组织整理行动提示,可以在选择行动时减少大量不必要的选项。如:“电话”、”在家时、“在电脑旁”、”外出、“与乔相关的事”、有关员工会议的安排,诸如此类。情境通常是按照所用的工具或物理位置进行分类的,但还有其他的独特方法可以对提醒内容进行筛选。比如,在长途旅行前创建一个临时的“出行前”类别。也可以为像“创造性写作”这样需要不同的思考时间和思考模式的行动单独分类。为生活或工作的重点领域——“财务”、“家庭”、“管理”等进行内容分类。这些分类方法没有对错,只要适合自己就好。\n有多少时间:选择行动的第二个要素就是你所拥有的空闲时间的长短。对于那些花费时间较短的行动,你可以利用每日的零碎时间来完成。此外,数小时的绞尽脑汁的工作后,你会希望转一下注意力,做一些对时间要求不长的行动再合适不过\n有多少精力:为了提高工作效率,你可以改变工作的情境,或者转移工作焦点,但是你无法决定自己的精力是否充沛。当你状态不佳时,我的经验是做一些花费极少精力或创造力就能完成的事情。即使状态不佳,也没有理由变得松懈、低效,所以建立一个轻松的小事清单很有必要。\n重要性:根据前面的三个要素,划定了下一步行动的选择范围,之后要考虑的就是相对重要性。很对人对“哪件事情最重要”的问题感到头疼,解决这个问题需要你对自己的责任、目标和价值观又一个清醒的认识。\n评估每日工作的三分类法 生活中,我们所进行的活动不外乎以下三种: 执行事先安排好的工作;处理突发事件;安排自己的工作 。\n通常,人们容易卷入随时冒出来的紧急事务中,而对第一项和第三项置之不理。这种做法是弊大于利的。这种紧急情况是可以理解的。但是如果没有对自己正在做的工作和紧急事件进行评估,我的焦虑感会不断增加。也就是说,在遇到意外出现的工作时,一定要冷静的考虑清楚它是否值得你停下其他的工作,千万不要为了忙碌而忙碌。\n总体检视工作的六层次法 工作中的六个层次可以类比成高度:\n5楼视野:人生 4楼视野:长期展望 3三视野:1~2年的目标 2楼视野:关注和责任范围 1楼视野:当前的工作 地面:目前的行动 每一层次都要服从上一层次,越是在上面的层次,重要性越高。\n","date":"2019-10-20","permalink":"https://kinneyzhang.github.io/post/reading-notes-of-getting-things-done-one/","summary":"收集阶段:归拢材料 捕获工作和生活中的所有未竟之事,这个阶段通常要花费1~6个小时。很多事情不是那么重要或紧急,但是它们会控制你,无谓消耗你的大量精力,始终让你觉","title":"《搞定i》笔记"},{"content":"写日志的一个重要好处就是通过每日的反思不断调整,使得事情朝着正确的方向发展。正确的标准是什么?首先自己得舒心,如果事情越做越难受,大抵是什么方面出了问题;其次是对事情的进展是否起促进作用,意识具有主观能动性,正确的意识促进事物的发展,错误的意识阻碍事物的发展;再者,检查事情的现状与初衷是否相符,如果忘记了为何出发,如何知道去向何处。\n写日志的目的是什么?日志,顾名思义,一日的记录。记录的内容可以涉及方方面面,大体可以分为虚实两方面,即经历与思考。记录的目的主要有几个方面:一、给生活留下印记,所以要尽可能的全面真实。二、作为写作的素材,每日的灵感是很宝贵的思想财富,大概率可以应用于将来的写作中。三、锻炼表达与语言组织能力,在缺少与人言语交流的情况下,写文字也可以很好的锻炼遣词造句和逻辑表达。四、抒发情绪与观点,大部分文学作品都带有作者强烈的自我意识与情绪表达,写作很好的满足了作者的表达需求。五、思考与反思,这是我写日志最主要的原因与动力,也是我认为的最有价值的属性。没有十全十美的人,人总要在不断反思与自我批判中成长。反思的反义词叫“浑浑噩噩”。\n应该通过何种形式来组织日志?以自己最舒服,最喜欢的方式。日志不是博客、新闻或论文等,不需要考虑受众。怎么喜欢怎么来就行。\n形式不受限制并不是意味着毫无限制。回顾写日志的目的,至少要清晰的描述事件,准确的表达观点。如果你想作为写作可以参考的素材,最好附上相关引用链接,介绍清楚来龙去脉。如果要锻炼文字表达能力,要求就更高了,注意句式,注意运用修辞与描写方法,内心活动什么的。切忌漫无目的的意识流。要求再高一点,写长文一定要列提纲;短文也要做到心中有框架。关于如何列提纲,参考 这篇文章 ,以后我也会总结这篇文章中的观点(好吧,原链接挂了,我也还没来得及总结…)。\n如果你在没有做研究的情况下撰写文章观点,基本上是不可能全面的;即使全面了,某些内容也会随着时间的推移而过时。这就是为什么写日志的最后一步叫\u0026quot;revise\u0026quot;修改。都说好文章是改出来的,没毛病!\n","date":"2019-08-05","permalink":"https://kinneyzhang.github.io/post/thinking-about-journaling/","summary":"写日志的一个重要好处就是通过每日的反思不断调整,使得事情朝着正确的方向发展。正确的标准是什么?首先自己得舒心,如果事情越做越难受,大抵是什么方面出了问题;其次是","title":"关于写日志"},{"content":"我以为学习计算机科学的学生在刚进入大学的时候,学院学校就要让学生了解到这个学科的森林,而非树木。建立学科体系结构和了解学科本质对于学习相当重要。我指的不是像每个专业大一的导论课,讲解大学四年学习课程的基础内容,最后用一张考试卷评判知识点的掌握程度。\n构建学生对于学科的森林的认识应当不是一般的大学老师可以胜任的。计算机科学体系庞杂,分支众多,能够站在一定的高度还原计算机科学本质的专家教授才能上好这堂大学专业第一课。换句话说,这门课对讲授者的要求很高,讲授的内容要直观又不失深度,严肃而充满乐趣。课堂的作业多以开放性的思考与探索为主。课程考核学生对专业的理解与思考。\n要给学生树立学习的榜样。站得高才能看得远,看得见远方的美景才有去向远方的动力。新生对在校学习可以达到的高度,将来研究内容的深度,自己能够创造的价值是缺乏认知的。要相信每一个学生都想变得更好,前提是给学生看到远方并给予希望。\n看得到未来的理想生活,了解了努力的方向,学生的心中便种下了一颗种子,用四年的时间让它发芽成长开花结果。一个人、两个人、一群人,人是环境的产物,一群人的行动会汇聚成强大的磁场,吸引周围更多的人加入这个积极的队伍。\n多学一个知识不如多听一堂讲座,多做几道题目不如多写几篇思考。大学的课程不以知识技能为导向,多以思考理解为导向,以理解带动知识学习,这样学生的学习效率会不会有极大的提高?\n","date":"2019-08-02","permalink":"https://kinneyzhang.github.io/post/thinking-about-cs-teaching-in-college/","summary":"我以为学习计算机科学的学生在刚进入大学的时候,学院学校就要让学生了解到这个学科的森林,而非树木。建立学科体系结构和了解学科本质对于学习相当重要。我指的不是像每个","title":"关于大学计算机科学"},{"content":"我相信,一本好书是值得反复阅读品鉴的。今天读完了《月亮与六便士》,想着应该写点什么,毕竟这是我时隔好久完整地读完一本书了。\n对于很难坚持阅读的我来说,这一刻是有重要意义的。这意味着我过去很多年在阅读上的心结打开了,也宣告着以后我会以更加积极的心态与行动去拥抱阅读这件事。曾经有多少次的心血来潮,买了一本本书;曾经又有多少次下定决心,从读书笔记开始,庄严的进入阅读的殿堂;多少次拿起kindle,又多少次把它丢在宿舍的某个角落很久……\n有多少次的坚持,就有多少次的放弃。一直以来,我都在思考探索着适合我的阅读方式,憧憬着像某位大师饱读群书,挥笔如有神。到头来发现,重要的不是形式,而是我的这颗心,我为什么要阅读。\n以前阅读的目的很功利,想着读完一本书,自己的文笔就能有质的提升。看完一篇文章,就记录下里面的精彩句子,写上自己的收获与感悟。读每一句的时候都在揣摩文字背后的深意,学习遣词造句的技巧。可是,这样的阅读方式,太累了!坚持不了多久就放弃了,到头来发现不仅没有多少收获,反而因阅读这件事耗费了大量的精力。\n如果阅读的过程如此的让人不堪其负,这不是阅读最初该有的模样!\n说到这,暂且把对阅读的思考放一放,回到《月亮与六便士》这本书。\n当翻到书的最后一页的时候,我有些茫然,心里有种说不出来的滋味。是一种意犹未尽?是期待着有更加传奇,震撼人心的事情发生?还是觉得自己没有完全读懂这本书?都有一点!在我们普通人看来斯特里克兰的人生是有传奇色彩的,这种传奇不是我们能够复刻但也远非不可触及的。如今在影视圈泛滥的一些修仙玄幻题材的作品讲诉的故事引人入胜,但观众也只是看着开心罢了,不会有人去效仿或者思考其背后的逻辑。但斯特里克兰的故事是实实在在的生活啊!任何一个现实中的人看到这样的故事,大抵上不会没有思考,不会不想去了解这样的人活着的逻辑吧。\n关于斯特里克兰的故事其实并不复杂,我可以用很简短的一段话来概括:一个家庭美满,事业成功的证券经纪人,一夜之间抛弃一切,远走他乡,从伦敦到巴黎,追求他的画画梦想。他在巴黎穷困潦倒,吃尽苦头,勾引朋友的妻子导致她自杀。他对周围的一切都残忍冷酷,包括他自己。最终来到了南太平洋的一座小岛,娶妻生子,与世隔绝,终于创造出改写现代艺术史的不朽之作。可临终前却叮嘱自己的土著妻子一把火烧了自己的杰作……\n上述文字是小说导读的内容,在开始阅读这本书之前你就会了解到主人公的一生,你或许会讨厌这样的人设,觉得他冷酷无情,毫无道德观念。没错,毛姆想要刻画的斯特里克兰正是这样的一个人。但请记住,他的无情不是自私,因为他对自己也这样!他所追求的是一种原始的美,一种对美的渴望与创造。这种渴望让他像着了魔一样忽视了一切的人,事,感受。我把这种状态理解为追求理想的理想主义。用通俗的话来说就是我们经常说的追寻自己的内心,不要太在意别人对你的评价!哦豁,多么耳熟能详的话,写过高中作文的你一定深谙这个道理,并且还会引经据典,阐述论证。但是人就是这样,知道不一定懂得,懂得不一定会践行。当现实中的你面对抉择的时候,早就把这些话抛诸脑后了吧。\n人往往不是自己渴望成为的人,而是不得不成为的人。真正的痛苦不是不去追求,而是活着求而不得,守着错误的选择煎熬一生。这种时候你有斯特里克兰的勇气吗?你可以为了曾经的梦想去舍弃安定的生活吗?或者说反过来,你能够为了平淡的生活舍弃自己追寻的梦想吗?只要遵循自己的内心,月亮或者六个便士,无论你选那种,都是幸福的,这大概是这部小说想要告诉我们的吧。\n什么是生活的意义?不是看到别人画画也去画画,不是听爸爸说,听老师说,听别人说你要做什么。这些没人能真正告诉你,需要你自己满怀勇气,去探索找寻。去问一问自己的心,我想要的究竟是什么。\n回到阅读这件事。既然阅读的过程让我痛苦,那我为什么还要不断的尝试呢?爸爸妈妈老师说,你要多读书呀,腹有诗书才能写好作文。可事实证明抱着这样的心态去读书,我经历了一次次坚持后的放弃。何为读书的意义?我为什么要阅读?阅读时候的我为何无法享受其中?……我不断的拷问着自己的内心,不停的找寻思考。直到读完月亮与六便士,我以一种平静的心态,以一个局外人的目光去窥视主人公的一生,小心翼翼地走进斯特里克兰的生活。我会为他的离家出走愤懑不解,也会为他流落街头同情惋惜,我想要去探索他的精神世界,想要搞懂他的行为逻辑,想从小说的故事去思考自身,对应现实……一切都在阅读中不经意的进行。一直读到最后一页,长抒了一口气:真是,精彩!\n这才是真正的享受阅读。\n一本好书是值得反复阅读品鉴的,我想每年重读一遍《月亮与六便士》,这本畅销书中的经典。\n","date":"2019-07-21","permalink":"https://kinneyzhang.github.io/post/pick-up-reading-after-read-the-moon-and-sixpence/","summary":"我相信,一本好书是值得反复阅读品鉴的。今天读完了《月亮与六便士》,想着应该写点什么,毕竟这是我时隔好久完整地读完一本书了。 对于很难坚持阅读的我来说,这一刻是有重","title":"重拾阅读"},{"content":"理论 子弹短句分为三类:任务(task,需要做的事情), 事件(event,你的经历), 笔记(note,不想遗忘的信息) 任务子弹分类:任务(●), 完成的任务(x), 迁移的任务(\u0026gt;), 计划中的任务(\u0026lt;), 不想关的任务(划去) 事件子弹:用“○”表示,简明客观的记录即将发生或已经发生的事件,方便日后回顾与解决问题 笔记子弹:用“-”表示,当某件事情有重要或有趣的细节值得记录时使用 优先符号:用“★”表示,用于标注重要的子弹短句,常于任务子弹搭配 灵感符号:用\u0026quot;!\u0026ldquo;标注笔记子弹,表明这条笔记让我产生了想法,思考或见解,供后续整理 集子:模块化的集子解决混乱。子弹笔记的四个核心集子:每日记录,月度记录,未来记录,索引 每日记录:快速记录一天的任务,事件,笔记,让思想减负 月度记录:分为日历页和任务页,日历页是事件发生的时间轴,任务页梳脑中所有思绪 未来记录:每日记录中有未来之事迁移到未来记录中,月度记录的时候查看未来记录进行迁移 月度迁移:回顾上月任务未完成情况,分成4中情况:1.舍弃 2.重新抄写 3.迁移到个性化集子 4.迁移到未来记录 年度迁移:回顾上一年的未完成情况,考察迁移那些项目,集子 实践 反思:日反思规划,夜反思回顾。 意义:很多人把追求快乐当作人生目标,事实上快乐不可占有。当你达成某个目标或者得到你想要的生活后,你的快速适应会让你觉得平淡无奇,快乐感逐渐消退。快乐只是一种情绪,是我们着手进行其他目标时的结果。我们更应该关注怎么做,即寻找生活的意义。观察那些让你产生好奇心,那些“大放光芒”的事物,这些事物有可能具有意义。寻找这些事物的本质。 目标:带意向的设定目标,目标的灵感可以来源与激情之源。创建目标集子,设定期限,分清主次,划去不必要的目标。分解长期目标为冲刺目标,分解前头脑风暴,每日反思 ,即时修正。 循序渐进:实现目标的过程不要期待巨变,要持续改善。 好句摘录 如果生活是大海,那么其中的每一天就像海浪一样,有的震撼,有的普通。子弹笔记就像海岸,在每一天的影响下得到雕琢。 若没能把想法积极的运用到生活中,就算是最强烈的信仰,最有益的经验也会消散。 无论一项行动有多么简单,其背后都蕴含了无数选择。 眼睛只能看到光亮,耳朵只能听到声音,而一颗聆听的心却能感知到意义。 好奇心是我们在看到某种潜能时产生的触电般的兴奋劲。好奇心点亮幻想与惊讶,就像磁铁一样,把我们从封闭的自我中拉出来,融入世界中,它超越理智、欲望、个人利益,甚至是快乐。 享乐效应指当前环境的改变给人带来快乐时,人们通常会很快适应环境的改变,恢复到平常的快乐程度。 快乐是我们着手进行其他目标时的结果。如果快乐是行为的结果,那我们就不该问自己如何才能快乐了。相反,我们该问问自己,要怎么做。 作家克托尔.加西亚曾说:“你的ikigai即是你擅长的事,又是你热衷的事”。多年来,人们用许多不同的字眼和实践来描述这一点,但都殊途同归的回到生活意义的核心。 感受那些“大放光芒”的事物,这些事物有可能具有意义。 如果不带意向的胡乱设定目标,目标就有可能沦落为我们在遭遇龌蹉或悲痛时下意识的反应。 获得干劲的一大妙招就是意识到时间有限。 目标的灵感启发应当源自自身的生活经历。不论是带给你欢乐的积极动力还是带给你悲惨教训的生活苦难,你的生活中肯定有真正的激情之源。把这些经历运用起来,这些都是强有力的意义源泉,你可以从中找到有意义的目标。 宏大的目标往往费时又费力,在这一路上你会面对各种挑战,耐力常常是你最狡猾最致命的对手。因此,要实现宏大的目标,常常需要切实的需求看来激励自己度过数日,数月,甚至数年的风风雨雨。这项需求必须足够强劲,才能抵御一路上的分心、借口和疑惑。 这个世界上有天真的问题,乏味的问题,用词不当的问题,自我批评不足提出的问题。但每一次发问都是为了了解世界。这世界上没有愚蠢的问题。 ","date":"2019-03-05","permalink":"https://kinneyzhang.github.io/post/reading-notes-of-bullet-journal/","summary":"理论 子弹短句分为三类:任务(Task,需要做的事情), 事件(Event,你的经历), 笔记(Note,不想遗忘的信息) 任务子弹分类:任务(●), 完成的任务(x),","title":"《子弹笔记》摘录"},] [{"content":"每次写「谈谈最近」,都感觉自己对许多事情有了之前没有的,或不一样的想法。如果没有这些想法,我也不会打开电脑敲下这些文字,毕竟一层不变的自己总是在惯性的推动下度过平常的每一天。这样的状态相对舒适,只需应付当前遇到的一个个任务和问题,不需要过多思考状态本身。但当我感到不舒适,或者对自己感到不满的时候,我知道这便是成长的邀约。我愿意欣然接受,并写下这些文字:\n理解与沟通 我们大多数人生活的环境和接触到的社会价值观念还算是健康正常的,这让我们内心有了一杆秤。潜意识里,我们常常要用这杆秤来衡量自己的说话做事的方式,这是理性的反应。但这杆秤的平衡点是动态变化的。有些时候,秤歪了,我们调整视角,把它当做了新的平衡点。但新的平衡不是标准的平衡,直到我们吃了亏,内心受了伤,我们会重新审视并调整平衡。分析原因:是不是关心则乱了?是不是逾越了界限了?是不是又不自信了?是不是丢失自己了?\u0026hellip; 这种不健身的状态不仅让自己感到不适,别人也会不太舒适。\n那么健康的与人相处的状态应该是什么样子的呢?首先得让自己舒服。如果自己在一段关系的相处中感到不适,那么一定是有问题的,要么是自己有问题,要么是别人有问题。这时需要用内心的秤去衡量,如果结论是自己的问题,那么不应该放过这次机会。不去做些什么改变,便永远没有长进,然后便是周而复始的踩坑。总是不顺利的人生,在怨天尤人之前,是不是应该先反思一下自己?如果是别人的问题,也需要内心的衡量,我们毕竟不能要求别人什么。如果不能接受,也不想有过多的牵扯,那么保持冷漠,无话可说了。如果不是,那尊重、包容和理性沟通不失为一种好的方式。绝大多数人一辈子都遇不到那个soulmate,灵魂契合,无话不谈,如同精心设计过的齿轮,一拍即合。或许,沟通和理解才是生活的常态的吧。这种状态就像做框架设计,没有绝对完美的设计,看似完美的背后总是存在诸多的磨合和妥协。这中“妥协”并不意味着失去自我,“妥协”是一种艺术,最终获得是比单独的“完美”更加理想的状态。\n","date":"2022-03-20","permalink":"https://kinneyzhang.github.io/talk/talk-recently-220320/","summary":"每次写「谈谈最近」,都感觉自己对许多事情有了之前没有的,或不一样的想法。如果没有这些想法,我也不会打开电脑敲下这些文字,毕竟一层不变的自己总是在惯性的推动下度过","title":"谈谈最近「220320」"},{"content":"这是一个简短的总结,目的是理清当下自己的千头万绪。\n千头万绪的原因是想做的事情很多,把事情摊开后发现每件事情或多或少、或快或慢的在推进着,但忘了考虑轻重缓急,这是我内心始终不舒畅的原因。\n最近一直在学习《深入理解c指针》,学完了前四章。学习c指针的起因是看c语言描述的链表算法遇到了这块的知识,搞不清楚便找了这本书来看。但c指针学着学着,就忘了学习的初衷,想着把所有c指针的知识都学完,算法便落下了。这种状况就像:我想从a地到b地,但是a到b直达的道路不同,需要先绕行到c,再从c到b。但当到达c时,便在ac上继续走了下去,全然忘了b的存在。\n所以要时刻提醒自己当下最重要的事情是什么,其余的事情或多或少的是为重要的事情做铺垫,别倒置了本末。\n再次对自己强调一下:除工作外,我当下最重要的事情是学习数据结构与算法,第一步是学习基本的数据结构和算法,做到能够不借助任何工具敲出来。参考书籍《数据结构与算法分析——c语言描述》。这个过程中会遇到与c语言相关的不懂的知识,比如指针等。通过看其他的书,或者搜索解决后,就立马回到主线上。持续学习很重要的一点是明确。明确目标后,事情便成功了一半。我会列出所有的基础数据结构和算法,每学完一个,就写一篇博客文章总结与之相关的细节。我不去定“几天学一个算法,一周学几个”这样的具体的目标,因为工作之余的时间还想做很多有意义的事情,把学习计划定的过于具体会有因不合时宜时而产生的压力。只要心中一直想着这件事情,只要在一直推进着就可以了。等基础的算法学习完了,再看一些进阶的内容或刷一些leetcode的题目等,后面的事情根据实际情况再规划,不多赘述。\n生活不能只有学习,我还有很多的兴趣。这些兴趣有的有目的性,有的没有目的性,但无论有无目的,事情归根结底都会对人产生好的或不好的影响。我的内心也有一个期待它们能够带给我的状态和逐渐成长成为的模样。先列列这些事情,再想想怎么计划。\n这些事情有:练字,阅读,写作,尤克里里,记账,听播客,看剧看电影看动漫,唱歌,口语表达,英语\u0026hellip;\n上班累的时候,写个几行字,不定计划。最终目的当然是想写一手好字,并借此拾起与手写相关的兴趣。好的字看起来赏心悦目,让人心情好。不定计划。\n阅读指的是读那些严肃文学或小说名著。阅读的好处自然不用多说。那种精神富足的状态,是我曾经得以窥见的心满意足。阅读是一辈子的事情,当下的我只求养成随手拿起一本便能读下去的习惯,能够享受文字带来的精神上的惬意满足。不定计划。\n写作与阅读息息相关。用文字表达想法,本就是件酣畅淋漓的事情。想写就写,想停就停,不做计划。\n最近想学一门乐器,尤克里里最适合。买了26寸尤克里里、指弹独奏的书籍和手指练习器。我会带着它出差,用空余时间慢慢练习精进。我喜欢这些不会过时的,需要用长久时间来打磨技艺的物件。\n用ledger记账一段时间,最终还是放弃了。每日的整理记录太繁琐,在论坛和有相同经历的人交流后,认识到:我们普通人记账的目的只是对收入和开销有大概的把握,不必像会计那么精确。所以打算一周或一个月集中整理记录一次,忽略那些非常不起眼的收支。\n通勤的时候喜欢听听播客,这是我已经养成的习惯。那些听过的内容,大多是过耳云烟,但也潜移默化在内心留下了印记,我不想把这件事情变得太功利,保持现状就好。\n最近在看《知否知否,应是绿肥红瘦》,很久没有看剧了。好的剧集不仅让人赞叹剧集制作和演员表演,剧情也能引人思考。最重要的是作为消遣,让大脑放松。看电影动漫也是如此。不定计划。\n目前我唱歌最大的问题是:音准,希望尤克里里的练习能够帮我在这方面有所提升。\n口语表达的第一步就是普通话,我的普通话一言难尽。曾经尝试过一些切实可行的练习办法,现在需要重新拾起。\n一直想拾起我这个半吊子英语,尤其是口语。我知道提升的方法途径,但一直没认真考虑这件事。理清必要性后,我觉可以认真定个计划了。\n这些兴趣或想做的事情,真正需要我花心思去制定计划的只有:练习普通话表达和英语(口语)。兴趣不应该给自己带来压力,这样才能顶住重要的事情带来的压力。接下来,我会去明确三件事情的内容,然后便去做吧。\n","date":"2021-12-30","permalink":"https://kinneyzhang.github.io/talk/talk-recently-211230/","summary":"\u003cp\u003e这是一个简短的总结,目的是理清当下自己的千头万绪。\u003c/p\u003e\n\u003cp\u003e千头万绪的原因是想做的事情很多,把事情摊开后发现每件事情或多或少、或快或慢的在推进着,但忘了考虑轻重缓急,这是我内心始终不舒畅的原因。\u003c/p\u003e","title":"谈谈最近「211230」"},{"content":"国庆回去一趟的收获还是很多的,最大的收获就是关于工作和人际交往的思考。和康哥、舅舅聊天,让我意识到自己对待工作的态度应该更加的积极一点。和谢婷的相处,让我意识到自己平时应该更加积极,更自信的与人沟通。\n先说工作。 我认真的问了问自己的内心:张凯你对于目前的工作的态度和想法是什么?我的回答是按照要求完成自己该完成的工作。自己主动研究项目的想法当然也是有的,但态度就比较佛系,更倾向于把空余的时间用在算法和其他编程技能的学习。我发现,自己的内心会有一种潜意识:当前的工作只是一个过渡,做到“不求有功,但求无过”的程度就可以了。我会想把更多的时间用在为下一份满意的工作的学习和铺垫以及更长远的个人能力的提升上。\n这种态度,导致我对待工作没有刚开始的那种热情和专研的劲头。渐渐地我会意识到有点不对劲,直到回去和康哥以及舅舅交流后,才发现了问题所在。我很庆幸能够及时的认识到问题并去思考解决的方法。\n人生没有“过渡阶段”,任何时期都应该认真对待。这种“过渡阶段”的心态会导致我们忽略当下生活的品质和提升自己的机会。我们不应该为了将来某个不确定的愿景而生活,而应认真的过好当下的每一天。即使不是最理想的状态,也不要亏待自己。其实也很难讲,什么样子的生活是最理想的。即使某天我们实现了曾经的目标,也会欲求不满,渴望的更多更好。这样的生活多累呀!\n想通了这一点,我会把更多的时间与精力投入当下在做的项目上。我想,无论3年后,我是跳槽到条件更优越的公司,还是继续呆在亚信,现在的每一天,都尽力的去学习、去积累。学习项目所涉及的技术和思路,积累沟通和解决问题的经验。利用好身边的资源,提升自己的同时,回馈学到的知识和经验给工作和身边的同事。这就是我关于工作的思考。\n当然,专注当下并不意味着放弃更长远的计划。算法和python还是要学的,只是我现在的态度没有那么的坚决了,因为我需要在学习中不断的思考真正适合我的和我希望从事的未来的计算机工作的方向。\n再说沟通。 和谢婷相处,让我有种危机感,我觉得这是件好事。\n这种危机感是关于人际交往方面的。我一直觉得,两个人相处需要不断的互相学习和进步。如果一方“领先”另一方太多,双方心理难免有些不平衡,这是很现实的问题。生活中,情侣或夫妻很多的矛盾源头都和这个道理有关。一见钟情是因为外貌,一直相伴是因为对方有突出的品质和优点。当对方在某一方面不断进步时,自己要么在另一方面亮眼,要么在同一方面不要落下太多。\n有了这个思考,我会发现自己在生活中,有很多地方可以做得更好。比如,和同事一起吃饭时,可以更加积极的去沟通,而不是闷不作声。类似的例子还有很多。\n以前我一直拿性格当借口,拒绝和人交流,但实际上性格并不是问题的根源。从《掌控习惯》一书中,我学习到“习惯在很大程度上影响了性格的形成”。没有天生的内向和外向,内向的人只是习惯了不去表达,如果逐渐有意识地改变这种习惯,便会塑造不同的性格。这是比较理想的思考,需要很多的努力。但当我有了这种意识开始,就没有想象的那么困难了。\n就先说这么多,我去锻炼了,哈哈哈。\n","date":"2021-10-06","permalink":"https://kinneyzhang.github.io/talk/talk-recently-211006/","summary":"国庆回去一趟的收获还是很多的,最大的收获就是关于工作和人际交往的思考。和康哥、舅舅聊天,让我意识到自己对待工作的态度应该更加的积极一点。和谢婷的相处,让我意识到","title":"谈谈最近「211006」"},{"content":"「谈谈最近」的目的是总结自己最近一段时间的状态和思绪,为之后的调整方向做规划。\n目的很明确,不是为了写文章而写文章,而是为了自己变得更好而写文章。所以写今天的「谈谈最近」之前就非常有必要复盘一下上一期的规划做到了哪些,没做到哪些。该表扬的表扬,该反思的反思,然后做新的规划。\n复盘210627 复盘了下《谈谈最近210627》,愈发的让我坚定写「谈谈最近」系列文章的意义。\n1 作息时间:刚来昆明的时候,晚上有很多时候是1点多才睡。最近一段时间,基本保持在12左右,最迟不超过12点半。虽然没有达到计划的11点熄灯睡觉,但也算是个改进。接下来,有意识地在11点半左右熄灯睡觉。做到这一点意味着,11点左右就要提前完成所有睡前洗漱等工作。\n2 饮食方面:没有“开小灶”,除了礼拜天会出去吃点好的,其余时间,自己吃的都比较清淡。大多数时候中午吃的是三明治,喝一杯纯牛奶。唯一需要注意的是,礼拜天出去吃的时候,有几次吃得比较撑。接下来,减少每周出去吃的次数,计划一周最多一次。\n3 减肥健身:这应该是最值得表扬自己的地方。从作出要去健身房锻炼的决定,到办卡的时间不到一周,可见这是困扰我许久的问题,有着强烈的渴望去解决。刚开始,去健身的次数还比较少,最近基本保持一周去3到4次,逐渐养成了吃的较多后去锻炼的习惯。接下来,继续保持,目标明确。我的目标就是减肥瘦身,所以要保证有氧运动(跑步机,椭圆机)的时间至少30分钟以上。\n4 关于日记和其他:从我以往的经验来看,日记适合我,因为它给我的心理带来了负担。任何会带来负担的事情,都不会做得长久,所以我选择不去做。更适合我的方式是:在非常有感触的时候记录下思考;在意识到自己做得不足的时候写下反思;在思绪混乱的时候写写近况。而这些,在我的博客里都有对应的记录方式。所以说,这一点也算是做到了。\n谈谈近况 最近买了许多书和杂志,有编程类的,有文学类的,有观点类的:《流畅的python》、《clojure编程实战》、《vue.js3.0从入门到实战》、《代码大全2》、《计算机程序的构造与解释(sicp)》、《算法导论》、《小狗钱钱/小狗钱钱2》、《富爸爸,穷爸爸》、《奇风岁月》、《麦田里的守望者》、《第一财经周刊》、《新周刊》。\npython与算法 买的这些书,当然都是我想看的,但却没有那么多的时间。文学类和杂志看起来没什么困难。关键是计算机的书,学起来耗时费力。我想学习或深入学习的知识太多了,一堆书放在面前,不知从何着手。所以,必须考虑当下或短期内,我最需要学习的计算机知识是什么,如何安排时间。\n最近看了些招聘的信息,感觉想要在it行业走得更远,有更高的薪资,必须要刷算法。并且,算法的学习可以给我开源的项目提供更多的idea和可能性。综合考虑后,选择的两本书是《流畅的python》和《算法导论》。python是工具,目的是方便实现各种算法伪代码。\n学习的时间就安排在下班后,计划至少学习1个番茄钟(45min)。先这么来安排,学习几天后,根据情况再调整。\n","date":"2021-09-04","permalink":"https://kinneyzhang.github.io/talk/talk-recently-210904/","summary":"「谈谈最近」的目的是总结自己最近一段时间的状态和思绪,为之后的调整方向做规划。 目的很明确,不是为了写文章而写文章,而是为了自己变得更好而写文章。所以写今天的「谈","title":"谈谈最近「210904」"},{"content":"在昆明工作了快一个月了。工作前期虽然困难了点,但最近渐入佳境,也有了些可以自由支配的时间。关于这些时间如何合理使用,一直没能好好的思考。被惯性推着走,我的选择是打游戏,看比赛直播。作为娱乐方式,这些无可厚非。但我真的不适合这种常态化的娱乐。我必须得搞点事情,通过明确的计划来缓解自己长时间娱乐后的负罪感,通过完成目标后的成就感和优越感来让自己变得自信。\n具体的说,首先是作息。对于作息时间,我最近的状态有点随缘。想早点睡,就可以立马熄灯睡觉;想再看会儿手机也能到1点多才睡,大多数时候是12点左右睡觉。对于想要改变,但无法掌控自己的作息的人,“随缘”的状态可太难了。这种状态得益于我先前很长时间的经验积累和思想建设。所以,我现在只需要通过具体的计划,轻轻推自己一把,就能让作息回到理想的状态。我的想法是:下班之后,回到宿舍的第一件事情是洗澡。洗完澡褪去一天工作的疲惫后,睡前的时间才能很好的利用。那么,睡前该干些什么呢?\n我觉得,我应该思考是睡前最适合做什么。适应身体的规律,才能持之以恒。思考不出来,就通过搜索,借鉴有经验的人和专家的建议。知乎上看了一些回答,有身体拉伸、阅读、暗记、反思、规划等。除了第一点和第三点,其余之前都有过经验。身体拉伸确实可以考虑,所以有益于健康的事情,我都不怕麻烦。第三点说的是,做一些不需要逻辑思考的记忆,比如英语单词。关于英语学习,我得单独好好思考一下。\n还有就是早上什么时间起床的问题,是否需要给起床后,上班前的时间安排固定的习惯?先待定吧。\n其次是饮食。昆明这边的饮食还是偏辣的,这样我就免不了要吃辣。不管“专家、公知们”怎么吹吃辣的好处,吃辣对我就是没有好处,对皮肤,头发,肠胃都不好。我能够做的就是尽量避免吃辣,吃的清淡。之前吃了很多天三明治,有一天晚上实在嘴馋了,点了烧烤。点之前很期待,吃的时候很过瘾,吃完就很难受。在吃方面,我已经不止一次有类似的经历了。食多无味,但食前只管满足当时的欲望,哪里会想都食后的事情。所以怎么办呢?克制呀克制。食色性也,要用我们的大脑来控制这两种人类本能的欲望。克制不是压抑,克制是在不该的时候就不要作。\n减肥健身这件事,从到昆明开始就在我脑子里徘徊,但到现在还是原地踏步。一直想去滇池边跑步,但缺少一起的伙伴,自己也没有足够的动力,计划一直搁浅。路上总是看到游泳健身的宣传,想着要不干脆找个附近的健身房,用金钱来督促自己。思量再三,我决定先去公司附近找找适合跑步的地方,找不到就去滇池边,去个几次如果觉得实在是不方便,就到健身房办卡吧。如果我能将辛苦自己变成感动自己,那也不错,哈哈。\n关于写日记。很多人都有写日记的情结,我也不例外。之前最长坚持过三个多月,写到后面越发怀疑写日记的意义,感觉是在浪费时间。原因是考研复习那段时间生活很单调,记录的内容便也单调。单调的内容写多了便会怀疑。我想着,今时不同往日,工作后的生活要丰富很多,我也有了先前的经验,决定重拾写日记。关于为什么要写日记,我之前写过一片文章《关于写日志》,所以这件事的意义是毋庸置疑的。\n思绪基本理清了,总结一下接下来的主要方向:\n上班: 按照计划完成任务。完成任务间隙,梳理v8项目代码,最后写一份信息管理代码分析文档。 下班: 找到合适的地点跑步锻炼。 饮食: 避免辛辣刺激。 睡前: 日记总结反思今天,规划明天(找到一种可以看树状结构的记录方式)。拉伸,阅读,背单词等再细化。 睡觉: 11点左右熄灯睡觉。 其他: 其他先待定,循序渐进,动态调整。 ","date":"2021-06-27","permalink":"https://kinneyzhang.github.io/talk/talk-recently-210627/","summary":"在昆明工作了快一个月了。工作前期虽然困难了点,但最近渐入佳境,也有了些可以自由支配的时间。关于这些时间如何合理使用,一直没能好好的思考。被惯性推着走,我的选择是","title":"谈谈最近「210627」"},{"content":"2019接近尾声,这一年充满了选择与变化,虽然过程曲折但还算充实。我也慢慢的朝着自己想要的状态去走,有些慢,但没偏离轨迹。\n回顾2019,顺利毕业,完整读了6本书,听了许多播客,搭建了自己的博客网站,尝试hack emacs,养成了一些好的习惯。最开心的是更加的了解自己,知道如何和自己相处。\n2年前买的kindle,放在宿舍吃灰了很久,寒假心血来潮的重新拿起便重拾了阅读这件事情。\n从《月亮与六便士》开始,我开始认清一个道理:追随自己的内心,月亮或六便士,无论选择哪个,只要是心中所愿,便无谓对错。我想真正理解并且有勇气去践行这个道理,才算读懂了毛姆笔下的斯特里克兰,才算理解他那些”荒谬”的行为背后的逻辑。\n《杀死一只知更鸟》,太爱这部小说了!无论是前半段斯库特和杰姆孩童生活的描写,还是后半段关于审判与正义的冲突,都相当的精彩。阿迪克斯的父亲形象也在家庭生活与坚持司法公正中得以刻画。\n《奇风岁月》涵盖的主题更多,涉及亲情、友情、死亡、写作、勇气、正义、悬疑、种族歧视….这样一本美国学生必读的经典就是一部生活的百科全书,每个人都能从中找到共鸣,其中有许多情节打动了我。\n《挪威的森林》自然不用多说,村上春树最畅销的小说,写少男少女的爱情。只是我没有太多的感触,可能是经历得不够。剩下两部是实用类的,《子弹笔记》和《如何有效阅读一本书》。目前在读 david allen 的《搞定i:无压生活的艺术》,也就是我们所熟知的gtd时间管理系统的原著,已读70%。《麦田里的守望者》已读40%。\n2019,听《大内密探》、《日谈公园》、《软件那些事儿》、《故事fm》、《黑水公园》、《文化土豆》、《晓说》等播客,让我看到了更广阔的世界和多样的人生。\n2019,购买了vps和域名,学着搭建自己的博客网站,经历了从动态网站到静态网站,从markdown到org mode。博客的搭建和浏览别人的博客让我对计算机这个行业有了更深的认同感。\n2019,继续折腾emacs,并且不满足于折腾。学习了elisp,从一些小函数开始,期待写一个自己的package。\n2019,养成了两个好习惯,记账,写日记。记账用 ledger 这个命令行工具,对自己每一笔开销做到心中有数。写日记用emacs的 org-journal package,常记录常反思。更重要的是认识到,在gtd的系统中,一个习惯的养成并不困难。\n2019,跑步断断续续,自己和自己较量也别有一番乐趣。最后认识到跑步机就能解决所有的问题。发际线变高,但问题不大。\n2019,京东动画纵火事件,让不看动漫我,开始关注到这家动画公司。看了《凉宫春日的忧郁》、《轻音少女》、《冰果》等,这种从未有过的体验,给生活增添了许多的乐趣。凉宫中的神曲《god knows》百听不厌,呆唯的形象在脑海中挥之不去。我也第一次的对声优这个行业有了一点了解。\n2019,最最重要的是,更了解自己。懈怠的时候知道为什么会懈怠,积极的时候知道为什么会积极,状态不好时知道如何调整状态。不苛责自己,也不放纵自己。意识到,人一天的精力是有限的,精力不足的时候无需咬牙坚持,精力充沛的时候做最重要的事情。所谓的坚持,不是给自己打鸡血,不是赌咒发誓,而是和自己的身体与精神的博弈。生活中每一个悬而未决和待做的事情都会消耗精力,将这些事情交给一个可靠的系统,自己要做的就是按照逻辑来行动。\n2019有一些不完美的地方,但也正是这些不完美,让我更加期待2020。\n2020,计划读完《搞定》,然后hack org agenda 2.0,充分践行;学完驾照;跑步;学习专业上的内容;考研复习;hack emacs;多读几本书;每周总结,每月总结……\n每个人的人生都有自己的节奏,有的人走的快,有的人走的慢;有的人年少功成,有的人厚积薄发。不用去比较,也无需理会世俗的观念,只要坚定和自信迈出的每一步就好!\n","date":"2019-12-31","permalink":"https://kinneyzhang.github.io/talk/at-the-end-of-2019/","summary":"2019接近尾声,这一年充满了选择与变化,虽然过程曲折但还算充实。我也慢慢的朝着自己想要的状态去走,有些慢,但没偏离轨迹。 回顾2019,顺利毕业,完整读了6本书","title":"去你的2019,来我的2020"},] [{"content":" index \u0026gt; 计算机科学 \u0026gt; emacs\nelisp正则 https://www.emacswiki.org/emacs/regularexpression ","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/gknows/emacs/","summary":"INDEX \u0026gt; 计算机科学 \u0026gt; Emacs Elisp正则 https://www.emacswiki.org/emacs/RegularExpression","title":"emacs"},{"content":" index \u0026gt; 计算机科学 \u0026gt; git\ngit branch 查看分支 git branch \u0026lt;分支名\u0026gt; 创建分支 git merge \u0026lt;分支名:合并到另一个\u0026gt; 合并分支 git branch -d \u0026lt;分支名\u0026gt; 删除分支 git checkout \u0026lt;分支名\u0026gt; 切换分支 ","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/gknows/git/","summary":"INDEX \u0026gt; 计算机科学 \u0026gt; Git git branch 查看分支 git branch \u0026lt;分支名\u0026gt; 创建分支 git merge \u0026lt;分支名:合并到另一个\u0026gt; 合并分支 git branch -D \u0026lt;分支名\u0026gt; 删除分支 git checkout \u0026lt;","title":"git"},{"content":" index \u0026gt; 生活消费 \u0026gt; 个人成长 \u0026gt; gtd\n自己的gtd系统设计 (presonal prefrence)\n不引入复杂概念,取消优先级等,慎用 schedule,deadline等概念 todo 中每次不超过2个任务,给心理减负 定义着重关注的项目,其余放到默认项目中 头脑风暴\n以项目为基础,以场景为依赖的个人gtd系统 一个好的功能强大的系统的设计一定是复杂的,但使用起来一定要简单。尽量减少概念,不同的概念尽量统一起来。 project based,三要素区分 基本要素:场景,地点,工具 不同要素之间有依赖关系及默认值。 通过这种依赖来屏蔽多要素的思考。 场景(想让自己处于什么样的状态中,单选): 工作:做工作相关的内容 学习:自我学习和提升 奖励:完成目标后给予自己的奖励,是精心设计过的事情。 工具(手上有哪些工具,可以干些什么,多选) 电脑 手机 kindle 相机 无 地点(在什么地方,可以/应该做些什么) 公司 住处 户外 时间(在什么时间段或特定时间要做什么) 某一年内 某一月内 某一周内 某一天内 某个时间阶段:上午,下午,晚上 某个时间段:几点到几点 某个时间点:几点几分 是否有截止日期,截止日期是什么时间(别滥用截止日期) 状态 待办 完成 委托 bujo提供了一个在不同时间周期间任务迁移的思路:年视图,月视图,周视图,天视图。 关注点:每天只需要关注天视图和周视图。 para提供了一个领域大局观的视角和把笔记融入系统的思路。 每个子任务定义都要有明确的含义。 ","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/gknows/gtd/","summary":"INDEX \u0026gt; 生活消费 \u0026gt; 个人成长 \u0026gt; GTD 自己的GTD系统设计 (presonal prefrence) 不引入复杂概念,取消优先级等,慎用 schedule,deadline等概念 TODO 中每次不超过2个任务,给心理减负 定","title":"gtd"},{"content":" 快捷访问\u0026gt; 习惯培养 / 费曼学习法\n计算机科学 (0) 汇编语言 (0) 数据结构与算法 (20) 操作系统 (0) 数据库 (0) sqlite (4) linux (16) emacs (2) python (0) java (2) git (5) 生活消费 (0) 出行与旅游 (0) 经验常识 (14) 个人成长 (0) gtd (43) 习惯培养 (11) 课题研究 (0) 博客撰写 (0) 书籍阅读 (1) 费曼学习法 (176) ","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/gknows/index/","summary":"快捷访问\u0026gt; 习惯培养 / 费曼学习法 计算机科学 (0) 汇编语言 (0) 数据结构与算法 (20) 操作系统 (0) 数据库 (0) sqlite (4) Linux (16) Emacs (2) Python (0) Java (2) Git (5) 生活消费 (0) 出行与旅游 (0) 经验常识 (14) 个人成长 (0) GTD","title":"index"},{"content":" index \u0026gt; 计算机科学 \u0026gt; java\njava8 集合stream https://www.runoob.com/java/java8-streams.html ","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/gknows/java/","summary":"INDEX \u0026gt; 计算机科学 \u0026gt; Java Java8 集合Stream https://www.runoob.com/java/java8-streams.html","title":"java"},{"content":" index \u0026gt; 计算机科学 \u0026gt; linux\nwindows免输入密码同步远程主机 rsync: 数据(远程)同步工具(cwrsync) plink: putty 的命令行连接工具 cygnative: 使用 cygwin 程序可以通过标准输入输出与本地win32程序通信。download 传输win本地数据到远程主机:\nrsync -av -e \u0026quot;cygnative plink -ssh -p \u0026lt;port\u0026gt; -pw \u0026lt;passwd\u0026gt;\u0026quot; ./public/ root@119.28.186.136:/var/www/hugoblog/ putty download page putty is a free implementation of ssh and telnet for windows and unix platforms. docs page this manual documents putty, and its companion utilities pscp, psftp, plink, pageant and puttygen. grep, sed, awk ","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/gknows/linux/","summary":"INDEX \u0026gt; 计算机科学 \u0026gt; Linux Windows免输入密码同步远程主机 rsync: 数据(远程)同步工具(cwrsync) plink: PuTTY 的命令行连接工具 cygnative: 使用 cygwin 程序可以通过标准输入输出与本地win3","title":"linux"},{"content":" index \u0026gt; 计算机科学 \u0026gt; python\n","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/gknows/python/","summary":"INDEX \u0026gt; 计算机科学 \u0026gt; Python","title":"python"},{"content":" index \u0026gt; 计算机科学 \u0026gt; 数据库 \u0026gt; sqlite\n常用命令 .tables .mode list|column|insert|line|tabs|tcl|csv|html 改变输出格式 .exit ","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/gknows/sqlite/","summary":"INDEX \u0026gt; 计算机科学 \u0026gt; 数据库 \u0026gt; sqlite 常用命令 .tables .mode list|column|insert|line|tabs|tcl|csv|html 改变输出格式 .exit","title":"sqlite"},{"content":" index \u0026gt; 生活消费 \u0026gt; 个人成长\ngtd | 习惯培养 | 课题研究 | 博客撰写 | 书籍阅读\n","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/gknows/%E4%B8%AA%E4%BA%BA%E6%88%90%E9%95%BF/","summary":"INDEX \u0026gt; 生活消费 \u0026gt; 个人成长 GTD | 习惯培养 | 课题研究 | 博客撰写 | 书籍阅读","title":"个人成长"},{"content":" index \u0026gt; 生活消费 \u0026gt; 个人成长 \u0026gt; 习惯培养\n晚上按时睡觉(11:00) 10月\n11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 23:30 晚上按时睡觉(10:30) 9月\n01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 √ 23:10 √ 23:38 √ √ √ √ 23:50 00:30 23:50 23:00 √ √ 23:30 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 23:40 √ √ 23:00 23:15 00:15 23:30 √ 22:50 00:10 22:50 23:40 00:10 √ 00:10 22:30 左右: 12/30 40% 23:00 之前: 16/30 53% 00:00 之前: 25/30 83% 00:00 之后: 05/30 16% 结论: 22:30 调整为 23:00 更合理。\n","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/gknows/%E4%B9%A0%E6%83%AF%E5%9F%B9%E5%85%BB/","summary":"INDEX \u0026gt; 生活消费 \u0026gt; 个人成长 \u0026gt; 习惯培养 晚上按时睡觉(11:00) 10月 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 23:30 晚上按时睡觉(10:30) 9月 01 02 03 04 05 06 07 08 09 10 11","title":"习惯培养"},{"content":" index \u0026gt; 生活消费 \u0026gt; 个人成长 \u0026gt; 书籍阅读\n费曼学习法\n费曼学习法(2022年9月) 66% 学习的本质 第一章 掌握一门知识有多难 第二章 何为费曼学习法 确立一个学习对象 第三章 我们为什么学习 第四章 聚焦目标 第五章 规划:和目标建立“强联系” 第六章 费曼技巧:目标原则 理解我们要学习的知识 第七章 归类和对比知识的来源 第八章 形成一张思维和流程导图 第九章 阅读与记忆的原则 第十章 第一次复述 第十一章 费曼技巧:系统化原则 输出是最强大的学习力 第十二章 以教代学 第十三章 用输出倒逼输入 第十四章 第二次复述 第十五章 费曼技巧:输出原则 回顾和反思 第十六章 怀疑和探索让我们更聪明 第十七章 寻找反证 第十八章 “内存留存率”决定了我们的学习效能 第十九章 费曼技巧:回顾原则 简化和吸收 第二十章 好东西太多,也会消化不良 第二十一章 纵向拓展和精进 第二十二章 深度挖掘,实现知识的内容 第二十三章 第三次复述 第二十四章 费曼技巧:简化原则 ","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/gknows/%E4%B9%A6%E7%B1%8D%E9%98%85%E8%AF%BB/","summary":"INDEX \u0026gt; 生活消费 \u0026gt; 个人成长 \u0026gt; 书籍阅读 费曼学习法 费曼学习法(2022年9月) 66% 学习的本质 第一章 掌握一门知识有多难 第二章 何为费曼学习法 确立一个学习对象 第三章 我们为什么学","title":"书籍阅读"},{"content":" index \u0026gt; 生活消费 \u0026gt; 出行与旅游\n","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/gknows/%E5%87%BA%E8%A1%8C%E4%B8%8E%E6%97%85%E6%B8%B8/","summary":"INDEX \u0026gt; 生活消费 \u0026gt; 出行与旅游","title":"出行与旅游"},{"content":" index \u0026gt; 生活消费 \u0026gt; 个人成长 \u0026gt; 博客撰写\n","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/gknows/%E5%8D%9A%E5%AE%A2%E6%92%B0%E5%86%99/","summary":"INDEX \u0026gt; 生活消费 \u0026gt; 个人成长 \u0026gt; 博客撰写","title":"博客撰写"},{"content":" index \u0026gt; 计算机科学 \u0026gt; 操作系统\n","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/gknows/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/","summary":"INDEX \u0026gt; 计算机科学 \u0026gt; 操作系统","title":"操作系统"},{"content":" index \u0026gt; 计算机科学 \u0026gt; 数据库\nsqlite\n","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/gknows/%E6%95%B0%E6%8D%AE%E5%BA%93/","summary":"INDEX \u0026gt; 计算机科学 \u0026gt; 数据库 sqlite","title":"数据库"},{"content":" index \u0026gt; 计算机科学 \u0026gt; 数据结构与算法\n基础数据结构 array 数组的本质 我们需要关心哪些重点的知识点以及应用\n数组是计算机编程语言中一种基础的数据结构。创建数组会申请一整块固定大小的计算机内存。内存空间的连续性给数组带来了常量级访问数组元素的时间复杂度。当我们知道了数组的第一个元素的地址,就可以通过简单的地址偏移的加减运算,取出任意位置的元素的地址和值,这就是所谓的“随机访问”。\n而在编程语言的使用中,我们不需要关心具体内存地址,通过“数组下标”就可以访问到对应位置的元素。而计算机会经过下面的计算:\n数组特定下标的元素值 = 取地址中的值(数组首地址 + 下标 * 单个元素所占内存长度)\n取地址中的值: “根据地址取出其中存放的值”是计算机底层的一个基础操作,这里不多讨论。 数组首地址: 数组的首地址一般就是我们申请数组内存后赋值给的那个变量所代表的地址。 下标: 从这个公式就很容易理解为什么数组的下标是从0开始的。因为下标代表的是偏移单位。0表示没有地址偏移,即第一个元素。我们当然也可以设计出用下标1代表第一个元素的数组,但这样每次计算都会多出一个\u0026quot;下标-1\u0026quot;的操作,降低了计算效率。由于数组大小是固定是,因此下标超过 size-1 会访问到数组内存空间以外的地址,这通常是不允许的,会抛出“数组访问越界错误”。 单个元素所占内存长度: 以上公式成立的一个前提条件是:数组中的元素所占内存大小是相同的。因此,一般数组中的所有元素都要求是相同类型。这个类型也就是我们所说的数组类型。 vector list ","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/gknows/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/","summary":"INDEX \u0026gt; 计算机科学 \u0026gt; 数据结构与算法 基础数据结构 Array 数组的本质 我们需要关心哪些重点的知识点以及应用 数组是计算机编程语言中一种基础的数据结构。创建数组会申请一整块固定大小","title":"数据结构与算法"},{"content":" index \u0026gt; 计算机科学 \u0026gt; 汇编语言\n","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/gknows/%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80/","summary":"INDEX \u0026gt; 计算机科学 \u0026gt; 汇编语言","title":"汇编语言"},{"content":" index \u0026gt; 生活消费\n出行与旅游 | 经验常识 | 个人成长\n","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/gknows/%E7%94%9F%E6%B4%BB%E6%B6%88%E8%B4%B9/","summary":"INDEX \u0026gt; 生活消费 出行与旅游 | 经验常识 | 个人成长","title":"生活消费"},{"content":" index \u0026gt; 生活消费 \u0026gt; 经验常识\n感冒 类型 风寒感冒 风热感冒 共同症状 发热怕冷,鼻塞流涕,头身疼痛 发热怕冷,鼻塞流涕,头身疼痛 寒热 怕冷为主,发热次之 发热为主,怕冷次之 汗 无汗 有汗 鼻涕 清涕 浊涕 咽喉 咽喉发痒 咽喉发痛 咳嗽 咳嗽声重,气急,稀薄色白 咳嗽频繁剧烈,咳痰费力,稠/稠黄 口渴 不口渴 口渴 舌苔 发白 发黄 亚健康 嘴巴周围长痘该如何调理? 少吃辛辣、刺激、油腻的食品,不吃垃圾食品;切忌酗酒;规律三餐时间,饮食注意均衡营养,多吃新鲜的蔬菜水果,多饮水。 可以有效减少因饮食不当造成的嘴周长痘。 每天11点之前一定要睡觉,失眠者要提高睡眠质量;养成早睡早起的好习惯可以有效改善身体亚健康。 ","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/gknows/%E7%BB%8F%E9%AA%8C%E5%B8%B8%E8%AF%86/","summary":"INDEX \u0026gt; 生活消费 \u0026gt; 经验常识 感冒 类型 风寒感冒 风热感冒 共同症状 发热怕冷,鼻塞流涕,头身疼痛 发热怕冷,鼻塞流涕,头身疼痛 寒热 怕冷为主,发热次之 发热为主,怕冷次之 汗 无汗 有汗","title":"经验常识"},{"content":" index \u0026gt; 计算机科学\n汇编语言 | 数据结构与算法 | 操作系统 | 数据库 | linux | emacs | python | java | git\n","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/gknows/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6/","summary":"INDEX \u0026gt; 计算机科学 汇编语言 | 数据结构与算法 | 操作系统 | 数据库 | Linux | Emacs | Python | Java | Git","title":"计算机科学"},{"content":" index \u0026gt; 生活消费 \u0026gt; 个人成长 \u0026gt; 课题研究\n","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/gknows/%E8%AF%BE%E9%A2%98%E7%A0%94%E7%A9%B6/","summary":"INDEX \u0026gt; 生活消费 \u0026gt; 个人成长 \u0026gt; 课题研究","title":"课题研究"},{"content":" index \u0026gt; 生活消费 \u0026gt; 个人成长 \u0026gt; 书籍阅读 \u0026gt; 费曼学习法\nstep one: 学习的本质 掌握一门知识有多难 传统的学习方式的问题:以输入为主,教条主义,标准化应用。 学习本质是输出,而不是输入。 掌握知识的目的是什么?与真实的世界建立联系,运用知识,理解我们身边发生的一切,而不是把所有的意义都寄托在未来。 费曼学习法为我们提供的三种能力:远见、穿透力、智慧 何为费曼学习法 简单高效的思维模式 好的思维需要正反馈:不要独自专研和学习,多与外界互动,寻求反馈可以促进知识和能力在学习中的自增强。 输出加快思考的成熟:马太效应。一次成功的输出同时也增强输入的能力,进而又促进下一次的输出,这样不断的壮大知识体系和应用能力,加快思考的成熟。 费曼学习法让思考可以量化,任何问题的思考量化体现在六个方面 方向-\u0026gt;锁定思考的方向:重点解决的问题和环节 归纳-\u0026gt;确定思考的主要逻辑:基本的立场、三观和逻辑。可以理解为我们思考问题背后的前提条件和隐含的上下文。 验证-\u0026gt;验证思考的效果:通过输出来验证学习和思考的效果。看自己能否做到向别人阐述自己的见解和分析。(自我向) 反馈-\u0026gt;反馈正确或错误:根据听众的反馈,强化正确的,修改或删除错误的。(他人向) 简化-\u0026gt;把复杂的思考过程简单化:提炼思考要点,用三言两语总结思考的目标、逻辑、结果。 吸收-\u0026gt;消化思考的成果:转化为可以应用的内容,用来解决工作、学习、生活中的实际问题。 费曼学习法五个步骤 第一步:目标 -\u0026gt; 确立学习对象 -\u0026gt; 产生专注力 第二步:理解 -\u0026gt; 理解要学习的知识 -\u0026gt; 系统化的存优去劣 第三步:输出 -\u0026gt; 想别人输出学到的知识 -\u0026gt; 以教代学 第四步:回顾 -\u0026gt; 回顾和反思学到的知识 -\u0026gt; 深度分析 第五步:简化 -\u0026gt; 通过简化吸收形成自己的知识体系 -\u0026gt; 内化知识 step two: 确立一个学习对象 我们为什么学习 现实中,人们大部分场景下的学习处于一种无意识的状态。表现为:\n服从式学习:老师/父母让我学什么,就学什么。 工具性学习:就业/培训需要学什么,就学什么。 服从式的被动输入,只收获对知识粗浅的印象,工具性学习局限于功利性的应用部分。这样一直处于输入的状态中,很难艰深的理解知识。\n费曼学习法与众不同的地方在于:通过有意识的主动学习,它是以“输出”为载体的有选择的输入。而实现这一步的前提是重新确立学习的意义。\n如何重新确立学习的意义?\n这就是“学数学有什么用”和“数学有什么用”的区别,人们容易困在前者的逻辑里面。\n主动性的学习要改变一个心态:我学这些知识是为了什么 -\u0026gt; 这些本身知识可以发挥什么作用,我从这些价值中确定哪个作为我的学习目标。所以需要充分理解知识本身,包括它尚未开发的价值。\n追求四个方面的进步\n目标明确的学习可以改变一个人的思维方式。\n开放性思维:充分了解一门知识,使我们能够接受新的观点,拓展视野 批判性思维:主动独立学习,可以形成批判思考的习惯 逻辑性思维:目标专注,反复思考一个问题可以锻炼思维的逻辑性 清晰凝练的表达力:以教代学的输出可以锻炼语言组织和表达能力 聚焦目标 学习不能倡导以过度的消磨意志力为荣,学习应该是轻松的,轻松到几个易于理解的步骤便能收获非常大的成效。事实上,许多精英人士,他们不是天才,确是学习的高手。\n不要妄想长期对同一个目标保持专注,目标应该是动态的。 一旦确立了目标,便要抓住学习的黄金时间,将全部的精力聚焦,心无旁骛学通、学精和化为己有。 聚焦目标、制定目标的好处是 让你的思维更加清晰。看到这个目标更多的可能性,对要学习的内容有更深的理解,并形成具体的思路。 让你的行动更有针对性。对目标的专注力越强,行动就越有针对性。不论学习概念还是工作技能,效率都会大大增强。 一个很有启发的例子: 非常想读一本书时,就立刻安排时间,不要等到空闲的时候再说。因为再过一段时间,对这本书的兴趣就不大了。因此,当感觉来了就快速的通读一遍,找到感兴趣的内容,锁定这些自己有兴趣或需要了解的部分,集中精力去阅读它们。假如时间不够,就把这些知识标注下来,制定一个阅读计划。等到约定的时间一到,再以准备充分的状态迎接这场\u0026quot;战斗\u0026quot;,非把它吃透了不可。\n如何找到正确的方向\n问自己一些关键性的问题,比如“对我而言,最重要的那件事情是什么?”。有时候我们凭直觉就能给出一个答案,有的时候需要提出一系列的问题来引导我们思考。确定宏观的目标后,我们还需要思考为了实现这个目标,当下应该做什么,这么做。并且把这个目标当作“每天都要去做”的最重要的事情。\n当自己无法理清最重要的方向的时候,该如何寻找,提出什么样的问题呢?想想自己的兴趣。兴趣是一切高质量学习的驱动力,将目标和兴趣联系起来,更容易沉下心,有持续的动力去实现这些目标。那么,如何从兴趣中找到自己的目标呢?这时,需要多做思考调研,深入到对感兴趣的领域内容的分析,做一些初步的尝试,继而确定学习的对象。\n规划:和目标建立“强联系” 痛点:拘泥于研究计划的细节,却忽视了目标的可行性;目标订的高大上,让人热血沸腾,具体的规划却一塌糊涂,和目标的关联不强;计划的挺好,在执行的过程中逐渐迷失,无法得到预期的结果\u0026hellip;\n为了避免遇到这些痛点,需要我们在制定规划时先对目标和计划做一次全面的剖析:挖掘出你和目标之间的“强联系”。\n两个步骤:\n论证学习这门知识/做这件事的必要性:我真的需要它吗?为它投入时间和金钱是否值得?我是否还要再想一想? 确认规划与目标的实际联系:我的计划和目标的匹配度是多少?对我而言这个计划是否可行?有没有更高效的方法? 你的目标可能是错误的\n错误的目标意味着按照这个目标去努力,并不能得到你想要的结果,白白浪费了许多时间和精力。以我的经验,错误的目标往往呈现出偏执的、功利性的特征。\n那么如何判断目标是否正确?smart原则。\ns(specific) - 明确和具体的:目标必须清晰和可以形容。 m(measurable) - 可以衡量/量化的:目标必须量化和能够评估。 a(achievable) - 自身能力可以达到的:目标必须在能力范围内。 r(rewarding) - 能产生满足感/成就感的:目标必须有积极的意义。 t(time-bound) - 有时间限制的:目标必须有实现的期限。 找到一个更好的方向\n意识到了目标可能是错误的,就需要找到一个更好的目标的方向。在按照这方向的学习的过程中,如果总是撞上现实的墙,就要反思目标是否正确。要时刻保证自己有调整方向,甚至掉转船头的意识和勇气。\n\u0026ldquo;活到老,学到老\u0026quot;这句话不是教人无视学习的挫折,而是让你在自己的舒适区学习,学习的舒适区有两个标准:\n一个正确而适合自己的学习方向,它符合自身的兴趣。 一个在自己能力范围内的合理的目标。它符合自身的能力。 规划一条高效能学习之路\n一个高效的学习路径,一定要为三件事情预留出足够的时间。这些时间的付出绝对是值得的。\n留出锁定最重要目标的时间:将主要的精力聚焦到最重要的目标上面。 留出做正确规划的时间:不要还没准备好就匆匆开始学习。 留出调整目标和规划的时间:根据反馈随时修正目标,改善或改变学习计划。 费曼技巧:目标原则 目标本身,要符合这五个原则才是一个值得投入精力为之努力的目标。\n目标的全面性原则\n要有全局和整体的观念。比如学习一门知识是某个行业所必须的,不是为了学而学。\n目标的重点性原则\n制定目标要有侧重点,不要想着面面俱到,将有限的精力用于最关键的知识点上。也可以针对自己某一方面的不足,通过学习切中要害。\n目标的挑战性原则\n目标要有一定的挑战和难度,可以激发求知欲和学习的动力。学习要坚持高标准、严要求,倾尽全力才能掌握和理解一门知识,在学习的过程中也可以训练创新能力。\n目标的可行性原则\n既要有挑战,又不能超出我们的能力范围。要经过一定的努力才能实现,而不是倾尽全力只能学得一点皮毛。\n目标的可调整性原则\n根据自己以及外界环境的变化,要灵活的调整目标以及提前准备 plan b。\nstep three: 理解我们要学习的知识 归类和对比知识的来源 将知识有逻辑的系统化\n从自己的立场、角度和思维方式出发,去理解知识,然后将它们纳入到一个宏观的知识体系中,通过的知识的互相印证和科学对比,对既有的知识体系形成补充。通俗的讲,就是要将抽象复杂的概念用自己能够理解的语言去描述。\n第一、要明白我们学习是为了什么:学习要有一种非功利、非偏执、非倾向的目的,单纯是为了理解和掌握知识。抱着这样的心态,更易事半功倍。(心理要素)\n第二、要拥有一个足够宽阔的视野:学习的时候要把清空,以一个孩子的视角去看待问题,可以看到这个世界更多的可能性。因为成年人的阅历、工作生活的经验和世界观往往会给我们接受新知识带来局限性。(心理要素)\n第三、要建立最可能客观科学的逻辑:系统化就像建房子之前画的图纸。学习时,按照科学的逻辑,将不同来源的知识各归其位,有利于于我们对信息进行对比和筛选。避免良莠不分或消化不良。\n筛选和留下最可靠的知识\n学习最重要的不是找到那些价值千金的知识,而是通过对知识的筛选和吸收建立自己的思维框架。\n知识是海量的,我们不可能也没必要全部吸收。\n有针对性的提取书中某些重要知识点,重点阅读和理解。 根据书籍的目录,标题等标识梳理出一个框架,再按图索骥寻找对应的知识点。 根据自己的短板和需求去收集信息和筛选知识。 通过筛选保留可靠和重要的重视。 分辨假知识\n屏蔽来源不确定的知识 小心对待差异化的知识 用对比的方式挑选和分辨知识 形成一张思维和流程导图 横向拓展:让知识\u0026quot;可视化\u0026rdquo;\n文字语言“碎片化”的特点决定了它不利于高效的学习。 视觉是最高效的感官通道,让知识可视化让我们以较低的成本高效的理解和学习。\n思维导图的第一个作用是:把我们的注意力指向最关键的信息,这有助于把握本质和记忆。\n方法:比如,你可以先对自己要读的一本书画一张概念图,把主题、用途等要素图形化,突出理论和观点;再画一张结构图,目录、大章及概念之间的关系,把书的不同板块分类,形成简明的层次结构,有利于针对性的阅读;还可以画一张因果图,列出书中的观点的前后因果,论据与推理逻辑之间的关系,这能为你提供一个独立思考的窗口。\n知识场景的可视化:对知识产生画面感,在画面中,记住关键的要素。\n知识关系的可视化:知识关系的可视化揭示了不同信息、知识点之间的关联性,从碎片化中建立完整的系统。从整体的角度理解和掌握知识。\n学习过程的可视化:通过动图、视频了解知识的原理比文字描述更容易理解。\n画出一个学习流程\n思维导图的第二个作用的:可以深度参与我们学习时的“认知加工”的过程,使理解知识更轻松和可控。轻松在于节省思考的精力,展示了知识的框架和主要知识点;可控在于能把握学习的时间,知道当下自己处于哪个环节,何时到达目的。\n方法:画出简单清晰的步骤图。第一步做什么(目的和方法),第二步做什么(目的和方法),依次类推,一般不超过五步。没完成一步,在后面打一个 \u0026ldquo;√\u0026rdquo;。\n第一步:短时记忆:在费曼学习法中,理解知识的第一步是构建一个系统。可以是主题系统,也可以是分析系统。列出这些主题或需要分析的问题,在大脑中会形成短时记忆,它们会产生聚焦作用,在阅读时指明了一个方向。\n第二步:心理表象:心理表象对知识进行视觉表达,即知识以形象化的方式在我们大脑中形成抽象的概念。图像化的记忆有利于在大脑中植入心理表象,这比存粹记住一段文字有效生动。\n第三步:双重编码:大脑中存在两种功能独立又相互联系的信息加工系统:一种以文字语言为基础,另一种以表象语言(图像)为基础。对信息同时进行这两种加工,等于实施了双重编码,记忆更牢靠,理解也更深刻。因此我们需要把文字形象和图片(视频)形象结合起来理解所学的知识。\n第四步:长时记忆:进行双重编码的知识和信息,才会被大脑转变为长时记忆。\n思维和流程到处有助于帮助我们解决以下5个问题:\n快速的获取自己需要的信息。 掌握理解和分析知识的方法。 建立自己思考问题的框架。 形成高质量的学习笔记。 为知识的输出做好准备。 阅读与记忆的原则 一个有启发的具体实践:\n阅读一本书或了解不熟悉的材料时,准备笔记本、笔,先将书用不超过20分钟的时间大体翻阅一遍,然后在笔记本中写下一些简短的概要:\n书或资料的主题 —— 是讲什么的,有何主旨\n书或资料的作者 —— 作者的资历,有何专长\n书或资料的结构 —— 分类和不同板块的分主题\n随后,一边阅读,一边结合这三个内容丰富框架,填充主题思想,提炼知识要点、理论基础、论证过程,并记下自己的疑问。一定要用笔记下来,而不是让一个又一个问号在脑海中短暂的浮现。\n在学习中建立自己的思维框架,对知识进行系统性的理解和吸收,是高效阅读与记忆的一个基本原则。 这个原则分为两个小原则:\n第一,快速的获取有益信息:初期大量阅读的积累有助于形成筛选有效信息的标准。随着阅读量的增加,我们筛选有益信息的能力越来越强,速度越来越快。\n第二,学习发现问题和分析问题的方法:建立自己的思维框架后,便拥有了一个发现问题和分析问题的成熟的工具。在学习中发现问题,将问题提取出来做系统性的理解。通过独立的思维框架,把问题延伸到自己身上,结合自身情况,对问题抽丝剥茧,形成一套自己的解决办法。\n第一次复述 复述一遍的好处:\n建立长时记忆 加深对知识的理解 更加主动的学习 对知识展开联想 得到关于问题的反馈 复述的三个阶段:\n第一个阶段 凭印象复述:描绘印象最深刻的内容,不用顾虑是否准确,再根据这些内容进行二次学习和比对。 第二个阶段 复述中提出问题:凭印象复述后,紧接着把这些内容与自己的经验结合,对比、怀疑、分析,对理解的点提出一系列为什么。 第三个阶段 复述中加入自己的观点:通过对已学内容的复述,一边说一边对比检查,把自己的观点加进去,实现新知识与自身已有知识系统的衔接。最终在新知识的基础上形成自己的观点。 费曼技巧:系统化原则 构建知识系统就像修建一条四通八达的交通网络,每一条路由它出发点,也有它的终点,路和路有交汇点,也有信息处理中枢,互不重叠,知识就是这张网络中的汽车运输的养料,它们运转有序,去往该去的地方。\n系统思考从本质上讲,就是从事物/知识的的互动关系入手,而非从事物/知识本身入手。系统化的原则是: 归纳、筛选和分析。\n归纳:确认可靠来源,对知识进行归类 筛选:找到需要的知识,排除出假知识 分析:确立一个逻辑,形成思维导图 人类的思维方式有四种:水平思维、发散思维、收敛思维 和 系统思维。前三种可以看作是系统思维的工具。\n水平-归类对比: 从多方面、多角度看待同一事情,在对比中验证。 发散-思维导图: 在不同知识之间建立联系,发散、联想、分析看看有什么新的东西出现。思维导图是很有效的一种方式。 收敛-知识结构: 将零散的知识点信息聚集起来。把知识结构化、系统化的过程,可以简化学习的知识,同时改善或构建一个属于你自己的知识框架。 step four: 输出是最强大的学习力 以教代学 \u0026ldquo;以教代学\u0026quot;是费曼学习法的核心。假设有一个外行人站在面前,要用对方听得懂的语言把知识解释给他听。经过反馈,再检查自己的学习效果。以教代学的一个重要方法是\u0026quot;类比\u0026rdquo;,将复杂的概念类比成普通人能够理解的例子。\n为什么需要“以教代学”?因为这是让学习的内容留存率最高的方式。下面是不同学习方式的内容留存率:\n听别人讲 5% 自己阅读 10% 视觉+听觉 20% 与别人讨论 50% 将知识用于实践 75% 教授给别人 90% 以教代学最重要的要求是:在解释一个概念时,只需把它总结成一两句话,就能让一个完全不了解甚至没听过这个领域的菜鸟听得懂,还要有很深的印象。你要用最通俗的语言去阐述它,用最普通的词语和最短的句子,同时做到精确无误。这就可以让你在传授这个概念时检查自己是否正常已经学到了全部知识,加深对知识的理解,顺带发现隐藏的问题。\n语言简洁易懂 精准到位,没有歧义 讲出一定的深度 加上自己的理解 阐述知识点的同时,我们也在强化自己对于知识尤其是重点内容的认知,然后促进自己对这些内容的“二次学习”。\n用输出倒逼输入 输出的\u0026quot;记忆学原理\u0026quot;\n输出最直接的好处就是加强了对特定内容的留存率。在记忆学的研究中,记忆不仅是神经活动,还是复杂的心理活动。这一过程的包括:\n识记-编码:通过对信息的感知,在大脑中留下印象。这是记忆的开始,也是记忆的关键部分。有意识记,加强第一印象,对后续的记忆帮助很大。识记时,大脑有个编码的过程,把应该记忆的部分挑选出来。如何提高编码的效率呢?保证自己拥有一个完整的知识系统,对信息进行系统化的程序处理,这样接触的知识进入大脑前会被自动化的归类。能够被归类到内容,有了联系和去处,便容易被编码。 保持-存储:存储信息就是大脑形成神经回路的过程,使神经元的连接愈发紧密并产生定式。对一个知识有了短时记忆后,随后详细了解相关内容,反复阅读和做笔记,大脑就会推动产生神经回路,形成长时记忆。联系越多,记忆就越牢。 再现-检索:当开始输出时,大脑会检索关键信息,再现,二次组合,使长时记忆更加牢靠。比如每次回忆,温习,重述时,会有新的感悟。 回忆-巩固:输出是一次高质量的复习,起到巩固记忆和提炼核心知识的目的。通过有针对性的、反复的输出,长时记忆可以转化为永久记忆。 场景和思维模拟\n在输出知识时设计相应的场景,并逼真的模拟出不同场景中人们对应的思维方式,是我们如临其境,可以强有力的刺激大脑的学习和记忆功能,获得意想不到的效果。\n比如,模拟解说者场景,模拟受询者场景,模拟传授者场景,模拟质疑者场景\u0026hellip; 这很有趣。\n长期记忆的“双重编码”理论:对文字信息的处理以‘意码’为主,即抽象理解,对非文字信息的处理以‘形码’为主,即图像理解。费曼学习法提倡,在双重编码中创造一种模拟环境:把学习对象放到一个应用场景中,把自己放到一个需要表达它、分析它、理解它的实实在在的角色上。\n输出是主动学习\n学习改变命运 × 高质量的主动学习才能改变命运 √ 第二次复述 第一次复述是把自己当作倾听者,第二次复述是进入一个真实的传授知识的场景,向别人甚至多个人阐述你对某项知识的看法。\n利用分组讨论的机会\n分组学习是自主学习的一种高效方式 帮助你设计复述提纲并且准备一些问题 从听者那里获得中肯的评价和异议 为只是注入你的灵魂\n只有在复述的同时,让知识刻上自己的印记,学习才拥有了灵魂,知识也具备了新的活力。\n体现独具特色的语言技巧 结合现实阐述你对知识的理解 表达出你个人的分析和见解 费曼技巧:输出原则 只要是系统的学习和掌握一门知识或技能,就必须为它们找到一个或多个出口,去输出它们,使用它们。经过这个环节的锻炼,你学到的知识才能真正地变成你拥有的一种本领,转化为你的智慧。\n费曼技巧的输出原则是一个递进同时又互相联系的过程:\n场景和思维模拟 语言通俗易懂 简洁的同时具有深度 强化对重点知识的理解 利用分组讨论获得反馈 step five: 回顾和反思 怀疑和探索让我们更聪明 即使经过了一次、二次复述,或对其他人在很多场合进行了阐述,仍然会有许多不理解的内容,这些内容叫“盲维”。消除盲维度的过程,正是我们对知识采取怀疑和深度探索的环节——怀疑那些令自己感到困惑的知识,探索那些仍未搞清楚的知识,而且要主动的回顾和总结,反思和修正。不主动的怀疑和探索,不管读了多少书,背下多少理论,你学到的永远是皮毛。\n重新对比数据和事实\n如果对外输出时遇到了问题或麻烦,第一反应即不应该是否定知识,也不应该是为了维护自己的正确性而竭力辩解。我们需要做到:\n重新检查数据库: 把关于这个理论的所有信息,包括立论、论据、论证逻辑、其他信息等列一个清单,逐一检查。找出遗漏,理解错误,记忆有误或事实不清的部分。 重新验证知识的关联: 找出知识点之间的关联,串联不同的信息,使上下文逻辑严密,这样听者不会因为脱离语境而困惑。在知识和现实之间找到或建造一个坚固的桥梁。 在对比中,如果知识正确,会再一次加强我们对知识的理解。如果发现知识不正确,要谨慎的寻找原因。\n是自身知识的欠缺导致了理解的偏差? 知识存储不足以理解当前的知识,并非知识本身的问题。 是原知识的观点和逻辑存在问题? 知识本身存在问题,需要反思自己筛选知识的环节。 用于修正的策略\n除非准备降低学习难度,调整学习计划,从最基本的学起。否则需要评估自己的理解能力,列出一张“学习清单”,先提高知识存储,并且要注意筛选可靠的知识来源。等具备了较高的知识理解能力和善于寻找正确知识来源时,学习的正反馈才会增加。\n人在学习时的思维同时受到经验和好奇心的影响。经验告诉你这个知识有用,好奇心让你保持怀疑。经验保证下线,好奇心决定下线。我们在学习中应该保持一点不安分的好奇心和怀疑一切定论的意识。在学习中战胜经验的惯性,养成敏锐捕捉一切“争议点”的好习惯。\n发现缺口\n缺口是什么? 1) 较为独特的知识点,包括其他数据没有的数据,未论述到的事实和与众不同的观点。2) 能引发深度思考的观点,可以补充知识盲区的论点。\n比如,在别的地方学习一些知识,始终有迷惑不解之处,在这里却找到了答案。或某个特定的信息触发了我的灵感,或某段解释阐明了相关原理。\n学习时不要盲信知识,同类知识也不要只学一本书、一个人的观点或一种论证的逻辑,应该多方参照,反复验证,始终保持强烈的怀疑精神,才能探得真知。\n回归知识的本质\n费曼曾经说:“我们为何学习呢?知识对我们究竟意味着什么?知识的本质又是什么?解决了这三个问题,我们也就找到了人生的答案。无论我们去学习何种知识,都能把它融入我们的生活场景中,化作属于自己的力量。”\n不懂得知识到底是什么,知识意味着什么,那么学得再多也只是把它们挂在墙上或摆在好看的展台上当成欣赏的玩物。请好好想一想,你有没有把知识当作玩物、把学习视为制造玩物的经历呢?现实中不少人都是这样的。学音乐知识,是为了让别人佩服自己懂音乐,显得有艺术细胞;学收藏知识,是为了向客人炫耀家里的藏品多么有价值;学天文知识,是为了让朋友知道自己对宇宙的历史十分精通;学美术知识,是为了在异性面前展示自己的艺术才能;学投资知识,是为了让人们佩服自己的理财知识。\n知识的本质是人生的进步和成长,是我们与环境的融合并产生新认识的过程。从根本上说,知识是我们对世界的理解,比以此获取的改造世界的能力。\n只有我们意识到知识可以为人生注入进步和成长的因子,可以是我们的学习与思维产生巨大的改变,才能真正爱上学习,并在学习中养成深度思考和辩证分析的好习惯,否则你的学习永远都只能是一种“表面的浏览”和“机械的记忆”。\n寻找反证 寻找反证的过程就是有目的的反思。反思不同于回顾和总结。因为回顾和总结是对学习的结果进行温习与提炼,对学习的效果进行评估,反思则是对学习的质量进行结构,保证自己学到的是正确的知识。\n第一,反思能够帮助我们发现知识本身存在的误区:对知识进行反思,从不同的角度反复推敲,也是在反思我们平时积累的经验和过去的知识体系是否存在误区,然后在学习的过程中弥补这些缺口。\n第二,反思能促进我们在已有知识的基础上产生新的知识:在学以致用中对知识检查,将经得起事实校验的知识运用起来,转化为我们的思维成果,内化为我们的实际能力。在反思时,可以通过联想,将生活中的其他经验、经历和知识相联结,发现它们的关系重新认识和审视自己过去的表现,这就能把自己已有的知识,重新组织,产生新的知识。\n重视否定式证据\n有目的的反思需要我们重视否定式的证据,不仅要了解正面的信息,还要了解负面的信息。主要有以下四个方法:\n相反的数据:一种是带有某种个人倾向性的统计出来的信息,一种是有科学实验和证明的权威数据。 逻辑漏洞:知识本身的逻辑存在问题。 过时的知识:过去正确,但不适用于今天的知识。 相反的权威观点:即使是专家和权威的观点,也不能全盘接受,也要看看其他专家和权威的相反的观点和质疑。认真研究和分析这些质疑是否有力,逻辑是否站得住脚。 当知识卡壳:回到理解不清的地方,找出薄弱环节\n记忆有误:温习知识 理解有误:重点理解 争议是深入学习的切入点\n遇到争议的问题,一般有两种处理方式:\n向上回避争议:忽略或绕过争议,只去理解自己第一时间能处理的问题。这是一种浅学习模式。\n向下解决争议:沉下心来化解争议,从有争议的知识点中获得宝贵的智慧。这是一种深度学习模式。\n争议代表着智慧,也代表突破口,我们要重点关注存在争议的问题和内容。这些内容不仅代表着知识的难点和痛点,同时还起到了衔接其他知识、开启脑洞、激发我们持续性的系统化思考的作用。\n没有最可靠的结论\n别相信任何一种学术结论,知识是动态变化的,一方面我们不断地解构它、理解它,另一方面也通过论证、怀疑和反思更新它的内容,使知识保持常新,尽可能的真实而客观。\n知识-\u0026gt;解构-\u0026gt;论证-\u0026gt;怀疑-\u0026gt;反思-\u0026gt;知识\n和已有的知识建立多角度的类比关系\n科学的角度: 1)逻辑严谨、数据正确、观点合理 2)禁得起最为苛刻的质疑。\n实用的角度: 能够落地。能够将理论通过实践转化为现实的结果,对自己的生活、工作、情感乃至人生都有帮助。哪怕修生养性,也是实用性的一种体现。\n系统的角度: 从系统化的角度把新知识与我们既有的知识体系进行对比,建立内在联系。\n“内存留存率”决定了我们的学习效能 费曼技巧:回顾原则 step five: 简化和吸收 好东西太多,也会消化不良 纵向拓展和精进 深度挖掘,实现知识的内容 第三次复述 费曼技巧:简化原则 那些让我有启发的例子 最近几年来,我对“学习的能力”这个范畴进行了细致的研究和认真的思考,收集了数十万份世界各国不同阶层、高校和行业的资料,也深入了解了费曼先生的教学思想和在学习方面的观点。我发现人和人在学习中最大的差别从来不是优胜者拥有什么绝密而不可示人的成功秘诀和惊人的天赋,而是他们在学习时养成了一些与众不同的好习惯。他们既善于输出知识,也敢于质疑知识,反思自己的学习成果,不断地挖掘和精进,才完成了从量变到质变的飞跃,将平庸者远远地甩在了身后。\n费曼说:“最好的学习是我们能从一个问题里找到新的问题,你不喜欢、不赞赏、不认可的东西,那才是知识这顶皇冠上的宝石。如果你憎恶争议,或者没有挑刺的习惯,就如同你扔掉了这颗宝石,只戴一顶漂亮但毫无价值的帽子。”\n","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/gknows/%E8%B4%B9%E6%9B%BC%E5%AD%A6%E4%B9%A0%E6%B3%95/","summary":"INDEX \u0026gt; 生活消费 \u0026gt; 个人成长 \u0026gt; 书籍阅读 \u0026gt; 费曼学习法 STEP ONE: 学习的本质 掌握一门知识有多难 传统的学习方式的问题:以输入为主,教条主义,标准化应用。 学习本质是输出,而不是输入。","title":"费曼学习法"},] [{"content":" 随想 · 日志 · 书签 · 摄影 · 视频\n庙街城 | 卡佛书店(桃子湖) | 止间书店\n卡佛书店(桃子湖店) \u0026nbsp;\n","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/pho/kafo/","summary":"随想 · 日志 · 书签 · 摄影 · 视频 庙街城 | 卡佛书店(桃子湖) | 止间书店 卡佛书店(桃子湖店) \u0026nbsp;","title":""},{"content":" 随想 · 日志 · 书签 · 摄影 · 视频\n庙街城 | 卡佛书店(桃子湖) | 止间书店\n","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/pho/miaojie/","summary":"随想 · 日志 · 书签 · 摄影 · 视频 庙街城 | 卡佛书店(桃子湖) | 止间书店","title":""},{"content":" 随想 · 日志 · 书签 · 摄影 · 视频\n庙街城 | 卡佛书店(桃子湖) | 止间书店\n止间书店 \u0026nbsp;\n","date":"0001-01-01","permalink":"https://kinneyzhang.github.io/pho/zhijian/","summary":"随想 · 日志 · 书签 · 摄影 · 视频 庙街城 | 卡佛书店(桃子湖) | 止间书店 止间书店 \u0026nbsp;","title":""},]
✖
enter your icp or a interesting slogan...