-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.json
1 lines (1 loc) · 351 KB
/
index.json
1
[{"content":"\u0026lt;dependencies\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.httpcomponents\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;httpclient\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;4.4\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.httpcomponents\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;httpmime\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;4.4\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; CloseableHttpClient httpClient = HttpClients.createDefault(); HttpPost uploadFile = new HttpPost(\u0026#34;...\u0026#34;); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.addTextBody(\u0026#34;field1\u0026#34;, \u0026#34;yes\u0026#34;, ContentType.TEXT_PLAIN); // This attaches the file to the POST: File f = new File(\u0026#34;[/path/to/upload]\u0026#34;); builder.addBinaryBody( \u0026#34;file\u0026#34;, new FileInputStream(f), ContentType.APPLICATION_OCTET_STREAM, f.getName() ); HttpEntity multipart = builder.build(); uploadFile.setEntity(multipart); CloseableHttpResponse response = httpClient.execute(uploadFile); HttpEntity responseEntity = response.getEntity(); 4.0 版本需要按照下面的写法。\nHttpClient httpclient = new DefaultHttpClient(); HttpPost httppost = new HttpPost(url); FileBody bin = new FileBody(new File(fileName)); StringBody comment = new StringBody(\u0026#34;Filename: \u0026#34; + fileName); MultipartEntity reqEntity = new MultipartEntity(); reqEntity.addPart(\u0026#34;bin\u0026#34;, bin); reqEntity.addPart(\u0026#34;comment\u0026#34;, comment); httppost.setEntity(reqEntity); HttpResponse response = httpclient.execute(httppost); HttpEntity resEntity = response.getEntity(); 为什么需要这个呢,因为需要根据「江苏省危货平台与港口企业对接规范」调用接口,其中做了很多限制。\n文件需要单独按照 content-type 为 multipart/form-data 上传,其他的入参还是按照键值对的形式。 url 中用 ? 拼接参数 SM2 加签进行签名验证 时间戳防重复攻击 身份验证(企业唯一标识等) ","permalink":"https://sheerwill.xyz/posts/main/20240826192831-making_a_multipart_form_data_post_request_in_java/","summary":"\u0026lt;dependencies\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.httpcomponents\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;httpclient\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;4.4\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.httpcomponents\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;httpmime\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;4.4\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; CloseableHttpClient httpClient = HttpClients.createDefault(); HttpPost uploadFile = new HttpPost(\u0026#34;...\u0026#34;); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.addTextBody(\u0026#34;field1\u0026#34;, \u0026#34;yes\u0026#34;, ContentType.TEXT_PLAIN); // This attaches the file to the POST: File f = new File(\u0026#34;[/path/to/upload]\u0026#34;); builder.addBinaryBody( \u0026#34;file\u0026#34;, new FileInputStream(f), ContentType.APPLICATION_OCTET_STREAM, f.getName() ); HttpEntity multipart = builder.build(); uploadFile.setEntity(multipart); CloseableHttpResponse response = httpClient.execute(uploadFile); HttpEntity responseEntity = response.getEntity(); 4.0 版本需要按照下面","title":"Making a Multipart/Form-Data POST Request in Java"},{"content":"pom.xml 文件中需要增加一些包。\n\u0026lt;repositories\u0026gt; \u0026lt;repository\u0026gt; \u0026lt;id\u0026gt;opencv\u0026lt;/id\u0026gt; \u0026lt;url\u0026gt;https://mvnrepository.com/artifact/org.openpnp/opencv\u0026lt;/url\u0026gt; \u0026lt;/repository\u0026gt; \u0026lt;repository\u0026gt; \u0026lt;id\u0026gt;djl.ai\u0026lt;/id\u0026gt; \u0026lt;url\u0026gt;https://oss.sonatype.org/content/repositories/snapshots/\u0026lt;/url\u0026gt; \u0026lt;/repository\u0026gt; \u0026lt;/repositories\u0026gt; \u0026lt;dependencies\u0026gt; \u0026lt;!-- DJL Core API --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;ai.djl\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;api\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${djl.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- DJL PyTorch Engine --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;ai.djl.pytorch\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;pytorch-engine\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${djl.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- DJL PyTorch Model Zoo --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;ai.djl.pytorch\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;pytorch-model-zoo\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${djl.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- DJL Basic Dataset --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;ai.djl\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;basicdataset\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${djl.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- DJL Image Processing --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;ai.djl\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;basicdataset\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${djl.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- https://mvnrepository.com/artifact/org.openpnp/opencv --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.openpnp\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;opencv\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;4.9.0-0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; Java 中缺少一些追踪算法,无法在视频中追踪物体,达到视频计数的目的,只能够按帧进行标记。\nimport com.fasterxml.jackson.databind.ObjectMapper; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class YOLOv8Util { public static Map\u0026lt;String, Integer\u0026gt; runScript(String pythonInterpreter, String scriptPath, String workingDirectory, String source, boolean viewImg, boolean saveImg, List\u0026lt;String\u0026gt; classes, String weights) throws IOException, InterruptedException { List\u0026lt;String\u0026gt; command = buildCommand(pythonInterpreter, scriptPath, source, viewImg, saveImg, classes, weights); ProcessBuilder pb = new ProcessBuilder(command); pb.directory(new File(workingDirectory)); // 设置工作目录 Process process = pb.start(); String lastJsonOutput = null; // 获取脚本的标准输出 try (BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream())); BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { // 读取标准输出 String line; while ((line = stdInput.readLine()) != null) { lastJsonOutput = line.trim(); System.out.println(\u0026#34;Standard Output: \u0026#34; + line); } // 读取错误输出 while ((line = stdError.readLine()) != null) { System.err.println(\u0026#34;Error Output: \u0026#34; + line); } } // 等待脚本执行完毕 // int exitCode = process.waitFor(); // System.out.println(\u0026#34;Exited with code : \u0026#34; + exitCode); // 如果有 JSON 输出,则解析为 Map if (lastJsonOutput != null) { ObjectMapper mapper = new ObjectMapper(); return mapper.readValue(lastJsonOutput, Map.class); } else { throw new IOException(\u0026#34;No JSON output received from the Python script.\u0026#34;); } } // https://github.com/ultralytics/ultralytics/blob/main/examples/YOLOv8-Region-Counter/readme.md private static List\u0026lt;String\u0026gt; buildCommand(String pythonInterpreter, String scriptPath, String source, boolean viewImg, boolean saveImg, List\u0026lt;String\u0026gt; classes, String weights) { List\u0026lt;String\u0026gt; command = new ArrayList\u0026lt;\u0026gt;(); command.add(pythonInterpreter); command.add(scriptPath); command.add(\u0026#34;--source\u0026#34;); command.add(source); // if (viewImg) { // command.add(\u0026#34;--view-img\u0026#34;); // } // if (saveImg) { // command.add(\u0026#34;--save-img\u0026#34;); // } command.add(\u0026#34;--device\u0026#34;); command.add(\u0026#34;cpu\u0026#34;); command.add(\u0026#34;--classes\u0026#34;); command.addAll(classes); // 添加多个类索引 command.add(\u0026#34;--weights\u0026#34;); command.add(weights); return command; } public static void main(String[] args) { String pythonInterpreter = \u0026#34;/Users/luciuschen/YOLO/bin/python3\u0026#34;; String scriptPath = \u0026#34;/Users/luciuschen/YOLO/yolov8-video.py\u0026#34;; String workingDirectory = \u0026#34;/Users/luciuschen/YOLO/\u0026#34;; // 创建一个固定大小的线程池 ExecutorService executor = Executors.newFixedThreadPool(4); ClassLoader classLoader = Main.class.getClassLoader(); URL resource = classLoader.getResource(\u0026#34;script/best.pt\u0026#34;); Path path = Paths.get(resource.getPath()); try { // 提交任务给线程池并获取 Future 对象 Future\u0026lt;Map\u0026lt;String, Integer\u0026gt;\u0026gt; future = executor.submit(new PythonScriptRunnerTask(pythonInterpreter, scriptPath, workingDirectory, \u0026#34;/Users/luciuschen/YOLO/test.mp4\u0026#34;, true, true, Arrays.asList(\u0026#34;0\u0026#34;, \u0026#34;2\u0026#34;), path.toString())); // 获取任务的执行结果 Map\u0026lt;String, Integer\u0026gt; resultMap = future.get(); System.out.println(\u0026#34;Result Map: \u0026#34; + resultMap); } catch (Exception e) { e.printStackTrace(); } finally { // 关闭线程池 executor.shutdown(); } } } class PythonScriptRunnerTask implements Callable\u0026lt;Map\u0026lt;String, Integer\u0026gt;\u0026gt; { private final String pythonInterpreter; private final String scriptPath; private final String workingDirectory; private final String source; private final boolean viewImg; private final boolean saveImg; private final List\u0026lt;String\u0026gt; classes; private final String weights; public PythonScriptRunnerTask(String pythonInterpreter, String scriptPath, String workingDirectory, String source, boolean viewImg, boolean saveImg, List\u0026lt;String\u0026gt; classes, String weights) { this.pythonInterpreter = pythonInterpreter; this.scriptPath = scriptPath; this.workingDirectory = workingDirectory; this.source = source; this.viewImg = viewImg; this.saveImg = saveImg; this.classes = classes; this.weights = weights; } @Override public Map call() throws Exception { return YOLOv8Util.runScript(pythonInterpreter, scriptPath, workingDirectory, source, viewImg, saveImg, classes, weights); } } ","permalink":"https://sheerwill.xyz/posts/main/20240826191654-direct_invocation_of_yolov8_trained_models_in_java/","summary":"pom.xml 文件中需要增加一些包。 \u0026lt;repositories\u0026gt; \u0026lt;repository\u0026gt; \u0026lt;id\u0026gt;opencv\u0026lt;/id\u0026gt; \u0026lt;url\u0026gt;https://mvnrepository.com/artifact/org.openpnp/opencv\u0026lt;/url\u0026gt; \u0026lt;/repository\u0026gt; \u0026lt;repository\u0026gt; \u0026lt;id\u0026gt;djl.ai\u0026lt;/id\u0026gt; \u0026lt;url\u0026gt;https://oss.sonatype.org/content/repositories/snapshots/\u0026lt;/url\u0026gt; \u0026lt;/repository\u0026gt; \u0026lt;/repositories\u0026gt; \u0026lt;dependencies\u0026gt; \u0026lt;!-- DJL Core API --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;ai.djl\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;api\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${djl.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- DJL PyTorch Engine --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;ai.djl.pytorch\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;pytorch-engine\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${djl.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- DJL PyTorch Model Zoo --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;ai.djl.pytorch\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;pytorch-model-zoo\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${djl.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- DJL Basic Dataset --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;ai.djl\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;basicdataset\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${djl.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- DJL Image Processing --\u0026gt; \u0026lt;dependency\u0026gt;","title":"Direct Invocation of YOLOv8 Trained Models in Java"},{"content":"给定的 Lamport 时间戳 \\(L(a)\\) 和 \\(L(b)\\) 并且 \\(L(a) \u0026lt; L(b)\\),我们推断不出 \\(a \\rightarrow b\\) 或者 \\(a || b\\).1\n要区分平行的这些事件,需要 vertor clocks:\n假定分布式系统中有 \\(n\\) 个节点,\\(N = \\langle N_{1}, N_{2},\u0026hellip;, N_{n}\\rangle\\) 事件 \\(a\\) 的向量时间戳写成 \\(V(a) = \\langle t_{1}, t_{2},\u0026hellip;, t_{n} \\rangle\\) \\(t_{i}\\) 是 \\(N_{i}\\) 节点发生时间的数量 每个节点都有一个当前的向量时间戳 \\(T\\) 节点 \\(N_{i}\\) 上的事件,向量时间戳 \\(T[i]\\) 递增。 每个消息都附带上向量时间戳 接收者将消息中的向量时间戳合并到本地 除了标量和向量之间的区别之外,向量时钟算法与 Lamport 时钟非常相似。一个节点的向量时钟的初始值是系统中的每个节点的事件数,也就是 0。每当节点 \\(N_{i}\\) 发生事件时,它就会增加其向量钟中的第 \\(i\\) 个条目(它自己的条目)。(In practice, this vector is often implemented as a map from node IDs to integers rather than an array of integers. 在实践中,这里的向量通常是节点 ID 对应 Integer 的 map,而不是 Integer 的数组。)。当一个消息在网络上被发送时,发送者当前的向量时间戳被附加到该消息上。最后,当一个消息被接收时,接收者将消息中的向量时间戳与它的本地时间戳合并,方法是取两个向量的元素的最大值,然后接收者增加它自己的条目。\nVector clocks algorithm Vector clocks example 假定节点向量是 \\(N = \\langle A, B, C \\rangle\\):\nFigure 1: vector-example\n事件 \\(e\\) 的向量时间戳是一系列事件的集合, \\(e\\) 和它的因果以来:\\({e} \\cup {a | a \\rightarrow e}\\)23\n比如,\\(\\langle2, 2, 0\\rangle\\),第一个 2 是表示来自于 \\(A\\) 的两个事件,第二个 2 是表示来自于 \\(B\\) 的两个事件,并且没有来自于 \\(C\\) 的事件。\nVector clocks ordering Define the following order on vector timestamps(in a system with \\(n\\) nodes):\n\\(T = T\u0026rsquo; \\iff T[i] = T\u0026rsquo;[i]\\) for all \\(i \\in {1, \u0026hellip;, n}\\) \\(T \\le T\u0026rsquo; \\iff T[i] \\le T\u0026rsquo;[i]\\) for all \\(\\in {1, \u0026hellip;, n}\\) \\(T \u0026lt; T\u0026rsquo; \\iff T \\le T\u0026rsquo;\\) and \\(T \\neq T\u0026rsquo;\\) \\(T || T\u0026rsquo; \\iff T \u0026gt; T\u0026rsquo;\\) and \\(T\u0026rsquo; \u0026gt; T\\) \\(V(a) \\le V(b) \\iff ({a} \\cup {e | e \\rightarrow a}) \\subseteq ({b} \\cup{} {e | e \\rightarrow b})\\)\nProperties of this order:\n\\((V(a) \u0026lt; V(b)) \\iff (a \\rightarrow b)\\) \\((V(a) = V(b)) \\iff (a = b)\\) \\((V(a) \\rVert V(b)) \\iff (a \\rVert b)\\) 然后,我们对向量的时间戳进行部分排序。如果第一个向量的每个元素都小于或等于第二个向量的相应元素,我们就说一个向量小于或等于另一个向量。如果一个向量小于或等于另一个向量,并且它们至少有一个元素是不同的,那么这个向量就严格小于另一个向量。然而,如果一个向量在一个元素中的值较大,而另一个向量在另一个元素中的值较大,那么这两个向量是不可比的。例如,\\(T = \\langle2, 2, 0\\rangle\\) 和 \\(T\u0026rsquo; = \\langle0, 0, 1\\rangle\\) 是不可比的,因为 \\(T[1] \u0026gt; T\u0026rsquo;[1]\\) 但 \\(T[3] \u0026lt; T\u0026rsquo;[3]\\)。\n向量时间戳的部分顺序与发生之前关系所定义的部分顺序完全对应。因此,向量时钟算法为我们提供了一种在实践中计算发生前关系的机制。\n具体参考 Lamport Clocks 中的逻辑双条件。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n\\(\\cup\\) 并集的意思,\\(\\cap\\) 交集的意思。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n集合的描述方法,描述格式为 \\({x | P(x), x \\in \\Omega}\\)。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://sheerwill.xyz/posts/main/20220602235916-vector_clocks/","summary":"给定的 Lamport 时间戳 \\(L(a)\\) 和 \\(L(b)\\) 并且 \\(L(a) \u0026lt; L(b)\\),我们推断不出 \\(a \\rightarrow b\\) 或者 \\(a || b\\).1 要区分平行的这些事件,需要 vertor clocks: 假定分布式系统中有 \\(n\\) 个节点,\\(N = \\langle N_{1},","title":"Vector clocks"},{"content":"Definition 最弱的广播类型称为先进先出(FIFO)广播。在这个模型中,由同一节点发送的信息按照发送的顺序传递。例如下图中,\\(m_{1}\\) 必须在 \\(m_{3}\\) 之前被传递,因为它们都是由 \\(A\\) 发送的。然而,\\(m_{2}\\) 可以在 \\(m_{1}\\) 和 \\(m_{3}\\) 之前、之间或之后的任何时间被传递。\nFigure 1: FIFO-broadcast\n同一个节点发送的消息必须要按照发送的顺序传递,不同节点发送的消息可以是任意的顺序,比如 \\((m_{2}, m_{1}, m_{3})\\),\\((m_{1}, m_{2}, m_{3})\\) 或者 \\((m_{1}, m_{3}, m_{2})\\)。\n关于这些广播协议的另一个细节:我们假设每当一个节点广播一个消息时,它也会将该消息传递给自己(在上图中用回环箭头表示)。一个节点知道它自己广播了什么消息,所以传递给自己看似没有必要,但在 Total order broadcast 中是需要这个步骤。\n上图中的执行示例是有效的先进先出「广播」,但它违反了因果性:虽然 \\(B\\) 在接收了 \\(m_{1}\\) 之后才广播的 \\(m_{2}\\),但节点 \\(C\\) 在 \\(m_{1}\\) 之前就传递了 \\(m_{2}\\)。Causal broadcast 提供了一个比 FIFO broadcast 更严格的排序属性。顾名思义,它确保信息按因果顺序传递:也就是说,如果一条信息的广播发生在另一条信息的广播之前,那么所有节点必须按这个顺序传递这两条信息。如果两个消息是同时广播的,一个节点可以按任何一个顺序传递它们。\nFIFO broadcast algorithm 节点 \\(N_{i}\\) 发送的每个 FIFO broadcast 消息都会附带发送节点编号 \\(i\\) 和一个序列号(该节点发送消息对应的序列号,第一条消息对应为 0,第二条消息对应为 1)。每个节点的本地状态由序列号 \\(sendSeq\\)(计算该节点广播的消息数量)、\\(delivered\\)(一个向量,每个节点有一个条目,统计了每个节点发送消息并传递到该节点的数量)和 \\(buffer\\)(一个缓存,用于保留消息直到它们准备好被传递给 Application,也就是 Receiving versus delivering 中提到的 deliver 阶段)组成。该算法检查来自任何发送者的与预期的下一个序列号相匹配的消息,然后递增该序列号,确保来自每个特定发送者的消息按照序列号递增的顺序被传递。\nSummary FIFO broadcast 的保持消息顺序的原理和 Lamport clocks 很相似,都是用递增的整数来确定同一个节点上的消息传递的顺序,\\(deliver\\) 是个存放着所有节点 \\(sendSeq\\) 的向量,存放于每个节点,配合 \\(sendSeq\\) 控制来自同一个节点的消息传递的顺序。\n","permalink":"https://sheerwill.xyz/posts/main/20220616105609-fifo_broadcast/","summary":"Definition 最弱的广播类型称为先进先出(FIFO)广播。在这个模型中,由同一节点发送的信息按照发送的顺序传递。例如下图中,\\(m_{1}\\) 必须在 \\(m_{3}\\) 之","title":"FIFO broadcast"},{"content":"Definition 总秩序广播(total order broadcast),有时也被称为原子广播(atomic broadcast)。FIFO broadcast 和 Causal broadcast 允许不同的节点以不同的顺序传递信息,total order broadcast 在各节点之间强制执行一致性,确保所有节点以相同的顺序传递信息。精确的传递顺序没有规定,只要它在所有节点上都是一样的。\nFigure 1: total-order-broadcast-1\n所有三个节点都按照 \\(m_{1}\\), \\(m_{2}\\), \\(m_{3}\\) 的顺序传递消息\nFigure 2: total-order-broadcast-2\n所有三个节点都按照 \\(m_{1}\\), \\(m_{3}\\), \\(m_{2}\\) 的顺序传递信息。只要节点同意,这两种执行方式都是有效的。\n与 Causal broadcast 一样,节点可能需要保留消息,等待其他需要首先传递的消息。例如,节点 \\(C\\) 可以按任一顺序接收消息 \\(m_{2}\\) 和 \\(m_{3}\\)。如果算法确定 \\(m_{3}\\) 应该在 \\(m_{2}\\) 之前传递,但如果节点 \\(C\\) 首先收到 \\(m_{2}\\),那么 \\(C\\) 将需要保留 \\(m_{2}\\),直到收到 \\(m_{3}\\) 之后。\n在这些图上可以看到另一个重要的细节:在 FIFO broadcast 和 Causal broadcast 的情况下,当一个节点广播一个消息时,它可以立即将该消息传递给自己,而不必等待与任何其他节点的通信。这在全序广播中不再是这样:例如,在 图total-order-broadcast-1 上,\\(m_{2}\\) 需要在 \\(m_{3}\\) 之前被传递,所以节点 \\(A\\) 向自己传递 \\(m_{3}\\) 必须等到 \\(A\\) 从 \\(B\\) 那里收到 \\(m_{2}\\) 之后。\n最后,FIFO-total order broadcast 和 total order broadcast 很像,多了一个先进先出的要求,即同一节点广播的任何消息都按其发送的顺序交付。实际上图total-order-broadcast-1 和图total-order-broadcast-2 的例子就是有效的 FIFO-total order broadcast,因为 \\(m_{1}\\) 都是在 \\(m_{3}\\) 之前被传递的。\nTotal order broadcast algorithms Single leader approach:\n指定一个节点为 leader 想要广播消息,将其发送给 leader,leader 再将其通过 FIFO broadcast 广播给其他节点。 Problem: leader 崩溃 \\(\\Longrightarrow\\) 没有消息被传递 安全地更换 leader 很困难 Lamport clocks approach:\n每条消息附带上 lamport 时间戳 按照时间戳的总顺序传递消息 Problem: 无法知道是否收到了所有时间戳小于 \\(T\\)1 的消息,需要在缓存中缓存来自每个节点时间戳大于等于 \\(T\\) 的消息。 上述两个方法,都不具有容错性。单个节点崩溃以及 leader 节点崩溃都会使得其他节点无法传递消息。\n当前节点的时间戳 \\(T\\)\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://sheerwill.xyz/posts/main/20220616135058-total_order_broadcast/","summary":"Definition 总秩序广播(total order broadcast),有时也被称为原子广播(atomic broadcast)。FIFO broadcast 和 Causal broadcast 允许不同的节点以不同","title":"Total order broadcast"},{"content":"Remote Procedure Call Remote Procedure Call (RPC) 使得调用远程函数就像调用本地函数一样,这样就不需要关心资源的具体问题,实际上 RPC 是将方法调用转换成了网络通信。但是遇到通信失败怎么办?\n如果在函数调用过程中,服务挂了? 如果消息丢失了? 如果消息延迟了? 如果出了问题,回复消息是否安全? 要解决这些问题,就需要将网络以及节点之间的情况抽象出来,以及时序的问题。在设计分布式算法时,需要更加抽象一点,以便更好的推理整个过程。\nSystem model Network behaviour The two generals problem 描述了网络通信丢失的场景。\n假设两个节点之是间双向的点对点的通信。\nReliable links 一条消息只会在被一个节点发送的时候才会收到,并且消息是可以重新排序的。\n中间没有伪造消息等行为,发送了就一定会被收到。\nFair-loss links 消息可能会丢失、重复或重新排序,如果你一直重新发送,消息终将会传递过去。\n每次发送消息成功的概率都是非零,这个时候只要一直重试,我们就假设消息一定会传递到另外一个节点。\nFair-lose links 其实可以通过不断的重试以及并且对重复收到的消息去重,就可以达到 Reliable links 的效果。\nArbitrary links 可能传递过程中会有恶意的第三方对消息进行干预。\n比如公共场合的公共 WiFi 就会出现这种情况。\nArbitrary links 通过 TLS 的加持,就可以达到 Fair-loss links 的效果。\nNodes behaviour The Byzantine generals problem 描述了节点出错,导致全体协作策略失败以及容错率的问题。\n假设每个节点都执行指定的算法,假设有以下情况。\nCrash-stop (fail-stop) 如果一个节点出现问题挂了,并且在这之后永远停机。\nCrash-recovery (fail-recovery) 一个节点挂了,并丢失了内存状态,但会在过段时间后恢复。\nByzantine (fail-arbitrary) 节点偏离了原本的算法,可能会发生任何事情,包括崩溃或者恶意的行为。\nSynchrony (timing) assumption Synchronous 消息延迟不超过已知的上限。到达这个上限后,消息要么被传递,要么丢失。节点以已知的速度执行算法。每一步代码的执行都有执行时间的上限。\nPartically synchronous 系统中一部分时间内是异步的,另一部分是同步的。\nAsynchronous 消息可以任意地被延迟,节点可以任意地暂停,并没有时序上的保证。\nClocks and time in distributed systems 分布式系统中经常涉及到需要测量时间,比如:\nSchedulers, timeouts, failure detectors, retry timers Performance measurements, statistics, profiling (performance analysis 也称为 profiling) Log files \u0026amp; databases: record when an event occurred Data with time-limited validity (e.g. cache entries) Determining order of events across several nodes 分布式系统中会遇到两种类型的时钟:\nphysical clocks: count number of seconds elapsed logical clocks: count events, e.g. messages sent Clock synchronisation 物理时钟会有时间上的偏移,通常是通过和电脑、手机、电视等相对准确的地方来校准物理时钟的时间,比如手表,老式的钟摆。\n电脑上的时间同样会有偏移,是通过 Network Time Protocol(NTP) 来校准电脑的时间,也就是物理时钟的校准。\nBroadcast protocols and logical time Figure 1: ordering-of-messages\n\\(m_{1}\\) = \u0026ldquo;A says: The moon is made of cheese!\u0026rdquo;\n\\(m_{2}\\) = \u0026ldquo;B says: Oh no it isn\u0026rsquo;t!\u0026rdquo;\nUser C sees \\(m_{2}\\) first, \\(m_{1}\\) second, even though logically \\(m_{1}\\) happened before \\(m_{2}\\).\nProblem: even with synced clocks, \\(t_{2 }\u0026lt; t_{1}\\) is possible. Timestamp order is inconsistent with expected order!\n即便我们已经通过 NTP 尽量保证物理时钟上的同步,但这个时候依旧会发生因果关系不一致的情况,所以这个时候就需要逻辑时钟。\n逻辑时钟是在分布式系统中专门用于捕获系统中发生事件之间的因果关系,因此逻辑时钟其实就是一种计数器,每次事件发生的时候都会往前递增,所以随着事件的发生它会随着时间向前移动,但它和物理时间并没有实际的关系。\nLogical time Logical clocks: designed to capture causal dependencies.\n\\((e_{1} \\rightarrow e_{2}) \\Longrightarrow (T(e_{1}) \u0026lt; T(e_{2}))\\)\n当事件1发生在事件2之前,那么事件1的时间戳应该小于事件2的时间戳,这是逻辑时钟要保证的最基本的原则。\n下面将研究一下两种逻辑时钟:\nLamprot clocks 是在分布式系统中的每个节点都维护一个计数器,每一个当前节点事件 \\(e\\) 发生时自增 ;发送消息时,也会附带上当前节点的计数 \\(t\\) ,消息接收者收到消息后,取出消息中的计数器与当前节点计数器比较,去最大值后自增 1。\n这样可以实现局部顺序,再加上节点名称,形成(时间戳,节点名称)的组合后,就可以扩展为全局顺序。\n但这样也有缺点,算法在时间戳相同,节点名称不同的平行事件的顺序判定时,会出现与实际情况不符的情况。1\nVector clocks 除了是向量之外,向量时钟算法与 Lamport 时钟非常相似。一个节点的向量时钟的初始值是系统中的每个节点的事件数,也就是 0。每当节点 \\(N_{i}\\) 发生事件时,它就会增加其向量钟中的第 \\(i\\) 个条目(它自己的条目)。(In practice, this vector is often implemented as a map from node IDs to integers rather than an array of integers. 在实践中,这里的向量通常是节点 ID 对应 Integer 的 map,而不是 Integer 的数组。)。当一个消息在网络上被发送时,发送者当前的向量时间戳被附加到该消息上。最后,当一个消息被接收时,接收者将消息中的向量时间戳与它的本地时间戳合并,方法是取两个向量的元素的最大值,然后接收者增加它自己的条目\n这样就可以弥补 Lamport 的缺点,可以完全确定每个事件的因果顺序。\nDelivery order in broadcast protocols 许多网络提供点对点(单播)的信息传递,其中一个信息有一个特定的收件人。广播协议对网络进行了概括,使一条信息被发送到某个组的所有节点。组的成员可能是固定的,或者系统可能提供节点加入和离开组的机制。\n一些局域网在硬件层面提供组播或广播(例如,IP 组播),但互联网上的通信通常只允许单播。此外,硬件级的组播通常是在 best-effort 基础上提供的,允许消息被丢弃;要使其可靠,需要类似于这里讨论的重传协议。\n上面提到的 node behaviour 和 synchrony 的系统模型假设直接延伸到广播组。\nBroadcast protocols Broadcast (multicast) is group communication:\n一个节点发送消息,所有组内节点传递。 组内成为可以是固定的也可以是动态的 如果一个节点发生错误,剩余的组内成员顶上。 Note: 这个观念比 IP 组播更加普遍 建立在之前讲到的系统模型上:\n可以是 best-effort (可能会丢失消息) 或者 reliable (非故障节点传递每条消息,重传丢失的消息。) Asynchronous / partially synchronous timing model \\(\\Longrightarrow\\) 消息延迟没有上限 Receiving versus delivering Figure 2: receive-versus-deliver\n假定网络提供点对点的发送/接收,在广播算法从网络中收到消息后,传递给应用前会在缓存或者队列当中。\n下面将研究三种不同形式的「广播」。所有这些都是可靠的:每条消息最终都会被传递到每个非故障节点,但没有时间上的保证。然而,它们在每个节点上传递信息的顺序方面存在差异。事实证明,这种顺序上的差异对实现广播的算法有非常根本的影响。\nForms of reliable broadcast FIFO broadcast\n如果 \\(m_{1}\\) 和 \\(m_{2}\\) 从相同的节点广播,并且 broadcast(\\(m_{1}\\)) \\(\\longrightarrow\\) broadcast(\\(m_{2}\\)),那么 \\(m_{1}\\) 必然在 \\(m_{2}\\) 之前被传递。\nCausal broadcast\n如果 broadcast(\\(m_{1}\\)) \\(\\longrightarrow\\) broadcast(\\(m_{2}\\)) 那么 \\(m_{1}\\) 必然在 \\(m_{2}\\) 之前被传递。\nTotal order broadcast\n如果在同一个节点 \\(m_{1}\\) 在 \\(m_{2}\\) 之前被传递,那么所有节点 \\(m_{1}\\) 必然在 \\(m_{2}\\) 之前被传递。\nFIFO-total order broadcast\n是 FIFO broadcast 和 Total order broadcast 的结合\nRelationships between broadcast models Figure 3: broadcast-models-relationship\nFIFO-total order broadcast 是一个严格意义上比因果广播更强的模型;换句话说,每个有效的 FIFO-total order broadcast 协议也是一个有效的 causal broadcast 协议(反过来就不是了),其他协议也是如此。\nBroadcast algorithms 实现广播算法,简单来说,涉及到两个步骤。\n确保每个节点都能收到每天消息 以正确的顺序传递这些消息 首先研究如何可靠地传递消息,当一个节点想要广播一条消息时,它就单独向其他每个节点发送消息,使用上面提到过的 reliable links。但很可能一条消息丢失,发送的节点在重新发送前就崩溃了,这种情况下,丢失的这条消息对应的节点就无法接收到这条消息。\n为了提高可靠性,可以让其他节点帮忙。例如,当一个节点第一次收到一个特定消息时,它就把消息转发给其他每个节点(这种被称为急性可靠广播(Eager reliable broadcast))。这种算法确保了即使一些节点崩溃,所有剩下的非故障的节点都会收到每条消息。然而,这种算法很低效:在没有故障的情况下,每条消息在由 \\(n\\) 个节点组成的小组中要发送 \\(O(n^{2})\\) 次,每个节点要收到每条消息 \\(n-1\\) 次,意味着占用了大量的带宽。\neager reliable broadcast 的变体沿着不同维度(比如容错性、所有节点接收消息的时间和被使用的带宽)优化,其中最常见的是流言协议(Gossip protocols,也被叫做流行病协议(Epidemic protocols))。在这些协议中,一个想广播消息的节点将其发送给随机选择的少量固定数量的节点。第一次收到信息时,一个节点会将其转发给固定数量的随机选择的节点。这类似于流言、谣言或传染病在人群中的传播方式。\nGossip protocols 并不能保证所有节点都能收到消息:在随机选择节点时,有可能总是遗漏了一些节点。然而,如果算法的参数选择得当,信息不被传递的概率很小。Gossip protocols 很有吸引力,在正确的参数下,对消息丢失和节点崩溃有很强的弹性,同时还能保持高效。其实核裂变的发生过程,也类似于这种协议。\n可以在 eager reliable broadcast 或 gossip protocol 的基础上,建立 FIFO、causal 或者 total order broadcast。\nFIFO broadcast algorithm Causal broadcast algorithm Total order broadcast algorithms Replication 现在要讨论的是复制的问题,也就是在多个节点上维护相同数据的复制体,每个节点被称为副本。复制是许多分布式数据库、文件系统和其他存储系统的一个标准特征。它是我们实现容错的主要机制之一:如果一个副本出现故障,我们可以继续访问其他副本上的数据副本。2\n在不同节点维护相同数据的副本 数据库、文件系统、缓存等等 维护相同数据的节点叫做副本 如果一些副本出错,其他副本依旧是可访问的。 将负载分散到多个副本 如果数据没有变化,维护很简单,只需要复制。 专注于数据变化 相较于 RAID (Redundant Array of Independent Disks):在单个计算机内复制。\nRAID 只有一个控制器;在分布式系统中,每个节点都是独立的。 副本可以在全世界靠近用户的地方分布(CDN 本质其实是一种大规模分布式多级缓存系统) Manipulating remote state 在更新不同节点数据时,丢失数据或者丢失 TCP 返回的 \\(ACK\\)3 时,就会使得不同节点之间的副本不一致,不满足幂等性(Idempotent)。为了解决这个问题,可以给每次更新操作都加上一个逻辑时间戳,并将其作为数据存储在数据库当中;当需要删除这条记录时,实际上并没有删除,而是写入一个特殊的值(称为墓碑 tombstone),将其标记为删除。(其实就类似于软删除)\nFigure 4: reconciling-replicas\n在许多副本系统中,副本运行一个协议来检测和调和任何差异(这被称为反熵 anti-entropy),以便副本最终持有相同数据的一致副本。由于有了 tombstone,反熵过程可以分辨出已经被删除的记录和尚未被创建的记录之间的差异。由于有了逻辑时间戳,可以分辨出一条记录的哪个版本比较老,哪个版本比较新。然后,反熵过程会保留较新的记录并丢弃较旧的记录。\nFigure 5: concurrent-reconciling-replicas\n如果同时两个请求分别想把两个副本的 \\(x\\) 的值进行修改。\nLast writer wins (LWW):\n采用 Lamport clocks 时,$t2 \u0026gt; t1$则保留 \\(v_{2}\\) 丢弃 \\(v_{1}\\)。\nMulti-value register:\n采用 Vector clocks 时,\\(t_{2 } \u0026gt; t_{1}\\) 则保留 \\(v_{2}\\) 丢弃 \\(v_{1}\\); \\(t_{2} \\rVert t_{1}\\) 则 \\(\\{v_{1}, v_{2}\\}\\) 都保留。\n向量时钟的缺点是它们会变得很昂贵:每个客户端都需要在向量中输入一个条目,在有大量客户端的系统中(或者客户端每次重启都会有新的身份),这些向量会变得很大,可能会比数据本身占用更多内存。更多类型的逻辑时钟,如点状版本向量(Dotted version vectors)4,已经被开发出来以优化这种类型的系统。\nQuorums 具体如何进行复制的细节对系统的可靠性有很大影响。如果没有容错,拥有多个副本会使可靠性变差:副本越多,任何一个副本在任何时候出现故障的概率就越大(假设故障不是完全相关的)。然而,如果系统在一些有问题的复制体中仍然继续工作,那么可靠性就会提高:所有副本在同一时间出现问题的概率要比一个副本出现问题的概率低很多。\nRead-after-write consistency Figure 6: read-after-write-consistency\n一个副本写入了数据,从另外一个副本读取,用户并没有读取到他刚刚提交的内容。如果要保持读写一致性(read-after-write consistency),则要两个节点都同时写入和读取,一旦有一个不可访问就会破坏读写一致性,没有任何容错率可言。\nQuorum (2 out of 3) Figure 7: quorum\n通过使用三个副本可以解决这个难题5。将每个读写请求发送到所有三个副本,但只要收到 \\(\\geq2\\) 个响应,就认为请求成功。在这个例子中,写在复制体 \\(B\\) 和 \\(C\\) 上成功,而读在副本 \\(A\\) 和 \\(B\\) 上成功。对读和写都采用 \u0026ldquo;三选二 \u0026ldquo;的策略,可以保证至少有一个对读的响应来自看到最近写的副本(在这个例子中,这是副本 \\(B\\))。\n对于同一个请求,不同的副本可能会返回不同的响应:在上图中,\\(A\\) 处读取会返回初始值 \\((t_{0}, v_{0})\\),而 \\(B\\) 处的读取会返回该客户端之前写入的 \\((t_{1}, v_{1})\\)。利用时间戳,客户端可以知道哪个响应是最近的,将 \\(v_{1}\\) 返回给 Application。\n在这个例子中,响应写请求的副本集合 \\((B, C)\\) 是一个 write quorum,而响应读请求的集合 \\((A, B)\\) 是一个 read quorum。一般来说,quorum 是一个最小的节点集合,它必须对某个请求作出响应才能成功。为了确保读写一致性,write quorum 和 read quorum 必须有一个非空的交集:换句话说,read quorum 必须包含至少一个已经被承认的 write quorum。\n在分布式系统中,常见的 majority quorum 是由超过半数的节点组成的任意子集。在三个节点的系统 \\(\\{A, B, C\\}\\) 中,majority quorum 可以是 \\(\\{A, B\\}\\)、\\(\\{A, C\\}\\) 和 \\(\\{B, C\\}\\)。通常来讲,奇数节点的系统,\\(\\frac{n+1}{2}\\) 大小的任意子集就是 majority quorum,偶数节点的系统,需要四舍五入 \\(\\left \\lceil \\frac{n+1}{2} \\right \\rceil = \\frac{n+2}{2}\\)6。majority quorum 有个特性:任意两个 quorums 至少有一个共同的元素,也就是交集至少有一个节点。\n只要不超过 \\(n-w\\) 个副本不可用(\\(w\\) 是 write quorum 的数量),系统就可以正常的处理更新;同样的,只要不超过 \\(n-r\\) 个副本不可以(\\(r\\) 是 read quorum 的数量),系统就可以正常的处理读请求。对于 majority quorum 来说,三个副本的系统可以容错一个节点,五个副本的系统可以容错两个节点,以此类推。\n在这种基于 quorum 的方法下,一些副本会错过一些更新。上图中,副本 \\(A\\) 错过了 \\((t_{1}, v_{1})\\)。让副本互相同步使其数据一致的方法上面提到过,叫做反熵(anti-entropy)。\nFigure 8: read-repair\n另外一个方法是让客户端帮助完成传播更新的过程。例如上图中,客户端从 \\(B\\) 读取了 \\((t_{1}, v_{1})\\),但从 \\(A\\) 接收到的是 \\((t_{0}, v_{0})\\),而 \\(C\\) 没有回应。这个时候客户端使用原始的时间戳 \\(t_{1}\\) 将最新的值发送给 \\(A\\) 和 \\(C\\),\\(C\\) 如果已经更新了,也不过只是浪费一点点带宽。这种复制模式的数据库通常称为 Dynamo-style。\nReplication using broadcast 在 quorum 中,基本上使用了 best-effort 以及客户端辅助使其他副本数据一致的方法,但这些都是不可靠的,中途会发生丢失,并且排序无法保证。\nState machine replication (SMR) FIFO-total order broadcast: 每个节点以相同的顺序传递相同的消息。\nFIFO-total order broadcast 将每次更新,更新到所有副本。 副本通过传递的消息来确认自身状态 更新具有确定性 副本是一种状态机:开始于固定的初始状态,以相同的顺序经历相同序列状态的变换 \\(\\Longrightarrow\\) 所有副本都以相同的状态结尾 利用 FIFO-total order broadcast 很容易建立一个复制系统:将每个更新请求广播给副本,副本基于传递的每个消息更新其状态。副本作为一个状态机,其输入是消息的传递,这种就叫做 state machine replication (SMR)。只要更新逻辑是确定的:任何两个处于相同状态的复制体,被赋予相同的输入,最终必须处于相同的下一个状态。甚至错误也必须是确定的:如果一个副本的更新成功了,但在另一个副本上失败了,它们就会变得不一致。\nSMR 的一个优秀特点是,从一个状态到下一个状态只要是确定的,其中的逻辑可以任意复杂。例如,可以处理具有任意业务逻辑的整个数据库事务,而这个逻辑可以同时依赖于广播信息和数据库的当前状态。一些分布式数据库以每个副本独立执行相同的确定性交易代码(这被称为主动复制)来实现复制。这一原则也是区块链、加密货币和分布式账本的基础:区块链中的 \u0026ldquo;区块链 \u0026ldquo;无非是由 Total order broadcast 传递的消息序列,每个副本都确定性地执行这些区块中描述的交易,以确定账本的状态(例如,谁拥有哪些钱)。一个 \u0026ldquo;智能合约 \u0026ldquo;只是一个确定的程序,当一个特定的消息被传递时,副本会执行这个程序。\n密切相关的概念:\n可序列化事务(按传递顺序执行) 区块链、分布式账本、智能合约。 限制:\n不能立即更新状态,必须等待广播传递。 需要容错的 total order broadcast State machine replication 的缺点是 total order broadcast 的限制。当一个节点想通过 total order broadcast 来广播一个消息时,它不能立即将该消息传递给自己。之前讲过,传递给自己的消息也要遵从总秩序,所以当使用状态机复制时,想要更新其状态的副本要等待广播,与其他节点协调,并等待更新信息被传递给自己。状态机复制的容错性取决于底层 total order broadcast 的容错性。尽管如此,基于 total order replication 的复制还是被广泛使用。\n在 Total order broadcast 中提到过一种实现方法是指定一个节点作为 Leader,并将所有的消息通过它来路由,保证传递的顺序。这一原则也被广泛应用于数据库副本:许多数据库系统指定一个副本作为 leader,primary 或者 master。所有修改数据库的事务都需要在 leader 副本上执行。虽然 leader 会同时执行多个事务,但事务提交时会按照总的顺序提交。当事务提交后, leader 副本将数据变动广播给所有的 follow 副本,follow 副本按照之前提交的顺序在本地进行事务提交,这种方法被称为被动复制(passive replication)或者主从复制(primary-backup replication),这样的方式其实和 Total order broadcast 是等效的。\n至于之前提到的其他广播方式,也可以用作复制,但需要更加小心,保证副本之间数据的一致性。\nThe Raft consensus algorithm 为了实现容错的 FIFO-total order broadcast,Raft 的具体实现细节可以参见 Raft consensus algorithm,另外有个非常好的动画示意网站可以更加直观的观察整个过程。。\n集群内的节点都对选举出的领袖采取信任,因此 Raft 不是一种拜占庭容错算法。\nReplica consistency 事务的关键是原子性。当一个事务跨越多个节点时,希望整个事务仍然具有原子性:也就是说,要么所有节点都必须提交事务且具有持久性,要么所有节点都回滚。因此,各节点之间就事务应该终止回滚还是提交要达成一致。这里说的一致和 Raft 中的不太一样,细节上有很大不同。\nConsensus Atomic commit 一个或多个节点提出一个值 每个节点投票是否提交或中止 任意一个提出的值都可以被承认 如果所有的节点投票提交那么就必须提交;如果所有的节点都投票中止那么就必须中止。 只要 quorum 数量的节点可以工作,崩溃的节点是可以被容忍的。 如果不分的节点崩溃,必须中止。 用来保证多个节点 atomic commitment 最常用的算法是 two-phase commit protocol (2PC),也有 three-phase commit protocol,但这里不做研究。\nFigure 9: Two-phase commit (2PC)\n当使用 two-phase 提交时,客户端首先在参与交易的每个副本上启动一个常规的单节点交易,并在这些交易中执行常规的读写操作。当客户端准备好提交交易时,它向交易协调者(一个管理 2PC 协议的指定节点)发送提交请求。(在一些系统中,协调器是客户端的一部分)。\n协调器首先向参与交易的每个副本发送一个准备消息,每个副本都会回复一个消息,表明它是否能够提交交易(这是协议的第一阶段)。副本实际上还没有提交交易,但它们必须确保在第二阶段中,当协调者发出指示,它们一定能够提交交易。这就意味着,副本必须将交易的所有内容更新写入磁盘,并在回复准备信息之前检查完整性约束,同时继续持有交易的任何锁。\n协调器收集响应,并决定是否实际提交交易。如果所有节点都回复 OK,协调者就决定提交;如果任何节点想放弃,或者任何节点未能在某个超时时间内回复,协调者就决定放弃。然后,协调者将其决定发送给每个副本,它们都按照指示提交或放弃(这是第二阶段)。如果决定是提交,每个副本保证能够提交其事务,因为之前的准备请求奠定了基础。如果决定放弃,副本就会回滚该事务。\nReference Cachin, Guerraoui, Rodrigues (2011) Introduction to Reliable and Secure Distributed Programming (2. Ed.)., Springer. if \\(a \\rVert b\\) we could have either \\(a \\prec b\\) or \\(b \\prec a\\), so the order of the two events is determined arbitrarily by the algorithm.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nreplication 翻译成「复制」,名词;replica 翻译成「副本」,名词。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n后面整理 TCP 相关内容时,可以加个链接。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n待学习\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n基于 Quorum 投票的冗余控制算法\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n向上去整\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://sheerwill.xyz/posts/main/20220602202933-distribution_system/","summary":"Remote Procedure Call Remote Procedure Call (RPC) 使得调用远程函数就像调用本地函数一样,这样就不需要关心资源的具体问题,实际上 RPC 是将方法调用转换成了网络通信。但是遇到通信失败怎","title":"Distribution System"},{"content":"The Happens-before relation An event is something happening at one node (sending or receiving a message, or a local execution step).\nevent \\(a\\) happens before event \\(b\\), written \\(a \\rightarrow b\\).\n那么在分布式,有三种情况,满足其中一条,则 \\(a\\) 是发生在 \\(b\\) 之前:\n\\(a\\) 和 \\(b\\) 发生在相同节点,并且按照本地节点的执行顺序,\\(a\\) 发生在 \\(b\\) 之前。\n事件 \\(a\\) 是发送消息 \\(m\\),事件 \\(b\\) 是接收相同的消息 \\(m\\)(假定发送的消息都是唯一的)。\n有这么一个时间 \\(c\\) 满足,\\(a \\rightarrow c\\) 且 \\(c \\rightarrow b\\)。\nhappens-before relation 是局部的顺序:有可能存在既不满足 \\(a \\rightarrow b\\) 也不满足 \\(b \\rightarrow a\\),这样 \\(a\\) 和 \\(b\\) 就是并行的,写作 a || b。\nHappen-before relation example Figure 1: happens-before-example\n\\(a \\rightarrow b\\), \\(c \\rightarrow d\\), and \\(e \\rightarrow f\\) due to process order \\(b \\rightarrow c\\) and \\(d \\rightarrow f\\) due to messages \\(m_{1}\\) and \\(m_{2}\\) \\(a \\rightarrow c\\), \\(a \\rightarrow d\\), \\(a \\rightarrow f\\), \\(b \\rightarrow d\\), \\(b \\rightarrow f\\), and \\(c \\rightarrow f\\) due to transitivity \\(a || e\\), \\(b || e\\), \\(c || e\\), and \\(d || e\\) Causality Happens-before relation 和 Causality 在分布式系统中联系非常紧密\n当 \\(a \\rightarrow b\\), 那么 \\(a\\) 可能是 \\(b\\) 的因。 当 \\(a || b\\), \\(a\\) 和 \\(b\\) 之间不存在因果关系。 Figure 2: causality\nLet \\(\\prec\\) be a strict total order on events.\n如果 \\((a \\rightarrow b) \\Rightarrow (a \\prec b)\\) 符号 \\(\\prec\\) 就是所谓的因果顺序。\n因果关系的概念是从物理学中借鉴的,信息的传播速度不可能超过光速。因此如果事件 \\(a\\) 和事件 \\(b\\) 在空间上相距很远,但是在时间上相距很近,那么从事件 \\(a\\) 发出的信号不可能在事件 \\(b\\) 之前到达 \\(b\\) 的位置,反之亦然。因此 \\(a\\) 和 \\(b\\) 是肯定没有因果关系的。\n一个在空间上离 \\(a\\) 很近,时间上距离 \\(a\\) 很远的事件 \\(c\\),将在 \\(a\\) 的光锥内,也就是说 \\(a\\) 的信号有可能到达 \\(c\\),因此 \\(a\\) 可能影响 \\(c\\)。在分布式系统中,网络上的消息虽然不同于光束,但原理非常相似。\n","permalink":"https://sheerwill.xyz/posts/main/20220602202832-causality_and_happen_before_relation/","summary":"The Happens-before relation An event is something happening at one node (sending or receiving a message, or a local execution step). event \\(a\\) happens before event \\(b\\), written \\(a \\rightarrow b\\). 那么在分布式,有三种情况,满足其中一条,则 \\(a\\) 是发生在 \\(b\\) 之前: \\(a\\) 和 \\(b\\) 发生在相同节","title":"Causality and Happen-before relation"},{"content":"Definition Causal broadcast 算法有点类似于 FIFO broadcast:\n每条被广播的消息附上的不是一个序列号,而是一个整数的向量。这种算法有时被称为向量时钟算法,向量时钟算法中,向量元素计算每个节点发生的事件数量,而在 causal broadcast 算法中,向量元素计算来自每个发送者的已发送的消息数量。\n如果节点 \\(C\\) 在 \\(m_{1}\\) 之前收到 \\(m_{2}\\),\\(C\\) 的广播算法将不得不保留(延迟或缓冲)\\(m_{2}\\),直到 \\(m_{1}\\) 被传递之后,以确保消息按因果顺序传递。在下图的例子中,消息 \\(m_{2}\\) 和 \\(m_{3}\\) 是同时广播的。节点 \\(A\\) 和 \\(C\\) 按照 \\(m_{1}\\)、\\(m_{3}\\)、\\(m_{2}\\) 的顺序传递消息,而节点 \\(B\\) 按照 \\(m_{1}\\)、\\(m_{2}\\)、$m3$的顺序传递。这些传递顺序中的任何一个都是可以接受的,因为它们都与因果关系一致。\nFigure 1: causal-broadcast\n有因果关系的信息必须按因果顺序传递。并行的消息可以按照任何顺序传递。\n上图:broadcast(\\(m_{1}\\)) \\(\\longrightarrow\\) broadcast(\\(m_{2}\\)) and broadcast(\\(m_{1}\\)) \\(\\longrightarrow\\) broadcast(\\(m_{2}\\)) and broadcast(\\(m_{1}\\)) broadcast(\\(m_{3}\\)) \\(\\Longrightarrow\\) valid orders are: (\\(m_{1}\\), \\(m_{2}\\), \\(m_{3}\\)) or (\\(m_{1}\\), \\(m_{3}\\), \\(m_{2}\\))\nCausal broadcast algorithm 每个节点的本地状态由 \\(sendSeq\\)、\\(delivered\\) 和 \\(buffer\\) 组成,它们的含义与 FIFO broadcast 算法中相同。当一个节点想要广播一个消息时,我们会附上发送节点的编号 \\(i\\) 和 \\(deps\\)(表示该消息因果关系的向量)。\\(deps\\) 就是节点本地 \\(delivered\\) 的复制,统计了每个节点发送消息并传递到该节点的数量。在这次广播之前,所有已经在本地交付的消息必须出现在广播消息的因果顺序之前。然后将发送节点自己的这个向量的元素更新为等于 \\(sendSeq\\),这就保证了这个节点所广播的每条消息都与同一节点所广播的前一条消息有因果关系。\n当收到一个消息时,算法首先将其添加到 \\(buffer\\) 中,就像 FIFO broadcast 一样,然后在 \\(buffer\\) 中搜索任何准备好的消息。比较 \\(deps \\leq delivered\\)。如果这个节点已经交付了所有在因果顺序上必须在这个消息之前的消息,那么,任何在因果关系上准备好的消息都会被传递给 Application,并从 \\(buffer\\) 中移除,并且将 \\(delivered\\) 中的相应条目被递增。\nSummary Causal broadcast 的保持因果顺序的原理和 Vector clocks 很相似,向量中的每个条目,前者是消息发送者已发送消息的数量,后者是每个节点发生事件的数量。deps 取代 FIFO broadcast 中的 sendSeq 附带在广播的信息中,这样通过在 Vector clocks 中介绍的向量比较方法,可以确定消息之间的因果关系来控制消息传递的顺序。\n","permalink":"https://sheerwill.xyz/posts/main/20220616132407-causal_broadcast/","summary":"Definition Causal broadcast 算法有点类似于 FIFO broadcast: 每条被广播的消息附上的不是一个序列号,而是一个整数的向量。这种算法有时被称为向量时钟算法,向量时钟","title":"Causal broadcast"},{"content":"Lamport clocks algorithm Lamport clocks in words Figure 1: lamport-example-simple\n每个节点都维护一个计数器 \\(t\\),每一个当前节点事件 \\(e\\) 发生时,自增 1。 增长后的值设为 \\(L(e)\\) 发送消息时附带上当前的计数 \\(t\\) 消息接受者在消息中的时间戳和当前节点的时间戳之间取最大值后自增 1 Properties of this scheme:\nif \\(a \\rightarrow b\\) then \\(L(e) \u0026lt; L(b)\\) However, \\(L(a) \u0026lt; L(b)\\) does not imply \\(a \\rightarrow b\\) Possible that \\(L(a) = L(b)\\) for \\(a \\ne b\\) Lamport 时间戳本质上是一个整数,用来计算已发生事件的数量。因此,它与物理时间没有直接关系。在每个节点上,时间都会增加,因为每个事件的整数都会递增。该算法假设了一个 crash-stop 模型(如果时间戳被保持在稳定的存储中,即在磁盘上,则是 crash-recovery 模型)。\nLamport clocks example Figure 2: lamport-example\nLet \\(N(e)\\) be the node at which event \\(e\\) occurred.\nThen the pair \\((L(e), N(e))\\) uniquely identifies event \\(e\\).\nDefine a total order \\(\\prec\\) using Lamport timestamps:\n\\((a \\prec b) \\iff (L(a) \u0026lt; L(b) \\vee (L(a) = L(b) \\wedge N(a) \u0026lt; N(b)))\\)1 , 2\nThis order is causal: \\((a \\rightarrow b) \\longrightarrow (a \\prec b)\\)\n回顾之前的 happens-before relation 是局部顺序。利用 Lamport 时间戳可以将这种局部的顺序扩展到全局顺序。可以用(时间戳, 节点名称) 组合:首先比较时间戳,如果相同,则比较节点名称。\n对于任何两个事件 \\(a \\ne b\\),那么既不是 \\(a \\prec b\\) 也不是 \\(b \\prec a\\)。只要 \\(a \\rightarrow b\\),我们就有 \\(a \\prec b\\),换句话说,\\(\\prec\\) 是局部顺序 \\(\\rightarrow\\) 的线性扩展。然而,如果 \\(a \\vert \\vert b\\),我们可以有 \\(a \\prec b\\) 或 \\(b \\prec a\\),所以两个事件的顺序是由算法任意决定的。 比如 \\((1, A)\\) 和 \\((1, C)\\) 两个事件是平行的,按照 Lamport 的算法,\\((1, A)\\) 必然是在 \\((1, C)\\) 之前的,但实际情况可能是 \\((1, C)\\) 在前。\n\\(\\iff\\) is Logical biconditional,当且仅当的意思。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n\\(\\vee\\) is logical or, \\(\\wedge\\) is logical and.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://sheerwill.xyz/posts/main/20220609201658-lamport_clocks/","summary":"Lamport clocks algorithm Lamport clocks in words Figure 1: lamport-example-simple 每个节点都维护一个计数器 \\(t\\),每一个当前节点事件 \\(e\\) 发生时,自增 1。 增长后的值设为 \\(L(e)\\) 发送消息时附带上当前的计数 \\(t\\) 消息","title":"Lamport clocks"},{"content":" Figure 1: node-state-transitions-in-raft\n一个节点有三种状态:leader,candidate 和 follower。当一个节点第一次运行,或者崩溃后恢复时,处于 follower 的状态并且等待其他节点。如果一定时间内没有从 leader 或者 candidate 处收到消息,follower 会怀疑 leader 已经不可用了,并试图成为新的 leader。检测 leader 不可用是随机的,这样可以减少几个节点同时成为 candidate 争取票数成为 leader 的可能。\n当一个节点怀疑 leader 不可用时,会转变自身状态为 candidate,递增 term,并在这个 term 中去争取其他节点的票数进行选举。在选举过程中,如果收到其他 candidate 或 leader 发出更高的 term,则会回归到 follower 的状态。如果从 quorum 数量的节点收到票数并选举成功,就会从 candidate 的状态变为 leader 状态。如果在一定时间内没有收到足够的票数,选举会过期,然后 candidate 递增 term 并开始新一轮选举。\n一个节点除非崩溃、关闭或者收到其他 leader 或 candidate 处发来的更高的 term,否则会始终处在 leader 状态。当 leader 因为网络原因使得它无法与其他节点沟通时,其他节点就会进行选举,选举出新的 leader,这个时候更高的 term 就产生了。当收到更高的 term 时,之前的 leader 就会变为 follower 的状态。\nInitialisation 根据上面的伪代码,在初始化块中定义的变量构成了一个节点的状态。四个变量(currentTerm, votedFor, log, and commitLength)需要维护稳定的存储内(比如硬盘),来保证崩溃的时候状态不能丢失。其他变量在崩溃后恢复时会重置,所以可以存在内存当中。系统中有一个全局变量 nodes,并且每个节点都有一个唯一的 ID。(这边讲的算法没有包含 reconfiguration,也就是从系统中添加或移除节点。)\nlog 是一个数组,每个条目有属性 msg 和 term。属性 msg 里面就是我们需要通过 total order broadcast 传递的消息,属性 term 包含了被广播的 term 编号。log 数组是以 0 作为下标开始,log 是以尾部 append 新的条目来增长的,Raft 算法在各节点复制 log。当一个日志条目(以及之前的条目)被复制到 quorum 数量节点时,就提交事务。当 log 条目(以及之前的条目)被提交时,就会把条目中的 msg 传递给 Application。在一个 log 条目被提交之前,它还可能发生变化,但 Raft 保证,一旦一个 log 条目被提交后它就是确定了,所有节点将提交相同的日志条目序列。因此,从已提交的 log 条目中按其 log 顺序传递消息,我们就实现了 FIFO-total order broadcast。\n当一个节点怀疑 leader 不可用时,增加 currentTerm,将自己的角色设置为 candidate,并通过将 votedFor 和 votesReceived 设置为自己的节点 ID 来为自己投票。然后,它向其他每个节点发送一个 VoteRequest 消息,要求它们投票决定 candidate 是否可以成为新 leader。该消息包含 candidate 的 nodeId、它的 currentTerm(增量后)、它的日志中的条目数,以及它最新一条日志条目。\nVoting on a new leader 如果 candidate 的 term 是大于接收者的 term 时,接收者就会变为 follower(即便它在小的 term 里面是 leader)。然后,接收者会检查 candidate 的 log 至少和自己的 log 一样或者更新,防止 log 过期的 candidate 成为 leader,导致 log 丢失。candidate 的 log 最新条目的 term 要大于接收者 log 最新条目的 term;或者 term 相同,candidate 的 log 长度大于等于接收者的 log 长度。那么 candidate 的 log 就是可以接受的。在上面伪代码中的 logOk 可以看到这样的逻辑。\nvotedFor 变量记录了当前节点在 currentTerm 中给谁投了票,如果 currentTerm 就是 candidate 的 term,candidate 的 log 是最新的(也就是 logOk 为 true),并且在当前这个 term 中没有给其他节点投过票,那么就将 candidate 的 nodeId 记录在 votedFor 中,并发送包含 true 的 VoteResponse 的消息给 candidate。否则,则发送包含 false 的 VoteResponse。除了成功和失败的标识外,还会附带上当前节点的 ID 以及当前的 term。\nCollecting votes 回到 candidate 这边,如果收到的消息中 term 高于 candidate 的 term,则会取消选举并过渡到 follower 状态;如果两者的 term 相等,则 candidate 就会讲投票者的 ID 加入到 votesReceived 集合中。\n如果投票构成了 quorum,那么 candidate 会过渡到 leader 状态。作为领导者的第一个动作,就是更新 sendLength 和 ackedLength 变量(解释见下文),然后为每个 follower 调用 ReplicateLog (定义见(Raft (5/9): replicating from leader to followers)函数。这样做的目的是向每个 follower 发送一条消息,告知他们新的 leader 的情况。\nsentLength 和 ackedLength 给每个节点 ID 映射了一个整数(非 leader 不需要这些变量)。对于每个 follower F,sentLength[F] 记录了从 log 开始有多少 log 条目发送给了 F;ackedLength[F] 记录了有多少 log 条目已经被 F 确认收到了。在成为 Leader 之前,节点会将 sentLength[F] 初始化为 log 的长度(这里是假设所有的 follower 都已经同步了所有的 log,这里的假设是有问题的,具体解决会在 Raft (8/9): leader receiving log acknowledgements),ackedLength 初始化为 0。\nBroadcasting messages 以上伪代码中,当 Application 希望通过 total order broadcast 广播消息时,只需要简单的添加一个新的 log 条目进 log 当中,并更新 ackedLength 为最新 log 的长度,然后对于其他每个节点都调用 ReplicateLog。\nleader 在即使没有新消息需要广播时,也会定期为其他每个节点调用 ReplicateLog,这样可以让其他 follower 知道 leader 一直处于可用状态以及让丢失的消息得到重传。\nReplicating from leader to followers ReplicateLog 方法是从 leader 利用 followerId 发送任何新的 log 条目给 follower。prefixLen 是已经发送给 follower 消息的数量,suffix 就是还没有发送给 follower 的消息。所以当 sentLength[follower] = log.length 时,suffix 是一个空数组。\nFollowers receiving messages 首先,如果消息的 term 比 follower 当前的 term 高,则更新当前的 term,并接受消息的发送者为 leader。相等的时候,也会和之前一样的逻辑,承认发送者为 leader。\nprefixLen 为 suffix 包含 log 条目之前的条目数量,follower 要求自己当前的 log 长度要大于等于 prefixLen,并且 prefixLen 长度前的最后一个 log 条目中的 term 要与 leader 的 prefixTerm 相同1。Raft 算法保证两个节点相同下标对应的 log 条目中的 term 相同,那么在这之前的 log 都是相同。\n以上条件满足就会调用 AppendEntries 将 suffix 添加到自己的 log 当中。\nUpdating followers\u0026rsquo; logs follower 利用该函数讲从 leader 那里收到的条目扩充到 log 当中。如果节点本地的 log 长度大于 leader 发来消息中的 prefixLen,那么我们就要检查差异的这部份 log 条目是否与 suffix 中的条目一致。如果不一致,我们则需要舍弃这部份差异,只保留 prefixLen 长度的 log。这种情况发生在前一个 leader 发送的 log 已经被合并到节点本地 log 当中,而同时因为一些原因新的 leader 发送来新的 log。\n然后,任何新的 log 都会被追加到 follower 本地的 log 当中,即便是消息重复的情况下,这个操作也是幂等的。follower 检查 leaderCommit 是否大于本地的 commitLength,满足条件则可以将 log 当中的 msg 传递给 application,并对本地 commitLength 更新。\nLeader receiving log acknowledgements 这里回到 leader 这边。leader 会检查发送者的 term:如果发送者的 term 大于接收者的 term,那么意味着新 leader 选举已经开始,所以当前的 leader 会变为 follower 的状态。\n如果 term 相同,并且详细中的 success 为 true,则更新 sendLength 和 ackedLength,记录 follower 确认过的 log 数量,然后调用 CommitLogEntries 函数。如果 success 为 false,则 follower 没有接收消息,这个时候 leader 会递减 sendLength,并嗲用 ReplicateLog 重新发送 LogRequest 消息2。\nLeader committing log entries 被 quorum 确认过的 log 条目就可以被 leader 提交,当 log 条目被提交,消息就被传递给 Application。\n参考上一个步骤中 prefixTerm 的来源\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n这里算法是可以优化的,来减少重试次数。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://sheerwill.xyz/posts/main/20220623152601-raft_consensus_algorithm/","summary":"Figure 1: node-state-transitions-in-raft 一个节点有三种状态:leader,candidate 和 follower。当一个节点第一次运行,或者崩溃后恢复时,处于 follower 的状态并且等待其","title":"Raft consensus algorithm"},{"content":"Install brew install hugo Add backlink 在 themes/xxx/layouts/partials/ 下新增 backlink.html,内容如下。\n{{ $re := $.File.BaseFileName }} {{ $backlinks := slice }} {{ range .Site.AllPages }} {{ if and (findRE $re .RawContent) (not (eq $re .File.BaseFileName)) }} {{ $backlinks = $backlinks | append . }} {{ end }} {{ end }} {{ if gt (len $backlinks) 0 }} \u0026lt;div class=\u0026#34;bl-section\u0026#34;\u0026gt; \u0026lt;h4\u0026gt;Links to this note\u0026lt;/h4\u0026gt; \u0026lt;div class=\u0026#34;backlinks\u0026#34;\u0026gt; \u0026lt;ul\u0026gt; {{ range $backlinks }} \u0026lt;li\u0026gt;\u0026lt;a href=\u0026#34;{{ .RelPermalink }}\u0026#34;\u0026gt;{{ .Title }}\u0026lt;/a\u0026gt;\u0026lt;/li\u0026gt; {{ end }} \u0026lt;/ul\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; {{ else }} \u0026lt;div class=\u0026#34;bl-section\u0026#34;\u0026gt; \u0026lt;h4\u0026gt;No notes link to this note\u0026lt;/h4\u0026gt; \u0026lt;/div\u0026gt; {{ end }} 在 themes/xxx/layouts/_default/single.html 中增加下面代码,放到合适的地方。\n{{ partial \u0026#34;backlinks.html\u0026#34; . }} 放在文章下方的话可以插入如下位置\n\u0026lt;div class=\u0026#34;post-content\u0026#34;\u0026gt; {{- if not (.Param \u0026#34;disableAnchoredHeadings\u0026#34;) }} {{- partial \u0026#34;anchored_headings.html\u0026#34; .Content -}} {{- else }}{{ .Content }}{{ end }} {{ partial \u0026#34;backlinks.html\u0026#34; . }} \u0026lt;/div\u0026gt; MathJax support 方案1 在 themes/xxx/layouts/_default/single.html 中 header 中增加下面代码。\n\u0026lt;script type=\u0026#34;text/javascript\u0026#34; src=\u0026#34;https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML\u0026#34;\u0026gt; \u0026lt;/script\u0026gt; 方案2 增加 themes/xxx/layouts/partials/mathjax.html 文件,并在其中增加下面代码,实现即便是页面间的跳转,也可以实时渲染公式。\n\u0026lt;script\u0026gt; MathJax = { tex: { inlineMath: [[\u0026#39;$\u0026#39;, \u0026#39;$\u0026#39;], [\u0026#39;\\\\(\u0026#39;, \u0026#39;\\\\)\u0026#39;]], displayMath: [[\u0026#39;$$\u0026#39;,\u0026#39;$$\u0026#39;], [\u0026#39;\\\\[\u0026#39;, \u0026#39;\\\\]\u0026#39;]], processEscapes: true, processEnvironments: true }, options: { skipHtmlTags: [\u0026#39;script\u0026#39;, \u0026#39;noscript\u0026#39;, \u0026#39;style\u0026#39;, \u0026#39;textarea\u0026#39;, \u0026#39;pre\u0026#39;] } }; window.addEventListener(\u0026#39;load\u0026#39;, (event) =\u0026gt; { document.querySelectorAll(\u0026#34;mjx-container\u0026#34;).forEach(function(x){ x.parentElement.classList += \u0026#39;has-jax\u0026#39;}) }); \u0026lt;/script\u0026gt; \u0026lt;script src=\u0026#34;https://polyfill.io/v3/polyfill.min.js?features=es6\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script type=\u0026#34;text/javascript\u0026#34; id=\u0026#34;MathJax-script\u0026#34; async src=\u0026#34;https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;style\u0026gt; code.has-jax { font: inherit; font-size: 100%; background: inherit; border: inherit; color: #515151; } \u0026lt;/style\u0026gt; 在 themes/xxx/layouts/partials/header.html 中增加下面代码,放到合适的地方。\n{{ partial \u0026#34;mathjax.html\u0026#34; . }} 如果不想所有页面都加载 MathJax,就添加下面代码。\n{{ if .Params.mathjax }}{{ partial \u0026#34;mathjax.html\u0026#34; . }}{{ end }} 并通过 Custom Front-matter Parameters 设置 mathjax: true。具体是添加下面代码到需要导出并用 MathJax 展示的 org 文件头部,导出后就是 .md 文件中的 metadata。\n#+HUGO_CUSTOM_FRONT_MATTER: :mathjax true 另外对于一些用到额外包以及复杂的块 Latex,MathJax 的支持并不是很好。可以将块导出为 svg,即在头部增加 #+options: tex:dvisvgm 就可以了。\n增强 MathJax 对于 algorithm2e 等一些算法包不支持,可以通过 pseudocode.js 支持页面渲染!\n","permalink":"https://sheerwill.xyz/posts/main/20220607104757-hugo/","summary":"Install brew install hugo Add backlink 在 themes/xxx/layouts/partials/ 下新增 backlink.html,内容如下。 {{ $re := $.File.BaseFileName }} {{ $backlinks := slice }} {{ range .Site.AllPages }} {{ if and (findRE $re .RawContent) (not (eq $re .File.BaseFileName)) }} {{ $backlinks = $backlinks | append . }} {{ end }} {{ end }}","title":"Hugo"},{"content":"References pass : 密码管理本不复杂 - Nayuki\u0026rsquo;s Archive Home · mssun/passforios Wiki · GitHub https://github.com/benthamite/dotfiles/blob/master/emacs/config.org#password-store Pass: The Standard Unix Password Manager Source: Setting up pass on git with a gpg key · GitHub Pass for iOS private repos 需要用 SSH Key 来访问。参考 Generating a new SSH key and adding it to the ssh-agent - GitHub Docs 生成后使用 pbcopy \u0026lt; ~/.ssh/id_ed25519.pub 命令复制公钥配置到 GitHub。刚刚同时生成的 id_ed25519 导入到 Pass for iOS 中拉取 repo。\ngpg --export-secret-keys -a \u0026lt;pub_key\u0026gt; \u0026gt; gpg_key gpg --export -a \u0026lt;pub_key\u0026gt; \u0026gt; gpg_key.pub 根据上述命令导出公私钥,导入到 Pass for iOS。\n新电脑设置 导入密钥 \u0026ndash;\u0026gt; gpg --import gpg_key,当导入新的密钥后出现以下提示\nIt is NOT certain that the key belongs to the person named in the user ID. If you really know what you are doing, you may answer the next question with yes.\n可以通过以下步骤验证信任\ngpg --edit-key YOUR@KEY.ID gpg\u0026gt; trust Please decide how far you trust this user to correctly verify other users\u0026#39; keys (by looking at passports, checking fingerprints from different sources, etc.) 1 = I don\u0026#39;t know or won\u0026#39;t say 2 = I do NOT trust 3 = I trust marginally 4 = I trust fully 5 = I trust ultimately m = back to the main menu Your decision? 5 Do you really want to set this key to ultimate trust? (y/N) y Remarks 当提示 gpg: waiting for lock (held by 5175) 可以用 $ ls -l ~/.gnupg/*.lock 查看相关的锁文件,删除即可。\n","permalink":"https://sheerwill.xyz/posts/main/20240114084921-setting_up_pass_on_git_with_a_gpg_key/","summary":"References pass : 密码管理本不复杂 - Nayuki\u0026rsquo;s Archive Home · mssun/passforios Wiki · GitHub https://github.com/benthamite/dotfiles/blob/master/emacs/config.org#password-store Pass: The Standard Unix Password Manager Source: Setting up pass on git with a gpg key · GitHub Pass for iOS private repos 需要用 SSH Key 来访问。参考 Generating a new SSH key and adding it to the ssh-agent - GitHub Docs 生","title":"setting up pass on git with a gpg key"},{"content":"从 FreeMdict Forum 下载的扫描电子书,发帖人提到了书签文件可以用来查找,这个想法是我之前没有想到的。尝试用 pdftk-java 来导入书签文件。\nbrew install pdftk-java 书签文件如下的格式,相同页码的单词数字相同。\n@\t1 a or an\t1 a-/an-\t2 -a\t2 a fortiori\t3 à la\t3 à la carte\t4 a posteriori\t4 a priori\t4 abacus\t4 abbreviations\t4 abide and abode\t6 -ability\t6 ablative\t6 able and able to\t7 -able/-ible\t7 abled\t8 abolition or abolishment\t8 Aboriginal and Aborigine\t8 about, about to, and not about to\t9 about face or about turn\t9 abridgement or abridgment\t9 abscissa\t9 absent\t10 absolute\t10 abstract nouns\t11 academia, academe and academy\t11 accents and diacritics\t12 acceptance or acceptation\t13 accessory or accessary\t13 accidentally or accidently\t13 acclaim\t13 accommodation, accomodation and accommodations\t14 accompanist or accompanyist\t14 accusative\t14 ACE\t14 -acious/-aceous\t14 acknowledgement or acknowledgment\t14 acro-\t15 acronyms\t15 active verbs\t16 acuity or acuteness\t16 acute accents\t16 ad or advert\t16 AD or A.D.\t17 ad hoc, ad-hoc and adhoc\t17 ad hominem\t18 ad infinitum\t18 ad lib, ad-lib or adlib\t18 ad personam\t18 ad rem\t18 adage\t18 adaptation or adaption\t18 adapter or adaptor\t19 addendum\t19 addition or additive\t19 addresses\t19 adherence or adhesion\t19 adieu\t19 adjacent, adjoining and adjunct\t20 在实际处理中发现几个问题\n导入的书签一页只能对应一个书签 # 请将 \u0026#39;input.txt\u0026#39; 替换为你的输入文件名 input_file_name = \u0026#39;input.txt\u0026#39; # 请将 \u0026#39;output.txt\u0026#39; 替换为你的输出文件名 output_file_name = \u0026#39;output.txt\u0026#39; def merge_lines_with_same_number(lines): merged_lines = {} for line in lines: parts = line.split(\u0026#39;\\t\u0026#39;) if len(parts) == 2: text, number = parts number = number.strip() text = text.strip() if number in merged_lines: merged_lines[number].append(text) else: merged_lines[number] = [text] return merged_lines def write_merged_lines_to_file(merged_lines, output_file): for number, texts in merged_lines.items(): merged_text = \u0026#39; | \u0026#39;.join(texts) output_file.write(f\u0026#34;{merged_text}\\t{number}\\n\u0026#34;) with open(input_file_name, \u0026#39;r\u0026#39;) as input_file: lines = input_file.readlines() merged_lines = merge_lines_with_same_number(lines) with open(output_file_name, \u0026#39;w\u0026#39;) as output_file: write_merged_lines_to_file(merged_lines, output_file) print(f\u0026#34;已将每行后面数字相同的英文内容合并,并用\u0026#39;|\u0026#39;分隔,并保存为\u0026#39;{output_file_name}\u0026#39;\u0026#34;) 导出的内容需要处理成以下格式,其中 PageMediaBegin 往下的内容都不是必须的,头部的信息是必须的,是我在 PDF Expert 中添加了一个书签后导出的格式。 InfoBegin InfoKey: ModDate InfoValue: D:20231110091307+08\u0026amp;apos;00\u0026amp;apos; InfoBegin InfoKey: CreationDate InfoValue: D:20231107093455Z InfoBegin InfoKey: Producer InfoValue: macOS Version 13.6.1 (Build 22G313) Quartz PDFContext, AppendMode 1.1 PdfID0: 27ee54f1394bde6950015ebab5958d48 PdfID1: a0546a75c44ec4b30b12873737127f5b NumberOfPages: 793 BookmarkBegin BookmarkTitle: My Bookmarks BookmarkLevel: 1 BookmarkPageNumber: 0 BookmarkBegin BookmarkTitle: also BookmarkLevel: 2 BookmarkPageNumber: 54 PageMediaBegin PageMediaNumber: 1 PageMediaRotation: 0 PageMediaRect: 0 0 3,500 5,325.673 PageMediaDimensions: 3,500 5,325.673 上面书签的格式和论坛提供的书签不一致,需要处理成这样的格式。 # 请将 \u0026#39;input.txt\u0026#39; 替换为你的输入文件名 input_file_name = \u0026#39;input.txt\u0026#39; # 请将 \u0026#39;output.txt\u0026#39; 替换为你的输出文件名 output_file_name = \u0026#39;output.txt\u0026#39; def convert_to_bookmark(line, page_number): # 格式化为Bookmark文本 return f\u0026#34;BookmarkBegin\\nBookmarkTitle: {line.strip()}\\nBookmarkLevel: 2\\nBookmarkPageNumber: {page_number}\\n\u0026#34; with open(input_file_name, \u0026#39;r\u0026#39;) as input_file: lines = input_file.readlines() bookmarks = [convert_to_bookmark(line, i+1) for i, line in enumerate(lines)] with open(output_file_name, \u0026#39;w\u0026#39;) as output_file: output_file.writelines(bookmarks) print(f\u0026#34;已将每一行转换为Bookmark格式,并保存为\u0026#39;{output_file_name}\u0026#39;\u0026#34;) 书签导入并不会按照对应的页码,这里我开始意识到和我理解的 bookmarks 不是一个东西, pdftk-java 添加的应该是 Ouline。因此需要将错位的条目校对好以及填充好头部,这样才会让 Outline 与页面一一对应。 封面 1 封面黑白 2 出版信息 3 目录 4 出版说明 5 编译者序 6 编译者序 7 著者前言 8 著者前言 9 著者前言 10 内容提要、体例及查询方法 11 内容提要、体例及查询方法 12 空白页 13 @ | a or an\t14 a-/an- | -a\t15 a fortiori | à la\t16 à la carte | a posteriori | a priori | abacus | abbreviations\t17 abbreviations 18 abide and abode | -ability | ablative\t19 able and able to | -able/-ible\t20 abled | abolition or abolishment | Aboriginal and Aborigine\t21 about, about to, and not about to | about face or about turn | abridgement or abridgment | abscissa\t22 absent | absolute\t23 最终处理后的结果是,可以非常方便地检索扫描件当中的内容了。 Figure 1: pdf-java-example\nSource: Create bookmarks for your PDF with pdftk | Opensource.com\n2023-11-13 更新\n原来是可以都设置成一级 bookmark,同时对于开始的位置作设置。\n","permalink":"https://sheerwill.xyz/posts/main/20231111222839-importing_bookmark_files_for_scanned_pdfs/","summary":"从 FreeMdict Forum 下载的扫描电子书,发帖人提到了书签文件可以用来查找,这个想法是我之前没有想到的。尝试用 pdftk-java 来导入书签文件。 brew install pdftk-java 书签文件如下的格式,相同","title":"Importing Bookmark Files for Scanned PDFs"},{"content":"得益于 Emacs 的扩展性,丰富的插件生态,我构建的笔记和 GTD 流程是杂糅在一起的。并没有完全遵照「卡片笔记法」或 GTD 的流程,其中也有不完善的地方,在不断地使用中对流程进行改动,细微处调整。\n笔记流程和 GTD 杂糅在一起,就意味着他们必须能够循环自洽,其中自动的步骤是黏合剂,手动的部分是思考的平台。\n输入先不谈杂七杂八的各种途径,比较规整的输入途径有 RSS 和书籍。其中 RSS 可以订阅各类博客、YouTube 等,Podcast 其实也可以在 Emacs 当中听,但一般都是在非工作时间才会听 Podcast,这里略过不谈。\n博客就单纯通过 elfeed 订阅,elfeed 本身没有查看订阅列表以及每个订阅源最近的更新时间,但是 Emacs 最自由的就是可以通过自定义函数实现。其中我根据不同的 tag 加了不同的 face,可以理解成 CSS 当中对于页面元素添加了样式,这样可以更为专注地分类阅读。\nFigure 1: elfeed-list\n在这之前书籍和 RSS 的处理是通过 Readwise Reader 来进行处理的,原因有二。\n统一了 PDF 和 ePub 的阅读体验 可以配合 immersive-translate 快速的阅读英文文章和书籍,决定是否需要精读。(不看好 GPT 的总结,以及总结本身就是思考的过程,这一步不应该被 AI 替代。) 但也有一些缺点,导出的自定义并不能满足细分场景,跟官方沟通后无果,就动了迁移的心思。刚好 Emacs 有个插件 emacs-immersive-translate 可以实现类似浏览器中「沉浸式翻译」的效果,因此 RSS 和书籍都重新迁移到 Emacs 当中。emacs-immersive-translate 我使用的是 OpenAI 的 API 进行翻译,感觉更加自然准确一点。\n另外巧合发现 Karthink 的另外一个包 elfeed-tube 可以增强 elfeed 中订阅 YouTube 的体验,通过 elfeed-tube-fetch 按照时间顺序拉取字幕,配合 emacs-immersive-translate 就可以快速的浏览视频内容,再决定哪部分需要详细观看。详细观看时,不需要打开浏览器,拉到对应的时间观看。只需要开启 elfeed-tube-mpv-follow-mode 并用 elfeed-tube-mpv 打开视频观看,这些都可以快速的用键盘操作完成,点击对应的字幕就可以跳转到需要详细观看的部分。\n接下来,不管是 YouTube 还是博客文章,处理过程都是一致的。「摘录-笔记」的结构写在 daily log 文件中(成体系的系列视频或者文章参考下面书籍的处理办法),比如写作日期的当天就是 2023-08-02.org 这个 org 文件。这个文件当中分为了几个部分。\nTask 是当天完成的 To-Dos What I read? 阅读文章的笔记 log 的主题,包含了工作问题的解决思路,备忘(会加 tag 方便检索),Fleeting Notes 等等,每一条的前面都会有记录的时间点。 每天结束的时候,都会重新回顾一下当天的 daily log 文件,记录阅读的文章以及记录的工作内容时都会根据内容添加 tag,在遇到相同类别的内容需要整理时,才会整理到具体的笔记当中。不管需不需要整理出具体的笔记,当天的 log 文件都会按照当前的结构归档(结构归档就是按照原来文件内的排版结构,具体到在某个标题下的 To-Dos 归档后依旧在对应的标题下)到 journal.org 文件中,即当前年的 log 是归档到 journal.org;过去一年后,比如过去的 2022 年,就有一个 2022.org 文件,未来还会有 2023.org 等。\n至此,RSS 讲完,书籍(包含 PDF/ePub)都是用 Ebib 进行管理,Ebib 主要是管理 bibtex 的包,其中可以添加对应书籍的位置,因此也可以用来管理书籍,与很多人常用的 Zotero 在功能上很像。不需要作笔记的书籍就随便很多,不管是在 Kindle 还是微信读书,或 iOS 自带的 iBook 都可以阅读。\nFigure 2: elfeed-list\n这里的 bibtex 来源可以是 Zotero 拉取的,也可以是手动下面网站拉取的。\nLead2Amazon.com\nZoteroBib\ndoi2bib\nBibSonomy\nBibTeX Search\nGoogle Scholar\nACM\nIEEE Xplore\nebib 当中可以通过 ebib-add-reading-list-item 添加具体的书目到 books.org 当中,books.org 是一个 org 文件,纳入到 org-agenda 文件下。这里解释一下,GTD 事项管理物理上分为两大部分,inbox.org 和 books.org 等按照事项不同分类的文件(比如还有 work.org)。\nbooks.org 当中也按照书籍不同的分类,通过 refile 调整到不同的标题下。ebib 当中可以自定义很多东西,比如添加到 books.org 中的书目的待办事项关键字默认是 TODO,我自定义成 PROJECT 关键字。将书籍的目录手动复制到待办事项下,以 checkbox 的形式存在,这样读完一个章节可以勾去一条,同时在事项的最后会有百分比显示进度。\nFigure 3: todo-item\n这里的读书书目,并不是每个都会添加到 agenda view 当中,只有添加了 reading tag 的书目才会显示,agenda view 也是有相当高的自定义空间的。为了更方便的查看某书目的阅读已经创建了多久,自定义了函数,在 agenda view 中会在前显示已创建的时长,比如下图的 11.7M,M 是 Month 的含义。\nFigure 4: agenda-view\n回到 ebib 上来,通过 ebib-popup-note 可以创建对应书籍的笔记,这里笔记的创建模板也是做了自定义的,更加符合个人的习惯。笔记内容同样是手动复制的目录,按照「总结-摘抄-笔记」的结构。摘抄-笔记的结构好理解,总结就是读完一本书,会针对内容进行总结,形式是 QUE-Question,CLM-Claim,EVD-Evidence,同时也参考了「卡片笔记法」,对于会被其他笔记引用多次的内容会从中抽离出来,单独成卡。\nFigure 5: discourse-graph-example\n笔记使用的是 org-roam,更类似于 obsidian 的组织方式;阅读 PDF 的工具是 pdf-tools,ePub 的工具是 nov.el,后者也可以通过 emacs-immersive-translate 进行翻译,阅读英文书籍的速度就提高了很多。其中 nov.el 阅读 ePub 时,尤其是有批注的经典文学作品,我就很喜欢在 Calibre 内用正则替换一下原有的批注 HTML 标签,改为『』包裹,在 nov-mode 下根据 font-lock 设置不同于正文的样式,就很好区分正文和批注了,不会看上去都是黑色的字体。\nFigure 6: nov-font-lock\n笔记的漫游,是交给了 org-roam-ui,当初入坑 Emacs 就是因为这个包,逐渐建立起来了现在的一整套流程,可以算作从尾巴推全局了。\nFigure 7: org-roam-ui\n「杂七杂八的输入源」以及手机端和稍后读目前都是通过 Telegram 的 Saved messages,不管是文章、视频还是书籍,处理的逻辑都与上面相同。\n下面来着重讲一下上面提到的 GTD 部分。通过快捷键,Emacs 无论处于哪个工作流程,都可以快速的记录 To-Dos 到 inbox.org 中,然后整理到各自分类的 org 文件当中,这些文件都在 org-agenda 管辖当中。\n不管是 books.org 还是各种 To-Dos 分类的 org 文件,在完成后(关键字变成 DONE)都会自动复制到当天的 daily log 中的 Task 标题下,方便一天结束的时候进行回顾。这些完成的 To-Dos 放在文件中其实也会搅扰到视觉,因此我会手动归档到同目录的子文件夹下的同名 org 文件中,同样的是按照结构归档。\n对于跨度小,一天内能完成的 To-Dos 通常会进行计时,这里也做了自定义。需要开始做一个 To-Do 时,通过 org-clock-in 开始计时,同时关键字由 TODO 自动切换成 NEXT,完成时切换关键字 NEXT 为 DONE 时自动结束计时。如果不需要计时,正常的关键字切换流程也不影响。\n额外的一些在办公室能够完成的习惯跟踪,我也写在 agenda 当中,绿色代表完成,黄色和红色是未完成。黄色是指距离上次完成还不远,只是警告的含义。\n其实在 Emacs 传教作笔记以及 GTD 的时候,很多人最多提出的质疑就是和手机端的联动。笔记方面我使用的是 Plain Org for iOS,可以方便的查看 Dropbox 同步的内容。To-Dos 的同步则是用的 beorg,从 Mac 端同步到 iOS 后,继而同步到 Calendar 中,Fantastical 当中就可以展示,同时 iCloud 的同步下 Mac 端的 Fantastical 也可以同步展示。\n从 iOS 端到 Mac 端,也可以通过 beorg 来添加内容到 inbox.org,同步到 Mac 后再 refile 分配到其他分类的 org 文件。(iOS 端其实也可以很方便的直接把 To-Dos 创建到对应分类 org 的某个标题下。)如此这样就形成了闭环。\n","permalink":"https://sheerwill.xyz/posts/main/20230801192723-note_taking_and_gtd_with_emacs/","summary":"得益于 Emacs 的扩展性,丰富的插件生态,我构建的笔记和 GTD 流程是杂糅在一起的。并没有完全遵照「卡片笔记法」或 GTD 的流程,其中也有不完善的地方,在不断地","title":"Note-taking and GTD with Emacs"},{"content":"Misc Templatemaker\n制作各种纸盒的教程,可以选择对应的例子下载剪裁的 PDF。\nWi-Phi – Wireless Philosophy\n很赞的网站:Wireless Philosophy,简称 Wi-Phi ,一个开放的哲学网站,致力于以轻松有趣的动画形式阐释哲学问题及哲学思维方式。建于2013年,现在已经有105个哲学动画上线了。\nAnimagraffs - Animated infographics about everything.\n设计师和艺术家 Jacob O’Neal,把平面设计、3D 动画、充满趣味的表现手法、对各种事物运行原理的极大好奇,全部融进了他的作品。\nWriting WeChat Markdown Editor\n一款高度简洁的微信 Markdown 编辑器:支持 Markdown 语法、色盘取色、多图上传、一键下载文档、自定义 CSS 样式、一键重置等特性。\n查找 citations 的网站 zoterobib\nYour bibliography is empty. To add a source, paste or type its URL, ISBN, DOI, PMID, arXiv ID, or title into the search box above\ndoi2bib\ngive us a DOI and we will do our best to get you the BibTeX entry\nBibSonomy\nThe easy way to manage scientific publications and bookmarks\nBibSonomy helps you to manage your publications and bookmarks, to collaborate with your colleagues and to find new interesting material for your research.\nBibTeX Search\nYou\u0026rsquo;re searching over millions of BibTeX records\u0026hellip;\nGoogle Scholar\n站在巨人的肩膀上\nBook For books I usually use a site, where it is possible to get Bibtex citations from Amazon.com articles. This is very good for books, and some inproceedings and incollections might be found here as well.\nAmazon Article As a Software Engineer I quite often have to deal with technical papers from ACM or IEEE. Both their catalogs provide Bibtex export capabilities.\nACM\nIEEE Xplore\nAdvancing Technology for Humanity\nBook Library Genesis\n电子书资源查找,中英文都有,英文相对较多。\nEbook Reader for web\n在线阅读 PDF、azw3、epub、mobi 几种格式。\n漢川草廬\n一個文史哲愛好者所建立的繁體中文古籍網站\nChinese Text Project\n可以看到原文图片\n书格\n有品格的数字古籍图书馆, 书格\nzi.tools 字統网\n字典\n萌典 – 教育部國語、臺語、客語辭典民間版\n字典\nVideo BBC Sound effects\nBBC 提供的免费下载的音效的网站。\nMedicine 默沙东诊疗手册\n顾名思义,权威的诊疗查询手册。\nDesign Color Picker 日本の伝統色\n日本的一个颜色拾取网站,很漂亮。\nPlantUML Real World PlantUML\nPlantUML 例子\nFront-End codepen\nThe best place to build, test, and discover front-end code\n","permalink":"https://sheerwill.xyz/posts/main/20220506185941-pinboard/","summary":"Misc Templatemaker 制作各种纸盒的教程,可以选择对应的例子下载剪裁的 PDF。 Wi-Phi – Wireless Philosophy 很赞的网站:Wireless Philosophy,简称 Wi-Phi ,一个开放的哲学","title":"Pinboard"},{"content":" Some databases can indeed use an index as primary table store. The Oracle database calls this concept index-organized tables (IOT), other databases use the term clustered index. In this section, both terms are used to either put the emphasis on the table or the index characteristics as needed.\nAn index-organized table is thus a B-tree index without a heap table. This results in two benefits: (1) it saves the space for the heap structure; (2) every access on a clustered index is automatically an index-only scan.\n索引组织表以主键顺序组织物理数据存储,索引结构中的每个叶节点(Leaf Nodes)都存储着完整的行数据。对应的主键被称为聚簇索引(Clustered Indexes)(若没有定义主键,则第一个 NOT NULL UNIQUE 列是聚集索引;若没有 NOT NULL UNIQUE 列,InnoDB 会创建一个隐藏的 row-id 作为聚集索引。),除此之外的索引称为二级索引(Secondary Indexes)。\nFigure 1: b-tree-structure\nFigure 2: index-based-access-on-a-heap-table\nB-Tree 的上级 leaf nodes 存储的是下级 leaf nodes 中列值的最大值,可以从图中看出最底层的 leaf nodes 存储的是 ROWID,可以据此查找到堆表中的位置。\nFigure 3: secondary-index-on-an-IOT\n索引组织表的缺点 图 secondary-index-on-an-IOT 就是索引组织表中,二级索引获取数据的流程。这个流程在中文中叫做「回表(Table access by index rowid)」。\n索引组织表的好处 可以节省堆表占用的空间 访问聚簇索引可以直接获取数据(即「覆盖索引(covering index)」) 二级索引需要回表,即需要根据二级索引的叶节点内的主键,再对主键形成的聚簇索引进行遍历,查找到具体的记录。 ","permalink":"https://sheerwill.xyz/posts/main/20230210155015-clustered_indexes_index_organized_tables_iot/","summary":"Some databases can indeed use an index as primary table store. The Oracle database calls this concept index-organized tables (IOT), other databases use the term clustered index. In this section, both terms are used to either put the emphasis on the table or the index characteristics as needed. An index-organized table is thus a B-tree index without a heap table. This results in two benefits: (1) it saves the space","title":"Clustered Indexes / Index Organized Tables (IOT)"},{"content":" 除主键外的其他列上新建的索引都是二级索引。 二级索引的排序与物理记录的排序不一致,存储的是该列本身排序后的值和主键。 ","permalink":"https://sheerwill.xyz/posts/main/20230210235436-secondary_indexes/","summary":"除主键外的其他列上新建的索引都是二级索引。 二级索引的排序与物理记录的排序不一致,存储的是该列本身排序后的值和主键。","title":"Secondary Indexes"},{"content":"遍历二级索引树找到对应的主键后,再从索引组织表中查找数据所在的叶节点叫做「回表」。在英文中没有特定的词表示这个过程,叫做 table access by index rowidi。\n","permalink":"https://sheerwill.xyz/posts/main/20230211000136-table_access_by_index_rowid/","summary":"遍历二级索引树找到对应的主键后,再从索引组织表中查找数据所在的叶节点叫做「回表」。在英文中没有特定的词表示这个过程,叫做 table access by index rowidi","title":"Table access by index rowid"},{"content":"即可以直接通过遍历聚簇索引获取到非主键列数据的场景,即不需要「回表(Table access by index rowid)」操作。覆盖索引一般出现在「联合索引」中,因为叶节点中会存储「联合索引」所有列的数据(索引即数据)。\n","permalink":"https://sheerwill.xyz/posts/main/20230211001746-covering_index/","summary":"即可以直接通过遍历聚簇索引获取到非主键列数据的场景,即不需要「回表(Table access by index rowid)」操作。覆盖索引一般出现在「联合索引」中,因","title":"covering index"},{"content":"Introduction Emacs outshines all other editing software in approximately the same way that the noonday sun does the stars. It is not just bigger and brighter; it simply makes everything else vanish.\n– Neal Stephenson, In the Beginning was the Command Line (1998)\nInstall Emacs-plus With Homebrew brew tap d12frosted/emacs-plus brew install emacs-plus --with-native-comp --with-modern-vscode-icon ln -s /usr/local/opt/emacs-plus@29/Emacs.app /Applications Build Emacs on UbuntuLucius\u0026rsquo;s Workflow git clone git://git.sv.gnu.org/emacs.git sudo apt install build-essential libgtk-3-dev libgnutls28-dev libtiff5-dev libgif-dev libjpeg-dev libpng-dev libxpm-dev libncurses-dev texinfo autoconf cd emacs ./autogen.sh ./configure make -j8 sudo make install telega 中需要 svg 和 webp 的支持\nsudo apt-get install -y librsvg2-dev sudo apt-get install webp Ubuntu 上的 Dropbox 参考 Install - Dropbox,目前的情况是只有命令行启动的客户端才可以同步成功。\ncd ~ \u0026amp;\u0026amp; wget -O - \u0026#34;https://www.dropbox.com/download?plat=lnx.x86_64\u0026#34; | tar xzf - ~/.dropbox-dist/dropboxd 为了使用方便使用,进行了改键,通过下面的命令把 CAPS(66) 和 LCTRL(37) 互换。\nsudo gedit /us r/share/X11/xkb/keycodes/evdev 输入法安装 fcitx-rime,和 MacOS 上的配置通用,放在 ~/.config/fcitx/rime。\nsudo apt-get install fcitx-rime Personal Settings 目前在 purcell 中就增加或修改了这些文件。\n. ├── lisp ├── ligature.el ├── init-org.el ├── init-sis.el ├── init-rime.el ├── init-font.el ├── init-meow.el ├── beancount.el ├── init-elpa.el ├── init-local.el ├── init-latex.el ├── init-corfu.el ├── init-bibtex.el ├── init-themes.el ├── init-elfeed.el ├── init-telega.el ├── init-translate.el ├── init-org-modern.el ├── init-quick-menus.el ├── init-tree-sitter.el ├── init-org-enhance.el ├── telega-bridge-bot.el └── init-org-transclusion.el ligature.el GitHub - mickeynp/ligature.el: Display typographical ligatures in Emacs,这里是将其中的 ligature.el 下载到 .emacs.d/lisp/ 目录下,加上下列代码。\n;; Enable the www ligature in every possible major mode (ligature-set-ligatures \u0026#39;t \u0026#39;(\u0026#34;www\u0026#34;)) ;; Enable ligatures in programming modes (ligature-set-ligatures \u0026#39;prog-mode \u0026#39;(\u0026#34;www\u0026#34; \u0026#34;**\u0026#34; \u0026#34;***\u0026#34; \u0026#34;**/\u0026#34; \u0026#34;*\u0026gt;\u0026#34; \u0026#34;*/\u0026#34; \u0026#34;\\\\\\\\\u0026#34; \u0026#34;\\\\\\\\\\\\\u0026#34; \u0026#34;{-\u0026#34; \u0026#34;::\u0026#34; \u0026#34;:::\u0026#34; \u0026#34;:=\u0026#34; \u0026#34;!!\u0026#34; \u0026#34;!=\u0026#34; \u0026#34;!==\u0026#34; \u0026#34;-}\u0026#34; \u0026#34;----\u0026#34; \u0026#34;--\u0026gt;\u0026#34; \u0026#34;-\u0026gt;\u0026#34; \u0026#34;-\u0026gt;\u0026gt;\u0026#34; \u0026#34;-\u0026lt;\u0026#34; \u0026#34;-\u0026lt;\u0026lt;\u0026#34; \u0026#34;-~\u0026#34; \u0026#34;#{\u0026#34; \u0026#34;#[\u0026#34; \u0026#34;##\u0026#34; \u0026#34;###\u0026#34; \u0026#34;####\u0026#34; \u0026#34;#(\u0026#34; \u0026#34;#?\u0026#34; \u0026#34;#_\u0026#34; \u0026#34;#_(\u0026#34; \u0026#34;.-\u0026#34; \u0026#34;.=\u0026#34; \u0026#34;..\u0026#34; \u0026#34;..\u0026lt;\u0026#34; \u0026#34;...\u0026#34; \u0026#34;?=\u0026#34; \u0026#34;??\u0026#34; \u0026#34;;;\u0026#34; \u0026#34;/*\u0026#34; \u0026#34;/**\u0026#34; \u0026#34;/=\u0026#34; \u0026#34;/==\u0026#34; \u0026#34;/\u0026gt;\u0026#34; \u0026#34;//\u0026#34; \u0026#34;///\u0026#34; \u0026#34;\u0026amp;\u0026amp;\u0026#34; \u0026#34;||\u0026#34; \u0026#34;||=\u0026#34; \u0026#34;|=\u0026#34; \u0026#34;|\u0026gt;\u0026#34; \u0026#34;^=\u0026#34; \u0026#34;$\u0026gt;\u0026#34; \u0026#34;++\u0026#34; \u0026#34;+++\u0026#34; \u0026#34;+\u0026gt;\u0026#34; \u0026#34;=:=\u0026#34; \u0026#34;==\u0026#34; \u0026#34;===\u0026#34; \u0026#34;==\u0026gt;\u0026#34; \u0026#34;=\u0026gt;\u0026#34; \u0026#34;=\u0026gt;\u0026gt;\u0026#34; \u0026#34;\u0026lt;=\u0026#34; \u0026#34;=\u0026lt;\u0026lt;\u0026#34; \u0026#34;=/=\u0026#34; \u0026#34;\u0026gt;-\u0026#34; \u0026#34;\u0026gt;=\u0026#34; \u0026#34;\u0026gt;=\u0026gt;\u0026#34; \u0026#34;\u0026gt;\u0026gt;\u0026#34; \u0026#34;\u0026gt;\u0026gt;-\u0026#34; \u0026#34;\u0026gt;\u0026gt;=\u0026#34; \u0026#34;\u0026gt;\u0026gt;\u0026gt;\u0026#34; \u0026#34;\u0026lt;*\u0026#34; \u0026#34;\u0026lt;*\u0026gt;\u0026#34; \u0026#34;\u0026lt;|\u0026#34; \u0026#34;\u0026lt;|\u0026gt;\u0026#34; \u0026#34;\u0026lt;$\u0026#34; \u0026#34;\u0026lt;$\u0026gt;\u0026#34; \u0026#34;\u0026lt;!--\u0026#34; \u0026#34;\u0026lt;-\u0026#34; \u0026#34;\u0026lt;--\u0026#34; \u0026#34;\u0026lt;-\u0026gt;\u0026#34; \u0026#34;\u0026lt;+\u0026#34; \u0026#34;\u0026lt;+\u0026gt;\u0026#34; \u0026#34;\u0026lt;=\u0026#34; \u0026#34;\u0026lt;==\u0026#34; \u0026#34;\u0026lt;=\u0026gt;\u0026#34; \u0026#34;\u0026lt;=\u0026lt;\u0026#34; \u0026#34;\u0026lt;\u0026gt;\u0026#34; \u0026#34;\u0026lt;\u0026lt;\u0026#34; \u0026#34;\u0026lt;\u0026lt;-\u0026#34; \u0026#34;\u0026lt;\u0026lt;=\u0026#34; \u0026#34;\u0026lt;\u0026lt;\u0026lt;\u0026#34; \u0026#34;\u0026lt;~\u0026#34; \u0026#34;\u0026lt;~~\u0026#34; \u0026#34;\u0026lt;/\u0026#34; \u0026#34;\u0026lt;/\u0026gt;\u0026#34; \u0026#34;~@\u0026#34; \u0026#34;~-\u0026#34; \u0026#34;~\u0026gt;\u0026#34; \u0026#34;~~\u0026#34; \u0026#34;~~\u0026gt;\u0026#34; \u0026#34;%%\u0026#34;)) (global-ligature-mode \u0026#39;t) init-org.el org-roam 和 Emacs 自带的 agenda 可以很方便的将笔记和 GTD 结合起来。\n;;; init-org.el --- Org-mode config -*- lexical-binding: t -*- ;;; Commentary: ;; Among settings for many aspects of `org-mode\u0026#39;, this code includes ;; an opinionated setup for the Getting Things Done (GTD) system based ;; around the Org Agenda. I have an \u0026#34;inbox.org\u0026#34; file with a header ;; including ;; #+CATEGORY: Inbox ;; #+FILETAGS: INBOX ;; and then set this file as `org-default-notes-file\u0026#39;. Captured org ;; items will then go into this file with the file-level tag, and can ;; be refiled to other locations as necessary. ;; Those other locations are generally other org files, which should ;; be added to `org-agenda-files-list\u0026#39; (along with \u0026#34;inbox.org\u0026#34; org). ;; With that done, there\u0026#39;s then an agenda view, accessible via the ;; `org-agenda\u0026#39; command, which gives a convenient overview. ;; `org-todo-keywords\u0026#39; is customised here to provide corresponding ;; TODO states, which should make sense to GTD adherents. ;;; Code: ;; 都是方便插入超链接的 (when *IS-MAC* (maybe-require-package \u0026#39;grab-mac-link)) (maybe-require-package \u0026#39;org-cliplink) ;; 图片宽度 (setq org-image-actual-width nil) (add-hook \u0026#39;org-mode-hook \u0026#39;org-indent-mode) ;; 去除代码块内的缩进 (setq org-edit-src-content-indentation 0) (setq org-src-preserve-indentation t) (define-key global-map (kbd \u0026#34;C-c l\u0026#34;) \u0026#39;org-store-link) (define-key global-map (kbd \u0026#34;C-c a\u0026#34;) \u0026#39;org-agenda) (defvar sanityinc/org-global-prefix-map (make-sparse-keymap) \u0026#34;A keymap for handy global access to org helpers, particularly clocking.\u0026#34;) (define-key sanityinc/org-global-prefix-map (kbd \u0026#34;j\u0026#34;) \u0026#39;org-clock-goto) (define-key sanityinc/org-global-prefix-map (kbd \u0026#34;l\u0026#34;) \u0026#39;org-clock-in-last) (define-key sanityinc/org-global-prefix-map (kbd \u0026#34;i\u0026#34;) \u0026#39;org-clock-in) (define-key sanityinc/org-global-prefix-map (kbd \u0026#34;o\u0026#34;) \u0026#39;org-clock-out) (define-key global-map (kbd \u0026#34;C-c o\u0026#34;) sanityinc/org-global-prefix-map) ;; Various preferences (setq org-log-done t org-edit-timestamp-down-means-later t org-hide-emphasis-markers t org-catch-invisible-edits \u0026#39;show org-export-coding-system \u0026#39;utf-8 org-fast-tag-selection-single-key \u0026#39;expert org-html-validation-link nil org-export-kill-product-buffer-when-displayed t org-tags-column 80) ;; Lots of stuff from http://doc.norang.ca/org-mode.html ;; Re-align tags when window shape changes (with-eval-after-load \u0026#39;org-agenda (add-hook \u0026#39;org-agenda-mode-hook (lambda () (add-hook \u0026#39;window-configuration-change-hook \u0026#39;org-agenda-align-tags nil t)))) ;;; Capturing (setq org-directory \u0026#34;~/Dropbox/org/\u0026#34;) (global-set-key (kbd \u0026#34;C-c c\u0026#34;) \u0026#39;org-capture) (setq org-capture-templates `((\u0026#34;i\u0026#34; \u0026#34;inbox\u0026#34; entry (file \u0026#34;agenda/inbox.org\u0026#34;) ,(concat \u0026#34;* TODO %?\\n%U\u0026#34;)) (\u0026#34;n\u0026#34; \u0026#34;note\u0026#34; entry (file \u0026#34;agenda/note.org\u0026#34;) \u0026#34;* %? :NOTE:\\n%U\\n%a\\n\u0026#34; :clock-resume t) )) ;;; Refiling (setq org-refile-use-cache nil) ;; Targets include this file and any file contributing to the agenda - up to 5 levels deep (setq org-refile-targets \u0026#39;((nil :maxlevel . 5) (org-agenda-files :maxlevel . 5))) (with-eval-after-load \u0026#39;org-agenda (add-to-list \u0026#39;org-agenda-after-show-hook \u0026#39;org-show-entry)) (advice-add \u0026#39;org-refile :after (lambda (\u0026amp;rest _) (org-save-all-org-buffers))) ;; Exclude DONE state tasks from refile targets (defun sanityinc/verify-refile-target () \u0026#34;Exclude todo keywords with a done state from refile targets.\u0026#34; (not (member (nth 2 (org-heading-components)) org-done-keywords))) (setq org-refile-target-verify-function \u0026#39;sanityinc/verify-refile-target) (defun sanityinc/org-refile-anywhere (\u0026amp;optional goto default-buffer rfloc msg) \u0026#34;A version of `org-refile\u0026#39; which allows refiling to any subtree.\u0026#34; (interactive \u0026#34;P\u0026#34;) (let ((org-refile-target-verify-function)) (org-refile goto default-buffer rfloc msg))) (defun sanityinc/org-agenda-refile-anywhere (\u0026amp;optional goto rfloc no-update) \u0026#34;A version of `org-agenda-refile\u0026#39; which allows refiling to any subtree.\u0026#34; (interactive \u0026#34;P\u0026#34;) (let ((org-refile-target-verify-function)) (org-agenda-refile goto rfloc no-update))) ;; Targets start with the file name - allows creating level 1 tasks ;;(setq org-refile-use-outline-path (quote file)) (setq org-refile-use-outline-path t) (setq org-outline-path-complete-in-steps nil) ;; Allow refile to create parent tasks with confirmation (setq org-refile-allow-creating-parent-nodes \u0026#39;confirm) ;;; To-do settings ;; TODO ;; HOLD(h@) ; 进入时添加笔记 ;; HOLD(h/!) ; 离开时添加变更信息 ;; HOLD(h@/!) ; 进入时添加笔记,离开时添加变更信息 (setq org-todo-keywords (quote ((sequence \u0026#34;TODO(t)\u0026#34; \u0026#34;NEXT(n)\u0026#34; \u0026#34;|\u0026#34; \u0026#34;DONE(d!/!)\u0026#34;) (sequence \u0026#34;PROJECT(p)\u0026#34; \u0026#34;|\u0026#34; \u0026#34;DONE(d!/!)\u0026#34; \u0026#34;CANCELLED(c/!)\u0026#34;) (sequence \u0026#34;WAITING(w/!)\u0026#34; \u0026#34;DELEGATED(e!)\u0026#34; \u0026#34;HOLD(h)\u0026#34; \u0026#34;|\u0026#34; \u0026#34;CANCELLED(c/!)\u0026#34;))) org-todo-repeat-to-state \u0026#34;NEXT\u0026#34;) (setq org-todo-keyword-faces (quote ((\u0026#34;NEXT\u0026#34; :inherit warning) (\u0026#34;PROJECT\u0026#34; :inherit font-lock-string-face)))) ;;; Agenda views ;; 将没有时间标记的任务,放在上方显示。 (setq org-agenda-sort-notime-is-late nil) ;; 时间显示为两位数(9:30 -\u0026gt; 09:30) (setq org-agenda-time-leading-zero t) ;; 过滤掉部分 tags (setq org-agenda-hide-tags-regexp (regexp-opt \u0026#39;(\u0026#34;dynamic\u0026#34;))) (setq org-agenda-files (file-expand-wildcards \u0026#34;~/Dropbox/org/agenda/*.org\u0026#34;)) (setq-default org-agenda-clockreport-parameter-plist \u0026#39;(:link t :maxlevel 3)) ;; 计算待办事项创建至今的时间 (defun org-todo-age (\u0026amp;optional pos) (if-let* ((entry-age (org-todo-age-time pos)) (days (time-to-number-of-days entry-age))) (cond ((\u0026lt; days 1) \u0026#34;today\u0026#34;) ((\u0026lt; days 7) (format \u0026#34;%dd\u0026#34; days)) ((\u0026lt; days 30) (format \u0026#34;%.1fw\u0026#34; (/ days 7.0))) ((\u0026lt; days 358) (format \u0026#34;%.1fM\u0026#34; (/ days 30.0))) (t (format \u0026#34;%.1fY\u0026#34; (/ days 365.0)))) \u0026#34;\u0026#34;)) (defun org-todo-age-time (\u0026amp;optional pos) (let ((stamp (org-entry-get (or pos (point)) \u0026#34;TIMESTAMP_IA\u0026#34; t))) (when stamp (time-subtract (current-time) (org-time-string-to-time stamp))))) (setq org-agenda-compact-blocks t org-agenda-sticky t org-agenda-start-on-weekday nil org-agenda-span \u0026#39;day org-agenda-include-diary nil org-agenda-sorting-strategy \u0026#39;((agenda habit-down time-up user-defined-up effort-up category-keep) (todo category-up effort-up) (tags category-up effort-up) (search category-up)) org-agenda-window-setup \u0026#39;current-window org-agenda-custom-commands `((\u0026#34;N\u0026#34; \u0026#34;Notes\u0026#34; tags \u0026#34;NOTE\u0026#34; ((org-agenda-overriding-header \u0026#34;Notes\u0026#34;) (org-tags-match-list-sublevels t))) (\u0026#34;g\u0026#34; \u0026#34;GTD\u0026#34; ((agenda \u0026#34;\u0026#34; nil) (tags-todo \u0026#34;-inbox\u0026#34; ((org-agenda-overriding-header \u0026#34;Next Actions\u0026#34;) (org-agenda-tags-todo-honor-ignore-options t) (org-agenda-todo-ignore-scheduled \u0026#39;future) (org-agenda-skip-function \u0026#39;(lambda () (or (org-agenda-skip-subtree-if \u0026#39;todo \u0026#39;(\u0026#34;HOLD\u0026#34; \u0026#34;WAITING\u0026#34;)) (org-agenda-skip-entry-if \u0026#39;nottodo \u0026#39;(\u0026#34;NEXT\u0026#34;))))) (org-tags-match-list-sublevels t) (org-agenda-sorting-strategy \u0026#39;(todo-state-down effort-up category-keep)))) (tags-todo \u0026#34;-reading\u0026amp;-book/PROJECT\u0026#34; ((org-agenda-overriding-header \u0026#34;Project\u0026#34;) (org-agenda-prefix-format \u0026#34;%-11c%5(org-todo-age) \u0026#34;) (org-tags-match-list-sublevels t) (org-agenda-sorting-strategy \u0026#39;(category-keep)))) (tags-todo \u0026#34;+reading\u0026amp;+book/PROJECT\u0026#34; ((org-agenda-overriding-header \u0026#34;Reading\u0026#34;) (org-agenda-prefix-format \u0026#34;%-11c%5(org-todo-age) \u0026#34;) (org-tags-match-list-sublevels t) (org-agenda-sorting-strategy \u0026#39;(category-keep)))) (tags-todo \u0026#34;/WAITING\u0026#34; ((org-agenda-overriding-header \u0026#34;Waiting\u0026#34;) (org-agenda-tags-todo-honor-ignore-options t) (org-agenda-todo-ignore-scheduled \u0026#39;future) (org-agenda-sorting-strategy \u0026#39;(category-keep)))) (tags-todo \u0026#34;/DELEGATED\u0026#34; ((org-agenda-overriding-header \u0026#34;Delegated\u0026#34;) (org-agenda-tags-todo-honor-ignore-options t) (org-agenda-todo-ignore-scheduled \u0026#39;future) (org-agenda-sorting-strategy \u0026#39;(category-keep)))) (tags-todo \u0026#34;-inbox\u0026#34; ((org-agenda-overriding-header \u0026#34;On Hold\u0026#34;) (org-agenda-skip-function \u0026#39;(lambda () (or (org-agenda-skip-subtree-if \u0026#39;todo \u0026#39;(\u0026#34;WAITING\u0026#34;)) (org-agenda-skip-entry-if \u0026#39;nottodo \u0026#39;(\u0026#34;HOLD\u0026#34;))))) (org-tags-match-list-sublevels nil) (org-agenda-sorting-strategy \u0026#39;(category-keep)))) )) (\u0026#34;v\u0026#34; \u0026#34;Orphaned Tasks\u0026#34; ((agenda \u0026#34;\u0026#34; nil) (tags \u0026#34;inbox\u0026#34; ((org-agenda-overriding-header \u0026#34;Inbox\u0026#34;) (org-agenda-prefix-format \u0026#34;%-11c%5(org-todo-age) \u0026#34;) (org-tags-match-list-sublevels nil))) (tags-todo \u0026#34;+book\u0026amp;-reading/PROJECT\u0026#34; ((org-agenda-overriding-header \u0026#34;Book Plan\u0026#34;) (org-agenda-prefix-format \u0026#34;%-11c%5(org-todo-age) \u0026#34;) (org-tags-match-list-sublevels t) (org-agenda-sorting-strategy \u0026#39;(category-keep)))) (tags-todo \u0026#34;-inbox/-NEXT\u0026#34; ((org-agenda-overriding-header \u0026#34;Orphaned Tasks\u0026#34;) (org-agenda-tags-todo-honor-ignore-options t) (org-agenda-prefix-format \u0026#34;%-11c%5(org-todo-age) \u0026#34;) (org-agenda-todo-ignore-scheduled \u0026#39;future) (org-agenda-skip-function \u0026#39;(lambda () (or (org-agenda-skip-subtree-if \u0026#39;todo \u0026#39;(\u0026#34;PROJECT\u0026#34; \u0026#34;HOLD\u0026#34; \u0026#34;WAITING\u0026#34; \u0026#34;DELEGATED\u0026#34;)) (org-agenda-skip-subtree-if \u0026#39;nottododo \u0026#39;(\u0026#34;TODO\u0026#34;))))) (org-tags-match-list-sublevels t) (org-agenda-sorting-strategy \u0026#39;(category-keep)))) )))) (add-hook \u0026#39;org-agenda-mode-hook \u0026#39;hl-line-mode) (advice-add \u0026#39;org-refile :after \u0026#39;org-save-all-org-buffers) (advice-add \u0026#39;org-archive :after \u0026#39;org-save-all-org-buffers) (add-hook \u0026#39;org-capture-after-finalize-hook \u0026#39;org-save-all-org-buffers) (advice-add \u0026#39;org-capture-refile :after \u0026#39;org-save-all-org-buffers) ;;; Org clock ;; Save the running clock and all clock history when exiting Emacs, load it on startup (with-eval-after-load \u0026#39;org (org-clock-persistence-insinuate)) (setq org-clock-persist t) (setq org-clock-in-resume t) ;; Save clock data and notes in the LOGBOOK drawer (setq org-clock-into-drawer t) ;; Save state changes in the LOGBOOK drawer (setq org-log-into-drawer t) ;; Removes clocked tasks with 0:00 duration (setq org-clock-out-remove-zero-time-clocks t) ;; Show clock sums as hours and minutes, not \u0026#34;n days\u0026#34; etc. (setq org-time-clocksum-format \u0026#39;(:hours \u0026#34;%d\u0026#34; :require-hours t :minutes \u0026#34;:%02d\u0026#34; :require-minutes t)) ;;; Show the clocked-in task - if any - in the header line (defun sanityinc/show-org-clock-in-header-line () (setq-default header-line-format \u0026#39;((\u0026#34; \u0026#34; org-mode-line-string \u0026#34; \u0026#34;)))) (defun sanityinc/hide-org-clock-from-header-line () (setq-default header-line-format nil)) (add-hook \u0026#39;org-clock-in-hook \u0026#39;sanityinc/show-org-clock-in-header-line) (add-hook \u0026#39;org-clock-out-hook \u0026#39;sanityinc/hide-org-clock-from-header-line) (add-hook \u0026#39;org-clock-cancel-hook \u0026#39;sanityinc/hide-org-clock-from-header-line) (with-eval-after-load \u0026#39;org-clock (define-key org-clock-mode-line-map [header-line mouse-2] \u0026#39;org-clock-goto) (define-key org-clock-mode-line-map [header-line mouse-1] \u0026#39;org-clock-menu)) ;;; Archiving (setq org-archive-mark-done nil) (setq org-archive-location \u0026#34;%s_archive::* Archive\u0026#34;) (with-eval-after-load \u0026#39;org (define-key org-mode-map (kbd \u0026#34;C-M-\u0026lt;up\u0026gt;\u0026#34;) \u0026#39;org-up-element) (when *IS-MAC* (define-key org-mode-map (kbd \u0026#34;M-h\u0026#34;) nil) (define-key org-mode-map (kbd \u0026#34;C-c g\u0026#34;) \u0026#39;grab-mac-link))) (with-eval-after-load \u0026#39;org (org-babel-do-load-languages \u0026#39;org-babel-load-languages (seq-filter (lambda (pair) (locate-library (concat \u0026#34;ob-\u0026#34; (symbol-name (car pair))))) \u0026#39;((R . t) (ditaa . t) (rust . t) (dot . t) (emacs-lisp . t) (gnuplot . t) (haskell . nil) (ledger . t) (ocaml . nil) (octave . t) (ruby . t) (screen . nil) (sh . t) ;; obsolete (shell . t) (sql . t) (sqlite . t)))) ;; plantuml (org-babel-do-load-languages \u0026#39;org-babel-load-languages \u0026#39;(;; other Babel languages (plantuml . t) (python . t) (latex . t))) (setq org-plantuml-jar-path (expand-file-name \u0026#34;~/Dropbox/org/plantuml/plantuml.jar\u0026#34;))) ;; 这里应该就是 .zshrc 里面配置的 python3 (setq org-babel-python-command \u0026#34;python3\u0026#34;) (require-package \u0026#39;emacsql-sqlite-builtin) (require \u0026#39;emacsql-sqlite-builtin) (use-package org-roam :ensure t :demand t ;; ensure org-roam is loaded by default :init :custom (org-roam-directory (file-truename \u0026#34;~/Dropbox/org/\u0026#34;)) ;; 需要 emacs-plus@29 版本 (org-roam-database-connector \u0026#39;sqlite-builtin) (org-roam-db-location \u0026#34;~/Dropbox/org/org.db\u0026#34;) (org-roam-db-gc-threshold most-positive-fixnum) (org-roam-completion-everywhere t) (org-roam-capture-templates \u0026#39;( ;; #+OPTIONS: toc:nil 为了导出 .md 的格式更加符合使用 (\u0026#34;d\u0026#34; \u0026#34;default\u0026#34; plain (file \u0026#34;~/Dropbox/org/templates/default.org\u0026#34;) :if-new (file \u0026#34;main/%\u0026lt;%Y%m%d%H%M%S\u0026gt;-${slug}.org\u0026#34;) :unnarrowed t) (\u0026#34;p\u0026#34; \u0026#34;private\u0026#34; plain (file \u0026#34;~/Dropbox/org/templates/private.org\u0026#34;) :if-new (file \u0026#34;private/%\u0026lt;%Y%m%d%H%M%S\u0026gt;-${slug}.org\u0026#34;) :unnarrowed t) (\u0026#34;a\u0026#34; \u0026#34;article\u0026#34; plain (file \u0026#34;~/Dropbox/org/templates/article.org\u0026#34;) :if-new (file \u0026#34;main/%\u0026lt;%Y%m%d%H%M%S\u0026gt;-${slug}.org\u0026#34;) :unnarrowed t) (\u0026#34;n\u0026#34; \u0026#34;article-network\u0026#34; plain (file \u0026#34;~/Dropbox/org/templates/article-network.org\u0026#34;) :if-new (file \u0026#34;main/%\u0026lt;%Y%m%d%H%M%S\u0026gt;-${slug}.org\u0026#34;) :unnarrowed t) ) ) (org-roam-dailies-capture-templates ;; %\u0026lt;%H:%M\u0026gt; 为24小时制,%\u0026lt;%I:%M %p\u0026gt; 为12小时制 \u0026#39;( (\u0026#34;d\u0026#34; \u0026#34;default\u0026#34; entry \u0026#34;** %\u0026lt;%H:%M\u0026gt; %?\u0026#34; :if-new (file+head+olp \u0026#34;%\u0026lt;%Y-%m-%d\u0026gt;.org\u0026#34; \u0026#34;#+title: %\u0026lt;%Y-%m-%d\u0026gt;\\n#+ARCHIVE: journal.org::\\n\u0026#34; (\u0026#34;%\u0026lt;%Y-%m-%d\u0026gt;\u0026#34;))) (\u0026#34;r\u0026#34; \u0026#34;read\u0026#34; entry \u0026#34;*** %?\u0026#34; :if-new (file+head+olp \u0026#34;%\u0026lt;%Y-%m-%d\u0026gt;.org\u0026#34; \u0026#34;#+title: %\u0026lt;%Y-%m-%d\u0026gt;\\n#+ARCHIVE: journal.org::\\n\u0026#34; (\u0026#34;%\u0026lt;%Y-%m-%d\u0026gt;\u0026#34; \u0026#34;What I read? :read:\u0026#34;))) (\u0026#34;t\u0026#34; \u0026#34;tasks\u0026#34; entry \u0026#34;*** %?\u0026#34; :if-new (file+head+olp \u0026#34;%\u0026lt;%Y-%m-%d\u0026gt;.org\u0026#34; \u0026#34;#+title: %\u0026lt;%Y-%m-%d\u0026gt;\\n#+ARCHIVE: journal.org::\\n\u0026#34; (\u0026#34;%\u0026lt;%Y-%m-%d\u0026gt;\u0026#34; \u0026#34;Tasks :task:\u0026#34;))) (\u0026#34;n\u0026#34; \u0026#34;notes\u0026#34; entry \u0026#34;*** %?\u0026#34; :if-new (file+head+olp \u0026#34;%\u0026lt;%Y-%m-%d\u0026gt;.org\u0026#34; \u0026#34;#+title: %\u0026lt;%Y-%m-%d\u0026gt;\\n#+ARCHIVE: journal.org::\\n\u0026#34; (\u0026#34;%\u0026lt;%Y-%m-%d\u0026gt;\u0026#34; \u0026#34;Notes :note:\u0026#34;))) (\u0026#34;o\u0026#34; \u0026#34;online\u0026#34; entry \u0026#34;** %\u0026lt;%H:%M\u0026gt; %? :online:\u0026#34; :if-new (file+head+olp \u0026#34;%\u0026lt;%Y-%m-%d\u0026gt;.org\u0026#34; \u0026#34;#+title: %\u0026lt;%Y-%m-%d\u0026gt;\\n#+ARCHIVE: journal.org::\\n\u0026#34; (\u0026#34;%\u0026lt;%Y-%m-%d\u0026gt;\u0026#34;))) ) ) :bind ((\u0026#34;C-c n l\u0026#34; . org-roam-buffer-toggle) (\u0026#34;C-c n f\u0026#34; . org-roam-node-find) (\u0026#34;C-c n i\u0026#34; . org-roam-node-insert) (\u0026#34;C-c n j\u0026#34; . org-roam-dailies-capture-today) (\u0026#34;C-c n I\u0026#34; . org-roam-node-insert-immediate) (\u0026#34;C-c n m\u0026#34; . dired-copy-images-links) :map org-mode-map ;; Ctrl-Alt-i (\u0026#34;C-M-i\u0026#34; . completion-at-point)) :config (unless (file-exists-p org-roam-directory) (make-directory org-roam-directory t)) (org-roam-db-autosync-enable) ;;使用侧边栏而不是完整buffer (add-to-list \u0026#39;display-buffer-alist \u0026#39;(\u0026#34;\\\\*org-roam\\\\*\u0026#34; (display-buffer-in-direction) (direction . right) (window-width . 0.33) (window-height . fit-window-to-buffer))) (setq org-export-backends (quote (ascii html icalendar latex md))) ;; code hightlight (setq org-src-fontify-natively t) ;; If using org-roam-protocol (require \u0026#39;org-roam-protocol) (require \u0026#39;org-roam-export) (require \u0026#39;org-tempo) ;; Copy Done To-Dos to Today (defun org-roam-copy-todo-to-today () (interactive) (let ((org-refile-keep t) ;; Set this to nil to delete the original! (org-after-refile-insert-hook #\u0026#39;save-buffer) today-file pos) (save-window-excursion (org-roam-dailies-capture-today t \u0026#34;t\u0026#34;) (setq today-file (buffer-file-name)) (setq pos (point))) ;; Only refile if the target file is different than the current file (unless (equal (file-truename today-file) (file-truename (buffer-file-name))) (org-refile nil nil (list \u0026#34;Tasks\u0026#34; today-file nil pos))))) (add-to-list \u0026#39;org-after-todo-state-change-hook (lambda () ;; DONE 和 CANCELLED 的 To-Dos 自动复制到今日 ;; 同时过滤掉 habit 的 To-Dos (when (and (or (equal org-state \u0026#34;DONE\u0026#34;) (equal org-state \u0026#34;CANCELLED\u0026#34;)) (not (org-find-property \u0026#34;STYLE\u0026#34;))) (org-roam-copy-todo-to-today)))) ;; node-find 的时候展示文件夹 ;; org-roam-node-type (cl-defmethod org-roam-node-type ((node org-roam-node)) \u0026#34;Return the TYPE of NODE.\u0026#34; (condition-case nil (file-name-nondirectory (directory-file-name (file-name-directory (file-relative-name (org-roam-node-file node) org-roam-directory)))) (error \u0026#34;\u0026#34;))) ;; I encountered the following message when attempting ;; to export data: ;; ;; \u0026#34;org-export-data: Unable to resolve link: FILE-ID\u0026#34; (defun force-org-rebuild-cache () \u0026#34;Rebuild the `org-mode\u0026#39; and `org-roam\u0026#39; cache.\u0026#34; (interactive) (org-id-update-id-locations) ;; Note: you may need `org-roam-db-clear-all\u0026#39; ;; followed by `org-roam-db-sync\u0026#39; (org-roam-db-sync) (org-roam-update-org-id-locations)) ;; org-roam 作者提供的解决办法 ;; (setq org-id-track-globally t) ;; M-x org-id-update-id-locations ) ;; 解决 org-roam-node-find 时,内容局限于 buffer 宽度。 ;; https://github.com/org-roam/org-roam/issues/2066 (defun my/org-roam-node-read--to-candidate (node template) \u0026#34;Return a minibuffer completion candidate given NODE. TEMPLATE is the processed template used to format the entry.\u0026#34; (let ((candidate-main (org-roam-node--format-entry template node (1- (if (minibufferp) (window-width) (frame-width)))))) (cons (propertize candidate-main \u0026#39;node node) node))) (advice-add \u0026#39;org-roam-node-read--to-candidate :override #\u0026#39;my/org-roam-node-read--to-candidate) ;; 在记录的时候创建新的 node 时不退出当前状态,保存新建的 node。 (defun org-roam-node-insert-immediate (arg \u0026amp;rest args) (interactive \u0026#34;P\u0026#34;) (let ((args (push arg args)) (org-roam-capture-templates (list (append (car org-roam-capture-templates) \u0026#39;(:immediate-finish t))))) (apply #\u0026#39;org-roam-node-insert args))) ;; C-x d 进入 dired 模式,m 来标记对应需要复制链接的图片,C-c n m 即可复制到需要的图片插入文本。 ;; source: https://org-roam.discourse.group/t/is-there-a-solution-for-images-organization-in-org-roam/925 (defun dired-copy-images-links () \u0026#34;Works only in dired-mode, put in kill-ring, ready to be yanked in some other org-mode file, the links of marked image files using file-name-base as #+CAPTION. If no file marked then do it on all images files of directory. No file is moved nor copied anywhere. This is intended to be used with org-redisplay-inline-images.\u0026#34; (interactive) (if (derived-mode-p \u0026#39;dired-mode) ; if we are in dired-mode (let* ((marked-files (dired-get-marked-files)) ; get marked file list (number-marked-files ; store number of marked files (string-to-number ; as a number (dired-number-of-marked-files)))) ; for later reference (when (= number-marked-files 0) ; if none marked then (dired-toggle-marks) ; mark all files (setq marked-files (dired-get-marked-files))) ; get marked file list (message \u0026#34;Files marked for copy\u0026#34;) ; info message (dired-number-of-marked-files) ; marked files info (kill-new \u0026#34;\\n\u0026#34;) ; start with a newline (dolist (marked-file marked-files) ; walk the marked files list (when (org-file-image-p marked-file) ; only on image files (kill-append ; append image to kill-ring (concat \u0026#34;#+CAPTION: \u0026#34; ; as caption, (file-name-base marked-file) ; use file-name-base \u0026#34;\\n#+ATTR_ORG: :width 800\u0026#34; ; img width \u0026#34;\\n[[file:\u0026#34; marked-file \u0026#34;]]\\n\\n\u0026#34;) nil))); link to marked-file (when (= number-marked-files 0) ; if none were marked then (dired-toggle-marks))) ; unmark all (message \u0026#34;Error: Does not work outside dired-mode\u0026#34;) ; can\u0026#39;t work not in dired-mode (ding))) ; error sound ;; Save the corresponding buffers (defun gtd-save-org-buffers () \u0026#34;Save `org-agenda-files\u0026#39; buffers without user confirmation. See also `org-save-all-org-buffers\u0026#39;\u0026#34; (interactive) (message \u0026#34;Saving org-agenda-files buffers...\u0026#34;) (save-some-buffers t (lambda () (when (member (buffer-file-name) org-agenda-files) t))) (message \u0026#34;Saving org-agenda-files buffers... done\u0026#34;)) ;; Add it after refile (advice-add \u0026#39;org-refile :after (lambda (\u0026amp;rest _) (gtd-save-org-buffers))) (defun log-todo-next-creation-date (\u0026amp;rest ignore) \u0026#34;Log NEXT creation time in the property drawer under the key \u0026#39;ACTIVATED\u0026#39;\u0026#34; (when (and (string= (org-get-todo-state) \u0026#34;NEXT\u0026#34;) (not (org-entry-get nil \u0026#34;ACTIVATED\u0026#34;))) (org-entry-put nil \u0026#34;ACTIVATED\u0026#34; (format-time-string \u0026#34;[%Y-%m-%d]\u0026#34;)))) (add-hook \u0026#39;org-after-todo-state-change-hook #\u0026#39;log-todo-next-creation-date) ;; Put the text property afterwards with an advice (defun my-org-agenda-override-header (orig-fun \u0026amp;rest args) \u0026#34;Change the face of the overriden header string if needed. The propertized header text is taken from `org-agenda-overriding-header\u0026#39;. The face is only changed if the overriding header is propertized with a face.\u0026#34; (let ((pt (point)) (header org-agenda-overriding-header)) (apply orig-fun args) ;; Only replace if there is an overriding header and not an empty string. ;; And only if the header text has a face property. (when (and header (\u0026gt; (length header) 0) (get-text-property 0 \u0026#39;face header)) (save-excursion (goto-char pt) ;; Search for the header text. (search-forward header) (unwind-protect (progn (read-only-mode -1) ;; Replace it with the propertized text. (replace-match header)) (read-only-mode 1)))))) (defun my-org-agenda-override-header-add-advices () \u0026#34;Add advices to make changing work in all agenda commands.\u0026#34; (interactive) (dolist (fun \u0026#39;(org-agenda-list org-todo-list org-search-view org-tags-view)) (advice-add fun :around #\u0026#39;my-org-agenda-override-header))) (my-org-agenda-override-header-add-advices) ;; 只显示 outline 下第一个 TODO (defun my-org-agenda-skip-all-siblings-but-first () \u0026#34;Skip all but the first non-done entry.\u0026#34; (let (should-skip-entry) (unless (org-current-is-todo) (setq should-skip-entry t)) (save-excursion (while (and (not should-skip-entry) (org-goto-sibling t)) (when (org-current-is-todo) (setq should-skip-entry t)))) (when should-skip-entry (or (outline-next-heading) (goto-char (point-max)))))) (defun org-current-is-todo () (string= \u0026#34;TODO\u0026#34; (org-get-todo-state))) ;; 导出特定文件夹下所有内容到 hugo (defun ox-hugo/export-all (\u0026amp;optional org-files-root-dir dont-recurse) \u0026#34;Export all Org files (including nested) under ORG-FILES-ROOT-DIR. All valid post subtrees in all Org files are exported using `org-hugo-export-wim-to-md\u0026#39;. If optional arg ORG-FILES-ROOT-DIR is nil, all Org files in current buffer\u0026#39;s directory are exported. If optional arg DONT-RECURSE is nil, all Org files in ORG-FILES-ROOT-DIR in all subdirectories are exported. Else, only the Org files directly present in the current directory are exported. If this function is called interactively with \\\\[universal-argument] prefix, DONT-RECURSE is set to non-nil. Example usage in Emacs Lisp: (ox-hugo/export-all \\\u0026#34;~/org\\\u0026#34;).\u0026#34; (interactive) (org-transclusion-mode 1) (let* ((org-files-root-dir (or org-files-root-dir default-directory)) (dont-recurse (or dont-recurse (and current-prefix-arg t))) (search-path (file-name-as-directory (expand-file-name org-files-root-dir))) (org-files (if dont-recurse (directory-files search-path :full \u0026#34;\\.org$\u0026#34;) (directory-files-recursively search-path \u0026#34;\\.org$\u0026#34;))) (num-files (length org-files)) (cnt 1)) (if (= 0 num-files) (message (format \u0026#34;No Org files found in %s\u0026#34; search-path)) (progn (message (format (if dont-recurse \u0026#34;[ox-hugo/export-all] Exporting %d files from %S ..\u0026#34; \u0026#34;[ox-hugo/export-all] Exporting %d files recursively from %S ..\u0026#34;) num-files search-path)) (dolist (org-file org-files) (with-current-buffer (find-file-noselect org-file) (message (format \u0026#34;[ox-hugo/export-all file %d/%d] Exporting %s\u0026#34; cnt num-files org-file)) (org-hugo-export-wim-to-md :all-subtrees) (setq cnt (1+ cnt)))) (org-transclusion-mode -1) (message \u0026#34;Done!\u0026#34;))))) (provide \u0026#39;init-org) ;;; init-org.el ends here Org-babel 输出 :results 有不同的选项,其中 silent 是指:Do not insert results in the Org mode buffer, but echo them in the minibuffer. Usage example: ‘:results output silent’.\nRust 运行代码块,需要安装 GitHub - brotzeit/rustic: Rust development environment for Emacs,具体执行的时候会提示需要安装 lsp-mode,确定即可。\nPlantUML brew install graphviz Type Symbol Zero or One o|\u0026ndash; Exactly One ||\u0026ndash; Zero or Many }o\u0026ndash; One or Many }|\u0026ndash; @startumlg scale 2 Entity01 }|..|| Entity02 Entity03 }o..o| Entity04 Entity05 ||--o{ Entity06 Entity07 |o--|| Entity08 @enduml PlantUML 默认的样式比较古老,可以用更加现代一些的样式:GitHub - xuanye/plantuml-style-c4: 自定义的plantuml 样式,不过好久没更新了,前面的颜色也是参考的这个:GitHub - plantuml-stdlib/C4-PlantUML: C4-PlantUML combines the benefits of Pl\u0026hellip;。\ninit-sis.el 根据 meow 的 insert mode 切换,同时根据上下文来判断切换输入法;另外在特定 mode 下也切换输入法,使用更加顺滑。\n需要安装 macism\nbrew tap laishulu/macism brew install macism ;;; init-sis.el --- Custom configuration ;;; Commentary (require-package \u0026#39;sis) ;; meow insert mode switch (defvar meow-leaving-insert-mode-hook nil \u0026#34;Hook to run when leaving meow insert mode.\u0026#34;) (defvar meow-entering-insert-mode-hook nil \u0026#34;Hook to run when entering meow insert mode.\u0026#34;) (add-hook \u0026#39;meow-insert-mode-hook (lambda () (if meow-insert-mode (run-hooks \u0026#39;meow-entering-insert-mode-hook) (run-hooks \u0026#39;meow-leaving-insert-mode-hook)))) (use-package sis :config (setq sis-english-source \u0026#34;com.apple.keylayout.ABC\u0026#34;) (if *IS-MAC* (sis-ism-lazyman-config \u0026#34;com.apple.keylayout.ABC\u0026#34; \u0026#34;im.rime.inputmethod.Squirrel.Hans\u0026#34; \u0026#39;macism) (sis-ism-lazyman-config \u0026#34;1\u0026#34; \u0026#34;2\u0026#34; \u0026#39;fcitx)) ;; enable the /cursor color/ mode (sis-global-cursor-color-mode t) ;; enable the /respect/ mode ;; 打开会影响 meow-reverse,因为 meow 下退出模式编辑后都是默认英文,这里也无需开启。 ;; (sis-global-respect-mode t) ;; enable the /context/ mode for all buffers (sis-global-context-mode t) ;; enable the /inline english/ mode for all buffers ;; (sis-global-inline-mode t) ;; not delete the head spaces (setq sis-inline-tighten-head-rule nil) (setq sis-default-cursor-color \u0026#34;#FE6DB3\u0026#34;) (setq sis-other-cursor-color \u0026#34;orange\u0026#34;) (setq sis-prefix-override-keys (list \u0026#34;C-c\u0026#34; \u0026#34;C-x\u0026#34; \u0026#34;C-h\u0026#34;)) (add-hook \u0026#39;meow-leaving-insert-mode-hook #\u0026#39;sis-set-english) (add-to-list \u0026#39;sis-context-hooks \u0026#39;meow-entering-insert-mode-hook) ;; org title 处切换 Rime,telega 聊天时切换 Rime。 (add-to-list \u0026#39;sis-context-detectors (lambda (\u0026amp;rest _) ;; (when (or (and (eq major-mode \u0026#39;org-mode) (org-at-heading-p)) (when (or (eq major-mode \u0026#39;org-mode) (eq major-mode \u0026#39;telega-chat-mode)) \u0026#39;other))) (advice-add \u0026#39;org-agenda-todo :before #\u0026#39;sis-set-english) (advice-add \u0026#39;hydra-org-agenda-menu/body :after #\u0026#39;sis-set-english) :ensure) ;; MacOS 上防止进入 insert mode 覆盖要切换的中文状态 (defun sis--respect-focus-in-handler ()) (provide \u0026#39;init-sis) ;;; init-sis.el ends here 注:从其他 App 切回 Emacs 后进入 insert mode 之后切换到中文后立刻切换到英文,解决办法如下。(问题 emacs -Q 记录视频 Imgur: The magic of the Internet)\n是这样的, 你是从其它应用切回 emacs 后, 立刻就 i 进入 insert mode?\n如果稍微延迟一下再 i 进入 insert mode, 是不是就没这个问题了?\nemacs 处理窗口焦点的切换(focus in事件)是有一个延迟的, 这样操作就导致:\nfocus in 之后恢复当前 buffer 的 input source(因为离开前为 normal 状态,所以为 english) 你进入 insert mode 切换 input source(为 rime) 这二者抢占了。\n比较 dirty 但是直接的办法是, 你把下面这个函数在自己的配置文件中重新定义一下,让 sis 不要切换。\nemacs-smart-input-source/sis.el at 02777a46a4b7c18c2cd588e6e2f11e83ba2378e4 ·\u0026hellip;\n但是这块也不算是 sis 的 bug, sis 对于焦点切换也只能做到这一步。 包治百病的用法就是切回emacs稍微等一下。\n不过我又想到一点, macos 可以系统配置“自动切换到文稿的输入法”, 你如果配置了这个, 在使用 GUI Emacs 时,应该是不需要 sis 自己去恢复当前 buffer 的输入法的。 那就可以用空函数去覆盖上面那个函数了。 但是!!! macos 也会帮你主动地切输入法,就是不知道会不会也有延迟,导致同样的问题\n不过 sis 针对的是一般情况:非 MacOS 系统,或没系统设置“自动切换到文稿的输入法”,或用的是 Terminal Emacs 等等。\ninit-font.el 使用的是 Iosevka 和大多数中文字体配合,按照 1:1 缩放,偶数字号就可以做到等高等宽。这是选择 LXGW WenKai Screen 而不是 LXGW WenKai Mono 是因为后者的粗体非常不明显,作为文档文字不好区分。\n;;; init-font.el --- Dired customisations -*- lexical-binding: t -*- ;;; Commentary: ;;; Code: ;;; base on https://gist.github.com/coldnew/7398835 (defun qiang-font-existsp (font) (if (null (x-list-fonts font)) nil t)) ;; LXGW WenKai Mono 配合 Iosevka 按照 1:1 缩放,偶数字号就可以做到等高等宽。 (defvar zh-font-list \u0026#39;(\u0026#34;LXGW WenKai Screen\u0026#34; \u0026#34;FZSongKeBenXiuKai-R-GBK\u0026#34; \u0026#34;HanaMinB\u0026#34;)) (defvar en-font-list \u0026#39;(\u0026#34;Iosevka\u0026#34; \u0026#34;Latin Modern Mono\u0026#34; \u0026#34;Fira Code\u0026#34; \u0026#34;IBM Plex Mono\u0026#34;)) (defun qiang-make-font-string (font-name font-size) (if (and (stringp font-size) (equal \u0026#34;:\u0026#34; (string (elt font-size 0)))) (format \u0026#34;%s%s\u0026#34; font-name font-size) (format \u0026#34;%s %s\u0026#34; font-name font-size))) (defun qiang-set-font (english-fonts english-font-size chinese-fonts \u0026amp;optional chinese-font-scale) (setq chinese-font-scale (or chinese-font-scale 1)) (setq face-font-rescale-alist (cl-loop for x in zh-font-list collect (cons x chinese-font-scale))) \u0026#34;english-font-size could be set to \\\u0026#34;:pixelsize=18\\\u0026#34; or a integer. If set/leave chinese-font-scale to nil, it will follow english-font-size\u0026#34; (let ((en-font (qiang-make-font-string (cl-find-if #\u0026#39;qiang-font-existsp english-fonts) english-font-size)) (zh-font (font-spec :family (cl-find-if #\u0026#39;qiang-font-existsp chinese-fonts)))) ;; Set the default English font (message \u0026#34;Set English Font to %s\u0026#34; en-font) (set-face-attribute \u0026#39;default nil :font en-font) ;; Set Chinese font ;; Do not use \u0026#39;unicode charset, it will cause the English font setting invalid (message \u0026#34;Set Chinese Font to %s\u0026#34; zh-font) (dolist (charset \u0026#39;(kana han symbol cjk-misc bopomofo)) (set-fontset-font (frame-parameter nil \u0026#39;font) charset zh-font)))) (qiang-set-font en-font-list 14 zh-font-list) (add-to-list \u0026#39;face-font-rescale-alist \u0026#39;(\u0026#34;Apple Color Emoji\u0026#34; . 0.8)) (provide \u0026#39;init-font) ;;; init-font.el ends here init-rime.el 需要安装 Squirrel 并下载与之对应版本的 librime。\ncurl -L -O https://github.com/rime/librime/releases/download/1.7.3/rime-1.7.3-osx.zip unzip rime-1.7.3-osx.zip -d ~/.emacs.d/librime rm -rf rime-1.7.3-osx.zip 使用拼音输入法需要维护两份配置,但是备份位置可以是同一个地址,这样就达到了同步配置的目的。\ncp -rf ~/Library/Rime ~/.emacs.d/ ;;; init-rime.el --- Custom configuration ;;; Commentary (require-package \u0026#39;rime) (use-package rime :custom (default-input-method \u0026#34;rime\u0026#34;) (rime-librime-root \u0026#34;~/.emacs.d/librime/dist\u0026#34;) (rime-emacs-module-header-root \u0026#34;/usr/local/Cellar/emacs-plus@29/29.0.50/include\u0026#34;) :config (define-key rime-mode-map (kbd \u0026#34;C-i\u0026#34;) \u0026#39;rime-force-enable) ;; 方案切换选择 (define-key rime-mode-map (kbd \u0026#34;C-`\u0026#34;) \u0026#39;rime-send-keybinding) (setq rime-disable-predicates \u0026#39;(meow-normal-mode-p meow-motion-mode-p meow-keypad-mode-p ;; If cursor is in code. rime-predicate-prog-in-code-p ;; If the cursor is after a alphabet character. rime-predicate-after-alphabet-char-p ;; If input a punctuation after ;; a Chinese charactor with whitespace. rime-predicate-punctuation-after-space-cc-p rime-predicate-special-ascii-line-begin-p)) (setq rime-inline-predicates ;; If cursor is after a whitespace ;; which follow a non-ascii character. \u0026#39;(rime-predicate-space-after-cc-p ;; If the current charactor entered is a uppercase letter. rime-predicate-current-uppercase-letter-p)) ;;; support shift-l, shift-r, control-l, control-r (setq rime-inline-ascii-trigger \u0026#39;shift-r) ;; meow 进入 insert-mode,且是 org-mode 或 ;; telega-chat-mode 时,切换到 Rime。 (add-hook \u0026#39;meow-insert-enter-hook (lambda() (when (derived-mode-p \u0026#39;org-mode \u0026#39;telega-chat-mode) (set-input-method \u0026#34;rime\u0026#34;)))) ;; 退出 insert mode 时,恢复英文。 (add-hook \u0026#39;meow-insert-exit-hook (lambda() (set-input-method nil))) (setq rime-user-data-dir \u0026#34;~/.emacs.d/Rime\u0026#34;)) (defun rime-predicate-special-ascii-line-begin-p () \u0026#34;If \u0026#39;/\u0026#39; or \u0026#39;#\u0026#39; at the beginning of the line.\u0026#34; (and (\u0026gt; (point) (save-excursion (back-to-indentation) (point))) (let ((string (buffer-substring (point) (max (line-beginning-position) (- (point) 80))))) (string-match-p \u0026#34;^[\\/#]\u0026#34; string)))) (provide \u0026#39;init-rime) ;;; init-rime.el ends here 其中 (setq rime-inline-ascii-trigger 'shift-r) 需要配合 Rime 中 default.custom.yaml 配置中的 Shift_R: inline_ascii 才可生效。\npatch: # commit_code 上屏输出 code 并切换成英文 # commit_text 上屏输出内容并切换成英文 # clear 清除内容并切换成英文 ascii_composer/good_old_caps_lock: true ascii_composer/switch_key: Caps_Lock: commit_code Control_L: noop Control_R: noop Shift_L: commit_code Shift_R: inline_ascii 最终实现的效果是。\n进入 meow 的 insert-mode 自动启用 Rime,退出 insert-mode 后关闭 Rime。 在编程界面,Rime 开启时,代码中自动切换英文,注释中自动切换中文。 字母及英文字符紧跟后面输入自动切换英文。(遇到以英文在半句或者整句后的情况下,英文后要输入逗号或者句号等中文符号,可以使用 rime-force-enable 来强制中文模式,这样就可以在前面描述的情况下强制输入中文符号。) 以 # 或 / 开头,则切换英文。 中文后空格自动切换 inline 模式,输入大写字母自动切换 inline 模式。 其中在 Isearch 中使用,切换为 Rime 后需要再按一次 M-e 才可以正常使用。\ninit-meow.el 快捷键 功能 备注 M-S-\u0026lt; 移动光标到顶部 cmd-shift-\u0026lt; ;;; init-meow.el --- Custom configuration ;;; Commentary (require-package \u0026#39;meow) (require \u0026#39;meow) (defun meow-setup () (setq meow-cheatsheet-layout meow-cheatsheet-layout-qwerty) (meow-motion-overwrite-define-key \u0026#39;(\u0026#34;j\u0026#34; . meow-next) \u0026#39;(\u0026#34;k\u0026#34; . meow-prev) \u0026#39;(\u0026#34;\u0026lt;escape\u0026gt;\u0026#34; . ignore)) (meow-leader-define-key ;; SPC j/k will run the original command in MOTION state. \u0026#39;(\u0026#34;j\u0026#34; . \u0026#34;H-j\u0026#34;) \u0026#39;(\u0026#34;k\u0026#34; . \u0026#34;H-k\u0026#34;) ;; Use SPC (0-9) for digit arguments. \u0026#39;(\u0026#34;1\u0026#34; . meow-digit-argument) \u0026#39;(\u0026#34;2\u0026#34; . meow-digit-argument) \u0026#39;(\u0026#34;3\u0026#34; . meow-digit-argument) \u0026#39;(\u0026#34;4\u0026#34; . meow-digit-argument) \u0026#39;(\u0026#34;5\u0026#34; . meow-digit-argument) \u0026#39;(\u0026#34;6\u0026#34; . meow-digit-argument) \u0026#39;(\u0026#34;7\u0026#34; . meow-digit-argument) \u0026#39;(\u0026#34;8\u0026#34; . meow-digit-argument) \u0026#39;(\u0026#34;9\u0026#34; . meow-digit-argument) \u0026#39;(\u0026#34;0\u0026#34; . meow-digit-argument) \u0026#39;(\u0026#34;/\u0026#34; . meow-keypad-describe-key) \u0026#39;(\u0026#34;?\u0026#34; . meow-cheatsheet)) (meow-normal-define-key \u0026#39;(\u0026#34;0\u0026#34; . meow-expand-0) \u0026#39;(\u0026#34;9\u0026#34; . meow-expand-9) \u0026#39;(\u0026#34;8\u0026#34; . meow-expand-8) \u0026#39;(\u0026#34;7\u0026#34; . meow-expand-7) \u0026#39;(\u0026#34;6\u0026#34; . meow-expand-6) \u0026#39;(\u0026#34;5\u0026#34; . meow-expand-5) \u0026#39;(\u0026#34;4\u0026#34; . meow-expand-4) \u0026#39;(\u0026#34;3\u0026#34; . meow-expand-3) \u0026#39;(\u0026#34;2\u0026#34; . meow-expand-2) \u0026#39;(\u0026#34;1\u0026#34; . meow-expand-1) \u0026#39;(\u0026#34;-\u0026#34; . negative-argument) \u0026#39;(\u0026#34;;\u0026#34; . meow-reverse) \u0026#39;(\u0026#34;,\u0026#34; . meow-inner-of-thing) \u0026#39;(\u0026#34;.\u0026#34; . meow-bounds-of-thing) \u0026#39;(\u0026#34;[\u0026#34; . meow-beginning-of-thing) \u0026#39;(\u0026#34;]\u0026#34; . meow-end-of-thing) \u0026#39;(\u0026#34;a\u0026#34; . meow-append) \u0026#39;(\u0026#34;A\u0026#34; . meow-open-below) \u0026#39;(\u0026#34;b\u0026#34; . meow-back-word) \u0026#39;(\u0026#34;B\u0026#34; . meow-back-symbol) \u0026#39;(\u0026#34;c\u0026#34; . meow-change) \u0026#39;(\u0026#34;d\u0026#34; . meow-delete) \u0026#39;(\u0026#34;D\u0026#34; . meow-backward-delete) \u0026#39;(\u0026#34;e\u0026#34; . meow-next-word) \u0026#39;(\u0026#34;E\u0026#34; . meow-next-symbol) \u0026#39;(\u0026#34;f\u0026#34; . meow-find) \u0026#39;(\u0026#34;g\u0026#34; . meow-cancel-selection) \u0026#39;(\u0026#34;G\u0026#34; . meow-grab) \u0026#39;(\u0026#34;h\u0026#34; . meow-left) \u0026#39;(\u0026#34;H\u0026#34; . meow-left-expand) \u0026#39;(\u0026#34;i\u0026#34; . meow-insert) \u0026#39;(\u0026#34;I\u0026#34; . meow-open-above) \u0026#39;(\u0026#34;j\u0026#34; . meow-next) \u0026#39;(\u0026#34;J\u0026#34; . meow-next-expand) \u0026#39;(\u0026#34;k\u0026#34; . meow-prev) \u0026#39;(\u0026#34;K\u0026#34; . meow-prev-expand) \u0026#39;(\u0026#34;l\u0026#34; . meow-right) \u0026#39;(\u0026#34;L\u0026#34; . meow-right-expand) \u0026#39;(\u0026#34;m\u0026#34; . meow-join) \u0026#39;(\u0026#34;n\u0026#34; . meow-search) \u0026#39;(\u0026#34;o\u0026#34; . meow-block) \u0026#39;(\u0026#34;O\u0026#34; . meow-to-block) \u0026#39;(\u0026#34;p\u0026#34; . meow-yank) \u0026#39;(\u0026#34;q\u0026#34; . meow-quit) \u0026#39;(\u0026#34;Q\u0026#34; . meow-goto-line) \u0026#39;(\u0026#34;r\u0026#34; . meow-replace) \u0026#39;(\u0026#34;R\u0026#34; . meow-swap-grab) \u0026#39;(\u0026#34;s\u0026#34; . meow-kill) \u0026#39;(\u0026#34;t\u0026#34; . meow-till) \u0026#39;(\u0026#34;u\u0026#34; . meow-undo) \u0026#39;(\u0026#34;U\u0026#34; . meow-undo-in-selection) \u0026#39;(\u0026#34;v\u0026#34; . meow-visit) \u0026#39;(\u0026#34;w\u0026#34; . meow-mark-word) \u0026#39;(\u0026#34;W\u0026#34; . meow-mark-symbol) \u0026#39;(\u0026#34;x\u0026#34; . meow-line) \u0026#39;(\u0026#34;X\u0026#34; . meow-goto-line) \u0026#39;(\u0026#34;y\u0026#34; . meow-save) \u0026#39;(\u0026#34;Y\u0026#34; . meow-sync-grab) \u0026#39;(\u0026#34;z\u0026#34; . meow-pop-selection) \u0026#39;(\u0026#34;\u0026#39;\u0026#34; . repeat) \u0026#39;(\u0026#34;\u0026lt;escape\u0026gt;\u0026#34; . ignore))) (meow-setup) (meow-global-mode 1) ;; 失去焦点时,退出 insert mode。 (add-hook \u0026#39;focus-out-hook \u0026#39;meow-insert-exit) ;; 延长数字显示时间 (setq meow-expand-hint-remove-delay 3) (provide \u0026#39;init-meow) ;;; init-meow.el ends here beancount.el GitHub - beancount/beancount-mode: Emacs major-mode to work with Beancount le\u0026hellip;,这里是将其中的 beancount.el 下载到 .emacs.d/lisp/ 目录下,加上下列代码。\n(add-to-list \u0026#39;auto-mode-alist \u0026#39;(\u0026#34;\\\\.beancount\\\\\u0026#39;\u0026#34; . beancount-mode)) init-epla.el 增加 use-package 和 straight 的支持\n;;; init-elpa.el --- Settings and helpers for package.el -*- lexical-binding: t -*- ;;; Commentary: ;;; Code: (require \u0026#39;package) (require \u0026#39;cl-lib) ;;; Install into separate package dirs for each Emacs version, to prevent bytecode incompatibility (setq package-user-dir (expand-file-name (format \u0026#34;elpa-%s.%s\u0026#34; emacs-major-version emacs-minor-version) user-emacs-directory)) ;;; Standard package repositories (add-to-list \u0026#39;package-archives \u0026#39;( \u0026#34;melpa\u0026#34; . \u0026#34;https://melpa.org/packages/\u0026#34;) t) ;; Official MELPA Mirror, in case necessary. ;;(add-to-list \u0026#39;package-archives (cons \u0026#34;melpa-mirror\u0026#34; (concat proto \u0026#34;://www.mirrorservice.org/sites/melpa.org/packages/\u0026#34;)) t) ;; Work-around for https://debbugs.gnu.org/cgi/bugreport.cgi?bug=34341 (when (and (version\u0026lt; emacs-version \u0026#34;26.3\u0026#34;) (boundp \u0026#39;libgnutls-version) (\u0026gt;= libgnutls-version 30604)) (setq gnutls-algorithm-priority \u0026#34;NORMAL:-VERS-TLS1.3\u0026#34;)) ;;; On-demand installation of packages (defun require-package (package \u0026amp;optional min-version no-refresh) \u0026#34;Install given PACKAGE, optionally requiring MIN-VERSION. If NO-REFRESH is non-nil, the available package lists will not be re-downloaded in order to locate PACKAGE.\u0026#34; (when (stringp min-version) (setq min-version (version-to-list min-version))) (or (package-installed-p package min-version) (let* ((known (cdr (assoc package package-archive-contents))) (best (car (sort known (lambda (a b) (version-list-\u0026lt;= (package-desc-version b) (package-desc-version a))))))) (if (and best (version-list-\u0026lt;= min-version (package-desc-version best))) (package-install best) (if no-refresh (error \u0026#34;No version of %s \u0026gt;= %S is available\u0026#34; package min-version) (package-refresh-contents) (require-package package min-version t))) (package-installed-p package min-version)))) (defun maybe-require-package (package \u0026amp;optional min-version no-refresh) \u0026#34;Try to install PACKAGE, and return non-nil if successful. In the event of failure, return nil and print a warning message. Optionally require MIN-VERSION. If NO-REFRESH is non-nil, the available package lists will not be re-downloaded in order to locate PACKAGE.\u0026#34; (condition-case err (require-package package min-version no-refresh) (error (message \u0026#34;Couldn\u0026#39;t install optional package `%s\u0026#39;: %S\u0026#34; package err) nil))) ;;; Fire up package.el (setq package-enable-at-startup nil) (package-initialize) ;; package.el updates the saved version of package-selected-packages correctly only ;; after custom-file has been loaded, which is a bug. We work around this by adding ;; the required packages to package-selected-packages after startup is complete. (defvar sanityinc/required-packages nil) (defun sanityinc/note-selected-package (oldfun package \u0026amp;rest args) \u0026#34;If OLDFUN reports PACKAGE was successfully installed, note that fact. The package name is noted by adding it to `sanityinc/required-packages\u0026#39;. This function is used as an advice for `require-package\u0026#39;, to which ARGS are passed.\u0026#34; (let ((available (apply oldfun package args))) (prog1 available (when available (add-to-list \u0026#39;sanityinc/required-packages package))))) (advice-add \u0026#39;require-package :around \u0026#39;sanityinc/note-selected-package) (when (fboundp \u0026#39;package--save-selected-packages) (require-package \u0026#39;seq) (add-hook \u0026#39;after-init-hook (lambda () (package--save-selected-packages (seq-uniq (append sanityinc/required-packages package-selected-packages)))))) (require-package \u0026#39;fullframe) (fullframe list-packages quit-window) (let ((package-check-signature nil)) (require-package \u0026#39;gnu-elpa-keyring-update)) (defun sanityinc/set-tabulated-list-column-width (col-name width) \u0026#34;Set any column with name COL-NAME to the given WIDTH.\u0026#34; (when (\u0026gt; width (length col-name)) (cl-loop for column across tabulated-list-format when (string= col-name (car column)) do (setf (elt column 1) width)))) (defun sanityinc/maybe-widen-package-menu-columns () \u0026#34;Widen some columns of the package menu table to avoid truncation.\u0026#34; (when (boundp \u0026#39;tabulated-list-format) (sanityinc/set-tabulated-list-column-width \u0026#34;Version\u0026#34; 13) (let ((longest-archive-name (apply \u0026#39;max (mapcar \u0026#39;length (mapcar \u0026#39;car package-archives))))) (sanityinc/set-tabulated-list-column-width \u0026#34;Archive\u0026#34; longest-archive-name)))) (add-hook \u0026#39;package-menu-mode-hook \u0026#39;sanityinc/maybe-widen-package-menu-columns) ;; Bootstrap `use-package\u0026#39; (when (version\u0026lt; emacs-version \u0026#34;29.0\u0026#34;) (unless (package-installed-p \u0026#39;use-package) (package-refresh-contents) (package-install \u0026#39;use-package)) (eval-and-compile (setq use-package-always-ensure nil) (setq use-package-always-defer nil) (setq use-package-always-demand nil) (setq use-package-expand-minimally nil) (setq use-package-enable-imenu-support t)) (eval-when-compile (require \u0026#39;use-package))) ;; Install straight.el (defvar bootstrap-version) (let ((bootstrap-file (expand-file-name \u0026#34;straight/repos/straight.el/bootstrap.el\u0026#34; user-emacs-directory)) (bootstrap-version 5)) (unless (file-exists-p bootstrap-file) (with-current-buffer (url-retrieve-synchronously \u0026#34;https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el\u0026#34; \u0026#39;silent \u0026#39;inhibit-cookies) (goto-char (point-max)) (eval-print-last-sexp))) (load bootstrap-file nil \u0026#39;nomessage)) (provide \u0026#39;init-elpa) ;;; init-elpa.el ends here init-local.el 在 init.el 中有如下代码。\n;; Allow users to provide an optional \u0026#34;init-local\u0026#34; containing personal settings (require \u0026#39;init-local nil t) 默认没有 init-local.org 这个文件,创建一个,就可以往里面塞自己的配置了。\nConsult Purcell 中已经包含了这个 package,也有 consult-ripgrep 这个方法。需要安装 ripgrep。\nbrew install ripgrep (defun bms/org-roam-rg-search () \u0026#34;Search org-roam directory using consult-ripgrep. With live-preview.\u0026#34; (interactive) (let ((consult-ripgrep-command \u0026#34;rg --null --ignore-case --type org --line-buffered --color=always --max-columns=500 --no-heading --line-number . -e ARG OPTS\u0026#34;)) (consult-ripgrep org-roam-directory))) (global-set-key (kbd \u0026#34;C-c rr\u0026#34;) \u0026#39;bms/org-roam-rg-search) 搜索 org-roam-directory 下的内容。\nDeft 用作一些不想放在 org-roam 中的一些笔记的操作。\n(require-package \u0026#39;deft) (setq deft-extensions \u0026#39;(\u0026#34;md\u0026#34; \u0026#34;tex\u0026#34; \u0026#34;org\u0026#34; \u0026#34;mw\u0026#34; \u0026#34;conf\u0026#34;)) (setq deft-directory \u0026#34;~/Dropbox/org/\u0026#34;) (setq deft-recursive t) (advice-add \u0026#39;deft-parse-title :override #\u0026#39;cm/deft-parse-title) (setq deft-strip-summary-regexp (concat \u0026#34;\\\\(\u0026#34; \u0026#34;[\\n\\t]\u0026#34; ;; blank \u0026#34;\\\\|^#\\\\+[[:alpha:]_]+:.*$\u0026#34; ;; org-mode metadata \u0026#34;\\\\|^#\\s[-]*$\u0026#34; ;; org-mode metadata \u0026#34;\\\\|^:PROPERTIES:\\n\\\\(.+\\n\\\\)+:END:\\n\u0026#34; \u0026#34;\\\\)\u0026#34;)) (global-set-key [f7] \u0026#39;deft) (global-set-key (kbd \u0026#34;C-x C-g\u0026#34;) \u0026#39;deft-find-file) ;; deft parse title (defun cm/deft-parse-title (file contents) \u0026#34;Parse the given FILE and CONTENTS and determine the title. If `deft-use-filename-as-title\u0026#39; is nil, the title is taken to be the first non-empty line of the FILE. Else the base name of the FILE is used as title.\u0026#34; (let ((begin (string-match \u0026#34;^#\\\\+[tT][iI][tT][lL][eE]: .*$\u0026#34; contents))) (if begin (string-trim (substring contents begin (match-end 0)) \u0026#34;#\\\\+[tT][iI][tT][lL][eE]: *\u0026#34; \u0026#34;[\\n\\t ]+\u0026#34;) (deft-base-filename file)))) Ox-hugo 导出到 Hugo,方便与他人分享内容。\n(require-package \u0026#39;ox-hugo) (with-eval-after-load \u0026#39;ox (require \u0026#39;ox-hugo)) 导出到 Hugo 还需要在文件头部添加\n#+HUGO_BASE_DIR: ~/Dropbox/hugo/ #+HUGO_SECTION: posts/main #+HUGO_WEIGHT: auto #+HUGO_AUTO_SET_LASTMOD: t 需要到处为 \u0026lt;mark\u0026gt;\u0026lt;/mark\u0026gt; 时1\n#+header: :trim-pre nil :trim-post nil #+begin_mark marked text #+end_mark init-latex.el ;;; init-latex.el --- Custom configuration ;;; Commentary (require-package \u0026#39;auctex) (require-package \u0026#39;cdlatex) (require-package \u0026#39;cdlatex) (add-hook \u0026#39;LaTeX-mode-hook \u0026#39;turn-on-cdlatex) ;; 导出中文 PDF ;; 需要将 elegantpaper.cls 文件放在 org 目录下 ;; 需要安装依赖 brew install pygments ;; org 文件头部增加 ;; #+LATEX_COMPILER: xelatex ;; #+LATEX_CLASS: elegantpaper ;; #+OPTIONS: prop:t (with-eval-after-load \u0026#39;ox-latex ;; http://orgmode.org/worg/org-faq.html#using-xelatex-for-pdf-export ;; latexmk runs pdflatex/xelatex (whatever is specified) multiple times ;; automatically to resolve the cross-references. (setq org-latex-pdf-process \u0026#39;(\u0026#34;latexmk -xelatex -quiet -shell-escape -f %f\u0026#34;)) (add-to-list \u0026#39;org-latex-classes \u0026#39;(\u0026#34;elegantpaper\u0026#34; \u0026#34;\\\\documentclass[lang=cn]{elegantpaper} [NO-DEFAULT-PACKAGES] [PACKAGES] [EXTRA]\u0026#34; (\u0026#34;\\\\section{%s}\u0026#34; . \u0026#34;\\\\section*{%s}\u0026#34;) (\u0026#34;\\\\subsection{%s}\u0026#34; . \u0026#34;\\\\subsection*{%s}\u0026#34;) (\u0026#34;\\\\subsubsection{%s}\u0026#34; . \u0026#34;\\\\subsubsection*{%s}\u0026#34;) (\u0026#34;\\\\paragraph{%s}\u0026#34; . \u0026#34;\\\\paragraph*{%s}\u0026#34;) (\u0026#34;\\\\subparagraph{%s}\u0026#34; . \u0026#34;\\\\subparagraph*{%s}\u0026#34;))) (setq org-latex-listings \u0026#39;minted) (add-to-list \u0026#39;org-latex-packages-alist \u0026#39;(\u0026#34;cache=false\u0026#34; \u0026#34;minted\u0026#34; t))) (setq org-preview-latex-default-process \u0026#39;dvisvgm) ;; xenops (require-package \u0026#39;xenops) (add-hook \u0026#39;LaTeX-mode-hook #\u0026#39;xenops-mode) (with-eval-after-load \u0026#39;xenops (setq xenops-math-image-scale-factor 1.3 xenops-image-try-write-clipboard-image-to-file nil xenops-reveal-on-entry nil xenops-math-image-margin 0 xenops-math-latex-max-tasks-in-flight 16 xenops-auctex-electric-insert-commands nil) (defun eli/change-xenops-latex-header (orig \u0026amp;rest args) (let ((org-format-latex-header \u0026#34;\\\\documentclass[dvisvgm,preview]{standalone}\\n\\\\usepackage{arev}\\n\\\\usepackage{color}\\n[PACKAGES]\\n[DEFAULT-PACKAGES]\u0026#34;)) (apply orig args))) (advice-add \u0026#39;xenops-math-latex-make-latex-document :around #\u0026#39;eli/change-xenops-latex-header) (advice-add \u0026#39;xenops-math-file-name-static-hash-data :around #\u0026#39;eli/change-xenops-latex-header) (defun eli/delete-region () (if (use-region-p) (delete-region (region-beginning) (region-end)))) (advice-add \u0026#39;xenops-handle-paste-default :before #\u0026#39;eli/delete-region) (defun eli/xenops-math-add-cursor-sensor-property () (-when-let* ((element (xenops-math-parse-element-at-point))) (let ((beg (plist-get element :begin)) (end (plist-get element :end)) (props \u0026#39;(cursor-sensor-functions (xenops-math-handle-element-transgression)))) (add-text-properties beg end props) (add-text-properties (1- end) end \u0026#39;(rear-nonsticky (cursor-sensor-functions)))))) (advice-add \u0026#39;xenops-math-add-cursor-sensor-property :override #\u0026#39;eli/xenops-math-add-cursor-sensor-property) ;; Vertically align LaTeX preview in org mode (setq xenops-math-latex-process-alist \u0026#39;((dvisvgm :programs (\u0026#34;latex\u0026#34; \u0026#34;dvisvgm\u0026#34;) :description \u0026#34;dvi \u0026gt; svg\u0026#34; :message \u0026#34;you need to install the programs: latex and dvisvgm.\u0026#34; :image-input-type \u0026#34;dvi\u0026#34; :image-output-type \u0026#34;svg\u0026#34; :image-size-adjust (1.7 . 1.5) :latex-compiler (\u0026#34;latex -interaction nonstopmode -shell-escape -output-format dvi -output-directory %o %f\u0026#34;) :image-converter (\u0026#34;dvisvgm %f -n -e -b 1 -c %S -o %O\u0026#34;)))) ;; from https://list.orgmode.org/874k9oxy48.fsf@gmail.com/#Z32lisp:org.el (defun eli/org--match-text-baseline-ascent (imagefile) \u0026#34;Set `:ascent\u0026#39; to match the text baseline of an image to the surrounding text. Compute `ascent\u0026#39; with the data collected in IMAGEFILE.\u0026#34; (let* ((viewbox (split-string (xml-get-attribute (car (xml-parse-file imagefile)) \u0026#39;viewBox))) (min-y (string-to-number (nth 1 viewbox))) (height (string-to-number (nth 3 viewbox))) (ascent (round (* -100 (/ min-y height))))) (if (or (\u0026lt; ascent 0) (\u0026gt; ascent 100)) \u0026#39;center ascent))) (defun eli/xenops-preview-align-baseline (element \u0026amp;rest _args) \u0026#34;Redisplay SVG image resulting from successful LaTeX compilation of ELEMENT. Use the data in log file (e.g. \\\u0026#34;! Preview: Snippet 1 ended.(368640+1505299x1347810).\\\u0026#34;) to calculate the decent value of `:ascent\u0026#39;. \u0026#34; (let* ((inline-p (eq \u0026#39;inline-math (plist-get element :type))) (ov-beg (plist-get element :begin)) (ov-end (plist-get element :end)) (cache-file (car (last _args))) (ov (car (overlays-at (/ (+ ov-beg ov-end) 2) t))) img new-img ascent) (when (and ov inline-p) (setq ascent (+ 1 (eli/org--match-text-baseline-ascent cache-file))) (setq img (cdr (overlay-get ov \u0026#39;display))) (setq new-img (plist-put img :ascent ascent)) (overlay-put ov \u0026#39;display (cons \u0026#39;image new-img))))) (advice-add \u0026#39;xenops-math-display-image :after #\u0026#39;eli/xenops-preview-align-baseline) ;; from: https://kitchingroup.cheme.cmu.edu/blog/2016/11/06/ ;; Justifying-LaTeX-preview-fragments-in-org-mode/ ;; specify the justification you want (plist-put org-format-latex-options :justify \u0026#39;right) (defun eli/xenops-justify-fragment-overlay (element \u0026amp;rest _args) (let* ((ov-beg (plist-get element :begin)) (ov-end (plist-get element :end)) (ov (car (overlays-at (/ (+ ov-beg ov-end) 2) t))) (position (plist-get org-format-latex-options :justify)) (inline-p (eq \u0026#39;inline-math (plist-get element :type))) width offset) (when (and ov (imagep (overlay-get ov \u0026#39;display))) (setq width (car (image-display-size (overlay-get ov \u0026#39;display)))) (cond ((and (eq \u0026#39;right position) (not inline-p) (\u0026gt; width 50)) (setq offset (floor (- fill-column width))) (if (\u0026lt; offset 0) (setq offset 0)) (overlay-put ov \u0026#39;before-string (make-string offset ? ))) ((and (eq \u0026#39;right position) (not inline-p)) (setq offset (floor (- (/ fill-column 2) (/ width 2)))) (if (\u0026lt; offset 0) (setq offset 0)) (overlay-put ov \u0026#39;before-string (make-string offset ? ))))))) (advice-add \u0026#39;xenops-math-display-image :after #\u0026#39;eli/xenops-justify-fragment-overlay) ;; from: https://kitchingroup.cheme.cmu.edu/blog/2016/11/07/ ;; Better-equation-numbering-in-LaTeX-fragments-in-org-mode/ (defun eli/xenops-renumber-environment (orig-func element latex colors cache-file display-image) (let ((results \u0026#39;()) (counter -1) (numberp) (outline-regexp org-outline-regexp)) (setq results (cl-loop for (begin . env) in (org-element-map (org-element-parse-buffer) \u0026#39;latex-environment (lambda (env) (cons (org-element-property :begin env) (org-element-property :value env)))) collect (cond ((and (string-match \u0026#34;\\\\\\\\begin{equation}\u0026#34; env) (not (string-match \u0026#34;\\\\\\\\tag{\u0026#34; env))) (cl-incf counter) (cons begin counter)) ((and (string-match \u0026#34;\\\\\\\\begin{align}\u0026#34; env) (string-match \u0026#34;\\\\\\\\notag\u0026#34; env)) (cl-incf counter) (cons begin counter)) ((string-match \u0026#34;\\\\\\\\begin{align}\u0026#34; env) (prog2 (cl-incf counter) (cons begin counter) (with-temp-buffer (insert env) (goto-char (point-min)) ;; \\\\ is used for a new line. Each one leads ;; to a number (cl-incf counter (count-matches \u0026#34;\\\\\\\\$\u0026#34;)) ;; unless there are nonumbers. (goto-char (point-min)) (cl-decf counter (count-matches \u0026#34;\\\\nonumber\u0026#34;))))) (t (cons begin nil))))) (when (setq numberp (cdr (assoc (plist-get element :begin) results))) (setq latex (concat (format \u0026#34;\\\\setcounter{equation}{%s}\\n\u0026#34; numberp) latex)))) (funcall orig-func element latex colors cache-file display-image)) (advice-add \u0026#39;xenops-math-latex-create-image :around #\u0026#39;eli/xenops-renumber-environment)) (autoload #\u0026#39;mathpix-screenshot \u0026#34;mathpix\u0026#34; nil t) (with-eval-after-load \u0026#39;mathpix (let ((n (random 4))) (setq mathpix-app-id (with-temp-buffer (insert-file-contents \u0026#34;~/.emacs.d/private/mathpix-app-id\u0026#34;) (nth n (split-string (buffer-string) \u0026#34;\\n\u0026#34;))) mathpix-app-key (with-temp-buffer (insert-file-contents \u0026#34;~/.emacs.d/private/mathpix-app-key\u0026#34;) (nth n (split-string (buffer-string) \u0026#34;\\n\u0026#34;))))) (setq mathpix-screenshot-method \u0026#34;flameshot gui --raw \u0026gt; %s\u0026#34;)) (provide \u0026#39;init-latex) ;;; init-latex.el ends here 离线分享的时候最大程度的保持原貌的方法就是 PDF。\n需要安装 texlive\nbrew install texlive Latex 语法可以参考:Latex\n导出 导出含有中文时是需要配置 CJK,ElegantPaper 可以跨过这一步设置,只需要将 elegantpaper.cls 放到需要导出 PDF 的目录下,同时安装依赖。\nbrew install pygments 在导出文件头部增加\n#+LATEX_COMPILER: xelatex #+LATEX_CLASS: elegantpaper #+OPTIONS: prop:t 预览 预览需要安装 firamath 字体,可以有更好的展示效果。另外 dvisvgm 应该在 texlive 中已经包含才对,但是依旧找不到。这里我重新从源码编译了一份。\ninit-corfu.el 增加了 kind-icon\n;;; init-corfu.el --- Interactive completion in buffers -*- lexical-binding: t -*- ;;; Commentary: ;;; Code: ;; WAITING: haskell-mode sets tags-table-list globally, breaks tags-completion-at-point-function ;; TODO Default sort order should place [a-z] before punctuation (setq tab-always-indent \u0026#39;complete) (when (maybe-require-package \u0026#39;orderless) (with-eval-after-load \u0026#39;vertico (require \u0026#39;orderless) (setq completion-styles \u0026#39;(orderless basic)))) (setq completion-category-defaults nil completion-category-overrides nil) (setq completion-cycle-threshold 4) (when (maybe-require-package \u0026#39;corfu) (setq-default corfu-auto t) (with-eval-after-load \u0026#39;eshell (add-hook \u0026#39;eshell-mode-hook (lambda () (setq-local corfu-auto nil)))) (setq-default corfu-quit-no-match \u0026#39;separator) (add-hook \u0026#39;after-init-hook \u0026#39;global-corfu-mode) (when (featurep \u0026#39;corfu-popupinfo) (with-eval-after-load \u0026#39;corfu (corfu-popupinfo-mode))) (when (maybe-require-package \u0026#39;kind-icon) ; Enable `kind-icon\u0026#39; (with-eval-after-load \u0026#39;corfu (add-to-list \u0026#39;corfu-margin-formatters #\u0026#39;kind-icon-margin-formatter) )) ) (provide \u0026#39;init-corfu) ;;; init-corfu.el ends here 在切换主题的时候,kind-icon 会有残留,可以执行 M-x kind-icon-reset-cache 清除 cache 即可。\ninit-bibtex.el 管理 Bibtex 可以方便在文章中引用,以及新增读书笔记,结合 agenda 更好的管理读书条目以及进度。\n其中自定义了笔记模板和读书列表模板,前者是增加了 citation key 作为 roam ref,org ID 的生成以及时间戳;后者则是将默认的 TODO 关键字换成了 PROJECT 关键字,并将 active timestamps 改为 inactive timestamps,方便计算每个 PROJECT 创建了多长时间展示在 agenda 当中。\n;;; init-ebib.el --- Custom configuration ;;; Commentary (use-package bibtex :defer t :config (setq bibtex-file-path \u0026#34;~/Dropbox/org/bib/\u0026#34; bibtex-files \u0026#39;(\u0026#34;bibtex.bib\u0026#34;) bibtex-notes-path \u0026#34;~/Dropbox/org/main/\u0026#34; bibtex-align-at-equal-sign t bibtex-autokey-titleword-separator \u0026#34;-\u0026#34; bibtex-autokey-year-title-separator \u0026#34;-\u0026#34; bibtex-autokey-name-year-separator \u0026#34;-\u0026#34; bibtex-dialect \u0026#39;biblatex)) (use-package ebib :straight t :commands ebib-zotero-protocol-handler :init :config (setq ebib-default-directory bibtex-file-path ebib-bib-search-dirs `(,bibtex-file-path) ebib-file-search-dirs `(,(concat bibtex-file-path \u0026#34;files/\u0026#34;)) ebib-notes-directory bibtex-notes-path ebib-reading-list-file \u0026#34;~/Dropbox/org/agenda/books.org\u0026#34; ebib-bibtex-dialect bibtex-dialect ebib-file-associations \u0026#39;((\u0026#34;pdf\u0026#34; . \u0026#34;open\u0026#34;)) ebib-index-default-sort \u0026#39;(\u0026#34;timestamp\u0026#34; . descend) ;; 笔记模板 ebib-notes-template \u0026#34;:PROPERTIES:\\n:ID: %i\\n:ROAM_REFS: @%k\\n:END:\\n#+title: %t\\n#+description: %d\\n#+date: %s\\n%%?\\n\u0026#34; ebib-notes-template-specifiers \u0026#39;((?k . ebib-create-key) (?i . ebib-create-id) (?t . ebib-create-org-title) (?d . ebib-create-org-description) (?l . ebib-create-org-link) (?s . ebib-create-org-time-stamp)) ;; 读书列表模板 ebib-reading-list-template \u0026#34;* %M %T\\n:PROPERTIES:\\n%K\\n:END:\\n%F\\n%S\\n\u0026#34; ebib-reading-list-template-specifiers \u0026#39;((?M . ebib-reading-list-project-marker) (?T . ebib-create-org-title) (?K . ebib-reading-list-create-org-identifier) (?F . ebib-create-org-file-link) (?S . ebib-create-org-stamp-inactive)) ebib-preload-bib-files bibtex-files ebib-use-timestamp t) (defun ebib-create-key (key _db) \u0026#34;Return the KEY in DB for the Org mode note.\u0026#34; (format \u0026#34;%s\u0026#34; key)) (defun ebib-create-id (_key _db) \u0026#34;Create an ID for the Org mode note.\u0026#34; (org-id-new)) (defun ebib-create-org-time-stamp (_key _db) \u0026#34;Create timestamp for the Org mode note.\u0026#34; (format \u0026#34;%s\u0026#34; (with-temp-buffer (org-insert-time-stamp nil)))) ;; 替换官方的 ebib-reading-list-todo-marker (defcustom ebib-reading-list-project-marker \u0026#34;PROJECT\u0026#34; \u0026#34;Marker for reading list items that are still open.\u0026#34; :group \u0026#39;ebib-reading-list :type \u0026#39;(string :tag \u0026#34;Project marker\u0026#34;)) ;; 获取 [%Y-%m-%d %a %H:%M] 格式的时间戳 (defun ebib-create-org-stamp-inactive (_key _db) \u0026#34;Create inactive timestamp for the Org mode note.\u0026#34; (let ((org-time-stamp-custom-formats org-time-stamp-custom-formats)) (format \u0026#34;%s\u0026#34; (with-temp-buffer (org-time-stamp-inactive nil)))))) (require-package \u0026#39;citar) (use-package citar :no-require :custom (org-cite-global-bibliography \u0026#39;(\u0026#34;~/Dropbox/org/bib/bibtex.bib\u0026#34;)) (citar-notes-paths (list \u0026#34;~/Dropbox/org/main\u0026#34;)) (citar-library-paths (list \u0026#34;~/Dropbox/org/bib/files\u0026#34;)) (org-cite-insert-processor \u0026#39;citar) (org-cite-follow-processor \u0026#39;citar) (org-cite-activate-processor \u0026#39;citar) (citar-bibliography org-cite-global-bibliography) ;; optional: org-cite-insert is also bound to C-c C-x C-@ :bind (:map org-mode-map :package org (\u0026#34;C-c b\u0026#34; . #\u0026#39;org-cite-insert))) (provide \u0026#39;init-bibtex) ;;; init-bibtex.el ends here 搜索条目可以用 \u0026amp; 来搜索,参考 Ebib Manual。\ninit-themes.el 用的 Screen shots of the Ef themes for GNU Emacs | Protesilaos Stavrou,ef-spring 作为非编码时的主题,ef-night 作为编码时的主题。另外设置背景透明以及光标所在代码块按照括号高亮的函数也在放在这里。\n;;; init-themes.el --- Defaults for themes -*- lexical-binding: t -*- ;;; Commentary: ;;; Code: (require-package \u0026#39;ef-themes) (require-package \u0026#39;gruvbox-theme) ;; Don\u0026#39;t prompt to confirm theme safety. This avoids problems with ;; first-time startup on Emacs \u0026gt; 26.3. (setq custom-safe-themes t) ;; If you don\u0026#39;t customize it, this is the theme you get. (setq-default custom-enabled-themes \u0026#39;(ef-spring)) ;; Ensure that themes will be applied even if they have not been customized (defun reapply-themes () \u0026#34;Forcibly load the themes listed in `custom-enabled-themes\u0026#39;.\u0026#34; (dolist (theme custom-enabled-themes) (unless (custom-theme-p theme) (load-theme theme))) (custom-set-variables `(custom-enabled-themes (quote ,custom-enabled-themes)))) (add-hook \u0026#39;after-init-hook \u0026#39;reapply-themes) ;; Toggle between light and dark (defun light () \u0026#34;Activate a light color theme.\u0026#34; (interactive) (disable-theme (car custom-enabled-themes)) (setq custom-enabled-themes \u0026#39;(ef-spring)) (reapply-themes)) (defun dark () \u0026#34;Activate a dark color theme.\u0026#34; (interactive) (disable-theme (car custom-enabled-themes)) (setq custom-enabled-themes \u0026#39;(gruvbox)) (reapply-themes)) (when (maybe-require-package \u0026#39;dimmer) (setq-default dimmer-fraction 0.15) (add-hook \u0026#39;after-init-hook \u0026#39;dimmer-mode) (with-eval-after-load \u0026#39;dimmer ;; TODO: file upstream as a PR (advice-add \u0026#39;frame-set-background-mode :after (lambda (\u0026amp;rest args) (dimmer-process-all)))) (with-eval-after-load \u0026#39;dimmer ;; Don\u0026#39;t dim in terminal windows. Even with 256 colours it can ;; lead to poor contrast. Better would be to vary dimmer-fraction ;; according to frame type. (defun sanityinc/display-non-graphic-p () (not (display-graphic-p))) (add-to-list \u0026#39;dimmer-exclusion-predicates \u0026#39;sanityinc/display-non-graphic-p))) ;; fake transparency (defun user/change-background-opacity (alpha) (interactive \u0026#34;nOpacity: \u0026#34;) (if (and (\u0026gt;= alpha 0) (\u0026lt;= alpha 100)) (user/set-background-opacity alpha) (format-message \u0026#34;Opacity has to be between 0 and 100 but is %d.\u0026#34; alpha))) (defun user/set-background-opacity (alpha) (set-frame-parameter (selected-frame) \u0026#39;alpha alpha) (add-to-list \u0026#39;default-frame-alist (cons \u0026#39;alpha alpha))) ;; (user/set-background-opacity 90) ;; true transparency ;; (defun kb/toggle-window-transparency () ;; \u0026#34;Toggle transparency.\u0026#34; ;; (interactive) ;; (let ((alpha-transparency 75)) ;; (pcase (frame-parameter nil \u0026#39;alpha-background) ;; (alpha-transparency (set-frame-parameter nil \u0026#39;alpha-background 100)) ;; (t (set-frame-parameter nil \u0026#39;alpha-background alpha-transparency))))) ;; (kb/toggle-window-transparency) (provide \u0026#39;init-themes) ;;; init-themes.el ends here init-elfeed.el ;;; init-elfeed.el --- Custom configuration ;;; Commentary (require-package \u0026#39;elfeed) (global-set-key (kbd \u0026#34;C-x w\u0026#34;) \u0026#39;elfeed) (use-package elfeed-org :ensure t :config (elfeed-org) (setq rmh-elfeed-org-files (list \u0026#34;~/Dropbox/org/elfeed.org\u0026#34;))) (defun concatenate-authors (authors-list) \u0026#34;Given AUTHORS-LIST, list of plists; return string of all authors concatenated.\u0026#34; (mapconcat (lambda (author) (plist-get author :name)) authors-list \u0026#34;, \u0026#34;)) (defun my-search-print-fn (entry) \u0026#34;Print ENTRY to the buffer.\u0026#34; (let* ((date (elfeed-search-format-date (elfeed-entry-date entry))) (title (or (elfeed-meta entry :title) (elfeed-entry-title entry) \u0026#34;\u0026#34;)) (title-faces (elfeed-search--faces (elfeed-entry-tags entry))) (feed (elfeed-entry-feed entry)) (feed-title (when feed (or (elfeed-meta feed :title) (elfeed-feed-title feed)))) (entry-authors (concatenate-authors (elfeed-meta entry :authors))) (tags (mapcar #\u0026#39;symbol-name (elfeed-entry-tags entry))) (tags-str (mapconcat (lambda (s) (propertize s \u0026#39;face \u0026#39;elfeed-search-tag-face)) tags \u0026#34;,\u0026#34;)) (title-width (- (window-width) 10 elfeed-search-trailing-width)) (title-column (elfeed-format-column title (elfeed-clamp elfeed-search-title-min-width title-width elfeed-search-title-max-width) :left)) (authors-width 135) (authors-column (elfeed-format-column entry-authors (elfeed-clamp elfeed-search-title-min-width authors-width 131) :left))) (insert (propertize date \u0026#39;face \u0026#39;elfeed-search-date-face) \u0026#34; \u0026#34;) (insert (propertize title-column \u0026#39;face title-faces \u0026#39;kbd-help title) \u0026#34; \u0026#34;) (insert (propertize authors-column \u0026#39;face \u0026#39;elfeed-search-date-face \u0026#39;kbd-help entry-authors) \u0026#34; \u0026#34;) ;; (when feed-title ;; (insert (propertize entry-authors ;; \u0026#39;face \u0026#39;elfeed-search-feed-face) \u0026#34; \u0026#34;)) (when entry-authors (insert (propertize feed-title \u0026#39;face \u0026#39;elfeed-search-feed-face) \u0026#34; \u0026#34;)) ;; (when tags ;; (insert \u0026#34;(\u0026#34; tags-str \u0026#34;)\u0026#34;)) ) ) (setq elfeed-search-print-entry-function #\u0026#39;my-search-print-fn) (provide \u0026#39;init-elfeed) ;;; init-elfeed.el ends here init-telega.el Building TDLib brew install tdlib 的版本过低,需要自行编译,参考 TDLib build instructions 。这个之后需要 M-x telega-server-build 重新加载 telega-server。\nxcode-select --install /bin/bash -c \u0026#34;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\u0026#34; brew install gperf cmake openssl git clone https://github.com/tdlib/td.git cd td rm -rf build mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Release -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl/ -DCMAKE_INSTALL_PREFIX:PATH=/usr/local .. cmake --build . --target install cd .. cd .. ls -l /usr/local 如果报错 \u0026quot;user-error: TDLib is not installed into \u0026quot;/usr/local\u0026quot;. Set ‘telega-server-libs-prefix’ to the TDLib installion path\u0026quot;,则可以通过 M-: (setq telega-server-libs-prefix “/path/to/tdlib/install/path”) RET 然后 M-x telega-server-build RET 重新构建。\nRunning telega-server in docker brew install --cask docker brew install ffmpeg docker pull zevlg/telega-server:latest 在配置中加上下面代码即可。\n(setq telega-use-docker t) 快捷键 功能 备注 C-c C-v 发送图片 brew install pngpaste ! 点赞 C-c C-a Attachment type 其中 markup 可以发送 md 等格式 C-c C-c 光标移到消息后,按快捷键取消搜索。 M-g r 跳转到最新消息 或者 M-g \u0026gt; 头像裂开的问题:是由于字体高度问题导致的,安装 init-font.el 中的设置,Iosevka 大多数中文字体配合,并设置 (add-to-list 'face-font-rescale-alist '(\u0026quot;Apple Color Emoji\u0026quot; . 0.8)) 可以实现头像不裂开。但由于 Emoji 以及各种昵称中的特殊字符,比如 Noto Sans Egyptian Hieroglyphs 这种古埃及象形文字,以及 Noto Sans Kannada 和 STIXGeneral 等字体,出现在昵称以及 Reply 行中,都会导致头像裂开。\n有几种解决的方案。\n使用更纱黑体(字体太丑且并不能兼容那些使用第三点当中提到的特殊字体的情况)。 (defun telega-buffer-face-mode-variable () (interactive) (make-face \u0026#39;my-telega-face) ;; (set-face-attribute \u0026#39;my-telega-face nil :font \u0026#34;M+ 1m\u0026#34;) (set-face-attribute \u0026#39;my-telega-face nil :font \u0026#34;Sarasa Mono SC Nerd 13\u0026#34;) (setq buffer-face-mode-face \u0026#39;my-telega-face) (buffer-face-mode)) (add-hook \u0026#39;telega-root-mode-hook \u0026#39;telega-buffer-face-mode-variable) (add-hook \u0026#39;telega-webpage-mode-hook \u0026#39;telega-buffer-face-mode-variable) (add-hook \u0026#39;telega-chat-mode-hook \u0026#39;telega-buffer-face-mode-variable) 使用 (setf (alist-get 2 telega-avatar-factors-alist) '(0.5 . 0.1)) 来使得本来两行显示的头像缩小至一行显示(头像显示几乎废了)。 在基础字号的基础上(这里是 14)调整特殊字符/字体的缩放就可以完美解决头像裂开的问题了,完整代码如下。 contrib telega 中有个 contrib 的子目录,当中有一些挺实用的功能。这里启用了三种,过滤广告、代码高亮以及短链。\n原生的加载子目录。\n(push (expand-file-name \u0026#34;contrib\u0026#34; (file-name-directory (locate-library \u0026#34;telega\u0026#34;))) load-path) 使用 straight 的话如下。\n:straight (:host github :repo \u0026#34;zevlg/telega.el\u0026#34; :files (:defaults \u0026#34;contrib\u0026#34;)) Animated Stickers git clone https://github.com/zevlg/tgs2png.git git submodule init git submodule update mkdir build cd build cmake .. make copy tgs2png somewhere into $PATH 这里我编译完就已经是可用的了。\ndisplay-buffer-alist 每次打开 telega 都是占了 emacs 窗口的一半(emacs 我都是全屏使用),需要手动去调整 telega 的窗口。这里设置了一下 display-buffer-alist 之后就可以固定 telega 的窗口在右侧,并且占据窗口的 35% 的宽度。\n(add-hook \u0026#39;telega-mode-hook (lambda () (display-buffer (current-buffer) \u0026#39;((display-buffer-in-side-window))))) (setq display-buffer-alist \u0026#39;((\u0026#34;\\\\*Telega Root\\\\*\u0026#34; . ((display-buffer-in-side-window) (window-width . 0.35) (side . right) (slot . 0) (dedicated . nil))))) 上述代码除了 dedicated 属性,都是由 ChatGPT Plus 生成。不设置该字段为 nil 会导致从 Telega 列表进入聊天 buffer 后重新打开新的 buffer,具体原因参照 dedicated 字段的解释参见 Displaying Buffers in Side Windows (GNU Emacs Lisp Reference Manual)。\n最后总的配置如下。\n;;; init-telega.el --- Custom configuration ;;; Commentary (require-package \u0026#39;telega) (require-package \u0026#39;all-the-icons) ;; If language-detection is available, ;; then laguage could be detected automatically ;; for code blocks without language explicitly specified. (require-package \u0026#39;language-detection) ;; 加载子文件夹 contrib (push (expand-file-name \u0026#34;contrib\u0026#34; (file-name-directory (locate-library \u0026#34;telega\u0026#34;))) load-path) (require \u0026#39;telega-mnz) (global-telega-mnz-mode 1) (setq telega-debug t) ;; 自动播放 gif (telega-autoplay-mode 1) (define-key global-map (kbd \u0026#34;C-c t\u0026#34;) telega-prefix-map) ;; 去除昵称、回复行的背景高亮 (defun my-telega-chat-mode () ;; telega-msg-inline-reply ⊆ telega-msg-heading ;; telega-msg-inline-forward ⊆ telega-msg-heading (set-face-attribute \u0026#39;telega-msg-heading nil :underline nil :inherit nil ;; Warning: setting attribute ‘:background’ of face ‘telega-msg-heading’: nil value is invalid, use ‘unspecified’ instead. :background \u0026#39;unspecified) (set-face-attribute \u0026#39;telega-msg-inline-reply nil :foreground \u0026#34;#86C166\u0026#34;) ;; 苗 NAE (set-face-attribute \u0026#39;telega-msg-inline-forward nil :foreground \u0026#34;#FFB11B\u0026#34;) (set-face-attribute \u0026#39;telega-entity-type-mention nil :underline \u0026#39;(:style line) :weight \u0026#39;bold) (set-face-attribute \u0026#39;telega-msg-self-title nil :foreground \u0026#34;#E2943B\u0026#34;) ;; 朽葉 KUCHIBA (set-face-attribute \u0026#39;telega-msg-user-title nil :weight \u0026#39;bold) (set-face-attribute \u0026#39;telega-button nil :foreground \u0026#34;#986DB2\u0026#34; :box \u0026#39;(:line-width (-2 . -2) :color \u0026#34;#986DB2\u0026#34; :style nil)) (set-face-attribute \u0026#39;telega-button-active nil :foreground \u0026#34;#ffffff\u0026#34; :background \u0026#34;#986DB2\u0026#34;) ;; (setq telega-chat-prompt-format \u0026#34;🏄🏻〉 \u0026#34;) ;; 缩小 emoji 及特殊字符/字体显示,防止头像裂开。 ;; (add-to-list \u0026#39;face-font-rescale-alist \u0026#39;(\u0026#34;Apple Color Emoji\u0026#34; . 0.8)) (add-to-list \u0026#39;face-font-rescale-alist \u0026#39;(\u0026#34;HanaMinA\u0026#34; . 0.9)) (add-to-list \u0026#39;face-font-rescale-alist \u0026#39;(\u0026#34;Noto Sans Egyptian Hieroglyphs\u0026#34; . 0.675)) (add-to-list \u0026#39;face-font-rescale-alist \u0026#39;(\u0026#34;STIXGeneral\u0026#34; . 0.675)) (add-to-list \u0026#39;face-font-rescale-alist \u0026#39;(\u0026#34;Apple Symbols\u0026#34; . 0.675)) (add-to-list \u0026#39;face-font-rescale-alist \u0026#39;(\u0026#34;Noto Sans Kannada\u0026#34; . 0.675)) (add-to-list \u0026#39;face-font-rescale-alist \u0026#39;(\u0026#34;Academy Engraved LET\u0026#34; . 0.675)) (add-to-list \u0026#39;face-font-rescale-alist \u0026#39;(\u0026#34;Kohinoor Devanagari\u0026#34; . 0.675)) (add-to-list \u0026#39;face-font-rescale-alist \u0026#39;(\u0026#34;Geneva\u0026#34; . 0.675)) (add-to-list \u0026#39;face-font-rescale-alist \u0026#39;(\u0026#34;Noto Sans Oriya\u0026#34; . 0.675))) (add-hook \u0026#39;telega-chat-mode-hook \u0026#39;my-telega-chat-mode) ;; 未读提示 (set-face-attribute \u0026#39;telega-unmuted-count nil :foreground \u0026#34;#FFB11B\u0026#34; :weight \u0026#39;bold) (set-face-attribute \u0026#39;telega-mention-count nil :foreground \u0026#34;#FE6DB3\u0026#34;) (set-face-attribute \u0026#39;telega-muted-count nil :foreground \u0026#34;#86C166\u0026#34; :weight \u0026#39;bold) ;; 多加一条横线在输入和聊天页面之间 (setq telega-symbol-underline-bar (propertize \u0026#34; \u0026#34; \u0026#39;face \u0026#39;telega-webpage-strike-through)) ;; 聊天列表高亮 (defun lg-telega-root-mode () (hl-line-mode 1)) (defun lg-telega-chat-update (chat) (with-telega-root-buffer (hl-line-highlight))) (add-hook \u0026#39;telega-chat-update-hook \u0026#39;lg-telega-chat-update) (add-hook \u0026#39;telega-root-mode-hook \u0026#39;lg-telega-root-mode) ;; Linux settings (when *IS-LINUX* (setq telega-root-show-avatars nil) (setq telega-user-show-avatars nil) (setq telega-chat-show-avatars nil) (setq telega-proxies (list \u0026#39;(:server \u0026#34;127.0.0.1\u0026#34; :port 7890 :enable t :type (:@type \u0026#34;proxyTypeSocks5\u0026#34; :username \u0026#34;\u0026#34; :password \u0026#34;\u0026#34;)) ))) ;; Opening files using external programs (if *IS-MAC* (setcdr (assq t org-file-apps-gnu) \u0026#39;browse-url-default-macosx-browser) (setcdr (assq t org-file-apps-gnu) \u0026#39;browse-url-xdg-open)) (setq telega-open-file-function \u0026#39;org-open-file) ;; 固定 telega 窗口在右侧 (add-hook \u0026#39;telega-mode-hook (lambda () (display-buffer (current-buffer) \u0026#39;((display-buffer-in-side-window))))) (setq display-buffer-alist \u0026#39;((\u0026#34;\\\\*Telega Root\\\\*\u0026#34; . ((display-buffer-in-side-window) (window-width . 0.35) (side . right) (slot . 0) (dedicated . nil))))) (provide \u0026#39;init-telega) ;;; init-telega.el ends here init-translate.el ;;; init-translate.el --- Custom configuration ;;; Commentary (require-package \u0026#39;go-translate) (use-package go-translate :bind ((\u0026#34;C-c t g\u0026#34; . gts-do-translate) (\u0026#34;C-c t p\u0026#34; . go-translate-at-point) (\u0026#34;C-c t s\u0026#34; . go-translate-save-kill-ring)) :config (setq gts-translate-list \u0026#39;((\u0026#34;en\u0026#34; \u0026#34;zh\u0026#34;))) (setq gts-default-translator (gts-translator :picker (gts-prompt-picker) :engines (list (gts-bing-engine) (gts-google-engine)) :render (gts-buffer-render))) ;; Pick directly and use Google RPC API to translate (defun go-translate-at-point () (interactive) (gts-translate (gts-translator :picker (gts-noprompt-picker) :engines (gts-google-rpc-engine) :render (gts-buffer-render)))) ;; Pick directly and add the results into kill-ring (defun go-translate-save-kill-ring () (interactive) (gts-translate (gts-translator :picker (gts-noprompt-picker) :engines (gts-google-engine :parser (gts-google-summary-parser)) :render (gts-kill-ring-render)))) (set-face-attribute \u0026#39;gts-render-buffer-me-header-backgroud-face nil :background \u0026#34;#7BA23F\u0026#34;)) (provide \u0026#39;init-translate) ;;; init-translate.el ends here init-org-modern.el ;;; init-org-modern.el --- Custom configuration ;;; Commentary (require-package \u0026#39;org-modern) (use-package org-modern :after org :hook (org-mode . org-modern-mode) :init (menu-bar-mode -1) (tool-bar-mode -1) (scroll-bar-mode -1) (setq org-modern-star [\u0026#34;➫\u0026#34; \u0026#34;✦\u0026#34; \u0026#34;✜\u0026#34; \u0026#34;✲\u0026#34; \u0026#34;✸\u0026#34; \u0026#34;❅\u0026#34;] org-hide-emphasis-markers t org-tags-column 0 org-catch-invisible-edits \u0026#39;show-and-error org-special-ctrl-a/e t org-insert-heading-respect-content t org-modern-table-horizontal 0.2 org-modern-list \u0026#39;((43 . \u0026#34;➤\u0026#34;) (45 . \u0026#34;▻\u0026#34;) (42 . \u0026#34;►\u0026#34;)) org-ellipsis \u0026#34;[+]\u0026#34;)) (modify-all-frames-parameters \u0026#39;((right-divider-width . 5) (left-divider-width . 5) (internal-border-width . 5))) (dolist (face \u0026#39;(window-divider window-divider-first-pixel window-divider-last-pixel)) (face-spec-reset-face face) (set-face-foreground face (face-attribute \u0026#39;default :background))) (set-face-background \u0026#39;fringe (face-attribute \u0026#39;default :background)) (custom-set-faces \u0026#39;(org-ellipsis ((t (:foreground \u0026#34;#90B44B\u0026#34;))))) ;; 鶸萌黄 (provide \u0026#39;init-org-modern) ;;; init-org-modern.el ends here init-quick-menus.el Daily Log 是按照每天的日期生成,并不是固定的文件名,因此这里写了自定义方法去跳转到今天/昨天的 Daily Log,方便用来回顾。\n;;; init-quick-menus.el --- Custom configuration ;;; Commentary ;; org-starter (require-package \u0026#39;org-starter) (setq org-refile-use-outline-path \u0026#39;file) (use-package org-starter :config ;; (add-hook! \u0026#39;after-init-hook \u0026#39;org-starter-load-all-files-in-path) (org-starter-def \u0026#34;~/Dropbox/org\u0026#34; :files (\u0026#34;agenda/inbox.org\u0026#34; :agenda t :key \u0026#34;i\u0026#34; :refile (:maxlevel . 2)) (\u0026#34;agenda/work.org\u0026#34; :agenda t :key \u0026#34;w\u0026#34; :refile (:maxlevel . 2)) (\u0026#34;agenda/tech-debt.org\u0026#34; :agenda t :key \u0026#34;d\u0026#34; :refile (:maxlevel . 2)) (\u0026#34;agenda/personal.org\u0026#34; :agenda t :key \u0026#34;p\u0026#34; :refile (:maxlevel . 2)) (\u0026#34;agenda/books.org\u0026#34; :agenda t :key \u0026#34;b\u0026#34; :refile (:maxlevel . 2)) (\u0026#34;agenda/someday.org\u0026#34; :agenda t :key \u0026#34;s\u0026#34; :refile (:maxlevel . 2)) (\u0026#34;agenda/agenda.org\u0026#34; :agenda t :key \u0026#34;a\u0026#34; :refile (:maxlevel . 2)) (\u0026#34;agenda/note.org\u0026#34; :agenda t :key \u0026#34;n\u0026#34; :refile (:maxlevel . 2)) (\u0026#34;daily/journal.org\u0026#34; :agenda t :key \u0026#34;j\u0026#34; :refile (:maxlevel . 2)) (\u0026#34;beancount/2022.beancount\u0026#34; :agenda nil :key \u0026#34;c\u0026#34;) (\u0026#34;beancount/account.beancount\u0026#34; :agenda nil :key \u0026#34;m\u0026#34;) (\u0026#34;beancount/credit.beancount\u0026#34; :agenda nil :key \u0026#34;e\u0026#34;) (\u0026#34;beancount/insurance.beancount\u0026#34; :agenda nil :key \u0026#34;u\u0026#34;) (\u0026#34;beancount/salary.beancount\u0026#34; :agenda nil :key \u0026#34;l\u0026#34;) (\u0026#34;beancount/house.beancount\u0026#34; :agenda nil :key \u0026#34;h\u0026#34;) ) ;; hydra (require-package \u0026#39;hydra) ;; 打开当前日期对应的 daily log 文件 (defun open-today-journal-file () \u0026#34;Open journal file for today.\u0026#34; (interactive) (let* ((file-name (format-time-string \u0026#34;%Y-%m-%d.org\u0026#34;)) (file-path (concat \u0026#34;~/Dropbox/org/daily/\u0026#34; file-name))) (if (file-exists-p file-path) (find-file file-path) (message \u0026#34;Journal file not found for today.\u0026#34;)))) (defun open-yesterday-journal-file () \u0026#34;Open journal file for yesterday.\u0026#34; (interactive) (let* ((yesterday-time (time-subtract (current-time) (days-to-time 1))) (file-name (format-time-string \u0026#34;%Y-%m-%d.org\u0026#34; yesterday-time)) (file-path (concat \u0026#34;~/Dropbox/org/daily/\u0026#34; file-name))) (if (file-exists-p file-path) (find-file file-path) (message \u0026#34;Journal file not found for yesterday.\u0026#34;)))) (defhydra hydra-org-agenda-menu (:color blue) \u0026#34; [--\u0026gt; Org-agenda-menu \u0026lt;--] ^^^^------------------------------------------------ _i_: inbox _w_: work _b_: books _d_: tech-debt _a_: agenda _p_: personal _n_: note _s_: someday _j_: journal _t_: today _y_: yesterday [--\u0026gt; Beancount-menu \u0026lt;--] ^^^^------------------------------------------------ _c_: 2022 _m_: account _e_: credit _u_: insurance _l_: salary _h_: house \u0026#34; (\u0026#34;i\u0026#34; org-starter-find-file:inbox) (\u0026#34;w\u0026#34; org-starter-find-file:work) (\u0026#34;d\u0026#34; org-starter-find-file:tech-debt) (\u0026#34;p\u0026#34; org-starter-find-file:personal) (\u0026#34;b\u0026#34; org-starter-find-file:books) (\u0026#34;n\u0026#34; org-starter-find-file:note) (\u0026#34;s\u0026#34; org-starter-find-file:someday) (\u0026#34;a\u0026#34; org-starter-find-file:agenda) (\u0026#34;j\u0026#34; org-starter-find-file:journal) (\u0026#34;t\u0026#34; (open-today-journal-file)) (\u0026#34;y\u0026#34; (open-yesterday-journal-file)) (\u0026#34;c\u0026#34; org-starter-find-file:2022) (\u0026#34;m\u0026#34; org-starter-find-file:account) (\u0026#34;e\u0026#34; org-starter-find-file:credit) (\u0026#34;u\u0026#34; org-starter-find-file:insurance) (\u0026#34;l\u0026#34; org-starter-find-file:salary) (\u0026#34;h\u0026#34; org-starter-find-file:house) ):bind(\u0026#34;C-c e\u0026#34; . hydra-org-agenda-menu/body)) (provide \u0026#39;init-quick-menus) ;;; init-quick-menus.el ends here init-tree-sitter.el 其中 elisp 官方还未支持,需要自己编译后放入 tree-sitter-langs 下面。\ngit clone https://github.com/Wilfred/tree-sitter-elisp gcc ./src/parser.c -fPIC -I./ --shared -o elisp.so cp ./elisp.so ~/.tree-sitter-langs/bin 这里第二步的时候报错了\n./src/parser.c:1:10: error: \u0026#39;tree_sitter/parser.h\u0026#39; file not found with \u0026lt;angled\u0026gt; include; use \u0026#34;quotes\u0026#34; instead #include \u0026lt;tree_sitter/parser.h\u0026gt; ^~~~~~~~~~~~~~~~~~~~~~ \u0026#34;tree_sitter/parser.h\u0026#34; 1 error generated. 这里需要将 src/parser.c 头部按照提示修改一下。\n- #include \u0026lt;tree_sitter/parser.h\u0026gt; + #include \u0026#34;tree_sitter/parser.h\u0026#34; 第三步,由于我是通过 MELPA 安装,路径为 .emacs.d/elpa-29.0/tree-sitter-langs-version/bin,其中 version 为具体的版本号。\n;;; init-tree-sitter.el --- Configure for tree sitter ;;; Commentary: (require-package \u0026#39;tree-sitter) (require-package \u0026#39;tree-sitter-langs) (require \u0026#39;tree-sitter) (require \u0026#39;tree-sitter-langs) ;;; Code: (global-tree-sitter-mode) (add-hook \u0026#39;tree-sitter-after-on-hook #\u0026#39;tree-sitter-hl-mode) ;; Load the language definition for Rust, if it hasn\u0026#39;t been loaded. ;; Return the language object. (tree-sitter-require \u0026#39;rust) (tree-sitter-require \u0026#39;python) (tree-sitter-require \u0026#39;javascript) (provide \u0026#39;init-tree-sitter) ;;; init-tree-sitter.el ends here init-org-enhance.el 一些增强 org 功能的函数,可以方便使用。\n;;; init-org-enhance.el --- Org-mode config -*- lexical-binding: t -*- ;;; Commentary: ;; ============================================================= ;; ============== Org-roam-backlinks-search ================== ;; ============================================================= ;; https://github.com/Vidianos-Giannitsis/Dotfiles/blob/master/emacs/.emacs.d/libs/zettelkasten.org#org-roam-backlinks-search (defcustom org-roam-backlinks-choices \u0026#39;(\u0026#34;View Backlinks\u0026#34; \u0026#34;Go to Node\u0026#34; \u0026#34;Quit\u0026#34;) \u0026#34;List of choices for `org-roam-backlinks-node-read\u0026#39;.\u0026#34;) (defun org-roam-backlinks-query* (NODE) \u0026#34;Gets the backlinks of NODE with `org-roam-db-query\u0026#39;.\u0026#34; (org-roam-db-query [:select [source dest] :from links :where (= dest $s1) :and (= type \u0026#34;id\u0026#34;)] (org-roam-node-id NODE))) (defun org-roam-backlinks-p (SOURCE NODE) \u0026#34;Predicate function that checks if NODE is a backlink of SOURCE.\u0026#34; (let* ((source-id (org-roam-node-id SOURCE)) (backlinks (org-roam-backlinks-query* SOURCE)) (id (org-roam-node-id NODE)) (id-list (list id source-id))) (member id-list backlinks))) (defun org-roam-backlinks-poi-or-moc-p (NODE) \u0026#34;Check if NODE has the tag POI or the tag MOC. Return t if it does.\u0026#34; (or (string-equal (car (org-roam-node-tags NODE)) \u0026#34;POI\u0026#34;) (string-equal (car (org-roam-node-tags NODE)) \u0026#34;MOC\u0026#34;))) (defun org-roam-backlinks--read-node-backlinks (source) \u0026#34;Runs `org-roam-node-read\u0026#39; on the backlinks of SOURCE. The predicate used as `org-roam-node-read\u0026#39;\u0026#39;s filter-fn is `org-roam-backlinks-p\u0026#39;.\u0026#34; (org-roam-node-read nil (apply-partially #\u0026#39;org-roam-backlinks-p source))) (defun org-roam-backlinks-node-read (node) \u0026#34;Read a NODE and run `org-roam-backlinks--read-node-backlinks\u0026#39;. Upon selecting a backlink, prompt the user for what to do with the backlink. The prompt is created with `completing-read\u0026#39; with valid options being everything in the list `org-roam-backlinks-choices\u0026#39;. If the user decides to view the selected node\u0026#39;s backlinks, the function recursively runs itself with the selection as its argument. If they decide they want to go to the selected node, the function runs `find-file\u0026#39; and the file associated to that node. Lastly, if they choose to quit, the function exits silently.\u0026#34; (let* ((backlink (org-roam-backlinks--read-node-backlinks node)) (choice (completing-read \u0026#34;What to do with NODE: \u0026#34; org-roam-backlinks-choices))) (cond ((string-equal choice (car org-roam-backlinks-choices)) (org-roam-backlinks-node-read backlink)) ((string-equal choice (cadr org-roam-backlinks-choices)) (find-file (org-roam-node-file backlink))) ((string-equal choice (caddr org-roam-backlinks-choices)))))) (defun org-roam-backlinks-search () \u0026#34;Select an `org-roam-node\u0026#39; and recursively search its backlinks. This function is a starter function for `org-roam-backlinks-node-read\u0026#39; which gets the initial node selection from `org-roam-node-list\u0026#39;. For more information about this function, check `org-roam-backlinks-node-read\u0026#39;.\u0026#34; (interactive) (let ((node (org-roam-node-read))) (org-roam-backlinks-node-read node))) (defun org-roam-backlinks-search-from-moc-or-poi () \u0026#34;`org-roam-backlinks-search\u0026#39; with an initial selection filter. Since nodes tagged as \\\u0026#34;MOC\\\u0026#34; or \\\u0026#34;POI\\\u0026#34; are the entry points to my personal zettelkasten, I have this helper function which is identical to `org-roam-backlinks-search\u0026#39; but filters initial selection to only those notes. That way, they initial selection has a point as it will be on a node that has a decent amount of backlinks.\u0026#34; (interactive) (let ((node (org-roam-node-read nil #\u0026#39;org-roam-backlinks-poi-or-moc-p))) (org-roam-backlinks-node-read node))) ;; ============================================================= ;; =========== Dynamic org-agenda with org-roam ============== ;; ============================================================= ;; https://gist.github.com/d12frosted/a60e8ccb9aceba031af243dff0d19b2e (defun vulpea-dynamic-p () \u0026#34;Return non-nil if current buffer has any todo entry. TODO entries marked as done are ignored, meaning the this function returns nil if current buffer contains only completed tasks.\u0026#34; (seq-find ; (3) (lambda (type) (eq type \u0026#39;todo)) (org-element-map ; (2) (org-element-parse-buffer \u0026#39;headline) ; (1) \u0026#39;headline (lambda (h) (org-element-property :todo-type h))))) (defun vulpea-dynamic-update-tag () \u0026#34;Update dynamic tag in the current buffer.\u0026#34; (when (and (not (active-minibuffer-window)) (vulpea-buffer-p)) (save-excursion (goto-char (point-min)) (let* ((tags (vulpea-buffer-tags-get)) (original-tags tags)) (if (vulpea-dynamic-p) (setq tags (cons \u0026#34;dynamic\u0026#34; tags)) (setq tags (remove \u0026#34;dynamic\u0026#34; tags))) ;; cleanup duplicates (setq tags (seq-uniq tags)) ;; update tags if changed (when (or (seq-difference tags original-tags) (seq-difference original-tags tags)) (apply #\u0026#39;vulpea-buffer-tags-set tags)))))) (defun vulpea-buffer-p () \u0026#34;Return non-nil if the currently visited buffer is a note.\u0026#34; (and buffer-file-name (string-prefix-p (expand-file-name (file-name-as-directory org-roam-directory)) (file-name-directory buffer-file-name)))) (defun vulpea-dynamic-files () \u0026#34;Return a list of note files containing \u0026#39;dynamic\u0026#39; tag.\u0026#34; ; (seq-uniq (seq-map #\u0026#39;car (org-roam-db-query [:select [nodes:file] :from tags :left-join nodes :on (= tags:node-id nodes:id) :where (like tag (quote \u0026#34;%\\\u0026#34;dynamic\\\u0026#34;%\u0026#34;))])))) (defun vulpea-agenda-files-update (\u0026amp;rest _) \u0026#34;Update the value of `org-agenda-files\u0026#39;.\u0026#34; ;; (setq org-agenda-files (vulpea-dynamic-files))) (setq org-agenda-files (seq-uniq (append (vulpea-dynamic-files) (file-expand-wildcards \u0026#34;~/Dropbox/org/agenda/*.org\u0026#34;))))) (add-hook \u0026#39;find-file-hook #\u0026#39;vulpea-dynamic-update-tag) (add-hook \u0026#39;before-save-hook #\u0026#39;vulpea-dynamic-update-tag) (advice-add \u0026#39;org-agenda :before #\u0026#39;vulpea-agenda-files-update) (advice-add \u0026#39;org-todo-list :before #\u0026#39;vulpea-agenda-files-update) ;; functions borrowed from `vulpea\u0026#39; library ;; https://github.com/d12frosted/vulpea/blob/6a735c34f1f64e1f70da77989e9ce8da7864e5ff/vulpea-buffer.el (defun vulpea-buffer-tags-get () \u0026#34;Return filetags value in current buffer.\u0026#34; (vulpea-buffer-prop-get-list \u0026#34;filetags\u0026#34; \u0026#34;[ :]\u0026#34;)) (defun vulpea-buffer-tags-set (\u0026amp;rest tags) \u0026#34;Set TAGS in current buffer. If filetags value is already set, replace it.\u0026#34; (if tags (vulpea-buffer-prop-set \u0026#34;filetags\u0026#34; (concat \u0026#34;:\u0026#34; (string-join tags \u0026#34;:\u0026#34;) \u0026#34;:\u0026#34;)) (vulpea-buffer-prop-remove \u0026#34;filetags\u0026#34;))) (defun vulpea-buffer-tags-add (tag) \u0026#34;Add a TAG to filetags in current buffer.\u0026#34; (let* ((tags (vulpea-buffer-tags-get)) (tags (append tags (list tag)))) (apply #\u0026#39;vulpea-buffer-tags-set tags))) (defun vulpea-buffer-tags-remove (tag) \u0026#34;Remove a TAG from filetags in current buffer.\u0026#34; (let* ((tags (vulpea-buffer-tags-get)) (tags (delete tag tags))) (apply #\u0026#39;vulpea-buffer-tags-set tags))) (defun vulpea-buffer-prop-set (name value) \u0026#34;Set a file property called NAME to VALUE in buffer file. If the property is already set, replace its value.\u0026#34; (setq name (downcase name)) (org-with-point-at 1 (let ((case-fold-search t)) (if (re-search-forward (concat \u0026#34;^#\\\\+\u0026#34; name \u0026#34;:\\\\(.*\\\\)\u0026#34;) (point-max) t) (replace-match (concat \u0026#34;#+\u0026#34; name \u0026#34;: \u0026#34; value) \u0026#39;fixedcase) (while (and (not (eobp)) (looking-at \u0026#34;^[#:]\u0026#34;)) (if (save-excursion (end-of-line) (eobp)) (progn (end-of-line) (insert \u0026#34;\\n\u0026#34;)) (forward-line) (beginning-of-line))) (insert \u0026#34;#+\u0026#34; name \u0026#34;: \u0026#34; value \u0026#34;\\n\u0026#34;))))) (defun vulpea-buffer-prop-set-list (name values \u0026amp;optional separators) \u0026#34;Set a file property called NAME to VALUES in current buffer. VALUES are quoted and combined into single string using `combine-and-quote-strings\u0026#39;. If SEPARATORS is non-nil, it should be a regular expression matching text that separates, but is not part of, the substrings. If nil it defaults to `split-string-default-separators\u0026#39;, normally \\\u0026#34;[ \\f\\t\\n\\r\\v]+\\\u0026#34;, and OMIT-NULLS is forced to t. If the property is already set, replace its value.\u0026#34; (vulpea-buffer-prop-set name (combine-and-quote-strings values separators))) (defun vulpea-buffer-prop-get (name) \u0026#34;Get a buffer property called NAME as a string.\u0026#34; (org-with-point-at 1 (when (re-search-forward (concat \u0026#34;^#\\\\+\u0026#34; name \u0026#34;: \\\\(.*\\\\)\u0026#34;) (point-max) t) (buffer-substring-no-properties (match-beginning 1) (match-end 1))))) (defun vulpea-buffer-prop-get-list (name \u0026amp;optional separators) \u0026#34;Get a buffer property NAME as a list using SEPARATORS. If SEPARATORS is non-nil, it should be a regular expression matching text that separates, but is not part of, the substrings. If nil it defaults to `split-string-default-separators\u0026#39;, normally \\\u0026#34;[ \\f\\t\\n\\r\\v]+\\\u0026#34;, and OMIT-NULLS is forced to t.\u0026#34; (let ((value (vulpea-buffer-prop-get name))) (when (and value (not (string-empty-p value))) (split-string-and-unquote value separators)))) (defun vulpea-buffer-prop-remove (name) \u0026#34;Remove a buffer property called NAME.\u0026#34; (org-with-point-at 1 (when (re-search-forward (concat \u0026#34;\\\\(^#\\\\+\u0026#34; name \u0026#34;:.*\\n?\\\\)\u0026#34;) (point-max) t) (replace-match \u0026#34;\u0026#34;)))) ;; ============================================================= ;; ====================== Archiving ========================== ;; ============================================================= ;; (setq org-archive-mark-done nil) ;; (setq org-archive-location \u0026#34;%s_archive::* Archive\u0026#34;) ;; https://gist.github.com/kepi/2f4acc3cc93403c75fbba5684c5d852d ;; org-archive-subtree-hierarchical.el ;; ;; version 0.2 ;; modified from https://lists.gnu.org/archive/html/emacs-orgmode/2014-08/msg00109.html ;; modified from https://stackoverflow.com/a/35475878/259187 ;; In orgmode ;; * A ;; ** AA ;; *** AAA ;; ** AB ;; *** ABA ;; Archiving AA will remove the subtree from the original file and create ;; it like that in archive target: ;; * AA ;; ** AAA ;; And this give you ;; * A ;; ** AA ;; *** AAA ;; ;; Install file to your include path and include in your init file with: ;; ;; (require \u0026#39;org-archive-subtree-hierarchical) ;; (setq org-archive-default-command \u0026#39;org-archive-subtree-hierarchical) ;; (provide \u0026#39;org-archive-subtree-hierarchical) (setq org-archive-default-command \u0026#39;org-archive-subtree-hierarchical) (require \u0026#39;org-archive) (defun org-archive-subtree-hierarchical--line-content-as-string () \u0026#34;Returns the content of the current line as a string\u0026#34; (save-excursion (beginning-of-line) (buffer-substring-no-properties (line-beginning-position) (line-end-position)))) (defun org-archive-subtree-hierarchical--org-child-list () \u0026#34;This function returns all children of a heading as a list. \u0026#34; (interactive) (save-excursion ;; this only works with org-version \u0026gt; 8.0, since in previous ;; org-mode versions the function (org-outline-level) returns ;; gargabe when the point is not on a heading. (if (= (org-outline-level) 0) (outline-next-visible-heading 1) (org-goto-first-child)) (let ((child-list (list (org-archive-subtree-hierarchical--line-content-as-string)))) (while (org-goto-sibling) (setq child-list (cons (org-archive-subtree-hierarchical--line-content-as-string) child-list))) child-list))) (defun org-archive-subtree-hierarchical--org-struct-subtree () \u0026#34;This function returns the tree structure in which a subtree belongs as a list.\u0026#34; (interactive) (let ((archive-tree nil)) (save-excursion (while (org-up-heading-safe) (let ((heading (buffer-substring-no-properties (line-beginning-position) (line-end-position)))) (if (eq archive-tree nil) (setq archive-tree (list heading)) (setq archive-tree (cons heading archive-tree)))))) archive-tree)) (defun org-archive-subtree-hierarchical () \u0026#34;This function archives a subtree hierarchical\u0026#34; (interactive) (let ((org-tree (org-archive-subtree-hierarchical--org-struct-subtree)) (this-buffer (current-buffer)) (file (abbreviate-file-name (or (buffer-file-name (buffer-base-buffer)) (error \u0026#34;No file associated to buffer\u0026#34;))))) (save-excursion (setq location org-archive-location afile (car (org-archive--compute-location (or (org-entry-get nil \u0026#34;ARCHIVE\u0026#34; \u0026#39;inherit) location))) ;; heading (org-extract-archive-heading location) infile-p (equal file (abbreviate-file-name (or afile \u0026#34;\u0026#34;)))) (unless afile (error \u0026#34;Invalid `org-archive-location\u0026#39;\u0026#34;)) (if (\u0026gt; (length afile) 0) (setq newfile-p (not (file-exists-p afile)) visiting (find-buffer-visiting afile) buffer (or visiting (find-file-noselect afile))) (setq buffer (current-buffer))) (unless buffer (error \u0026#34;Cannot access file \\\u0026#34;%s\\\u0026#34;\u0026#34; afile)) (org-cut-subtree) (set-buffer buffer) (org-mode) (goto-char (point-min)) (while (not (equal org-tree nil)) (let ((child-list (org-archive-subtree-hierarchical--org-child-list))) (if (member (car org-tree) child-list) (progn (search-forward (car org-tree) nil t) (setq org-tree (cdr org-tree))) (progn (goto-char (point-max)) (newline) (org-insert-struct org-tree) (setq org-tree nil))))) (newline) (org-yank) (when (not (eq this-buffer buffer)) (save-buffer)) (message \u0026#34;Subtree archived %s\u0026#34; (concat \u0026#34;in file: \u0026#34; (abbreviate-file-name afile)))))) (defun org-insert-struct (struct) \u0026#34;TODO\u0026#34; (interactive) (when struct (insert (car struct)) (newline) (org-insert-struct (cdr struct)))) (defun org-archive-subtree () (org-archive-subtree-hierarchical) ) ;; ============================================================= ;; ============== Hierachy for title nodes =================== ;; ============================================================= ;; Codes blow are used to general a hierachy for title nodes that under a file (cl-defmethod org-roam-node-doom-filetitle ((node org-roam-node)) \u0026#34;Return the value of \\\u0026#34;#+title:\\\u0026#34; (if any) from file that NODE resides in. If there\u0026#39;s no file-level title in the file, return empty string.\u0026#34; (or (if (= (org-roam-node-level node) 0) (org-roam-node-title node) (org-roam-get-keyword \u0026#34;TITLE\u0026#34; (org-roam-node-file node))) \u0026#34;\u0026#34;)) (cl-defmethod org-roam-node-doom-hierarchy ((node org-roam-node)) \u0026#34;Return hierarchy for NODE, constructed of its file title, OLP and direct title. If some elements are missing, they will be stripped out.\u0026#34; (let ((title (org-roam-node-title node)) (olp (org-roam-node-olp node)) (level (org-roam-node-level node)) (filetitle (org-roam-node-doom-filetitle node)) (separator (propertize \u0026#34; \u0026gt; \u0026#34; \u0026#39;face \u0026#39;shadow))) (cl-case level ;; node is a top-level file (0 filetitle) ;; node is a level 1 heading (1 (concat (propertize filetitle \u0026#39;face \u0026#39;(shadow italic)) separator title)) ;; node is a heading with an arbitrary outline path (t (concat (propertize filetitle \u0026#39;face \u0026#39;(shadow italic)) separator (propertize (string-join olp \u0026#34; \u0026gt; \u0026#34;) \u0026#39;face \u0026#39;(shadow italic)) separator title))))) (setq org-roam-node-display-template (concat \u0026#34;${type:10} ${doom-hierarchy:120} \u0026#34; (propertize \u0026#34;${tags:*}\u0026#34; \u0026#39;face \u0026#39;org-tag))) ;; ============================================================= ;; ======================== iscroll ========================== ;; ============================================================= (require-package \u0026#39;iscroll) (add-hook \u0026#39;org-mode-hook (lambda () (iscroll-mode 1))) ;; ============================================================= ;; =================== dwim-shell-command ==================== ;; ============================================================= (require-package \u0026#39;dwim-shell-command) (require \u0026#39;dwim-shell-command) (defun dwim-shell-commands-macos-reveal-in-finder () \u0026#34;Reveal selected files in macOS Finder.\u0026#34; (interactive) (dwim-shell-command-on-marked-files \u0026#34;Reveal in Finder\u0026#34; \u0026#34;import AppKit NSWorkspace.shared.activateFileViewerSelecting([\\\u0026#34;\u0026lt;\u0026lt;*\u0026gt;\u0026gt;\\\u0026#34;].map{URL(fileURLWithPath:$0)})\u0026#34; :join-separator \u0026#34;, \u0026#34; :silent-success t :shell-pipe \u0026#34;swift -\u0026#34;)) (provide \u0026#39;init-org-enhance) ;;; init-org-enhance.el ends here telega-bridge-bot.el 用来改善 Matrix 机器人在 TG 中转发的消息展示,包括头像、用户名等。具体源码参见 vendor/telega-bridge-bot.el · BlindingDark/BEmacs - Gitee.com。\ninit-org-transclusion.el 过滤的条件的具体细节见 Org-transclusion User Manual\n;;; init-org-transclusion.el --- Custom configuration ;;; Commentary (require-package \u0026#39;org-transclusion) (use-package org-transclusion :after org :bind((\u0026#34;C-c n t\u0026#34; . org-transclusion-mode))) (defun trival/org-transclusion-select-source (beg end) \u0026#34;Send transclusion information to kill-ring. See https://org-roam.discourse.group/t/alpha-org-transclusion/830/122\u0026#34; (interactive \u0026#34;r\u0026#34;) (let ((lbeg (line-number-at-pos beg)) (lend (line-number-at-pos end)) (filename (concat \u0026#34;~/\u0026#34;(string-remove-prefix (file-truename \u0026#34;~/\u0026#34;) (buffer-file-name))))) (with-temp-buffer (progn (insert \u0026#34;#+transclude: [[file:\u0026#34;) (insert filename) (insert (format \u0026#34;]] :lines %d-%d\u0026#34; lbeg lend)) (clipboard-kill-region (point-min) (point-max)))) (message \u0026#34;A transcluded link has been sent to your kill-ring.\u0026#34;))) (defun my/org-insert-link-transclusion (\u0026amp;optional COMPLETE-FILE LINK-LOCATION DESCRIPTION) (interactive \u0026#34;P\u0026#34;) (org-insert-link COMPLETE-FILE LINK-LOCATION DESCRIPTION) (org-transclusion-make-from-link)) (provide \u0026#39;init-org-transclusion) ;;; init-org-transclusion.el ends here Export 对于只需要导出某个 header 下的内容的需求,只需要在 header 上加上 :export: 即可。\nMarkdown file brew install pandoc 这里用的是 ox-pandoc,需要先 M-x package-install \u0026lt;RET\u0026gt; ox-pandoc \u0026lt;RET\u0026gt; 进行安装,M-x org-pandoc-export-as-gfm 算是我找到最符合 MarkDown 语法的转换了,Emacs 自己的转换会将 Table 转换成 HTML 标签的格式。\nHugo 在文件头部添加\n#+HUGO_BASE_DIR: ~/Dropbox/hugo/ #+HUGO_SECTION: posts/main #+HUGO_WEIGHT: auto #+HUGO_AUTO_SET_LASTMOD: t 需要导出为 \u0026lt;mark\u0026gt;\u0026lt;/mark\u0026gt; 时1\n#+begin_mark marked text #+end_mark 需要导出为块级 \u0026lt;mark\u0026gt;\u0026lt;/mark\u0026gt; 时,也就是前后的空格不进行 trim。\n#+header: :trim-pre nil :trim-post nil #+begin_mark marked text #+end_mark Latex Dependency 需要安装 texlive\nbrew install texlive Latex 语法 Latex 语法\nPDF 导出报错 Unicode character 考 (U+8003) not set up for use with LaTeX. 是因为 Latex 本身不支持中文。 中文 PDF 导出设置 使用 ElegantPaper,需要将 elegantpaper.cls 文件放在 org 目录下。 安装依赖 代码块需要用到 minted,需要用到 pygments 作为依赖。\nbrew install pygments 导出文件头部增加 #+LATEX_COMPILER: xelatex #+LATEX_CLASS: elegantpaper #+OPTIONS: prop:t Org Special Blocks\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://sheerwill.xyz/posts/main/20220723211325-vanilla_emacs_with_purcell/","summary":"Introduction Emacs outshines all other editing software in approximately the same way that the noonday sun does the stars. It is not just bigger and brighter; it simply makes everything else vanish. – Neal Stephenson, In the Beginning was the Command Line (1998) Install Emacs-plus With Homebrew brew tap d12frosted/emacs-plus brew install emacs-plus --with-native-comp --with-modern-vscode-icon ln -s /usr/local/opt/emacs-plus@29/Emacs.app /Applications Build Emacs on UbuntuLucius\u0026rsquo;s Workflow git clone git://git.sv.gnu.org/emacs.git sudo apt install","title":"Vanilla Emacs with Purcell"},{"content":"联合索引遵循「最左匹配原则」,MySQL 联合索引创建的时候会对 (a, b) 其中的 a 先进行排序,再在 a 的基础上对 b 进行排序。如果是 (a, b, c) 也是一样的方式。\nFigure 1: concatenated-indexes\n因此,判断是否符合「最左匹配原则」,是否能够使用索引,可以根据底层的索引实现逻辑来进行判断。即,按照创建索引时的顺序,联合索引中的第一个字段是有序的,一旦第一个字段确定值,那么第二个字段也是有序的,以此类推,有序即可用索引,否则不行。\n\u0026lt;,\u0026gt; 和 BETWEEN 可以用索引,但是按照上述的逻辑,范围查询字段后的索引字段就无法用上索引,因为范围查询字段的值并没有确定,后续的字段就是无序的。 LIKE 在 \u0026quot;a%\u0026quot; 这种右匹配时,会在索引是按照字母排序的情况下用上索引(% 左侧字符合适的情况下也可以建立前缀索引)。至于左匹配和全匹配,按照上述的逻辑,完全是无序的,无法使用索引。 WHERE b = ? AND a = ? AND c = ? 这种颠倒了顺序的 SQL 是不影响「最左匹配原则」的,数据库执行前,解释器会对其重新排序优化。 理解组合索引,需要先理解堆表和索引组织表结构。\nHeap Table Clustered Indexes / Index Organized Tables (IOT) ","permalink":"https://sheerwill.xyz/posts/main/clm-the_left_prefix_index_rule_is_determined_by_the_b+tree_structure/","summary":"联合索引遵循「最左匹配原则」,MySQL 联合索引创建的时候会对 (a, b) 其中的 a 先进行排序,再在 a 的基础上对 b 进行排序。如果是 (a, b, c) 也是一样的方式","title":"The Left-Prefix Index Rule is determined by the B+tree structure"},{"content":" B-Tree B+Tree All internal nodes and leaf nodes contain data pointers along with keys. Only leaf nodes contain data pointers along with keys, internal nodes contain keys only. There are no duplicate keys. Duplicate keys are present in this, all internal nodes are also present at leaves. Leaf nodes are not linked to each other. Leaf nodes are linked to each other. Sequential access of nodes is not possible. All nodes are present at leaves, so sequential access is possible just like a linked list. Searching for a key is slower. Searching is faster. For a particular number of entries, the height of the B-tree is larger. The height of the B+ tree is lesser than B-tree for the same number of entries. 其实这几点区别都是根据 B-Tree 和 B+tree 的结构总结出来的。\nFigure 1: b-tree-structure\n与上图中的 B+Tree 结构不同,B-Tree 每个节点中都存储者数据指针和 key,从上之下没有重复的 key,页节点之间也没有链接。\nB-Tree 中查找数据,需要遍历完第一个分支后回到根节点继续遍历第二个分支,以此类推查找。B+Tree 中除了页节点以外的节点只存储 key 值,一个 page 可以存储更多的 key,减少了树的高度,且 B+Tree 的叶节点间具有双向指针,呈双向链表的结构,查找起来更加迅速。 B-Tree 呈树状,每个节点 key 都不重复,因此不存在顺序结构。B+tree 中所有数据都存在叶节点,按照 key 顺序排列,可以按照顺序访问。 ","permalink":"https://sheerwill.xyz/posts/main/clm-the_difference_between_b_tree_and_b+tree/","summary":"B-Tree B+Tree All internal nodes and leaf nodes contain data pointers along with keys. Only leaf nodes contain data pointers along with keys, internal nodes contain keys only. There are no duplicate keys. Duplicate keys are present in this, all internal nodes are also present at leaves. Leaf nodes are not linked to each other. Leaf nodes are linked to each other. Sequential access of nodes is not possible. All","title":"The difference between B-tree and B+tree"},{"content":"Discourse context A heap is a table without a clustered index. One or more nonclustered indexes can be created on tables stored as a heap. Data is stored in the heap without specifying an order. Usually data is initially stored in the order in which is the rows are inserted into the table, but the Database Engine can move data around in the heap to store the rows efficiently; so the data order cannot be predicted. To guarantee the order of rows returned from a heap, you must use the ORDER BY clause. To specify a permanent logical order for storing the rows, create a clustered index on the table, so that the table is not a heap.\n堆表就是含有一列或者多列无聚簇索引的表,其中的数据存储无序。通常数据是按照初始化的顺序存储,但数据库引擎会为了更高效的数据插入,将原有数据移动到别处来释放空间给新的数据。因此,数据的顺序是不确定的,为了保证数据的顺序,可以在查询的时候使用 ORDER BY 关键字进行排序。要保证数据物理存储的顺序,就需要建立聚簇索引,这个时候就不是堆表了。\nWhen a table is stored as a heap, individual rows are identified by reference to an 8-byte row identifier (RID) consisting of the file number, data page number, and slot on the page (FileID:PageID:SlotID). The row ID is a small and efficient structure.\nROWID 是文件编号、数据页编号以及页面上的卡槽组成。(FileID:PageID:SlotID)\nreferences Heaps (Tables without Clustered Indexes) - SQL Server | Microsoft Learn ","permalink":"https://sheerwill.xyz/posts/main/evd-heap_table/","summary":"Discourse context A heap is a table without a clustered index. One or more nonclustered indexes can be created on tables stored as a heap. Data is stored in the heap without specifying an order. Usually data is initially stored in the order in which is the rows are inserted into the table, but the Database Engine can move data around in the heap to store the rows efficiently; so","title":"Heap Table"},{"content":" Some databases can indeed use an index as primary table store. The Oracle database calls this concept index-organized tables (IOT), other databases use the term clustered index. In this section, both terms are used to either put the emphasis on the table or the index characteristics as needed.\nAn index-organized table is thus a B-tree index without a heap table. This results in two benefits: (1) it saves the space for the heap structure; (2) every access on a clustered index is automatically an index-only scan.\n索引组织表以主键顺序组织物理数据存储,索引结构中的每个叶节点(Leaf Nodes)都存储着完整的行数据。对应的主键被称为聚簇索引(Clustered Indexes),除此之外的索引称为二级索引(Secondary Indexes)。\nFigure 1: b-tree-structure\nFigure 2: index-based-access-on-a-heap-table\nB-Tree 的上级 leaf nodes 存储的是下级 leaf nodes 中列值的最大值,可以从图中看出最底层的 leaf nodes 存储的是 ROWID,可以据此查找到堆表中的位置。\nFigure 3: secondary-index-on-an-IOT\n聚簇索引 在 MySQL 中通常主键即为「聚簇索引」;若没有定义主键,则第一个 NOT NULL UNIQUE 列是聚集索引;若没有 NOT NULL UNIQUE 列,InnoDB 会创建一个隐藏的 row-id 作为聚集索引。 聚簇索引是按照索引顺序对物理记录排序,存储完整的行记录。 二级索引 除主键外的其他列上新建的索引都是二级索引。 二级索引的排序与物理记录的排序不一致,存储的是该列本身排序后的值和主键。 索引组织表的缺点 图 secondary-index-on-an-IOT 就是索引组织表中,二级索引获取数据的流程。再次根据主键在索引组织表中遍历查找就叫做「回表」。\n索引组织表的好处 可以节省堆表占用的空间 访问聚簇索引可以直接获取数据(即「覆盖索引」) 二级索引需要回表,即需要根据二级索引的叶节点内的主键,再对主键形成的聚簇索引进行遍历,查找到具体的记录。 ","permalink":"https://sheerwill.xyz/posts/main/evd-clustered_indexes_index_organized_tables_iot/","summary":"Some databases can indeed use an index as primary table store. The Oracle database calls this concept index-organized tables (IOT), other databases use the term clustered index. In this section, both terms are used to either put the emphasis on the table or the index characteristics as needed. An index-organized table is thus a B-tree index without a heap table. This results in two benefits: (1) it saves the space","title":"Clustered Indexes / Index Organized Tables (IOT)"},{"content":" Figure 1: workflow\n如上图所示,融合了笔记和 GTD 管理。\nGTD Capture To-Dos 分为的来源分为六种:\nHabit Track: 即对于重复事件的循环,有利于习惯的养成。 Cyclic Tasks: 需要循环的任务。 生日提醒 周报 每日回顾 稍后读 etc. Inbox: 用来收集各种 To-Dos,设置的快捷键为 C-c c。每日回顾的时候分发到各自的分类,并行进行 Scheduled。 personal: 个人事宜 someday: 存放一些不知道什么时候会做的 To-Dos,有时间回顾的时候再次分类。 technical: 存放个人需要偿还的技术债 work: 工作相关 books: 阅读的书籍,通过 ebib 的 ebib-add-reading-list-item 添加。 Dynamic: 只要是 org 文件中的有 To-Dos,就会在保存文件时,自动给文件添加 :dynamic: 标签,纳入到 Agenda 文件中。 note: 对一些地方的备注或者 To-Dos,后者和 Dynamic To-Dos 场景重复。 Clarify 对 Inbox 当中的 To-Dos 进行分发,并用关键字标记 To-Dos 的属性,比如 PROJECT, DELEGRTED 等。对于 PROJECT 这类还要继续分解。\nOrganize 对明天需要做的或知道明确日期的 To-Dos 进行 Scheduled,对于 DONE 或 CANCELLED 的 To-Dos 会自动复制到今日的 Daily log 当中,这样呈现的结果,其实和 Logseq 中在 Journals 里面管理 To-Dos 是一致的。\n不过实现的过程,或者说内在逻辑是不一样的。在 Logseq 的 Journals 中管理 To-Dos 即便是通过查询汇聚展示 To-Dos 也没有 agenda 中这么灵活,另外前几天定下的需要后面完成的 To-Dos 有两种管理方式:1. 将 To-Dos 链接到需要做的某天,自动展示。这样的操作确实方便,但使得各种关联杂乱。2. 将 To-Dos 移动到对应需要做的某天。\n上面说的呈现结果一致,就是指的第二种,回顾起来也非常清晰。在 org-roam 配合 agenda 使用中,To-Dos 有了单独的收集仓库,整理分发。也可以通过自定义的函数在 agenda 和 org-roam 之间联动,比如对于 DONE 或 CANCELLED 的 To-Dos 会自动复制到今日的 Daily log 当中,比如 org-roam 中的 To-Dos 也会呈现在 agenda view 中,完成后也会自动复制到 Daily log 中。\n整体上来讲,内在逻辑 agenda 和 org-roam 配合起来更加清晰,灵活。\nReflect 回顾所有的 To-Dos,选取明天或者近期需要完成。对已完成或取消的 To-Dos 归档,是按照结构归档到同级别 *.org 文件的 archive 文件夹下的同名文件中。\n同时,要回顾一下 Notes 当中的对某处的笔记或设定的 To-Dos。\nEngage 按照重要,简单等因素顺序执行需要做的 To-Dos\nNote taking 输入源分为工作、书籍和文章(来自于 RSS、Newsletter、Twitter 等平台)。\nBook PDF 以及 Epub 格式的书籍或者论文通过 Ebib 管理 Bibtex,Ebib 的 ebib-reading-list-file 设置为 Agenda 中的 books.org,这样就可以很方便新建任务到 book.org 在 Agenda View 中看到自己当前在阅读的书籍/论文,并查看进度。\n书籍/论文对应的笔记则是在 references 文件夹下,可以方便笔记中进行引用。\n书籍除了 ebib 管理的 bibtex 汇总可以查看,还通过 Dynamic Table 进行管理,这样可以更加直观的看到书籍的评价以及关联到读书笔记。(其实 ebib 那里也可以关联到笔记,毕竟笔记的创建就是利用 ebib 的命令,连接在 external note 这个字段中。)\n文件顶部这样设置即可,根据自己的情况进行修改适配,其中 file 是指数据源,这里就是将数据源放在与表格同一个文件。\n#+title: Book reviews #+COLUMNS: %25ITEM(Item) %10AUTHOR(Author) %10CATEGORY(Category) %15STATE(State) %15SCORE(Score) %10VERSION(Version) %15REMARK(Remark) %25NOTELINK(Notelink) #+BEGIN: columnview :hlines 1 :id \u0026#34;file:~/Dropbox/org/references/Book-Reviews.org\u0026#34; :skip-empty-rows t #+END: 统计的 COLUMNS 来自与 Head 的 Properties,两者要一一对应,如此都设置好了之后在表格处(columnview 那里)按 C-c C-c 生成表格,即 Dynamic Table。\n:PROPERTIES: :Author: 作者 :Category: 分类 :Score: 9.7 :State: Reading :Version: 版本 :Remark: :Notelink: [[id:xxx][笔记名称]] :END: Daily log 每个 Daily log 文件头部都有 #+ARCHIVE: journal.org:: 来控制归档的位置,每天回顾完当天的 log 就归档到 journal.org 文件中,如果后续这个文件太大,还可以按照年份来继续归档。\n而且在 journal.org 中通过 C-c \\ 可以查看所有的 tags,选择对应的 tag 就可以过滤出想要的内容,其他内容全部收缩。\nArticles/Work 文章的阅读笔记是放在 Daily log 当中的,在 What I read? 的标题下,打上对应的 tags。后期回顾的时候,或者积累一定数量的同类型、同话题文章,可能会 refile 到 org-roam 内进行总结,形成知识点。\n工作中的事情都是记在 Daily log 中,每条 log 有明确的时间。\n对于问题的解决,有详细的原因和解决方案,后续遇到同样的问题可以参考之前的思路;对于与他人沟通的内容,有详细的操作记录、数据记录以及邮件聊天等记录链接或截图,有背锅风险时可以轻松应对。\n同样的,工作中某个类型的知识点的笔记积累多了,会在 org-roam 中进行总结,形成知识点。\nExport 输出是 org 的强项,Hugo 方便与人在线分享,PDF/Markdown 离线分享。\norg-roam-ui 方便的查看笔记之间的关系,进行系统性回顾。\nScanned Document Management 扫描件都是通过 org-attach 包辅助管理,主要就是将扫描件的主要内容手工录入,便于后期文件搜索,源文件作为附件用 org-attach 管理,存放在文本同目录下的 data 文件夹下,按照 org-attach 生成的 ID 存放。\nDaily Log 当中一些很长的文件、执行脚本、代码等也可以通过相同的方式进行管理,减少 Journal.org 的体积,减少分割次数。\n","permalink":"https://sheerwill.xyz/posts/main/20221204112118-lucius_s_workflow/","summary":"Figure 1: workflow 如上图所示,融合了笔记和 GTD 管理。 GTD Capture To-Dos 分为的来源分为六种: Habit Track: 即对于重复事件的循环,有利于习惯的养成。 Cyclic Tasks: 需要循环的任务。 生日提醒 周报","title":"Lucius's Workflow"},{"content":"在 Obsidian 中可以通过 command palette 中搜索 Insert LaTeX formula 就可以弹出弹框输入 \\(\\textsc{LaTeX}\\) 表达式了。其中的 \\color{} 可以指定生成文本的颜色。\n","permalink":"https://sheerwill.xyz/posts/main/20221208143127-excalidraw/","summary":"在 Obsidian 中可以通过 command palette 中搜索 Insert LaTeX formula 就可以弹出弹框输入 \\(\\textsc{LaTeX}\\) 表达式了。其中的 \\color{} 可以指定生成文本的颜色。","title":"Excalidraw"},{"content":"Refactoring Tips Refactoring Condition statements Converting callbacks to promises Refactoring Promise chains with async/await Refactoring Code examples Callback Hell ","permalink":"https://sheerwill.xyz/posts/main/20230208145009-javascript_refactoring/","summary":"Refactoring Tips Refactoring Condition statements Converting callbacks to promises Refactoring Promise chains with async/await Refactoring Code examples Callback Hell ","title":"JavaScript Refactoring"},{"content":"precision Beancount 会统计写过的每个货币的金额,然后选择最常见的作为显示格式,如果大多数时候都是整数,它就会用整数显示,所以最好尽量都写成你想要看到的精度,比如不要写「9 CNY」而是「9 CNY」,保持大部分金额都是你需要的精度。\n","permalink":"https://sheerwill.xyz/posts/main/20220821223012-beancount/","summary":"precision Beancount 会统计写过的每个货币的金额,然后选择最常见的作为显示格式,如果大多数时候都是整数,它就会用整数显示,所以最好尽量都写成你想要看到的精度,","title":"Beancount"},{"content":"平时是使用苹果内置键盘以及 HHKB,因为键位有一些不同,设置上也会有一些区别。\nFigure 1: Karabiner-Elements\n--- global: check_for_updates_on_startup: true show_in_menu_bar: true show_profile_name_in_menu_bar: true profiles: - complex_modifications: parameters: basic.simultaneous_threshold_milliseconds: 50 basic.to_delayed_action_delay_milliseconds: 500 basic.to_if_alone_timeout_milliseconds: 1000 basic.to_if_held_down_threshold_milliseconds: 500 mouse_motion_to_scroll.speed: 100 rules: - description: Change tab to cmd+alt+ctrl if pressed with other keys manipulators: - from: key_code: tab modifiers: optional: - any to: - key_code: left_control modifiers: - left_command - left_option to_if_alone: - key_code: tab type: basic - description: Command + Esc to Command + ` manipulators: - from: key_code: escape modifiers: mandatory: - left_command to: - key_code: grave_accent_and_tilde modifiers: - left_command type: basic - description: Change escape to cmd+shift+ctrl if pressed with other keys manipulators: - from: key_code: escape modifiers: optional: - any to: - key_code: left_shift modifiers: - left_command - left_control to_if_alone: - key_code: escape type: basic - description: \u0026#34;HHKB RCtF: Function keys\u0026#34; manipulators: - from: key_code: \u0026#34;1\u0026#34; modifiers: mandatory: - right_command to: - key_code: f1 type: basic - from: key_code: \u0026#34;2\u0026#34; modifiers: mandatory: - right_command to: - key_code: f2 type: basic - from: key_code: \u0026#34;3\u0026#34; modifiers: mandatory: - right_command to: - key_code: f3 type: basic - from: key_code: \u0026#34;4\u0026#34; modifiers: mandatory: - right_command to: - key_code: f4 type: basic - from: key_code: \u0026#34;5\u0026#34; modifiers: mandatory: - right_command to: - key_code: f5 type: basic - from: key_code: \u0026#34;6\u0026#34; modifiers: mandatory: - right_command to: - key_code: f6 type: basic - from: key_code: \u0026#34;7\u0026#34; modifiers: mandatory: - right_command to: - key_code: f7 type: basic - from: key_code: \u0026#34;8\u0026#34; modifiers: mandatory: - right_command to: - key_code: f8 type: basic - from: key_code: \u0026#34;9\u0026#34; modifiers: mandatory: - right_command to: - key_code: f9 type: basic - from: key_code: \u0026#34;0\u0026#34; modifiers: mandatory: - right_command to: - key_code: f10 type: basic - from: key_code: hyphen modifiers: mandatory: - right_command to: - key_code: f11 type: basic - from: key_code: equal_sign modifiers: mandatory: - right_command to: - key_code: f12 type: basic devices: - disable_built_in_keyboard_if_exists: true fn_function_keys: [] identifiers: is_keyboard: true is_pointing_device: false product_id: 6425 vendor_id: 1241 ignore: false manipulate_caps_lock_led: false simple_modifications: [] - disable_built_in_keyboard_if_exists: true fn_function_keys: [] identifiers: is_keyboard: true is_pointing_device: false product_id: 256 vendor_id: 2131 ignore: false manipulate_caps_lock_led: false simple_modifications: [] - disable_built_in_keyboard_if_exists: false fn_function_keys: [] identifiers: is_keyboard: true is_pointing_device: false product_id: 34304 vendor_id: 1452 ignore: false manipulate_caps_lock_led: true simple_modifications: [] fn_function_keys: - from: key_code: f1 to: - key_code: display_brightness_decrement - from: key_code: f2 to: - key_code: display_brightness_increment - from: key_code: f3 to: - key_code: mission_control - from: key_code: f4 to: - key_code: launchpad - from: key_code: f5 to: - key_code: illumination_decrement - from: key_code: f6 to: - key_code: illumination_increment - from: key_code: f7 to: - key_code: rewind - from: key_code: f8 to: - key_code: play_or_pause - from: key_code: f9 to: - key_code: fastforward - from: key_code: f10 to: - key_code: mute - from: key_code: f11 to: - key_code: volume_decrement - from: key_code: f12 to: - key_code: volume_increment name: \u0026#34;⌨️\u0026#34; parameters: delay_milliseconds_before_open_device: 1000 selected: true simple_modifications: [] virtual_hid_keyboard: caps_lock_delay_milliseconds: 0 country_code: 0 indicate_sticky_modifier_keys_state: true keyboard_type: ansi mouse_key_xy_scale: 100 - complex_modifications: parameters: basic.simultaneous_threshold_milliseconds: 50 basic.to_delayed_action_delay_milliseconds: 500 basic.to_if_alone_timeout_milliseconds: 1000 basic.to_if_held_down_threshold_milliseconds: 500 mouse_motion_to_scroll.speed: 100 rules: - description: Change tab to cmd+alt+ctrl if pressed with other keys manipulators: - from: key_code: tab modifiers: optional: - any to: - key_code: left_control modifiers: - left_command - left_option to_if_alone: - key_code: tab type: basic - description: Change grave_accent_and_tilde to cmd+shift+ctrl if pressed with other keys manipulators: - from: key_code: grave_accent_and_tilde modifiers: optional: - any to: - key_code: left_shift modifiers: - left_command - left_control to_if_alone: - key_code: grave_accent_and_tilde type: basic - description: \u0026#34;HHKB RCtF: Function keys\u0026#34; manipulators: - from: key_code: \u0026#34;1\u0026#34; modifiers: mandatory: - right_command to: - key_code: f1 type: basic - from: key_code: \u0026#34;2\u0026#34; modifiers: mandatory: - right_command to: - key_code: f2 type: basic - from: key_code: \u0026#34;3\u0026#34; modifiers: mandatory: - right_command to: - key_code: f3 type: basic - from: key_code: \u0026#34;4\u0026#34; modifiers: mandatory: - right_command to: - key_code: f4 type: basic - from: key_code: \u0026#34;5\u0026#34; modifiers: mandatory: - right_command to: - key_code: f5 type: basic - from: key_code: \u0026#34;6\u0026#34; modifiers: mandatory: - right_command to: - key_code: f6 type: basic - from: key_code: \u0026#34;7\u0026#34; modifiers: mandatory: - right_command to: - key_code: f7 type: basic - from: key_code: \u0026#34;8\u0026#34; modifiers: mandatory: - right_command to: - key_code: f8 type: basic - from: key_code: \u0026#34;9\u0026#34; modifiers: mandatory: - right_command to: - key_code: f9 type: basic - from: key_code: \u0026#34;0\u0026#34; modifiers: mandatory: - right_command to: - key_code: f10 type: basic - from: key_code: hyphen modifiers: mandatory: - right_command to: - key_code: f11 type: basic - from: key_code: equal_sign modifiers: mandatory: - right_command to: - key_code: f12 type: basic devices: [] fn_function_keys: - from: key_code: f1 to: - consumer_key_code: display_brightness_decrement - from: key_code: f2 to: - consumer_key_code: display_brightness_increment - from: key_code: f3 to: - key_code: mission_control - from: key_code: f4 to: - key_code: launchpad - from: key_code: f5 to: - key_code: illumination_decrement - from: key_code: f6 to: - key_code: illumination_increment - from: key_code: f7 to: - consumer_key_code: rewind - from: key_code: f8 to: - consumer_key_code: play_or_pause - from: key_code: f9 to: - consumer_key_code: fastforward - from: key_code: f10 to: - consumer_key_code: mute - from: key_code: f11 to: - consumer_key_code: volume_decrement - from: key_code: f12 to: - consumer_key_code: volume_increment name: \u0026#34;\u0026#34; parameters: delay_milliseconds_before_open_device: 1000 selected: false simple_modifications: - from: key_code: caps_lock to: - key_code: left_control virtual_hid_keyboard: country_code: 0 indicate_sticky_modifier_keys_state: true mouse_key_xy_scale: 100 ","permalink":"https://sheerwill.xyz/posts/main/20220805101927-karabiner_elements/","summary":"平时是使用苹果内置键盘以及 HHKB,因为键位有一些不同,设置上也会有一些区别。 Figure 1: Karabiner-Elements --- global: check_for_updates_on_startup: true show_in_menu_bar: true show_profile_name_in_menu_bar: true profiles: - complex_modifications: parameters: basic.simultaneous_threshold_milliseconds: 50 basic.to_delayed_action_delay_milliseconds: 500 basic.to_if_alone_timeout_milliseconds: 1000 basic.to_if_held_down_threshold_milliseconds: 500 mouse_motion_to_scroll.speed: 100 rules: - description: Change tab to","title":"Karabiner-Elements"},{"content":"Online Dictionary Helper Figure 1: online-dictionary-helper-result\nChrome extension 在线词典助手\nSettings Figure 2: online-dictionary-helper-setting\nAnki Template 模板的样式是根据卡片内容来设置的,其中 glossary 中本身就带了样式。\nfront \u0026lt;div class=\u0026#34;section\u0026#34;\u0026gt; \u0026lt;div id=\u0026#34;front\u0026#34; class=\u0026#34;items\u0026#34;\u0026gt; {{expression}}\u0026lt;span class=\u0026#34;audio\u0026#34;\u0026gt;{{audio}}\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; {{#reading}} \u0026lt;hr /\u0026gt; \u0026lt;div id=\u0026#34;front-extra1\u0026#34; class=\u0026#34;items\u0026#34;\u0026gt; \u0026lt;span\u0026gt;{{reading}}\u0026amp;nbsp;\u0026lt;/span\u0026gt; \u0026lt;span\u0026gt;{{extrainfo}}\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; {{/reading}} \u0026lt;/div\u0026gt; back {{FrontSide}} \u0026lt;div class=\u0026#34;section\u0026#34;\u0026gt; \u0026lt;div id=\u0026#34;back\u0026#34; class=\u0026#34;items\u0026#34;\u0026gt;{{glossary}}\u0026lt;/div\u0026gt; {{#sentence}} \u0026lt;hr /\u0026gt; \u0026lt;div id=\u0026#34;back-extra1\u0026#34; class=\u0026#34;items\u0026#34;\u0026gt;{{sentence}}\u0026lt;/div\u0026gt; {{/sentence}} {{#url}} \u0026lt;hr /\u0026gt; \u0026lt;div id=\u0026#34;back-extra2\u0026#34; class=\u0026#34;items\u0026#34;\u0026gt;\u0026lt;a href=\u0026#34;{{url}}\u0026#34;\u0026gt;Source\u0026lt;/a\u0026gt;\u0026lt;/div\u0026gt; {{/url}} \u0026lt;/div\u0026gt; style /* ODH Template author:ninja huang site:https://github.com/ninja33/odh */ .star { color: #ffbb00; } .card { margin: 12px; text-align: left; background-color: #fff; } .section { color: #414141; background-color: #fafafa; font-family: \u0026#34;Segoe UI\u0026#34;, Arial, \u0026#34;Microsoft Yahei\u0026#34;, sans-serif; font-size: 16px; box-shadow: 1px 1px 5px 0px rgba(0, 0, 0, 0.2), 0 0px 0px 1px rgba(0, 0, 0, 0.1); border-radius: 4px; margin: 12px 0; } .items { margin: 0 12px; padding: 10px 0; } .audio { margin-left: 5px; } .audio img { width: 64px; height: 64px; vertical-align: middle; } hr { border: 0; margin: 0 12px; border-top: 1px solid #e5e5e5; } .items hr { border: 0; margin: 12px 0; border-top: 1px solid #e5e5e5; } #front, #back { line-height: 1.5em; } #front { font-size: 2em; font-weight: bold; text-align: left; } #back { } #front-extra1 { font-size: 0.8em; } #front-extra2 { } #back-extra1 { } #back-extra1 { } ","permalink":"https://sheerwill.xyz/posts/main/20220706112803-anki/","summary":"Online Dictionary Helper Figure 1: online-dictionary-helper-result Chrome extension 在线词典助手 Settings Figure 2: online-dictionary-helper-setting Anki Template 模板的样式是根据卡片内容来设置的,其中 glossary 中本身就带了样式。 front \u0026lt;div class=\u0026#34;section\u0026#34;\u0026gt; \u0026lt;div id=\u0026#34;front\u0026#34; class=\u0026#34;items\u0026#34;\u0026gt; {{expression}}\u0026lt;span class=\u0026#34;audio\u0026#34;\u0026gt;{{audio}}\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; {{#reading}} \u0026lt;hr /\u0026gt; \u0026lt;div id=\u0026#34;front-extra1\u0026#34; class=\u0026#34;items\u0026#34;\u0026gt; \u0026lt;span\u0026gt;{{reading}}\u0026amp;nbsp;\u0026lt;/span\u0026gt; \u0026lt;span\u0026gt;{{extrainfo}}\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; {{/reading}} \u0026lt;/div\u0026gt; back","title":"Anki"},{"content":"Starship brew install starship add eval \u0026quot;$(starship init zsh)\u0026quot; to .zshrc\nmkdir -p ~/.config \u0026amp;\u0026amp; touch ~/.config/starship.toml 默认配置\n# 设置配置范例,开启编辑器的自动补全 \u0026#34;$schema\u0026#34; = \u0026#39;https://starship.rs/config-schema.json\u0026#39; # 在命令之间插入空行 add_newline = false # ~/.config/starship.toml # A minimal left prompt format = \u0026#34;\u0026#34;\u0026#34;$character\u0026#34;\u0026#34;\u0026#34; # move the rest of the prompt to the right right_format = \u0026#34;\u0026#34;\u0026#34;$all\u0026#34;\u0026#34;\u0026#34; [line_break] disabled = true # 将提示符的“❯”替换为“➜” [character] # “character”是我们正在配置的组件 success_symbol = \u0026#34;[❯](bold green)\u0026#34; # 设置“success_symbol” 字段为绿色加粗的“➜” # 禁用 package 组件,完全隐藏它的提示符 [package] disabled = true [localip] ssh_only = true format = \u0026#34;@[$localipv4](bold red) \u0026#34; disabled = false [time] disabled = false time_format = \u0026#34;%R\u0026#34; # Hour:Minute Format style = \u0026#34;bg:none\u0026#34; format = \u0026#39;[[♥ $time ](bg:none)]($style)\u0026#39; Exa brew install exa add to .zshrc\nalias ls=\u0026#39;exa\u0026#39; alias la=\u0026#39;exa -a\u0026#39; alias ll=\u0026#39;exa -lh\u0026#39; #alias la=\u0026#39;exa -lah\u0026#39; alias lr=\u0026#39;exa -lR\u0026#39; Ohmyzsh Plugin zsh-autosuggestions ","permalink":"https://sheerwill.xyz/posts/main/20220629204553-terminal/","summary":"Starship brew install starship add eval \u0026quot;$(starship init zsh)\u0026quot; to .zshrc mkdir -p ~/.config \u0026amp;\u0026amp; touch ~/.config/starship.toml 默认配置 # 设置配置范例,开启编辑器的自动补全 \u0026#34;$schema\u0026#34; = \u0026#39;https://starship.rs/config-schema.json\u0026#39; # 在命令之间插入空行 add_newline = false # ~/.config/starship.toml # A minimal left prompt format = \u0026#34;\u0026#34;\u0026#34;$character\u0026#34;\u0026#34;\u0026#34; # move the rest of","title":"Terminal"},{"content":"Extensions ESLint Markdown All in One Markdown Preview Github MoonScript Language One Dark Pro Prettier - Code formatter Settings { \u0026#34;editor.fontFamily\u0026#34;: \u0026#34;\u0026#39;Iosevka\u0026#39;, LXGW WenKai Mono, Menlo, Monaco, \u0026#39;Courier New\u0026#39;, monospace\u0026#34;, \u0026#34;editor.fontLigatures\u0026#34;: true, \u0026#34;editor.fontSize\u0026#34;: 14, \u0026#34;security.workspace.trust.untrustedFiles\u0026#34;: \u0026#34;open\u0026#34;, \u0026#34;editor.bracketPairColorization.enabled\u0026#34;: true, \u0026#34;editor.renderWhitespace\u0026#34;: \u0026#34;boundary\u0026#34;, \u0026#34;workbench.colorCustomizations\u0026#34;: { \u0026#34;editorBracketHighlight.foreground1\u0026#34;: \u0026#34;#EFBB24\u0026#34;, \u0026#34;editorBracketHighlight.foreground2\u0026#34;: \u0026#34;#F8C3CD\u0026#34;, \u0026#34;editorBracketHighlight.foreground3\u0026#34;: \u0026#34;#A5DEE4\u0026#34;, \u0026#34;editorBracketHighlight.foreground4\u0026#34;: \u0026#34;#8A6BBE\u0026#34;, \u0026#34;editorBracketHighlight.foreground5\u0026#34;: \u0026#34;#90B44B\u0026#34;, \u0026#34;editorBracketHighlight.foreground6\u0026#34;: \u0026#34;#FFB11B\u0026#34;, \u0026#34;editorBracketHighlight.unexpectedBracket.foreground\u0026#34;: \u0026#34;#E83015\u0026#34;, \u0026#34;tab.activeBackground\u0026#34;: \u0026#34;#724832\u0026#34;, \u0026#34;scrollbarSlider.background\u0026#34;: \u0026#34;#91AD70\u0026#34;, \u0026#34;scrollbarSlider.hoverBackground\u0026#34;: \u0026#34;#91AD70\u0026#34;, \u0026#34;scrollbarSlider.activeBackground\u0026#34;: \u0026#34;#91AD70\u0026#34; }, \u0026#34;[javascript]\u0026#34;: { \u0026#34;editor.defaultFormatter\u0026#34;: \u0026#34;esbenp.prettier-vscode\u0026#34; }, \u0026#34;[html]\u0026#34;: { \u0026#34;editor.defaultFormatter\u0026#34;: \u0026#34;esbenp.prettier-vscode\u0026#34; }, \u0026#34;[markdown]\u0026#34;: { \u0026#34;editor.defaultFormatter\u0026#34;: \u0026#34;esbenp.prettier-vscode\u0026#34; }, \u0026#34;editor.inlineSuggest.enabled\u0026#34;: true, \u0026#34;workbench.colorTheme\u0026#34;: \u0026#34;One Dark Pro Darker\u0026#34;, \u0026#34;[json]\u0026#34;: { \u0026#34;editor.defaultFormatter\u0026#34;: \u0026#34;esbenp.prettier-vscode\u0026#34; }, \u0026#34;[css]\u0026#34;: { \u0026#34;editor.defaultFormatter\u0026#34;: \u0026#34;esbenp.prettier-vscode\u0026#34; } } ","permalink":"https://sheerwill.xyz/posts/main/20220629203733-vscode/","summary":"Extensions ESLint Markdown All in One Markdown Preview Github MoonScript Language One Dark Pro Prettier - Code formatter Settings { \u0026#34;editor.fontFamily\u0026#34;: \u0026#34;\u0026#39;Iosevka\u0026#39;, LXGW WenKai Mono, Menlo, Monaco, \u0026#39;Courier New\u0026#39;, monospace\u0026#34;, \u0026#34;editor.fontLigatures\u0026#34;: true, \u0026#34;editor.fontSize\u0026#34;: 14, \u0026#34;security.workspace.trust.untrustedFiles\u0026#34;: \u0026#34;open\u0026#34;, \u0026#34;editor.bracketPairColorization.enabled\u0026#34;: true, \u0026#34;editor.renderWhitespace\u0026#34;: \u0026#34;boundary\u0026#34;, \u0026#34;workbench.colorCustomizations\u0026#34;: { \u0026#34;editorBracketHighlight.foreground1\u0026#34;: \u0026#34;#EFBB24\u0026#34;, \u0026#34;editorBracketHighlight.foreground2\u0026#34;: \u0026#34;#F8C3CD\u0026#34;, \u0026#34;editorBracketHighlight.foreground3\u0026#34;: \u0026#34;#A5DEE4\u0026#34;, \u0026#34;editorBracketHighlight.foreground4\u0026#34;: \u0026#34;#8A6BBE\u0026#34;, \u0026#34;editorBracketHighlight.foreground5\u0026#34;: \u0026#34;#90B44B\u0026#34;, \u0026#34;editorBracketHighlight.foreground6\u0026#34;: \u0026#34;#FFB11B\u0026#34;, \u0026#34;editorBracketHighlight.unexpectedBracket.foreground\u0026#34;: \u0026#34;#E83015\u0026#34;, \u0026#34;tab.activeBackground\u0026#34;: \u0026#34;#724832\u0026#34;, \u0026#34;scrollbarSlider.background\u0026#34;: \u0026#34;#91AD70\u0026#34;, \u0026#34;scrollbarSlider.hoverBackground\u0026#34;: \u0026#34;#91AD70\u0026#34;, \u0026#34;scrollbarSlider.activeBackground\u0026#34;: \u0026#34;#91AD70\u0026#34; }, \u0026#34;[javascript]\u0026#34;: { \u0026#34;editor.defaultFormatter\u0026#34;: \u0026#34;esbenp.prettier-vscode\u0026#34; }, \u0026#34;[html]\u0026#34;: { \u0026#34;editor.","title":"VSCode"},{"content":"\n语法 效果 \\bar{x} \\(\\bar{x}\\) \\grave{\\eta} \\(\\grave{\\eta}\\) \\dot{x} \\(\\dot{x}\\) \\acute{\\eta} \\(\\acute{\\eta}\\) \\breve{a} \\(\\breve{a}\\) \\hat{\\alpha} \\(\\hat{\\alpha}\\) \\check{\\alpha} \\(\\check{\\alpha}\\) \\ddot{y} \\(\\ddot{y}\\) \\tilde{\\iota} \\(\\tilde{\\iota}\\) 语法 效果 \\sin\\theta \\(\\sin\\!\\theta\\) \\arcsin\\frac{L}{r} \\(\\arcsin\\frac{L}{r}\\) \\sinh g \\(\\sinh\\ g\\) \\operatorname{sh}j \\(\\operatorname{sh}j\\) \\operatorname{argch}l \\(\\operatorname{argch}l\\) k'(x)=\\lim_{\\Delta x\\to 0}\\frac{k(x)-k(x-\\Delta x)}{\\Deltax} \\(k\u0026rsquo;(x)=\\lim_{\\Delta x\\to0}\\!\\frac{k(x)-k(x-\\Delta x)}{\\Delta x}\\) \\max H \\(\\max\\!H\\) \\sup t \\(\\sup t\\) \\lg X \\(\\lg\\!X\\) \\ker x \\(\\ker x\\) \\Pr x \\(\\Pr x\\) \\arg x \\(\\arg x\\) \\lceil\\frac{n+1}{2}\\rceil \\(\\lceil\\frac{n+1}{2}\\rceil\\) \\cos\\theta \\(\\cos\\theta\\) \\arccos\\frac{T}{r} \\(\\arccos\\frac{T}{r}\\) \\cosh h \\(\\cosh h\\) \\operatorname{argsh}k \\(\\operatorname{argsh}k\\) \\operatorname{th}i \\(\\operatorname{th}i\\) \\limsup S \\(\\limsup S\\) \\min L \\(\\min L\\) \\exp\\!t \\(\\exp\\!t\\) \\log X \\(\\log X\\) \\deg x \\(\\deg x\\) \\det x \\(\\det x\\) \\dim x \\(\\dim x\\) \\left \\lceil\\frac{n+1}{2}\\right \\rceil \\(\\left \\lceil\\frac{n+1}{2}\\right \\rceil\\) \\tan\\theta \\(\\tan\\theta\\) \\arctan\\frac{L}{T} \\(\\arctan\\frac{L}{T}\\) \\tanh i \\(\\tanh i\\) \\operatorname{ch}h \\(\\operatorname{ch}h\\) \\operatorname{argth}m \\(\\operatorname{argth}m\\) \\liminf I \\(\\liminf I\\) \\inf s \\(\\inf s\\) \\ln X \\(\\ln\\!X\\) \\log_\\alpha X \\(\\log_\\alpha X\\) \\gcd(T,U,V,W,X) \\(\\gcd(T,U,V,W,X)\\) \\hom x \\(\\hom x\\) \\lim_{t\\to n}T \\(\\lim_{t\\to n}T\\) 语法 效果 \\pmod{m} \\(\\pmod{m}\\) a \\bmod b \\(a \\bmod b\\) 语法 效果 \\nabla \\(\\nabla\\) \\dot x \\(\\dot x\\) \\partial x \\(\\partial x\\) \\ddot y \\(\\ddot y\\) \\mathrm{d}x \\(\\mathrm{d}x\\) 语法 效果 \\forall \\(\\forall\\) \\in \\(\\in\\) \\subseteq \\(\\subseteq\\) \\cup \\(\\cup\\) \\sqsupset \\(\\sqsupset\\) \\exists \\(\\exists\\) \\ni \\(\\ni\\) \\supset \\(\\supset\\) \\bigcup \\(\\bigcup\\) \\sqsupseteq \\(\\sqsupseteq\\) \\not\\in \\(\\not\\in\\) \\supseteq \\(\\supseteq\\) \\biguplus \\(\\biguplus\\) \\sqcap \\(\\sqcap\\) \\emptyset \\(\\emptyset\\) \\notin \\(\\notin\\) \\cap \\(\\cap\\) \\sqsubset \\(\\sqsubset\\) \\sqcup \\(\\sqcup\\) \\varnothing \\(\\varnothing\\) \\subset \\(\\subset\\) \\bigcap \\(\\bigcap\\) \\sqsubseteq \\(\\sqsubseteq\\) \\bigsqcup \\(\\bigsqcup\\) 语法 效果 p \\(p\\) \\bar{q} \\to p \\(\\bar{q} \\to p\\) \\lnot \\(\\lnot\\) \\land \\(\\land\\) \\lor \\(\\lor\\) \\neg q \\(\\neg q\\) \\wedge \\(\\wedge\\) \\vee \\(\\vee\\) \\setminus \\(\\setminus\\) \\bigwedge \\(\\bigwedge\\) \\bigvee \\(\\bigvee\\) \\smallsetminus \\(\\smallsetminus\\) 语法 效果 \\sqrt{3} \\(\\sqrt{3}\\) \\sqrt[n]{3} \\(\\sqrt[n]{3}\\) 语法 效果 A \\quad B \\(A \\quad B\\) NORMAL-\\mathrm{UPRIGHT} \\(NORMAL-\\mathrm{UPRIGHT}\\) NORMAL-\\mathrm{\\scriptsize SCRIPTSIZE} \\(NORMAL-\\mathrm{\\scriptsize SCRIPTSIZE}\\) A \\qquad B \\(A \\qquad B\\) NORMAL-\\mathrm{\\small SAMLL} \\(NORMAL-\\mathrm{\\small SMALL}\\) NORMAL-\\mathrm{\\tiny TINY} \\(NORMAL-\\mathrm{\\tiny TINY}\\) ","permalink":"https://sheerwill.xyz/posts/main/20220616162256-latex/","summary":"语法 效果 \\bar{x} \\(\\bar{x}\\) \\grave{\\eta} \\(\\grave{\\eta}\\) \\dot{x} \\(\\dot{x}\\) \\acute{\\eta} \\(\\acute{\\eta}\\) \\breve{a} \\(\\breve{a}\\) \\hat{\\alpha} \\(\\hat{\\alpha}\\) \\check{\\alpha} \\(\\check{\\alpha}\\) \\ddot{y} \\(\\ddot{y}\\) \\tilde{\\iota} \\(\\tilde{\\iota}\\) 语法 效果 \\sin\\theta \\(\\sin\\!\\theta\\) \\arcsin\\frac{L}{r} \\(\\arcsin\\frac{L}{r}\\) \\sinh g \\(\\sinh\\ g\\) \\operatorname{sh}j \\(\\operatorname{sh}j\\) \\operatorname{argch}l \\(\\operatorname{argch}l\\) k'(x)=\\lim_{\\Delta x\\to 0}\\frac{k(x)-k(x-\\Delta x)}{\\Deltax} \\(k\u0026rsquo;(x)=\\lim_{\\Delta x\\to0}\\!\\frac{k(x)-k(x-\\Delta x)}{\\Delta x}\\) \\max H \\(\\max\\!H\\) \\sup t \\(\\sup t\\) \\lg X \\(\\lg\\!X\\) \\ker x \\(\\ker x\\) \\Pr x \\(\\Pr x\\) \\arg x \\(\\arg x\\) \\lceil\\frac{n+1}{2}\\rceil \\(\\lceil\\frac{n+1}{2}\\rceil\\)","title":"Latex"},{"content":"重构小程序前端校验接口返参处理 原本代码是通过嵌套回调,控制提示之间的前后依赖关系,形成了回调地狱,需要重构。\n主要是用 Map (ES6+) 处理 if (code){} else if (code){},并且利用 Promise 封装函数,配合 async 和 await (ES7+),使得异步操作同步化,抽离出嵌套的逻辑。后续代码维护只需要区分「仅提示」和「打断」两种模式,不需要考虑提示之间的互相依赖,后期维护成本大大降低。\n比较业务代码可以发现,代码量直接干掉了 2/3。\n原始代码 // util.js /* 封装wx.request请求 url 接口url params 请求参数 md 请求方式 load 是否显示loading等待框 */ const getResult = function (url, params, md, cb, load, ctype) { //封装wx,request请求 var that = this; if (load) { wx.showLoading({ mask: true, title: \u0026#34;拼命加载中\u0026#34;, }); } var requestParams = { url: ycConfig[ycConfig.env].domain_url + url, data: params, dataType: \u0026#34;json\u0026#34;, method: md ? md : \u0026#34;POST\u0026#34;, header: { \u0026#34;content-type\u0026#34;: ctype || \u0026#34;application/json\u0026#34;, }, success: function (res) { wx.hideLoading(); if (res.data \u0026amp;\u0026amp; res.data.status == \u0026#34;error\u0026#34;) { typeof cb == \u0026#34;function\u0026#34; \u0026amp;\u0026amp; cb(res.data); } else { typeof cb == \u0026#34;function\u0026#34; \u0026amp;\u0026amp; cb(res.data); } }, complete: function (res) {}, fail: function () { wx.hideLoading(); wx.showToast({ title: \u0026#34;网络连接异常\u0026#34;, icon: \u0026#34;none\u0026#34;, }); }, }; wx.request(requestParams); }; module.exports = { getResult, }; // app.js let { getResult } = require(\u0026#34;utils/util.js\u0026#34;); App({ getResult(url, params, md, cb, load, ctype) { var that = this; if (url != \u0026#34;/ffpLogin.pl\u0026#34;) { var userInfo = wx.getStorageSync(\u0026#34;globalUserInfo\u0026#34;); //判断是否有效 if ( isEmpty(userInfo.wechatLoginToken) || isEmpty(userInfo.wechatOpenId) ) { wx.clearStorage(); //清理登录缓存数据 wx.showToast({ title: \u0026#34;登录失效,请重新登录!\u0026#34;, icon: \u0026#34;none\u0026#34;, }); wx.reLaunch({ url: \u0026#34;/pages/#/#\u0026#34;, }); return; } params.wechatLoginToken = userInfo.wechatLoginToken; params.wechatOpenId = userInfo.wechatOpenId; } var dic = {}; dic.cipher = that.encryption(params); function temp_cb(data) { if (data.code == \u0026#34;8888\u0026#34;) { wx.clearStorage(); //清理登录缓存数据 wx.showToast({ title: \u0026#34;登录失效,请重新登录!\u0026#34;, icon: \u0026#34;none\u0026#34;, }); wx.reLaunch({ url: \u0026#34;/pages/#/#\u0026#34;, }); return; } typeof cb == \u0026#34;function\u0026#34; \u0026amp;\u0026amp; cb(data); } getResult(url, dic, md, temp_cb, load, ctype); }, }); Page({ //提交订单 formSubmit: function (formSubmitRes) { var paraDic = { regPhone: this.data.regPhone, shipName: this.data.shipName, customer: this.data.customerName, planType: this.data.planCateID, customerType: this.data.customerID, arriveTime: this.data.isAllMention ? this.data.arrivalTime : \u0026#34;\u0026#34;, takeSite: this.data.isTakeSite ? this.data.takeSiteID : \u0026#34;\u0026#34;, }; var that = this; //提交订单 app.getResult( \u0026#34;/oilPlan/checkInfo.pl\u0026#34;, paraDic, \u0026#34;POST\u0026#34;, function (res) { var appleDetailDic = {}; if (that.data.isTakeSite) { appleDetailDic = { regPhone: that.data.regPhone, planType: that.data.planCateID, customerType: that.data.customerID, siteId: that.data.saleSiteID, shipName: that.data.shipName, customer: formSubmitRes.detail.value.customerName, takeSite: that.data.takeSiteID, planCode: that.data.planCode, pagesType: \u0026#34;0\u0026#34;, //type 0 新增,1 修改 isAgain: \u0026#34;1\u0026#34;, //修改识别 shipLength: formSubmitRes.detail.value.shipLength, //船舶 shipType: that.data.shipTypeSelect, //船舶类型 shipLoad: that.data.shipLoadSelect, //载重 isAllMention: that.data.isAllMention, nineyards: formSubmitRes.detail.value.nineyards, //九位码 arrivalTime: that.data.arrivalTime, //预计到站时间 otherPhone: formSubmitRes.detail.value.otherPhone, }; } else { appleDetailDic = { regPhone: that.data.regPhone, planType: that.data.planCateID, customerType: that.data.customerID, siteId: that.data.saleSiteID, shipName: that.data.shipName, customer: formSubmitRes.detail.value.customerName, takeSite: \u0026#34;\u0026#34;, planCode: that.data.planCode, pagesType: \u0026#34;0\u0026#34;, //type 0 新增,1 修改 isAgain: \u0026#34;1\u0026#34;, //修改识别 shipLength: formSubmitRes.detail.value.shipLength, //船舶 shipType: that.data.shipTypeSelect, //船舶类型 shipLoad: that.data.shipLoadSelect, //载重 isAllMention: that.data.isAllMention, nineyards: formSubmitRes.detail.value.nineyards, //九位码 arrivalTime: that.data.arrivalTime, //预计到站时间 otherPhone: formSubmitRes.detail.value.otherPhone, }; } if (res.code == \u0026#34;0000\u0026#34;) { //判断是否修改 if (that.data.isDataChange) { //修改 wx.navigateTo({ url: \u0026#34;/pages/plan/applyDetail/applyDetail?appleDetailDic=\u0026#34; + JSON.stringify(appleDetailDic), }); } else { //再来一单的申请详情页 wx.navigateTo({ url: \u0026#34;/pages/plan/planAgain-detail/planAgain-detail?appleDetailDic=\u0026#34; + JSON.stringify(appleDetailDic), }); } } else if (res.code == \u0026#34;0003\u0026#34;) { wx.showModal({ title: \u0026#34;提示\u0026#34;, content: res.msg, success(res) { if (res.confirm) { //创建新船 app.getResult( \u0026#34;/oilPlan/addShip.pl\u0026#34;, paraDic, \u0026#34;POST\u0026#34;, function (res) { if (res.code == \u0026#34;0000\u0026#34;) { if (appleDetailDic.customerType != 3) { app.getResult( \u0026#34;/oilPlan/checkInfo.pl\u0026#34;, paraDic, \u0026#34;POST\u0026#34;, function (res) { if (res.code == \u0026#34;0000\u0026#34;) { //判断是否修改 if (that.data.isDataChange) { //修改 wx.navigateTo({ url: \u0026#34;/pages/plan/applyDetail/applyDetail?appleDetailDic=\u0026#34; + JSON.stringify(appleDetailDic), }); } else { //再来一单的申请详情页 wx.navigateTo({ url: \u0026#34;/pages/plan/planAgain-detail/planAgain-detail?appleDetailDic=\u0026#34; + JSON.stringify(appleDetailDic), }); } } else if (res.code == \u0026#34;0010\u0026#34;) { //点击弹框 wx.showModal({ title: \u0026#34;提示\u0026#34;, content: res.msg, confirmText: \u0026#34;确定\u0026#34;, cancelText: \u0026#34;取消\u0026#34;, success(res) { if (res.confirm) { //判断是否修改 debugger; if (that.data.isDataChange) { //修改 wx.navigateTo({ url: \u0026#34;/pages/plan/applyDetail/applyDetail?appleDetailDic=\u0026#34; + JSON.stringify(appleDetailDic), }); } else { //再来一单的申请详情页 wx.navigateTo({ url: \u0026#34;/pages/plan/planAgain-detail/planAgain-detail?appleDetailDic=\u0026#34; + JSON.stringify(appleDetailDic), }); } } else if (res.cancel) { } }, }); } else { wx.showToast({ title: res.msg, icon: \u0026#34;none\u0026#34;, }); } }, \u0026#34;ture\u0026#34; ); } else { if (that.data.isDataChange) { //修改 wx.navigateTo({ url: \u0026#34;/pages/plan/applyDetail/applyDetail?appleDetailDic=\u0026#34; + JSON.stringify(appleDetailDic), }); } else { //再来一单的申请详情页 wx.navigateTo({ url: \u0026#34;/pages/plan/planAgain-detail/planAgain-detail?appleDetailDic=\u0026#34; + JSON.stringify(appleDetailDic), }); } } } else { wx.showToast({ title: res.msg, icon: \u0026#34;none\u0026#34;, }); } }, \u0026#34;ture\u0026#34; ); } else if (res.cancel) { } }, }); } else if (res.code == \u0026#34;0008\u0026#34;) { //点击弹框 wx.showModal({ title: \u0026#34;提示\u0026#34;, content: res.msg, confirmText: \u0026#34;仍然选择\u0026#34;, cancelText: \u0026#34;重新选择\u0026#34;, success(res) { if (res.confirm) { paraDic.ignore = 1; app.getResult( \u0026#34;/oilPlan/checkInfo.pl\u0026#34;, paraDic, \u0026#34;POST\u0026#34;, function (res) { if (res.code == \u0026#34;0000\u0026#34;) { if (that.data.isDataChange) { //修改 wx.navigateTo({ url: \u0026#34;/pages/plan/applyDetail/applyDetail?appleDetailDic=\u0026#34; + JSON.stringify(appleDetailDic), }); } else { //再来一单的申请详情页 wx.navigateTo({ url: \u0026#34;/pages/plan/planAgain-detail/planAgain-detail?appleDetailDic=\u0026#34; + JSON.stringify(appleDetailDic), }); } } else if (res.code == \u0026#34;0010\u0026#34;) { //点击弹框 wx.showModal({ title: \u0026#34;提示\u0026#34;, content: res.msg, confirmText: \u0026#34;确定\u0026#34;, cancelText: \u0026#34;取消\u0026#34;, success(res) { if (res.confirm) { //判断是否修改 if (that.data.isDataChange) { //修改 wx.navigateTo({ url: \u0026#34;/pages/plan/applyDetail/applyDetail?appleDetailDic=\u0026#34; + JSON.stringify(appleDetailDic), }); } else { //再来一单的申请详情页 wx.navigateTo({ url: \u0026#34;/pages/plan/planAgain-detail/planAgain-detail?appleDetailDic=\u0026#34; + JSON.stringify(appleDetailDic), }); } } else if (res.cancel) { } }, }); } else if (res.code == \u0026#34;0003\u0026#34;) { wx.showModal({ title: \u0026#34;提示\u0026#34;, content: res.msg, success(res) { if (res.confirm) { var paraDic = { shipName: that.data.shipName, customer: that.data.customerName, customerType: that.data.customerID, }; //创建新船 app.getResult( \u0026#34;/oilPlan/addShip.pl\u0026#34;, paraDic, \u0026#34;POST\u0026#34;, function (res) { if (res.code == \u0026#34;0000\u0026#34;) { if (appleDetailDic.customerType != 3) { var paraDic = { regPhone: appleDetailDic.regPhone, shipName: appleDetailDic.shipName, customer: appleDetailDic.customer, planType: appleDetailDic.planType, customerType: appleDetailDic.customerType, arriveTime: appleDetailDic.arriveTime, takeSite: appleDetailDic.takeSite, }; app.getResult( \u0026#34;/oilPlan/checkInfo.pl\u0026#34;, paraDic, \u0026#34;POST\u0026#34;, function (res) { if (res.code == \u0026#34;0000\u0026#34;) { //判断是否修改 if (that.data.isDataChange) { //修改 wx.navigateTo({ url: \u0026#34;/pages/plan/applyDetail/applyDetail?appleDetailDic=\u0026#34; + JSON.stringify(appleDetailDic), }); } else { //再来一单的申请详情页 wx.navigateTo({ url: \u0026#34;/pages/plan/planAgain-detail/planAgain-detail?appleDetailDic=\u0026#34; + JSON.stringify(appleDetailDic), }); } } else if (res.code == \u0026#34;0010\u0026#34;) { //点击弹框 wx.showModal({ title: \u0026#34;提示\u0026#34;, content: res.msg, confirmText: \u0026#34;确定\u0026#34;, cancelText: \u0026#34;取消\u0026#34;, success(res) { if (res.confirm) { //判断是否修改 debugger; if (that.data.isDataChange) { //修改 wx.navigateTo({ url: \u0026#34;/pages/plan/applyDetail/applyDetail?appleDetailDic=\u0026#34; + JSON.stringify( appleDetailDic ), }); } else { //再来一单的申请详情页 wx.navigateTo({ url: \u0026#34;/pages/plan/planAgain-detail/planAgain-detail?appleDetailDic=\u0026#34; + JSON.stringify( appleDetailDic ), }); } } else if (res.cancel) { } }, }); } else { wx.showToast({ title: res.msg, icon: \u0026#34;none\u0026#34;, }); } }, \u0026#34;ture\u0026#34; ); } else { if (that.data.isDataChange) { //修改 wx.navigateTo({ url: \u0026#34;/pages/plan/applyDetail/applyDetail?appleDetailDic=\u0026#34; + JSON.stringify(appleDetailDic), }); } else { //再来一单的申请详情页 wx.navigateTo({ url: \u0026#34;/pages/plan/planAgain-detail/planAgain-detail?appleDetailDic=\u0026#34; + JSON.stringify(appleDetailDic), }); } } } else { wx.showToast({ title: res.msg, icon: \u0026#34;none\u0026#34;, }); } }, \u0026#34;ture\u0026#34; ); } else if (res.cancel) { } }, }); } else { wx.showToast({ title: res.msg, icon: \u0026#34;none\u0026#34;, }); } }, \u0026#34;ture\u0026#34; ); } else if (res.cancel) { } }, }); } else if (res.code == \u0026#34;0010\u0026#34;) { //点击弹框 wx.showModal({ title: \u0026#34;提示\u0026#34;, content: res.msg, confirmText: \u0026#34;确定\u0026#34;, cancelText: \u0026#34;取消\u0026#34;, success(res) { if (res.confirm) { //判断是否修改 debugger; if (that.data.isDataChange) { //修改 wx.navigateTo({ url: \u0026#34;/pages/plan/applyDetail/applyDetail?appleDetailDic=\u0026#34; + JSON.stringify(appleDetailDic), }); } else { //再来一单的申请详情页 wx.navigateTo({ url: \u0026#34;/pages/plan/planAgain-detail/planAgain-detail?appleDetailDic=\u0026#34; + JSON.stringify(appleDetailDic), }); } } else if (res.cancel) { } }, }); } else { wx.showToast({ title: res.msg, icon: \u0026#34;none\u0026#34;, }); } }, \u0026#34;ture\u0026#34; ); }, }); 重构后的代码 // util.js // 封装 wx.request() 返回 Promise const wxRequest = (url, params, md, load, ctype) =\u0026gt; { if (load) { wx.showLoading({ mask: true, title: \u0026#34;拼命加载中\u0026#34;, }); } let taskController; let task = new Promise((resolve, reject) =\u0026gt; { taskController = wx.request({ url: ycConfig[ycConfig.env].domain_url + url, data: params, dataType: \u0026#34;json\u0026#34;, method: md ? md : \u0026#34;POST\u0026#34;, header: { \u0026#34;content-type\u0026#34;: ctype || \u0026#34;application/json\u0026#34;, }, success: (res) =\u0026gt; { wx.hideLoading(); resolve(res); }, fail: (err) =\u0026gt; { reject(() =\u0026gt; { wx.hideLoading(); wx.showToast({ title: \u0026#34;网络连接异常\u0026#34;, icon: \u0026#34;none\u0026#34;, }); }); }, }); }); return { task, taskController }; }; module.exports = { wxRequest, }; // app.js let { wxRequest } = require(\u0026#34;utils/util.js\u0026#34;); App({ getRequest(url, params, md, load, ctype) { var that = this; if (url != \u0026#34;/ffpLogin.pl\u0026#34;) { var userInfo = wx.getStorageSync(\u0026#34;globalUserInfo\u0026#34;); //判断是否有效 if ( isEmpty(userInfo.wechatLoginToken) || isEmpty(userInfo.wechatOpenId) ) { wx.clearStorage(); //清理登录缓存数据 wx.showToast({ title: \u0026#34;登录失效,请重新登录!\u0026#34;, icon: \u0026#34;none\u0026#34;, }); wx.reLaunch({ url: \u0026#34;/pages/#/#\u0026#34;, }); return; } params.wechatLoginToken = userInfo.wechatLoginToken; params.wechatOpenId = userInfo.wechatOpenId; } var dic = {}; dic.cipher = that.encryption(params); return new Promise((resolve, reject) =\u0026gt; { wxRequest(url, dic, md, load, ctype).task.then((res) =\u0026gt; { if (res.code == \u0026#34;8888\u0026#34;) { wx.clearStorage(); //清理登录缓存数据 wx.showToast({ title: \u0026#34;登录失效,请重新登录!\u0026#34;, icon: \u0026#34;none\u0026#34;, }); wx.reLaunch({ url: \u0026#34;/pages/#/#\u0026#34;, }); return; } else { resolve(res); } }); }); }, }); // 业务代码 Page({ checkInfo: (paraDic) =\u0026gt; { return app.getRequest(\u0026#34;/oilPlan/checkInfo.pl\u0026#34;, paraDic, \u0026#34;POST\u0026#34;, \u0026#34;ture\u0026#34;); }, addShip: (paraDic) =\u0026gt; { return app.getRequest(\u0026#34;/oilPlan/addShip.pl\u0026#34;, paraDic, \u0026#34;POST\u0026#34;, \u0026#34;ture\u0026#34;); }, innerRouter: function (isDataChange, appleDetailDic) { if (isDataChange) { //修改 wx.navigateTo({ url: \u0026#34;/pages/plan/applyDetail/applyDetail?appleDetailDic=\u0026#34; + JSON.stringify(appleDetailDic), }); } else { //再来一单的申请详情页 wx.navigateTo({ url: \u0026#34;/pages/plan/planAgain-detail/planAgain-detail?appleDetailDic=\u0026#34; + JSON.stringify(appleDetailDic), }); } }, //提交订单 formSubmit: function (formSubmitRes) { var paraDic = { regPhone: this.data.regPhone, shipName: this.data.shipName, customer: this.data.customerName, planType: this.data.planCateID, customerType: this.data.customerID, arriveTime: this.data.isAllMention ? this.data.arrivalTime : \u0026#34;\u0026#34;, takeSite: this.data.isTakeSite ? this.data.takeSiteID : \u0026#34;\u0026#34;, }; var that = this; var appleDetailDic = {}; if (that.data.isTakeSite) { appleDetailDic = { regPhone: that.data.regPhone, planType: that.data.planCateID, customerType: that.data.customerID, siteId: that.data.saleSiteID, shipName: that.data.shipName, customer: formSubmitRes.detail.value.customerName, takeSite: that.data.takeSiteID, planCode: that.data.planCode, pagesType: \u0026#34;0\u0026#34;, //type 0 新增,1 修改 isAgain: \u0026#34;1\u0026#34;, //修改识别 shipLength: formSubmitRes.detail.value.shipLength, //船舶 shipType: that.data.shipTypeSelect, //船舶类型 shipLoad: that.data.shipLoadSelect, //载重 isAllMention: that.data.isAllMention, nineyards: formSubmitRes.detail.value.nineyards, //九位码 arrivalTime: that.data.arrivalTime, //预计到站时间 otherPhone: formSubmitRes.detail.value.otherPhone, }; } else { appleDetailDic = { regPhone: that.data.regPhone, planType: that.data.planCateID, customerType: that.data.customerID, siteId: that.data.saleSiteID, shipName: that.data.shipName, customer: formSubmitRes.detail.value.customerName, takeSite: \u0026#34;\u0026#34;, planCode: that.data.planCode, pagesType: \u0026#34;0\u0026#34;, //type 0 新增,1 修改 isAgain: \u0026#34;1\u0026#34;, //修改识别 shipLength: formSubmitRes.detail.value.shipLength, //船舶 shipType: that.data.shipTypeSelect, //船舶类型 shipLoad: that.data.shipLoadSelect, //载重 isAllMention: that.data.isAllMention, nineyards: formSubmitRes.detail.value.nineyards, //九位码 arrivalTime: that.data.arrivalTime, //预计到站时间 otherPhone: formSubmitRes.detail.value.otherPhone, }; } const statusCode = new Map([ [ \u0026#34;0000\u0026#34;, (msg, options) =\u0026gt; { // 跳转新页面 that.innerRouter(options.isDataChange, options.appleDetailDic); }, ], [ \u0026#34;0001\u0026#34;, (msg, options) =\u0026gt; { wx.showToast({ title: msg, icon: \u0026#34;none\u0026#34;, }); }, ], [ \u0026#34;0003\u0026#34;, async (msg, options) =\u0026gt; { // wx.showModal(Object object) 支持 Promise 风格调用 const modalResult = await wx.showModal({ title: \u0026#34;提示\u0026#34;, content: msg, }); if (modalResult.confirm) { await that.addShip(options.paraDic); showMessage(await that.checkInfo(options.paraDic), options); } }, ], [ \u0026#34;0008\u0026#34;, async (msg, options) =\u0026gt; { const modalResult = await wx.showModal({ title: \u0026#34;提示\u0026#34;, content: msg, }); if (modalResult.confirm) { options.paraDic.ignore = 1; showMessage(await that.checkInfo(options.paraDic), options); } }, ], [ \u0026#34;0010\u0026#34;, async (msg, options) =\u0026gt; { const modalResult = await wx.showModal({ title: \u0026#34;提示\u0026#34;, content: msg, }); if (modalResult.confirm) { // 跳转新页面 that.innerRouter(options.isDataChange, options.appleDetailDic); } }, ], ]); // 除仅提示外的后端 code const showToastArr = [\u0026#34;0000\u0026#34;, \u0026#34;0008\u0026#34;, \u0026#34;0003\u0026#34;, \u0026#34;0010\u0026#34;]; // 根据请求后台得到的 code 处理不同逻辑 const showMessage = (res, options) =\u0026gt; { const code = res.data.code; [...statusCode].map(([key, value]) =\u0026gt; { key === (!showToastArr.includes(code) ? \u0026#34;0001\u0026#34; : code) ? value.call(this, res.data.msg, options) : \u0026#34;\u0026#34;; }); }; (async () =\u0026gt; { const options = {}; options.appleDetailDic = appleDetailDic; options.isDataChange = that.data.isDataChange; options.paraDic = paraDic; showMessage(await that.checkInfo(paraDic), options); })(); }, }); ","permalink":"https://sheerwill.xyz/posts/main/20220616092944-callback_hell/","summary":"重构小程序前端校验接口返参处理 原本代码是通过嵌套回调,控制提示之间的前后依赖关系,形成了回调地狱,需要重构。 主要是用 Map (ES6+) 处理 if (code){} else if (cod","title":"Callback Hell"},{"content":"Quorum 机制,是一种分布式系统中常用的,用来保证数据冗余和最终一致性的投票算法,其主要数学思想来源于鸽巢原理。\n基于 Quorum 投票的冗余控制算法 在有冗余数据的分布式存储系统当中,冗余数据对象会在不同的机器之间存放多份拷贝。但是同一时刻一个数据对象的多份拷贝只能用于读或者用于写。\n该算法可以保证同一份数据对象的多份拷贝不会被超过两个访问对象读写。\n分布式系统中的每一份数据拷贝对象都被赋予一票。每一个读操作获得的票数必须大于最小读票数(read quorum)(\\(V_{r}\\)),每个写操作获得的票数必须大于最小写票数(write quorum)(\\(V_{w}\\))才能读或者写。如果系统有V票(意味着一个数据对象有 \\(V\\) 份冗余拷贝),那么最小读写票数(quorum)应满足如下限制:\n\\(V_{r} + V_{w} \u0026gt; V\\) \\(V_{w} \u0026gt; \\frac{V}{2}\\) 第一条规则保证了一个数据不会被同时读写。当一个写操作请求过来的时候,它必须要获得 \\(V_{w}\\) 个冗余拷贝的许可。而剩下的数量是 \\(V - V_{w}\\) 不够 \\(V_{r}\\),因此不能再有读请求过来了。同理,当读请求已经获得了 \\(V_{r}\\) 个冗余拷贝的许可时,写请求就无法获得许可了。\n第二条规则保证了数据的串行化修改。一份数据的冗余拷贝不可能同时被两个写请求修改。\n算法的好处 在分布式系统中,冗余数据是保证可靠性的手段,因此冗余数据的一致性维护就非常重要。一般而言,一个写操作必须要对所有的冗余数据都更新完成了,才能称为成功结束。比如一份数据在5台设备上有冗余,因为不知道读数据会落在哪一台设备上,那么一次写操作,必须5台设备都更新完成,写操作才能返回。\n对于写操作比较频繁的系统,这个操作的瓶颈非常大。Quorum 算法可以让写操作只要写完3台就返回。剩下的由系统内部缓慢同步完成。而读操作,则需要也至少读3台,才能保证至少可以读到一个最新的数据。\nQuorum 的读写最小票数可以用来做为系统在读、写性能方面的一个可调节参数。写票数 \\(V_{w}\\) 越大,则读票数 \\(V_{r}\\) 越小,这时候系统读的开销就小。反之则写的开销就小。\n","permalink":"https://sheerwill.xyz/posts/main/20220615143721-quorum/","summary":"Quorum 机制,是一种分布式系统中常用的,用来保证数据冗余和最终一致性的投票算法,其主要数学思想来源于鸽巢原理。 基于 Quorum 投票的冗余控制算法 在有冗余数据","title":"Quorum"},{"content":" 在开发过程中,stub 很多时候就是针对不可控制的部分进行模拟,例如 Mock。 在分布式中,stub 就是用于转换远程过程调用(RPC)期间客户端和服务端之间传递参数的一段代码。 ","permalink":"https://sheerwill.xyz/posts/main/20220604180341-stub/","summary":"在开发过程中,stub 很多时候就是针对不可控制的部分进行模拟,例如 Mock。 在分布式中,stub 就是用于转换远程过程调用(RPC)期间客户端","title":"Stub"},{"content":"Remote Procedure Call 是一种=软件通信协议=,程序可以在不了解网络细节的前提下,向位于网络上的另外一台计算机中的本地程序请求服务。PRC 被用来像本地系统一样调用远程系统上的其他进程。\nRPC 是通过 Interface Definition Language (IDL) 来描述接口,使得在不同的平台上运行的不同程序编写的程序可以相互通信。(IDL 是不以任何一种特定编程语言的方式指定类型签名或者函数调用的语言。)\n基本上,你可以用 IDL 定义客户端和服务端之间的接口,这样 RPC 机制就可以创建跨网络调用功能所需要的代码存根(Code Stub)。\n+----------------+ | Client | | +----------+ | +---------------+ | | main | | | Server | | |----------| | | +----------+ | | | stub_cli |----(comms)---\u0026gt;| stub_svr | | | +----------+ | | |----------| | +----------------+ | | function | | | +----------+ | +---------------+ 在这个例子中,main 没有在同一个程序中调用函数,而是调用了一个客户端存根函数(与函数的原型相同),该函数负责将信息打包,并通过通讯通道将其传送给另一个进程。\n这里可以是同一台机器或者不同机器,RPC 的优势之一就是能够随意移动服务器。\n在服务器中,有个「监听者」进程,它将接收这些信息并将其传递给服务器。服务器的存根接收信息,解包并将其传递给真正执行的函数。\n真正的函数运行后返回结果给到服务器存根,服务器存根可以将返回的信息打包,并将其传回给客户端存根。客户端存根再将其解包并传回给 main。\n实际上的 IDL 大概像下面这样:\n[ uuid(f9f6be21-fd32-5577-8f2d-0800132bd567), version(0), endpoint(\u0026#34;ncadg_ip_udp:[1234]\u0026#34;, \u0026#34;dds:[19]\u0026#34;) ] interface function_iface { [idempotent] void function( [in] int handle, [out] int *status ); } 头部是一些用于链接客户端和服务端的网络信息,RPC 发生在「会话层」中。接口部分才是 IDL 编译器建立客户端和服务器端存根的地方,以便客户端和服务端能够进行通讯,使得 RPC 正常工作。\n","permalink":"https://sheerwill.xyz/posts/main/20220603220847-remote_procedure_call_rpc/","summary":"Remote Procedure Call 是一种=软件通信协议=,程序可以在不了解网络细节的前提下,向位于网络上的另外一台计算机中的本地程序请求服务。PRC 被用来像本地系统一样","title":"Remote Procedure Call (RPC)"},{"content":"Network Time Protocol (NTP) is an internet protocol used to synchronize with computer clock time sources in a network. It belongs to and is one of the oldest parts of the TCP/IP suite. The term NTP applies to both the protocol and the client-server programs that run on computers.\nMac 中可以在设置中找到 NTP 服务器的地址,比如下图中的地址就是 time.apple.com 。\nFigure 1: NTP\nEstimating time over a network Figure 2: NTP\nRound-trip network delay: \\(\\delta = (t_{4} - t_{1}) - (t_{3} - t_{2})\\)\nEstimated server time when client receives response: \\(t_{3} + \\frac{\\delta}{2}\\)(客户端收到响应时的服务器的时间)\nEstimated clock skew: \\(\\theta = t_{3} + \\frac{\\delta}{2} - t_{4} = \\frac{(t_{2} - t_{1} + t_{3} - t_{4})}{2}\\)(计算预估的服务器时间和当前客户端时间的偏差)\nCorrecting clock skew Once the client has estimated the clock skew θ, it needs to apply that correction to its clock.\nif \\(\\theta \u0026lt; 125 ms\\), slew the clock:\nslightly speed it up or slow it down by up to \\(500 ppm\\)1 (brings clocks in sync within ≈ 5 minutes)\nif \\(125 ms \\le \\theta \u0026lt; 1000 s\\), step the clock:\nsuddenly reset client clock to estimated server timestamp\nif \\(\\theta \\ge 1000 s\\), panic and do nothing (leave the problem for a human operator to resolve)\n依靠时钟同步的系统需要监控时钟偏移。从最后一条可以看出,如果 NTP Client 和 NTP Server 偏差过大,则会拒绝同步 NTP 的时间,留给人工来处理,因此我们需要避免出现这种偏差过大的情况。\n当初初学 Java 的时候,需要知晓某个函数或某段代码执行的时间,通常会用 System.currentTimeMillis() 来测量\n原来 System.currentTimeMillis() 用来测量某个函数运行的时间是错误的,会受到 NTP 的影响(机率虽然很小)。应该用 System.nanoTime() 来测量,不会受到 NTP 很大的影响,最多影响「单调时间」步进的频率。\n1 ppm = 1 microsecond/second = 86ms/day = 32s/year (ppm 是 part per million 的简称)\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://sheerwill.xyz/posts/main/20220602202411-network_time_protocol_ntp/","summary":"Network Time Protocol (NTP) is an internet protocol used to synchronize with computer clock time sources in a network. It belongs to and is one of the oldest parts of the TCP/IP suite. The term NTP applies to both the protocol and the client-server programs that run on computers. Mac 中可以在设置中找到 NTP 服务器的地址,比如下","title":"Network Time Protocol (NTP)"},{"content":"拜占庭将军问题(Byzantine Generals Problem),是由莱斯利·兰波特在其同名论文中提出的分布式对等网络通信容错问题。\n在分布式计算中,不同的计算机通过通讯交换信息达成共识而按照同一套协作策略行动。但有时候,系统中的成员计算机可能出错而发送错误的信息,用于传递信息的通讯网络也可能导致信息损坏,使得网络中不同的成员关于全体协作的策略得出不同结论,从而破坏系统一致性。拜占庭将军问题被认为是容错性问题中最难的问题类型之一。\n容错率:Theorem: need \\(3m+1\\) generals in total to tolerate \\(m\\) malicious generals。\n","permalink":"https://sheerwill.xyz/posts/main/20220602202317-the_byzantine_generals_problem/","summary":"拜占庭将军问题(Byzantine Generals Problem),是由莱斯利·兰波特在其同名论文中提出的分布式对等网络通信容错问题。 在分布式计算中,不同","title":"The Byzantine generals problem"},{"content":"Index MVCC Troubleshoot Issues in MySQL ","permalink":"https://sheerwill.xyz/posts/main/20220530185321-database/","summary":"Index MVCC Troubleshoot Issues in MySQL ","title":"Database"},{"content":"配置的整体如下,通过不同的 Hyper key 在键盘上设置了两层功能,软件控制以及窗口管理。\n├── Spoons │ ├── Emojis.spoon │ ├── SpoonInstall.spoon │ └── TextClipboardHistory.spoon ├── conf.moon ├── control.moon ├── headphones.moon ├── init.lua ├── keyboard.moon ├── layout.moon ├── main.moon ├── mouse.moon ├── pwd.moon ├── reload.moon ├── space.moon ├── tips.moon ├── usb.moon ├── util.moon └── wifi.moon Figure 1: HammerSpoon\n其中提示 App 键位的界面如下。\nFigure 2: HammerSpoon-Tips\n","permalink":"https://sheerwill.xyz/posts/main/20220520185239-hammerspoon/","summary":"配置的整体如下,通过不同的 Hyper key 在键盘上设置了两层功能,软件控制以及窗口管理。 ├── Spoons │ ├── Emojis.spoon │ ├── SpoonInstall.spoon │ └── TextClipboardHistory.spoon ├── conf.moon ├── control.moon ├── headphones.moon ├","title":"HammerSpoon"},{"content":"其实提高时间利用率,就是两点,事件所需时间的准确评估,另外一个就是牛逼的执行力。\n","permalink":"https://sheerwill.xyz/posts/main/20220602185216-the_core_of_efficiency_improvement/","summary":"其实提高时间利用率,就是两点,事件所需时间的准确评估,另外一个就是牛逼的执行力。","title":"The core of efficiency improvement"},{"content":"两军问题(英语:Two Generals\u0026rsquo; Problem)是计算机领域中的一个思想实验。两军问题显示,通过不可靠的通信通道交换信息并达成共识是难以实现的。在该问题中,两支军队的将军只能通过派遣信使穿越敌方领土来互相通信,以此约定在同一时间点共同进攻。该问题希望求解如何在两位将军派出的任何信使都可能被俘虏的情况下,就发动攻击的时间点达成一致。\n两军问题是拜占庭将军问题的一个特例,常被编入与计算机网络相关的入门课程中。在传输控制协议(TCP)相关的课程中,该问题可用作解释 TCP 协议无法保证通信双方之间的状态一致性的原因。该问题也适用于其他存在信息丢失的双方通信的情况。作为认识逻辑的一个重要概念,该问题突出了共识(英语:Common_knowledge_(logic))的重要性。一些学者也将此问题称作两军悖论(英语:Two Generals Paradox)或协同进攻问题(英语:Coordinated Attack Problem)。两军问题是第一个被证明无解的计算机通信问题。该证明的重要意义在于,其显示了对于存在通信错误的更广泛的问题(如拜占庭将军问题),同样是无解的。这也为所有分布式一致性协议的实现提供了一个符合现实的预期。\n","permalink":"https://sheerwill.xyz/posts/main/20220602202247-the_two_generals_problem/","summary":"两军问题(英语:Two Generals\u0026rsquo; Problem)是计算机领域中的一个思想实验。两军问题显示,通过不可靠的通信通道交换信息并达成共识是难以实现的。在该","title":"The two generals problem"},{"content":"Hardware HHKB Logitech MX Master 3 for Mac MacBook Pro (15-inch, 2017) DELL U2417H Bose QC 35 NFAUDIO 二单元娄氏动铁 NF2u Sony A7M3 iPhone 11 Pro Kindle Paper White Software Git Git Skills Design Tools Figma Excalidraw Pinboard Workflow HammerSpoon Hugo Karabiner-Elements Vanilla Emacs with Purcell Beancount Latex Anki Editor VSCode Terminal Squirrel 输入法词库扩充 - 肥猫百万维基词库\n下载 Releases 中的 zhwiki.dict.yaml 到 /Library/Rime/ 下,我使用的是「朙月拼音・简化字」版本,因此可以在 luna_pinyin_simp.custom.yaml 中找到下面配置。\n# 載入朙月拼音擴充詞庫 \u0026#34;translator/dictionary\u0026#34;: luna_pinyin.extended 扩展的词库都配置在 luna_pinyin.extended.dict.yaml 中,添加需要扩展的词库即可。\nimport_tables: - luna_pinyin - luna_pinyin.sgmain - luna_pinyin.hanyu - luna_pinyin.poetry - luna_pinyin.cn_en - zhwiki Chrome Extensions 在線詞典助手 和 Anki 配合记忆单词十分好用\nDark Reader 可以让浏览器的界面变成超级舒服的棕黄色,很适合阅读。\n為什麼你們就是不能加個空格呢? 强迫症福音,可以给网页中英文之间自动加上空格。\nThe Great Suspender 将暂时不用的 Chrome Tabs 暂停,降低内存的使用。\nRSSHub Radar 识别当前网站的 RSS\nLanguage Reactor 提供 NetFlix、Youtube、TurtleTube、Video File、Text 的翻译,以及 Flashcards。\nModern for Hacker News 为 Hacker News 提供现代化的显示\nCalibre E-book viewer 字体设置,Preferences \u0026gt; Styles 中添加下面的 CSS。\nbody \u0026gt; *, span { font-family: \u0026#34;LXGW WenKai Screen\u0026#34; !important; } Photos 因为现在 iCloud 导出的照片会将 Live photos 转换成 .MOV 格式的视频,所以先要删除这部份视频。\nrm -f *[IMG]*.MOV 然后执行脚本分发照片。\nimport os import io import shutil import pyheif import exifread import PIL.Image import PIL.ExifTags from os import listdir from datetime import datetime from os.path import isfile, join def mkexifdirs (filedicts, timeStr): dirlocal = dir if len(timeStr) == 0: timeStr = datetime.fromtimestamp(os.stat(filename).st_birthtime).strftime(\u0026#39;%Y:%m:%d %H:%M:%S\u0026#39;) dirlocal = dir + \u0026#39;no-exif/\u0026#39; timeStr = datetime.strptime(timeStr, \u0026#34;%Y:%m:%d %H:%M:%S\u0026#34;).strftime(\u0026#39;%Y-%m\u0026#39;) filedicts[\u0026#39;timeStr\u0026#39;] = timeStr filedicts[\u0026#39;directory\u0026#39;] = dirlocal + timeStr.split(\u0026#39;-\u0026#39;)[0] filedicts[\u0026#39;directorySec\u0026#39;] = dirlocal + timeStr.split(\u0026#39;-\u0026#39;)[0] + \u0026#39;/\u0026#39; + timeStr def movefile (filedicts, joined): directory = filedicts[\u0026#39;directory\u0026#39;] directorySec = filedicts[\u0026#39;directorySec\u0026#39;] if not os.path.exists(directory): os.makedirs(directory) if not os.path.exists(directorySec): os.makedirs(directorySec) else: if not os.path.exists(directorySec): os.makedirs(directorySec) # 目标路径存在 # 目标路径下无相同文件存在 if directorySec and not os.path.exists(os.path.join(directorySec, filename)): shutil.move(joined, directorySec) else: os.remove(joined) # 目标文件夹 dir = \u0026#39;/Users/luciuschen/Dropbox/Media/Photos/Girl/\u0026#39; # 存在 exif 信息并且当中含有 DateTime,则在当前目录下按照 DateTime 分类 # 不存在 exif 或者 exif 中不含有 DateTime,则在 no-exif 文件夹下进行分类 files = [f for f in listdir(dir) if isfile(join(dir, f))] filedicts = {\u0026#39;timeStr\u0026#39;: \u0026#39;\u0026#39;, \u0026#39;directory\u0026#39;: \u0026#39;\u0026#39;, \u0026#39;directorySec\u0026#39;: \u0026#39;\u0026#39;} for filename in files: if filename.startswith(\u0026#39;.\u0026#39;): continue joined = os.path.join(dir, filename) if filename.lower().endswith((\u0026#39;png\u0026#39;, \u0026#39;jpg\u0026#39;, \u0026#39;jpeg\u0026#39;)): img = PIL.Image.open(joined) if img._getexif(): exif = { PIL.ExifTags.TAGS[k]: v for k, v in img._getexif().items() if k in PIL.ExifTags.TAGS } if \u0026#39;DateTime\u0026#39; in exif: mkexifdirs (filedicts, exif[\u0026#39;DateTime\u0026#39;][0:19]) else: mkexifdirs(filedicts, \u0026#39;\u0026#39;) else: mkexifdirs(filedicts, \u0026#39;\u0026#39;) movefile(filedicts, joined) elif filename.lower().endswith(\u0026#34;.heic\u0026#34;): heif_file = pyheif.read(filename) tags = {} for metadata in heif_file.metadata: # data 数据中前六个字符 b\u0026#39;Exif 是我们不需要的 file_stream = io.BytesIO(metadata[\u0026#39;data\u0026#39;][6:]) tags = exifread.process_file(file_stream, details = False) if tags: break if tags: if tags.get(\u0026#34;EXIF DateTimeOriginal\u0026#34;): mkexifdirs (filedicts, str(tags.get(\u0026#34;EXIF DateTimeOriginal\u0026#34;))) else: mkexifdirs(filedicts, \u0026#39;\u0026#39;) else: mkexifdirs(filedicts, \u0026#39;\u0026#39;) movefile(filedicts, joined) 优化版本,这里将处理没有 exif 信息和有 exif 信息的方法合并,不同的部分作为参数。(后续需要学一下 macro 和 generics 的区别和使用场景)\nimport os import io import shutil import pyheif import exifread import PIL.Image import PIL.ExifTags from os import listdir from datetime import datetime from os.path import isfile, join def mkexifdirs (filedicts, timeStr, dirlocal): timeStr = datetime.strptime(timeStr, \u0026#34;%Y:%m:%d %H:%M:%S\u0026#34;).strftime(\u0026#39;%Y-%m\u0026#39;) filedicts[\u0026#39;timeStr\u0026#39;] = timeStr filedicts[\u0026#39;directory\u0026#39;] = dirlocal + timeStr.split(\u0026#39;-\u0026#39;)[0] filedicts[\u0026#39;directorySec\u0026#39;] = dirlocal + timeStr.split(\u0026#39;-\u0026#39;)[0] + \u0026#39;/\u0026#39; + timeStr def movefile (filedicts, joined): directory = filedicts[\u0026#39;directory\u0026#39;] directorySec = filedicts[\u0026#39;directorySec\u0026#39;] if not os.path.exists(directory): os.makedirs(directory) if not os.path.exists(directorySec): os.makedirs(directorySec) # 目标路径存在 # 目标路径下无相同文件存在 if directorySec and not os.path.exists(os.path.join(directorySec, filename)): shutil.move(joined, directorySec) else: os.remove(joined) dir = \u0026#39;/Users/luciuschen/\u0026#39; # 存在 exif 信息并且当中含有 DateTime,则在当前目录下按照 DateTime 分类 # 不存在 exif 或者 exif 中不含有 DateTime,则在 no-exif 文件夹下进行分类 files = [f for f in listdir(dir) if isfile(join(dir, f))] filedicts = {\u0026#39;timeStr\u0026#39;: \u0026#39;\u0026#39;, \u0026#39;directory\u0026#39;: \u0026#39;\u0026#39;, \u0026#39;directorySec\u0026#39;: \u0026#39;\u0026#39;} for filename in files: if filename.startswith(\u0026#39;.\u0026#39;): continue joined = os.path.join(dir, filename) filebirthtime = datetime.fromtimestamp(os.stat(filename).st_birthtime).strftime(\u0026#39;%Y:%m:%d %H:%M:%S\u0026#39;) if filename.lower().endswith((\u0026#39;png\u0026#39;, \u0026#39;jpg\u0026#39;, \u0026#39;jpeg\u0026#39;)): img = PIL.Image.open(joined) if img._getexif(): exif = { PIL.ExifTags.TAGS[k]: v for k, v in img._getexif().items() if k in PIL.ExifTags.TAGS } if \u0026#39;DateTime\u0026#39; in exif: mkexifdirs (filedicts, exif[\u0026#39;DateTime\u0026#39;][0:19], dir) else: mkexifdirs(filedicts, filebirthtime, dir + \u0026#39;no-exif/\u0026#39;) else: mkexifdirs(filedicts, filebirthtime, dir + \u0026#39;no-exif/\u0026#39;) movefile(filedicts, joined) elif filename.lower().endswith(\u0026#34;.heic\u0026#34;): heif_file = pyheif.read(filename) tags = {} for metadata in heif_file.metadata: # data 数据中前六个字符 b\u0026#39;Exif 是我们不需要的 file_stream = io.BytesIO(metadata[\u0026#39;data\u0026#39;][6:]) tags = exifread.process_file(file_stream, details = False) if tags and tags.get(\u0026#34;EXIF DateTimeOriginal\u0026#34;): mkexifdirs (filedicts, str(tags.get(\u0026#34;EXIF DateTimeOriginal\u0026#34;)), dir) else: mkexifdirs(filedicts, filebirthtime, dir + \u0026#39;no-exif/\u0026#39;) movefile(filedicts, joined) break ","permalink":"https://sheerwill.xyz/posts/main/20220508195206-toolbox/","summary":"Hardware HHKB Logitech MX Master 3 for Mac MacBook Pro (15-inch, 2017) DELL U2417H Bose QC 35 NFAUDIO 二单元娄氏动铁 NF2u Sony A7M3 iPhone 11 Pro Kindle Paper White Software Git Git Skills Design Tools Figma Excalidraw Pinboard Workflow HammerSpoon Hugo Karabiner-Elements Vanilla Emacs with Purcell Beancount Latex Anki Editor VSCode Terminal Squirrel 输入法词库扩充 - 肥猫百万","title":"Toolbox"},{"content":"Mysql 无法更新表 查询是否有锁表。 SHOW OPEN TABLES WHERE In_use \u0026gt; 0; 查询是否有正在执行的事务 SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX; Sort Chinese text fields alphabetically by first letter UTF8 default Proofing set is Utf8_general_ci, it is not in Chinese. You need to force MySQL to sort by Chinese.\nselect * FROM MyTable ORDER by CONVERT (Chinesecolumnname USING GBK) COLLATE gbk_chinese_ci; 为了达到更快更效率的查询,需要另外再建立一个索引列,并在索引列中插入标签字第一个字母或者是拼音。\n","permalink":"https://sheerwill.xyz/posts/main/20220530180443-troubleshoot_issues_in_mysql/","summary":"Mysql 无法更新表 查询是否有锁表。 SHOW OPEN TABLES WHERE In_use \u0026gt; 0; 查询是否有正在执行的事务 SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX; Sort Chinese text fields alphabetically by first letter UTF8 default Proofing set is Utf8_general_ci, it is not in Chinese. You need to force MySQL to sort by Chinese. select * FROM MyTable ORDER","title":"Troubleshoot Issues in MySQL"},{"content":"Sankey diagram 使用软件:Figma Plugin: Sankey Connect 展现数据流动的利器 案例:工作流描述 初始的思路是按照流程图来画,用 Excalidraw 插件画起来很快,但不够明晰。工作流也是处理数据流动的过程,适合用 Sankey diagram 来展示。\n对比\nFigure 1: take-notes-draft\nFigure 2: take-notes-draft_1\nFigure 3: take-notes-sankey\n技巧\nConnect 的渐变色是将颜色从 Solid 改为 Linear,然后把两端的色块拖动到 Node 处并改为对应 Node 的颜色。 Connect 要尽量弧度舒缓,有美感;留白要均匀。 Heat Map 使用软件:Figma\n展现过程中频次高低\n案例:描述 GTD 和 Note-Taking 过程中思考频次高低。\nFigure 4: gtd_note_taking_dark\nFigure 5: gtd_note_taking_light\n","permalink":"https://sheerwill.xyz/posts/main/20220503145636-build_diagram/","summary":"Sankey diagram 使用软件:Figma Plugin: Sankey Connect 展现数据流动的利器 案例:工作流描述 初始的思路是按照流程图来画,用 Excalidraw 插件画起来很快,但不够明晰。工作流也是处理数","title":"Build Diagram"},{"content":"这里用 setTimeout() 模仿异步请求。\nconst callbackFn = (firstName, callback) =\u0026gt; { setTimeout(() =\u0026gt; { if (!firstName) return callback(new Error(\u0026#34;no first name passed in!\u0026#34;)); // failed const fullName = `${firstName} Doe`; return callback(fullName); // succeed }, 2000); }; callbackFn(\u0026#34;John\u0026#34;, console.log); callbackFn(null, console.log); Promise 实现如下\nconst promiseFn = (firstName) =\u0026gt; { return new Promise((resolve, reject) =\u0026gt; { setTimeout(() =\u0026gt; { if (!firstName) reject(new Error(\u0026#34;no first name passed in!\u0026#34;)); // failed const fullName = `${firstName} Doe`; resolve(fullName); // succeed }, 2000); }); }; promiseFn(\u0026#34;Jane\u0026#34;).then(console.log); promiseFn().catch(console.log); 这里是 Promise 一些需要注意的点\nconst setDelay = (millisecond) =\u0026gt; { return new Promise((resolve, reject) =\u0026gt; { if (typeof millisecond != \u0026#34;number\u0026#34;) reject(new Error(\u0026#34;参数必须是number类型\u0026#34;)); setTimeout(() =\u0026gt; { resolve(`我延迟了${millisecond}毫秒后输出的`); }, millisecond); }); }; const setDelaySecond = (seconds) =\u0026gt; { return new Promise((resolve, reject) =\u0026gt; { if (typeof seconds != \u0026#34;number\u0026#34; || seconds \u0026gt; 10) reject(new Error(\u0026#34;参数必须是number类型,并且小于等于10\u0026#34;)); setTimeout(() =\u0026gt; { resolve(`我延迟了${seconds}秒后输出的,是第二个函数`); }, seconds * 1000); }); }; // Promise chains // then 式链式写法的本质其实是一直往下传递返回一个新的 Promise,也就是说 then 在下一步接收的是上一步返回的 Promise。 // 处理错误只需要在链式末尾 catch 进行处理就可以。 setDelay(2000) .then((result) =\u0026gt; { console.log(result); console.log(\u0026#34;我进行到第一步的\u0026#34;); return setDelaySecond(3); }) .then((result) =\u0026gt; { console.log(\u0026#34;我进行到第二步的\u0026#34;); console.log(result); }) .catch((err) =\u0026gt; { console.log(err); }); // Promise 中间返回自定义的值只需要用 `retunPromise.resolve()` 处理就可以。 setDelay(2000) .then((result) =\u0026gt; { console.log(\u0026#34;第一步完成了\u0026#34;); console.log(result); let message = \u0026#34;这是我自己想处理的值\u0026#34;; return Promise.resolve(message); // 这里返回我想在下一阶段处理的值 }) .then((result) =\u0026gt; { console.log(\u0026#34;第二步完成了\u0026#34;); console.log(result); // 这里拿到上一阶段的返回值 //return Promise.resolve(\u0026#39;这里可以继续返回\u0026#39;) }) .catch((err) =\u0026gt; { console.log(err); }); ","permalink":"https://sheerwill.xyz/posts/main/20220506193545-converting_callbacks_to_promises/","summary":"这里用 setTimeout() 模仿异步请求。 const callbackFn = (firstName, callback) =\u0026gt; { setTimeout(() =\u0026gt; { if (!firstName) return callback(new Error(\u0026#34;no first name passed in!\u0026#34;)); // failed const fullName = `${firstName} Doe`; return callback(fullName); // succeed }, 2000); }; callbackFn(\u0026#34;John\u0026#34;, console.log); callbackFn(null, console.log); Promise 实现如下 const promiseFn = (firstName) =\u0026gt; { return new Promise((resolve, reject) =\u0026gt; { setTimeout(() =\u0026gt; { if (!firstName)","title":"Converting callbacks to promises"},{"content":"Introduction Emacs outshines all other editing software in approximately the same way that the noonday sun does the stars. It is not just bigger and brighter; it simply makes everything else vanish.\n– Neal Stephenson, In the Beginning was the Command Line (1998)\nInstall Emacs With Homebrew First, Doom’s dependencies:\nbrew install git ripgrep brew install coreutils fd xcode-selected --install emacs-mac. It offers good integration with macOS, native emojis and better childframe support.\nbrew tap d12frosted/emacs-plus brew install emacs-plus --with-native-comp --with-modern-vscode-icon ln -s /usr/local/opt/emacs-plus@29/Emacs.app /Applications Doom Emacs With Emacs and Doom\u0026rsquo;s dependencies installed, next is to install Doom Emacs itself:\ngit clone https://github.com/hlissner/doom-emacs ~/.emacs.d ~/.emacs.d/bin/doom install 如果提示 .emacs.d 已存在,删除重新执行上述命令即可。\n.zshrc 中需要添加 export PATH=\u0026quot;$HOME/.emacs.d/bin:$PATH ,这样就可以直接使用 doom 命令了。\nDoom Doctor 问题 Warning: unable to detect fonts because fontconfig isn\u0026rsquo;t installed brew install fontconfig ! Couldn\u0026rsquo;t find shellcheck. Shell script linting will not work brew install shellcheck Couldn\u0026rsquo;t find the dot executable (from graphviz). org-roam will not be able to generate graph visualizations. brew install graphviz This installed grep binary was not built with support for PCRE lookaheads. Some advanced consult filtering features will not work as a result, see the module readme. # 因为 Mac 本身安装的是 BSD 的。 grep --version # 可以看到 grep (BSD grep, GNU compatible) 2.6.0-FreeBSD ,即 BSD 版本。 brew install grep # 安装 GNU 的 grep,并在 .zshrc 中配置 PATH=\u0026#34;/usr/local/opt/grep/libexec/gnubin:$PATH\u0026#34; 即可。再次查看版本可以看到如下。 # grep (GNU grep) 3.7 # Packaged by Homebrew # Copyright (C) 2021 Free Software Foundation, Inc. # License GPLv3+: GNU GPL version 3 or later \u0026lt;https://gnu.org/licenses/gpl.html\u0026gt;. # This is free software: you are free to change and redistribute it. # There is NO WARRANTY, to the extent permitted by law. Org-roam 具体的配置文件在 .doom.d/ 下,参见 .doom.d。\nShortcuts Shortcuts 功能 备注 C-RET 在当前 headline 所属的内容后建立一个同级 headline 无 headline 时创建一个一级 headline M-RET 在当前 headline 后建立一个同级 headline 同上 M-right 降低当前 headline 的层级 M-left 提高当前 headline 的层级 M-up 将当前 headline 及其内容作为整体向上移动 M-down 将当前 headline 及其内容作为整体向下移动 C-return 在当前列表项的内容后建立一个同级列表项 光标在列表项同一行时有效 M-RET 在当前列表项后建立一个同级列表项 同上 M-right 降低当前列表项的层级 同上 M-left 提高当前列表项的层级 同上 M-up 将当前列表项及其内容作为整体向上移动 同上 M-down 将当前列表项及其内容作为整体向下移动 同上 C-x-o 切换窗口 C-x 中有很多操作,可以看提示。 C-c C-c 在创建 node 编写完毕后快速保存 C-g 取消操作 C-x-d 进入 dired 模式 SPC f r 查找最近文件 C-c C-x C-v 显示图片 Shortcuts 功能 备注 C-c C-s 设置任务开始时间 C-c C-d 设置任务截止时间 C-c C-t 改变任务状态 S-Up/Down 设置任务优先级 [#A], [#B], [#C] C-c C-x C-i 开始任务计时 C-c C-x C-o 终止任务计时 C-c [ 将当前文件加入 Org-Agenda C-c ] 将当前文件从 Org-Agenda 移除 Shortcuts 功能 备注 t 在 Org-Agenda 的任务条目上, 修改任务状态。 C-c C-x C-a 归档 C-c C-w activate refile I 在 Org-Agenda 的任务条目上, 开始计时。 O 在 Org-Agenda 的任务条目上, 终止计时。 z 在任务上添加 note R clock mode, 显示耗时。 C-c C-x C-l 预览 Latex C-c \\ 按 tag 搜索 好像只能单文件内 C-C C-o 打开链接 Org grammar basic This is almost anything you need to know about Org mode syntax:\n* This Is A Heading ** This Is A Sub-Heading *** And A Sub-Sub-Heading Paragraphs are separated by at least one empty line. *bold* /italic/ _underlined_ +strikethrough+ =monospaced= [[http://Karl-Voit.at][Link description]] http://Karl-Voit.at → link without description - list item - another item - sub-item 1. also enumerated 2. if you like - [ ] yet to be done - [X] item which is done : Simple pre-formatted text such as for source code. : This also respects the line breaks. *bold* is not bold here. code block myresult = 42 * 23 print(\u0026#39;Hello Europe! \u0026#39; + str(myresult)) table | My Column 1 | My Column 2 | Last Column | |-------------+-------------+-------------| | 42 | foo | bar | | 23 | baz | abcdefg | |-------------+-------------+-------------| | 65 | | | Export 对于只需要导出某个 header 下的内容的需求,只需要在 header 上加上 :export: 即可。\nMarkdown file 这里用的是 ox-pandoc, M-x org-pandoc-export-as-gfm 算是我找到最符合 MarkDown 语法的转换了,Emacs 自己的转换会将 Table 转换成 HTML 标签的格式。\nHugo 在文件头部添加\n#+HUGO_BASE_DIR: ~/Dropbox/hugo/ #+HUGO_SECTION: posts/main #+HUGO_WEIGHT: auto #+HUGO_AUTO_SET_LASTMOD: t 需要导出为 \u0026lt;mark\u0026gt;\u0026lt;/mark\u0026gt; 时1\n#+begin_mark marked text #+end_mark 需要导出为块级 \u0026lt;mark\u0026gt;\u0026lt;/mark\u0026gt; 时,也就是前后的空格不进行 trim。\n#+header: :trim-pre nil :trim-post nil #+begin_mark marked text #+end_mark Latex Dependency 需要安装 texlive\nbrew install texlive Latex 语法 Latex 语法\nPDF 导出报错 Unicode character 考 (U+8003) not set up for use with LaTeX. 是因为 Latex 本身不支持中文。 中文 PDF 导出设置 使用 ElegantPaper,需要将 elegantpaper.cls 文件放在 org 目录下。 安装依赖 brew install pygments 导出文件头部增加 #+LATEX_COMPILER: xelatex #+LATEX_CLASS: elegantpaper #+OPTIONS: prop:t Org Special Blocks\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://sheerwill.xyz/posts/main/20220505220832-doom_emacs/","summary":"Introduction Emacs outshines all other editing software in approximately the same way that the noonday sun does the stars. It is not just bigger and brighter; it simply makes everything else vanish. – Neal Stephenson, In the Beginning was the Command Line (1998) Install Emacs With Homebrew First, Doom’s dependencies: brew install git ripgrep brew install coreutils fd xcode-selected --install emacs-mac. It offers good integration","title":"Doom Emacs"},{"content":"Plugin Scale Scale any frame, it’s contents, and all effects to any defined width or height. Sankey Connect Sankey Diagram Connect. ","permalink":"https://sheerwill.xyz/posts/main/20220503154744-figma/","summary":"Plugin Scale Scale any frame, it’s contents, and all effects to any defined width or height. Sankey Connect Sankey Diagram Connect. ","title":"Figma"},{"content":"删除以往的提交记录 git checkout --orphan latest_branch git add -A git commit -am \u0026#34;commit message\u0026#34; git branch -D main git branch -m main git push -f origin main 发现上次提交的内容有误,需要再次修改,但不想重复提交,而是合并到上一次提交中,并且不更新提交 comment。 git commit --amend --no-edit --amend: 修改最新的一次 commit,将这次的 stage change 直接添加到上一次 commit 中。 --no-edit: 不修改上一次 commit 的 comment,直接使用上一次的 comment。如果需要修改的是最新一次 comment 的内容,则不需要该参数,就可以修改 comment。 ","permalink":"https://sheerwill.xyz/posts/main/20220503160055-git_skills/","summary":"删除以往的提交记录 git checkout --orphan latest_branch git add -A git commit -am \u0026#34;commit message\u0026#34; git branch -D main git branch -m main git push -f origin main 发现上次提交的内容有误,需要再次修改,但不想重复提交,而是合并到上一次提","title":"Git Skills"},{"content":"The most common metric for calculating time complexity is Big O notation. This removes all constant factors so that the running time can be estimated in relation to N as N approaches infinity. In general you can think of it like this:\nstatement; Is constant. The running time of the statement will not change in relation to N.\nfor (i = 0; i \u0026lt; N; i++) statement; Is linear. The running time of the loop is directly proportional to N. When N doubles, so does the running time.\nfor (i = 0; i \u0026lt; N; i++) { for (j = 0; j \u0026lt; N; j++) statement; } Is quadratic. The running time of the two loops is proportional to the square of N. When N doubles, the running time increases by N * N.\nwhile (low \u0026lt;= high) { mid = (low + high) / 2; if (target \u0026lt; list[mid]) high = mid - 1; else if (target \u0026gt; list[mid]) low = mid + 1; else break; } Is logarithmic. The running time of the algorithm is proportional to the number of times N can be divided by 2. This is because the algorithm divides the working area in half with each iteration.\nvoid quicksort ( int list[], int left, int right ){ int pivot = partition ( list, left, right ); quicksort ( list, left, pivot - 1 ); quicksort ( list, pivot + 1, right ); } Is N * log ( N ). The running time consists of N loops (iterative or recursive) that are logarithmic, thus the algorithm is a combination of linear and logarithmic.\nIn general, doing something with every item in one dimension is linear, doing something with every item in two dimensions is quadratic, and dividing the working area in half is logarithmic. There are other Big O measures such as cubic, exponential, and square root, but they\u0026rsquo;re not nearly as common. Big O notation is described as O ( ) where is the measure. The quicksort algorithm would be described as O ( N * log ( N ) ).\nNote that none of this has taken into account best, average, and worst case measures. Each would have its own Big O notation. Also note that this is a VERY simplistic explanation. Big O is the most common, but it\u0026rsquo;s also more complex that I\u0026rsquo;ve shown. There are also other notations such as big omega, little o, and big theta. You probably won\u0026rsquo;t encounter them outside of an algorithm analysis course. ;)\n","permalink":"https://sheerwill.xyz/posts/main/20220506193205-how_to_find_time_complexity_of_an_algorithm/","summary":"The most common metric for calculating time complexity is Big O notation. This removes all constant factors so that the running time can be estimated in relation to N as N approaches infinity. In general you can think of it like this:\nstatement; Is constant. The running time of the statement will not change in relation to N.\nfor (i = 0; i \u0026lt; N; i++) statement; Is linear. The running time of the loop is directly proportional to N.","title":"How To Find Time Complexity Of An Algorithm"},{"content":"How To Find Time Complexity Of An Algorithm\nLanguage JavaScript Refactoring ","permalink":"https://sheerwill.xyz/posts/main/20220506193054-refactoring_code/","summary":"How To Find Time Complexity Of An Algorithm\nLanguage JavaScript Refactoring ","title":"Refactoring Code"},{"content":"前端网络请求回调中经常会根据后端返回的不同 code 进行不同的业务处理。\nif (res.data.code === \u0026#34;0000\u0026#34;) { //do something } else if (res.data.code === \u0026#34;0001\u0026#34;) { //do something } else if (res.data.code === \u0026#34;0002\u0026#34;) { //do something } else { //do something } 简单应用\nconst statusCode = new Map([ [\u0026#34;0000\u0026#34;, \u0026#34;请求成功\u0026#34;], [\u0026#34;0001\u0026#34;, \u0026#34;未授权\u0026#34;], [\u0026#34;0002\u0026#34;, \u0026#34;拒绝访问\u0026#34;], ]); statusCode.get(0001); 复杂业务函数处理\nconst getRequest = (params) =\u0026gt; { console.log(\u0026#34;请求打印\u0026#34;); }; const statusCode = new Map([ [ \u0026#34;0000\u0026#34;, async (params) =\u0026gt; { console.log(getRequest(params)); }, ], [ \u0026#34;0001\u0026#34;, (params) =\u0026gt; { console.log(\u0026#34;未授权\u0026#34;); }, ], [ \u0026#34;0002\u0026#34;, (params) =\u0026gt; { console.log(\u0026#34;拒绝访问\u0026#34;); }, ], ]); const params = {}; const showMessage = (code) =\u0026gt; { // Map 对象转数组 [...statusCode].forEach(([key, value]) =\u0026gt; { key === code ? value.call(params) : \u0026#34;\u0026#34;; }); }; showMessage(\u0026#34;0000\u0026#34;); // 未授权 forEach() 和 map() 的区别:\nforEach() 会修改原来的数组,而 map() 会得到一个新的数组并返回。所以需要生成新数组的时候,就用后者, 否则就用前者。 Map vs Object\nAccording to MDN:\nA Map object can iterate its elements in insertion order - a for..of loop will return an array of [key, value] for each iteration.\nObjects are similar to Maps in that both let you set keys to values, retrieve those values, delete keys, and detect whether something is stored at a key. Because of this, Objects have been used as Maps historically; however, there are important differences between Objects and Maps that make using a Map better.\nAn Object has a prototype, so there are default keys in the map. However, this can be bypassed using map = Object.create(null). The keys of an Object are Strings, where they can be any value for a Map. You can get the size of a Map easily while you have to manually keep track of size for an Object.\n","permalink":"https://sheerwill.xyz/posts/main/20220506193520-refactoring_condition_statements/","summary":"前端网络请求回调中经常会根据后端返回的不同 code 进行不同的业务处理。 if (res.data.code === \u0026#34;0000\u0026#34;) { //do something } else if (res.data.code === \u0026#34;0001\u0026#34;) { //do something } else if (res.data.code === \u0026#34;0002\u0026#34;) { //do something } else { //do something } 简单应用 const statusCode =","title":"Refactoring Condition statements"},{"content":"const setDelay = (millisecond) =\u0026gt; { return new Promise((resolve, reject) =\u0026gt; { if (typeof millisecond != \u0026#34;number\u0026#34;) reject(new Error(\u0026#34;参数必须是number类型\u0026#34;)); setTimeout(() =\u0026gt; { resolve(`我延迟了${millisecond}毫秒后输出的`); }, millisecond); }); }; const setDelaySecond = (seconds) =\u0026gt; { return new Promise((resolve, reject) =\u0026gt; { if (typeof seconds != \u0026#34;number\u0026#34; || seconds \u0026gt; 10) reject(new Error(\u0026#34;参数必须是number类型,并且小于等于10\u0026#34;)); setTimeout(() =\u0026gt; { resolve(`我延迟了${seconds}秒后输出的,注意单位是秒`); }, seconds * 1000); }); }; // Promise chains setDelay(1000) .then((result) =\u0026gt; { console.log(result); return setDelaySecond(2); }) .then((result) =\u0026gt; { console.log(result); return setDelay(1000); }) .then((result) =\u0026gt; { console.log(result); console.log(\u0026#34;完成\u0026#34;); }) .catch((err) =\u0026gt; { console.log(err); }); // async/await (async () =\u0026gt; { const result = await setDelay(1000); console.log(result); console.log(await setDelaySecond(2)); console.log(await setDelay(1000)); console.log(\u0026#34;完成了\u0026#34;); })(); await 是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西。\n如果等到的不是个 Promise 对象,那么 await 表达式的运算结果就等于它等到的东西。\n如果等到的是个 Promise 对象,那么 await 就会阻塞后面的代码,等着 Promise 对象的 resolved 状态,然后得到 resolve 的值,作为 await 表达式的运算结果。\n","permalink":"https://sheerwill.xyz/posts/main/20220506193624-refactoring_promise_chains_with_async_await/","summary":"const setDelay = (millisecond) =\u0026gt; { return new Promise((resolve, reject) =\u0026gt; { if (typeof millisecond != \u0026#34;number\u0026#34;) reject(new Error(\u0026#34;参数必须是number类型\u0026#34;)); setTimeout(() =\u0026gt; { resolve(`我延迟了${mi","title":"Refactoring Promise chains with async/await"},{"content":"平时使用这个功能的时候都是逐个点击各个「混合模式(blending modes)」试看效果。下面就解释一下各个「混合模式」的工作原理。\nFigure 1: DanHollick-1583080119068807168-20221020_205807-img1\n简单来说,「混合模式」是基于两种输入颜色,输出新颜色的方法。这里输入颜色被分为了「前景(混合色)」和「背景(基础色)」。\nFigure 2: DanHollick-1583080127629430787-20221020_205809-img1\n其中最简单的就是 Darken 和 Lighten,\nFigure 3: DanHollick-1583080139973283841-20221020_205812-img1\nFigure 4: DanHollick-1583080139973283841-20221020_205812-img2\nDarken 是分别比较前后景的 RGB 通道的值,选择最暗的值组成新的 RGB 颜色。RGB 通道的值范围在 0-255 之间,数字越小,颜色越暗。\n\\begin{gather*} C = min(A, B) \\end{gather*}\nFigure 5: DanHollick-1583080146923180032-20221020_205813-img1\nLighten 和 Darken 原理一样,只不过选择最亮的颜色,也就是数字更大的 RGB 通道值组成新的颜色。\n\\begin{gather*} C = max(A, B) \\end{gather*}\nFigure 6: DanHollick-1583080154439352320-20221020_205815-img1\nMultiply 和 Screen 与 Darken 和 Lighten 产生类似的效果,但是更加优雅。当有了 Multiply 和 Screen 后,就没有理由去使用 Darken 和 Lighten 了。\nFigure 7: DanHollick-1583080166774824962-20221020_205818-img1\nFigure 8: DanHollick-1583080166774824962-20221020_205818-img2\nMultiply 顾名思义,就是将每个通道的数值相乘。\n可以发现这里的数值不是 0-255 而是 0-1,这里是将 0-255 映射到 0-1 后计算。因为数值都是在 0-1 之间,所以乘积总是比原始值更暗。也因此,白色表现出透明(任何数值和 1 相乘都不变),黑色会一直存在(任何数值和 0 相乘都是 0)。\n\\begin{gather*} C=\\frac{A}{255}\\times\\frac{B}{255}\\times255=\\frac{AB}{255} \\end{gather*}\nFigure 9: DanHollick-1583080173980696576-20221020_205820-img1\nScreen 实际上和 Multiply 相似,但是它是先将各通道颜色反相后相乘,相乘的结果再反相后得到更浅的颜色。\n\\begin{gather*} C=(1-(1-\\frac{A}{255})(1-\\frac{B}{255}))\\times255 = 255-\\frac{(255-A)(255-B)}{255} \\end{gather*}\nFigure 10: DanHollick-1583080181740158976-20221020_205822-img1\n最后一对是 Color Burn 和 Color Dodge,可以看到明显的步进,所以结果颜色会更加饱和。\nFigure 11: DanHollick-1583080194952208385-20221020_205825-img1\nFigure 12: DanHollick-1583080194952208385-20221020_205825-img2\nColor Burn 的原理是对背景反相后除以前景,再将结果反相得到最终的颜色。\nFigure 13: DanHollick-1583080203634417664-20221020_205827-img1\nColor Dodge 类似,是将前景反相,除以背景。\n\\begin{gather*} C=\\frac{(1-A)}{B} \\end{gather*}\nFigure 14: DanHollick-1583080211129651200-20221020_205829-img1\nOverlay、Soft Light 和 Hard Light 被称为「对比度混合模式」,可以让亮部更亮,暗部更暗。\nFigure 15: DanHollick-1583080223775416323-20221020_205832-img1\nFigure 16: DanHollick-1583080223775416323-20221020_205832-img2\n如果背景是浅色(\u0026gt;127.5),就以一半的强度参与 Screen 计算,使得最终颜色变浅;\n\\begin{gather*} C=(1-(1-\\frac{A}{255})(1-\\frac{B}{255})\\times2)\\times255 = 255-\\frac{(255-A)(255-B)}{127.5} \\end{gather*}\n如果背景是暗色,也以一半的强度参与 Multiply 计算,使得最终颜色变深。\n\\begin{gather*} C=\\frac{A}{255}\\times(\\frac{B}{255}\\times2)\\times255=\\frac{AB}{127.5} \\end{gather*}\nFigure 17: DanHollick-1583080231170023424-20221020_205833-img1\nHard Light 和 Overlay 一样,只不过判断的依据换成了前景。\nFigure 18: DanHollick-1583080238753337344-20221020_205835-img1\nSoft Light 是标准混合模式中最复杂的一种。它产生的效果和 Overlay 相似,但更加微妙。\nFigure 19: DanHollick-1583080246802194437-20221020_205837-img1\nDifference 和 Exclusion 的结果几乎就是其中一个输入颜色的反相,所以称为反相混合模式,\nFigure 20: DanHollick-1583080260442107906-20221020_205840-img1\nFigure 21: DanHollick-1583080260442107906-20221020_205840-img2\nDifference 顾名思义,用前景减去背景的通道颜色值并取绝对值。\nFigure 22: DanHollick-1583080267622670336-20221020_205842-img1\nExclusion 也产生类似的效果,但方式要复杂的多。\nFigure 23: DanHollick-1583080275096965121-20221020_205844-img1\nHue、Saturation、Color 和 Luminosity 与其他模式不同,它们操作的是 Hue、Saturation 和 Luminosity,而不是 RGB 通道值。\n它们通过结合两个输入的 HSL 值得出一种新的颜色。\nFigure 24: DanHollick-1583080288610983936-20221020_205847-img1\nFigure 25: DanHollick-1583080288610983936-20221020_205847-img2\nHue 从前景提取 Hue 值,从背景中提取 Saturation 和 Luminosity 值,组成新的颜色。\nFigure 26: DanHollick-1583080296018083840-20221020_205849-img1\nSaturation 从前景提取 Saturation 值,从背景中提取 Hue 和 Luminosity 值,组成新的颜色。\nFigure 27: DanHollick-1583080303626620930-20221020_205851-img1\nColor 从前景提取 Hue 和 Saturation 值,从背景中提取 Luminosity 值,组成新的颜色。\nFigure 28: DanHollick-1583080311188910080-20221020_205853-img1\nLuminosity 从前景提取 Luminosity 值,从背景中提取 Hue 和 Saturation 值,组成新的颜色。\nFigure 29: DanHollick-1583080318516355075-20221020_205854-img1\n","permalink":"https://sheerwill.xyz/posts/main/20221025101809-blending_modes_figma/","summary":"平时使用这个功能的时候都是逐个点击各个「混合模式(blending modes)」试看效果。下面就解释一下各个「混合模式」的工作原理。 Figure 1: DanHollick-1583080119068807168-20221020_205807-img1 简","title":"Blending Modes(Figma)"},{"content":"提交完成后关闭当前 Tab,刷新「父级」Tab。 增加如下方法在工具类中。\nfunction reloadTabGrid(title){ if ($(\u0026#34;#frm_center\u0026#34; ).tabs(\u0026#39;exists\u0026#39;, title)) { $( \u0026#39;#frm_center\u0026#39;).tabs(\u0026#39;select\u0026#39; , title); window.top.reload_Abnormal_Monitor.call(); } } 在需要刷新的「父级」Tab 页面中添加。\nwindow.top[\u0026#34;reload_Abnormal_Monitor\u0026#34;]=function(){ $(\u0026#39;#tt\u0026#39;).datagrid( \u0026#34;load\u0026#34;); }; $.ajax({ url: \u0026#39;${ctx}/xxxx/xxxx.do\u0026#39;, type: \u0026#39;post\u0026#39;, data: param, complete: function () { parent.closeTabPage(\u0026#39;父级 Tab\u0026#39;); }, success: function (data) { if (data.success) { $.messager.alert(\u0026#39;操作提示\u0026#39;, data.opMsg, \u0026#39;info\u0026#39;); parent.reloadTabGrid( \u0026#34;当前要关闭的 Tab\u0026#34; ); } else { $.messager.alert(\u0026#39;操作提示\u0026#39;, data.opMsg, \u0026#39;warning\u0026#39;); } } }) 实现编辑某字段,其他可编辑字段联动计算。 EasyUI 也有 onClickCell 的方法,但那个比较适合单个 Cell 进行编辑,不适合这种联动计算变化数值的。\nvar lastIndex; function setEditing(rowIndex){ var editors = $(\u0026#39;#tt\u0026#39;).datagrid(\u0026#39;getEditors\u0026#39;, rowIndex); var priceEditor = editors[0]; var amountEditor = editors[1]; var costEditor = editors[2]; priceEditor.target.bind(\u0026#39;change\u0026#39;, function(){ calculate(); }); amountEditor.target.bind(\u0026#39;change\u0026#39;, function(){ calculate(); }); function calculate(){ var cost = priceEditor.target.val() * amountEditor.target.val(); $(costEditor.target).numberbox(\u0026#39;setValue\u0026#39;,cost); } } $(\u0026#39;#tt\u0026#39;).datagrid({ onClickRow:function(rowIndex){ if (lastIndex != rowIndex){ $(this).datagrid(\u0026#39;endEdit\u0026#39;, lastIndex); $(this).datagrid(\u0026#39;beginEdit\u0026#39;, rowIndex); setEditing(rowIndex); } lastIndex = rowIndex; } }); 这里在行与行之间切换编辑的时候,会自动关闭其他行的编辑状态,也就是 endEdit 的操作。\n但是当且仅有一行时,无法关闭,考虑可以通过回车来关闭编辑状态。\n$(document).keyup(function (e){ const key = e.which; if(key === 13){ $(\u0026#39;#gg\u0026#39;).datagrid(\u0026#39;endEdit\u0026#39;, lastIndex); $(\u0026#34;#gg\u0026#34;).datagrid(\u0026#34;acceptChanges\u0026#34;); lastIndex = undefined; } }); ","permalink":"https://sheerwill.xyz/posts/main/20221110154825-easyui/","summary":"提交完成后关闭当前 Tab,刷新「父级」Tab。 增加如下方法在工具类中。 function reloadTabGrid(title){ if ($(\u0026#34;#frm_center\u0026#34; ).tabs(\u0026#39;exists\u0026#39;, title)) { $( \u0026#39;#frm_center\u0026#39;).tabs(\u0026#39;select\u0026#39; , title); window.top.reload_Abnormal_Monitor.call(); } } 在需要刷新的「父级」Tab 页面中添加。 window.top[\u0026#34;reload_Abnormal_Monitor\u0026#34;]=function(){ $(\u0026#39;#tt\u0026#39;).datagrid(","title":"EasyUI"},{"content":"Readwise Reader 中无法识别中文批注版本 Epub 中的行内批注,为了更好的在 Readwise Reader 中阅读,以及当初在 Calibre 中阅读时批注,不好将批注内容很好的分地复制出来,所以需要将批注的格式进行修改。\n通过正则匹配替换内容,保留批注正文。\nFind Regex Replace Regex \u0026lt;span class=\u0026quot;xiu\u0026quot;\u0026gt;\u0026lt;span class=\u0026quot;ord\u0026quot;\u0026gt;绣\u0026lt;span class=\u0026quot;ord0\u0026quot;\u0026gt;旁\u0026lt;\\/span\u0026gt;\u0026lt;\\/span\u0026gt;(.*?)\u0026lt;\\/span\u0026gt; 「绣旁:\\1」 \u0026lt;span class=\u0026quot;xiu\u0026quot;\u0026gt;\u0026lt;span class=\u0026quot;ord\u0026quot;\u0026gt;绣\u0026lt;span class=\u0026quot;ord0\u0026quot;\u0026gt;眉\u0026lt;\\/span\u0026gt;\u0026lt;\\/span\u0026gt;(.*?)\u0026lt;\\/span\u0026gt; 「绣眉:\\1」 \u0026lt;span class=\u0026quot;xiu\u0026quot;\u0026gt;\u0026lt;span class=\u0026quot;ord\u0026quot;\u0026gt;绣\u0026lt;span class=\u0026quot;ord0\u0026quot;\u0026gt;夹\u0026lt;\\/span\u0026gt;\u0026lt;\\/span\u0026gt;(.*?)\u0026lt;\\/span\u0026gt; 「绣夹:\\1」 \u0026lt;span class=\u0026quot;jia\u0026quot;\u0026gt;\u0026lt;span class=\u0026quot;ord\u0026quot;\u0026gt;张\u0026lt;span class=\u0026quot;ord0\u0026quot;\u0026gt;旁\u0026lt;\\/span\u0026gt;\u0026lt;\\/span\u0026gt;(.*?)\u0026lt;\\/span\u0026gt; 「张旁:\\1」 \u0026lt;span class=\u0026quot;jia\u0026quot;\u0026gt;\u0026lt;span class=\u0026quot;ord\u0026quot;\u0026gt;张\u0026lt;span class=\u0026quot;ord0\u0026quot;\u0026gt;眉\u0026lt;\\/span\u0026gt;\u0026lt;\\/span\u0026gt;(.*?)\u0026lt;\\/span\u0026gt; 「张眉:\\1」 \u0026lt;span class=\u0026quot;jia\u0026quot;\u0026gt;\u0026lt;span class=\u0026quot;ord\u0026quot;\u0026gt;张\u0026lt;span class=\u0026quot;ord0\u0026quot;\u0026gt;夹\u0026lt;\\/span\u0026gt;\u0026lt;\\/span\u0026gt;(.*?)\u0026lt;\\/span\u0026gt; 「张夹:\\1」 \u0026lt;div class=\u0026quot;quote\u0026quot;\u0026gt;\\s(\u0026lt;p\u0026gt;)\u0026lt;span class=\u0026quot;ord\u0026quot;\u0026gt;张\u0026lt;span class=\u0026quot;ord0\u0026quot;\u0026gt;回\u0026lt;\\/span\u0026gt;\u0026lt;\\/span\u0026gt; \\1「张回: (\u0026lt;\\/p\u0026gt;)\\s\u0026lt;\\/div\u0026gt;(\\s\u0026lt;p\u0026gt;词曰) 」\\1\\2 (\u0026lt;\\/p\u0026gt;)\\s\u0026lt;\\/div\u0026gt;(\\s\u0026lt;p\u0026gt;诗曰) 」\\1\\2 \u0026lt;div class=\u0026quot;wenlong\u0026quot;\u0026gt;\\s(\u0026lt;p\u0026gt;)\u0026lt;span class=\u0026quot;ord\u0026quot;\u0026gt;文\u0026lt;span class=\u0026quot;ord0\u0026quot;\u0026gt;回\u0026lt;\\/span\u0026gt;\u0026lt;\\/span\u0026gt; \\1「文回: (\u0026lt;\\/p\u0026gt;\\s)\u0026lt;\\/div\u0026gt;\\s(\u0026lt;\\/body\u0026gt;) 」\\1\\2 这里主要用到的就是 () 内的内容可以用 \\ 加上数字来表示,进行保留。\nFind Regex Replace Regex \u0026lt;span class=\u0026quot;kt\u0026quot;\u0026gt;\u0026lt;img alt=\u0026quot;庚辰本\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00844.gif\u0026quot;/\u0026gt;(.*?)\u0026lt;/span\u0026gt; 「庚:\\1」 \u0026lt;span class=\u0026quot;kt\u0026quot;\u0026gt;\u0026lt;img alt=\u0026quot;甲戌本\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00842.gif\u0026quot;/\u0026gt;(.*?)\u0026lt;/span\u0026gt; 「甲:\\1」 \u0026lt;span class=\u0026quot;kt\u0026quot;\u0026gt;\u0026lt;img alt=\u0026quot;戚序本\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00845.gif\u0026quot;/\u0026gt;(.*?)\u0026lt;/span\u0026gt; 「戚:\\1」 \u0026lt;span class=\u0026quot;kt\u0026quot;\u0026gt;\u0026lt;img alt=\u0026quot;己卯本\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00843.gif\u0026quot;/\u0026gt;(.*?)\u0026lt;/span\u0026gt; 「己:\\1」 \u0026lt;span class=\u0026quot;kt\u0026quot;\u0026gt;\u0026lt;span class=\u0026quot;red\u0026quot;\u0026gt;\u0026lt;img alt=\u0026quot;庚辰本\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00844.gif\u0026quot;/\u0026gt;(.*?)\u0026lt;/span\u0026gt;\u0026lt;/span\u0026gt; 「庚:\\1」 \u0026lt;img alt=\u0026quot;甲戌本\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00842.gif\u0026quot;/\u0026gt;\u0026lt;img alt=\u0026quot;侧批\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00851.gif\u0026quot;/\u0026gt;((?!img).*?)(\u0026lt;/span\u0026gt;) 「甲侧:\\1」\\2 \u0026lt;img alt=\u0026quot;庚辰本\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00844.gif\u0026quot;/\u0026gt;\u0026lt;img alt=\u0026quot;侧批\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00851.gif\u0026quot;/\u0026gt;((?!img).*?)(\u0026lt;/span\u0026gt;) 「庚侧:\\1」\\2 \u0026lt;img alt=\u0026quot;甲戌本\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00842.gif\u0026quot;/\u0026gt;\u0026lt;img alt=\u0026quot;眉批\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00850.gif\u0026quot;/\u0026gt;((?!img).*?)(\u0026lt;/span\u0026gt;) 「甲眉:\\1」\\2 \u0026lt;img alt=\u0026quot;甲戌本\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00842.gif\u0026quot;/\u0026gt;\u0026lt;img alt=\u0026quot;夹批\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00852.gif\u0026quot;/\u0026gt;((?!img).*?)(\u0026lt;/span\u0026gt;) 「甲夹:\\1」\\2 \u0026lt;img alt=\u0026quot;庚辰本\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00844.gif\u0026quot;/\u0026gt;\u0026lt;img alt=\u0026quot;眉批\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00850.gif\u0026quot;/\u0026gt;((?!img).*?)(\u0026lt;/span\u0026gt;) 「庚眉:\\1」\\2 \u0026lt;img alt=\u0026quot;蒙府本\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00846.gif\u0026quot;/\u0026gt;\u0026lt;img alt=\u0026quot;侧批\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00851.gif\u0026quot;/\u0026gt;((?!img).*?)(\u0026lt;/span\u0026gt;) 「蒙侧:\\1」\\2 \u0026lt;img alt=\u0026quot;庚辰本\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00844.gif\u0026quot;/\u0026gt;\u0026lt;img alt=\u0026quot;夹批\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00852.gif\u0026quot;/\u0026gt;((?!img).*?)(\u0026lt;/span\u0026gt;) 「庚夹:\\1」\\2 \u0026lt;img alt=\u0026quot;戚序本\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00845.gif\u0026quot;/\u0026gt;\u0026lt;img alt=\u0026quot;夹批\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00852.gif\u0026quot;/\u0026gt;((?!img).*?)(\u0026lt;/span\u0026gt;) 「戚夹:\\1」\\2 \u0026lt;span class=\u0026quot;small kt red\u0026quot;\u0026gt;((?!span).*?)\u0026lt;/span\u0026gt; \\1 \u0026lt;span class=\u0026quot;x-small\u0026quot;\u0026gt;((?!span).*?)\u0026lt;/span\u0026gt; \\1 \u0026lt;span class=\u0026quot;small kt\u0026quot;\u0026gt;((?!span).*?)\u0026lt;/span\u0026gt; \\1 \u0026lt;span class=\u0026quot;red\u0026quot;\u0026gt;((?!span).*?)\u0026lt;/span\u0026gt; \\1 \u0026lt;img alt=\u0026quot;戚序本\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00845.gif\u0026quot;/\u0026gt;\u0026lt;img alt=\u0026quot;夹批\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00852.gif\u0026quot;/\u0026gt;((?!img).*?)(\u0026lt;/p\u0026gt;) 「戚夹:\\1」\\2 \u0026lt;img alt=\u0026quot;甲辰本\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00849.gif\u0026quot;/\u0026gt;\u0026lt;img alt=\u0026quot;夹批\u0026quot; class=\u0026quot;font_patch\u0026quot; src=\u0026quot;../Images/image00852.gif\u0026quot;/\u0026gt;((?!img).*?)(\u0026lt;/p\u0026gt;) 「甲夹:\\1」\\2 这里的 (?!span) 是防止出现 \u0026lt;span\u0026gt;\u0026lt;/span\u0026gt; 相互嵌套,导致匹配的内容并不是一个成对 HTML 标签的情况,也就是不允许 \u0026lt;span\u0026gt;\u0026lt;/span\u0026gt; 中存在额外的 \u0026lt;span\u0026gt; 标签。\n红楼梦批注的 HTML 结构很乱,余者手动校对。\n正则参考:正则表达式 - JavaScript | MDN\n","permalink":"https://sheerwill.xyz/posts/main/20221123120104-replace_text_in_calibre/","summary":"Readwise Reader 中无法识别中文批注版本 Epub 中的行内批注,为了更好的在 Readwise Reader 中阅读,以及当初在 Calibre 中阅读时批注,不好将批注内容很好的分地复制出来,所以需要将批注","title":"Replace text in Calibre"},{"content":"B-Tree 和 B+ Tree 的不同 B-Tree B+Tree All internal nodes and leaf nodes contain data pointers along with keys. Only leaf nodes contain data pointers along with keys, internal nodes contain keys only. There are no duplicate keys. Duplicate keys are present in this, all internal nodes are also present at leaves. Leaf nodes are not linked to each other. Leaf nodes are linked to each other. Sequential access of nodes is not possible. All nodes are present at leaves, so sequential access is possible just like a linked list. Searching for a key is slower. Searching is faster. For a particular number of entries, the height of the B-tree is larger. The height of the B+ tree is lesser than B-tree for the same number of entries. 堆表 (Heaps: Table without Clustered Indexes) A heap is a table without a clustered index. One or more nonclustered indexes can be created on tables stored as a heap. Data is stored in the heap without specifying an order. Usually data is initially stored in the order in which is the rows are inserted into the table, but the Database Engine can move data around in the heap to store the rows efficiently; so the data order cannot be predicted. To guarantee the order of rows returned from a heap, you must use the ORDER BY clause. To specify a permanent logical order for storing the rows, create a clustered index on the table, so that the table is not a heap.\n堆表就是含有一列或者多列无聚簇索引的表,其中的数据存储无序。通常数据是按照初始化的顺序存储,但数据库引擎会为了更高效的数据插入,将原有数据移动到别处来释放空间给新的数据。因此,数据的顺序是不确定的,为了保证数据的顺序,可以在查询的时候使用 ORDER BY 关键字进行排序。要保证数据物理存储的数据,就需要建立聚簇索引,这个时候就不是堆表了。\nWhen a table is stored as a heap, individual rows are identified by reference to an 8-byte row identifier (RID) consisting of the file number, data page number, and slot on the page (FileID:PageID:SlotID). The row ID is a small and efficient structure.\nROWID 是文件编号、数据页编号以及页面上的卡槽组成。(FileID:PageID:SlotID)\nFigure 1: b-tree-structure\nFigure 2: index-based-access-on-a-heap-table\nB-Tree 的上级 leaf nodes 存储的是下级 leaf nodes 中列值的最大值,可以从图中看出最底层的 leaf nodes 存储的是 ROWID,可以据此查找到堆表中的位置。\n索引组织表 (Clustered Indexes / Index Organized Tables (IOT)) Some databases can indeed use an index as primary table store. The Oracle database calls this concept index-organized tables (IOT), other databases use the term clustered index. In this section, both terms are used to either put the emphasis on the table or the index characteristics as needed.\nAn index-organized table is thus a B-tree index without a heap table. This results in two benefits: (1) it saves the space for the heap structure; (2) every access on a clustered index is automatically an index-only scan.\n索引组织表以主键顺序组织物理数据存储,索引结构中的每个叶节点(leaf nodes)都存储着完整的行数据。对应的主键被称为聚簇索引(Clustered Indexes),除此之外的索引称为二级索引(Secondary Indexes)。\nFigure 3: secondary-index-on-an-IOT\n聚簇索引 在 MySQL 中通常主键即为「聚簇索引」;若没有定义主键,则第一个 NOT NULL UNIQUE 列是聚集索引;若没有 NOT NULL UNIQUE 列,InnoDB 会创建一个隐藏的 row-id 作为聚集索引。 聚簇索引是按照索引顺序对物理记录排序,存储完整的行记录。 二级索引 除主键外的其他列上新建的索引都是二级索引。 二级索引的排序与物理记录的排序不一致,存储的是该列本身排序后的值和主键。 索引组织表的好处 可以节省堆表占用的空间 访问聚簇索引可以直接获取数据(即「覆盖索引」) 二级索引需要回表,即需要根据二级索引的叶节点内的主键,再对主键形成的聚簇索引进行遍历,查找到具体的记录。 索引组织表的缺点 图 secondary-index-on-an-IOT 就是索引组织表中,二级索引获取数据的流程。再次根据主键在索引组织表中遍历查找就叫做「回表」。\n各常用的关系型数据库索引类型 MySQL The MyISAM engine only uses heap tables while the InnoDB engine always uses clustered indexes. That means you do not directly have a choice.\nOracle Database The Oracle database uses heap tables by default. Index-organized tables can be created using the ORGANIZATION INDEX clause:\nCREATE TABLE ( id NUMBER NOT NULL PRIMARY KEY, [...] ) ORGANIZATION INDEX; The Oracle database always uses the primary key as the clustering key.\nPostgreSQL PostgreSQL only uses heap tables.\nYou can, however, use the CLUSTER clause to align the contents of the heap table with an index.\nSQL Server By default SQL Server uses clustered indexes (index-organized tables) using the primary key as clustering key. Nevertheless you can use arbitrary columns for the clustering key—even non-unique columns.\nTo create a heap table you must use the NONCLUSTERED clause in the primary key definition:\nCREATE TABLE ( id NUMBER NOT NULL, [...] CONSTRAINT pk PRIMARY KEY NONCLUSTERED (id) ); Dropping a clustered index transforms the table into a heap table.\nSQL Server’s default behavior often causes performance problems when using secondary indexes.\n联合索引 (Concatenated Indexes) 联合索引遵循「最左匹配原则(Leftmost Index Prefixes)」,MySQL 联合索引创建的时候会对 (a, b) 其中的 a 先进行排序,再在 a 的基础上对 b 进行排序。如果是 (a, b, c) 也是一样的方式。\nFigure 4: concatenated-indexes\n因此,判断是否符合「最左匹配原则」,是否能够使用索引,可以根据底层的索引实现逻辑来进行判断。即,按照创建索引时的顺序,联合索引中的第一个字段是有序的,一旦第一个字段确定值,那么第二个字段也是有序的,以此类推,有序即可用索引,否则不行。\n\u0026lt;,\u0026gt; 和 BETWEEN 可以用索引,但是按照上述的逻辑,范围查询字段后的索引字段就无法用上索引,因为范围查询字段的值并没有确定,后续的字段就是无序的。 LIKE 在 \u0026quot;a%\u0026quot; 这种右匹配时,会在索引是按照字母排序的情况下用上索引(% 左侧字符合适的情况下也可以建立前缀索引)。至于左匹配和全匹配,按照上述的逻辑,完全是无序的,无法使用索引。 WHERE b = ? AND a = ? AND c = ? 这种颠倒了顺序的 SQL 是不影响「最左匹配原则」的,数据库执行前,解释器会对其重新排序优化。 References Heaps (Tables without Clustered Indexes) - SQL Server | Microsoft Learn Markus Winand (2012) SQL Performance Explained Everything Developers Need to Know about SQL Performance ","permalink":"https://sheerwill.xyz/posts/main/20221008125127-index/","summary":"B-Tree 和 B+ Tree 的不同 B-Tree B+Tree All internal nodes and leaf nodes contain data pointers along with keys. Only leaf nodes contain data pointers along with keys, internal nodes contain keys only. There are no duplicate keys. Duplicate keys are present in this, all internal nodes are also present at leaves. Leaf nodes are not linked to each other. Leaf nodes are linked to each other. Sequential","title":"Index"},{"content":"","permalink":"https://sheerwill.xyz/posts/main/20221019183132-mvcc/","summary":"","title":"MVCC"},{"content":"去除双下巴 钢笔将需要去除的双下巴以及下巴下方的脖子部分圈取出来 CMD+Enter 将路径转为选区。 根据实际情况适当羽化几个像素 CMD+j 复制两层 选中复制出的两个图层中的上面一个,CMD+Alt+g 将上方图层用「剪贴蒙版」的形式置入下方图层。 对上方图层 CMD+T 执行「变换」,鼠标右键选择「变形(Warp)」后,往上拖动路径,直到双下巴消失,边缘平滑后按 Enter 确定。 ","permalink":"https://sheerwill.xyz/posts/main/20221013221348-photoshop_techniques/","summary":"去除双下巴 钢笔将需要去除的双下巴以及下巴下方的脖子部分圈取出来 CMD+Enter 将路径转为选区。 根据实际情况适当羽化几个像素 CMD+j 复制两层 选中复制出的两个图层中","title":"Photoshop Techniques"},{"content":"ID: 46dfdbc7-24f6-4edc-9e61-dab01aa185a1\nROAM_REFS: https://jethrokuan.github.io/org-roam-guide/ Introduction 最先接触到 Zettlekasten (German: \u0026ldquo;slip box\u0026rdquo;, plural zettlekästen) 是从 @Tisoga 的推文中得知,如获至宝。在这之前用过 MWeb(内测用户)、iA Writer、Bear、Notion、Obsidian(当时还不知道 Backlinks),都觉得不太合适,尤其是第一步文件夹的分类就让我头疼,很多内容是互相交叉的,并不是简单的可以归在一个分类下面,若是细分的分类过多,又会过于混乱,最后的结果就是“垃圾场”。\n根据 Zettlekasten 衍生出来的各种 APP 中的 Backlinks 将笔记串联起来,作为一个草稿箱,定期去整理回顾形成自己的知识。 另外一个极为吸引我的就是 Daily Notes 或者 Journals,非常适合我当前的工作场景。「生产问题」或者「零碎的需求」会不断从打断我的工作,有时候会非常紧急需要优先处理「需求」或者「生产问题」,解决后会有两种情况。\n问题或需求解决后,要花一些时间梳理之前在做的事情才能够重新接续之前的思路继续工作。\n问题解决后几个月遇到相同或类似的问题,虽然记得解决过,但是当时解决的思路以及细节需要注意的地方就模糊不清了。通常为了万无一失需要重复上次的工作,仔细查看过代码结合业务后,才能作出正确的判断,确保干净利落地解决问题。\n上面两种情况完全可以用 Journals 记录解决问题前的工作思路,记录解决问题后的思路,来避免上述两种情况发生。\nFigure 1: take-notes-sankey\n信息大爆炸的副作用就是信噪比降低,通过分阶段的信息处理,不断地归纳、处理形成自己的想法,并将其固化下来,作为知识库。\n流程设计 图中的流程其实就是多层的过滤器。\n从最初的信息源处到 Logseq 的过程,只需要考虑这个信息源是否值得读,是否值得扩展,答案确定则大胆地记入 Logseq。其中零散的思绪及文章阅读记录在 Journals 中,书籍这类大部头则在 page 中记录,前者因为零散在 Journals 中,需要加 page 的双链或 Tag。\nLogseq 中 Journals 中的事件重要的会被我移到 Org-roam 中的 Journals.org 文件中,这个文件类似于日记的作用,并不是每天都会记录,记录多了之后按照年份进行分割。\n从 Logseq 到 Org-roam 则是知识固化的过程,需要考虑该知识是否以后会被自己再次用到,是否值得扩展,答案确定则精炼后记入,并标明灵感来源,引用材料。因此,Logseq 中记录的都只能算是草稿,需要再次的整合,提高信噪比。\n对信息源处理时,可以用 Excalidraw 进行流程图、思维图等图的草稿绘制。后续有时间的时候再用 Figma 重新绘制,视觉和逻辑上的重新设计,使得阐述的内容更加直观。\n对于产品来说,通过设计降低理解门槛和使用门槛,是一件挺重要的事情。输出文章同样如此。\nOrg-roam 中也有收集想法的地方,Inbox.org,定期会进行清理。 notes 中 outline 结构是由 * 实现的,导出时是标题格式,为了随时可以导出发布,要注意 outline 结构。输出是通过 Org-export-dispatch 来转为 .md 或 .html 文件,为了更符合使用的格式,在 Emacs 中做了一些设置。\nOrg-roam-ui 非常优秀,不管是 UI 还是操作性,可以明晰的查看笔记之间的关联,知识是否形成闭环,哪些知识还需要进行拓展阅读。\n准则 卡片应该具有原子性。这点和编程的原则很像,一个方法只做一件事,在这里也就是只记录一个主题的内容。\n卡片内容应该具有独立性。这点和上面并不冲突,设想一下,为了保持每个卡片的纯净,X 和 Y 之间的关系,你可能会用 Z 去链接他们。但回顾的时候,没有 Z,单纯回顾 X 或 Y 无法得知两者之间的关联。所以这里不管在 X 或者 Y 中去描述与对方的关系都是可以的,一点点内容的冗余可以使得内容更加独立。\n遵守奥卡姆剃刀原则。对新工具充分调研,是否满足自己的需求后再考虑替换或增加到现有工作流当中。\nLiterature Notes 需要及时回顾,在一两天内转化为 Permanent Notes 或者直接删掉。\n误区 All-In-One 的思想。Markdown 的扩展语法实现并不统一,扩展语法的内容导出时经常要手动兼容,或不可再用。云端服务的不可持续性,互联网这么多年已经太多的云端服务用着用着就停掉了。\nPermanent Notes 并不是不需要回顾和修改了。\n记太多的内容。从资料中复制大量的原文。笔记应该是对所读内容的提炼:用自己的话改写观点和概念有助于加强理解。\n太复杂的笔记流程。复杂的工作流通常需要自动化,但自动化不利于笔记的整理,形成最重笔记时应该慎重考虑哪些是需要保留的,否则知识库就会变成垃圾场。\n\\begin{document} \\begin{algorithm} \\caption{Vector clocks algorithm} \\While{initialisation at node $Ni$} {T := \\langle0, 0, \u0026hellip;, 0\\rangle$ \\tcp{local variable at node $N_{i}$}} \\While{any event occurring at node $N_{i}$} {$T[i] = T[i] + 1$} \\While{request to send message $m$ at node $N_{i}$} {$T[i] := T[i] + 1$\\; send $(T, m)$ via network} \\While{receiving $(T\u0026rsquo;, m)$ at node $N_{i}$ via the network} {$T[j] := \\max(T[j], T\u0026rsquo;[j])$ for every $j \\in {1, \u0026hellip;, n}$\\; $T[i] := T[i] + 1$\\; deliver $m$ to the application} \\end{algorithm} \\end{document}\n","permalink":"https://sheerwill.xyz/posts/article/20220505162419-how_i_take_notes/","summary":"ID: 46dfdbc7-24f6-4edc-9e61-dab01aa185a1 ROAM_REFS: https://jethrokuan.github.io/org-roam-guide/ Introduction 最先接触到 Zettlekasten (German: \u0026ldquo;slip box\u0026rdquo;, plural zettlekästen) 是从 @Tisoga 的推文中得知,如获至宝。在这之前用过 MWeb(内测用户)、iA Writer、B","title":"How I Take Notes"},{"content":"ID: 1d28a46a-0a67-4633-863a-a0acf49b17c0\nIntroduction 2013年时,第一次接触到 GTD 是因为 Omnifocus 的美观,为此我还花了 $9.99 买了 iOS 的客户端。但从那个时候起,我一直没有用好 GTD 这套理论和各种工具,例如 Omnifocus、Things 等等。\n一直不明白问题出在哪里,一天下来不管是 Today 还是 Inbox 当中的 To-Do 不仅没有变少,甚至更多了。自我怀疑、自我谴责让自己觉得是拖延症、懒造成了 To-Do 积压。\n回顾过去几年中,除了 Things 以外,我还使用了 Due 和 Fantastical 这两款软件。前者是吃药的时候,循环提醒该吃药了;后者是今天或者未来某天必须要做的事情,为了不让我忘记这个事情,哪怕我不确定这个事情在当天的哪个时间段做,都会给它加一个提醒,这样即便是过了时间,只要没有 checked 就会一直在锁屏或者下拉通知内,这样我就可以*不断地被提醒,找到合适的时间段去完成这件事情*。\n至于 Things 时常被我厌恶,因为我有太多的 To-Do 积压在里面。每次打开它都有种翻开垃圾桶的感觉,没错,它就是垃圾桶。我再次捡起 Things 用的时候,基本都是我把“过期了的”、“没有意义的” To-Do 清空了才可以。这是一个循环往复的过程,过程中不断地伤害着我自己的自信心,并让我感到焦虑,永远做不完的事情,越积越多。\n在这样的情况下我了解到了 Implementation Intention。\nImplementation Intention 执行意图由心理学家 Peter M.Gollitzer 提出,是个体为实现某一目标制定的一种计划,表现为“如果-那么”形式,例如“如果我遇到突发情况,那么我将保持冷静”。\n普通人思考目标的时候,使用的是目标意图,即:我要做什么、我要成为什么。目标意图有个最大的缺点:建立目标容易,执行目标很难。而执行意图更多思考的是要怎么做的问 题。\n执行意图明确了个体在何时何地,以什么方式来实现目标,从而让个体更加容易地提取具体情景线索的心理表征,并通过这些线索建立起与目标所指行为的联系。在建立起线索和目标行为的联系之后,个体在随后遇到线索所指向的情景时(如考场),便可以用一种自动化的方式执行事先形成的计划,达成预设的目标。重要的是,执行意图的这种过程是自动化进行的,并不需要个体有意识地进行控制。\n再结合成功实行 GTD 的案例「Deep Habits: The Importance of Planning Every Minute of Your Work Day」和「Be a Schedule Builder, Not a To-Do List Maker」,发现他们都会去在前一天花费一些时间去细化第二天的任务列表,甚至精确到分钟,这其实就是上面提到的 Implementation Intention 的实际操作,When Where and How。\n案例1当中是通过日历来安排一天的工作,细致到分钟。若发生变故需要变更,则在右侧重新记录变更后的安排。\nFigure 1: gtd_dark\n案例2中则从容很多,不仅仅是安排了工作的内容,休闲娱乐的内容也会排在日历当中。(下图来自于案例2)\nFigure 2: gtd_schedule_builder\n其实回顾到上面我提到使用 Due 和 Fantastical 的经历,当中已经包含了执行意图的概念,任务都非常明晰,是今天必须要做的,是今天有非常合适的情景去做的,只不过是我没有意识到。\n「执行意图」会让人主动思考,避免囤积过多的任务。 举个例子,按照执行意图的方法。如果我买了一本书,那么我就要在最近的闲暇时间都去读它,并在读完后写读后感总结一下。那么当我买书或者安排任务的时候就会想到买书不仅仅是一个完结,后续意味着我要去读它,并写读后感等一大堆的操作,这样就不会去在自己当前书还没有读完的情况下,就去安排下一本书。\n例子中的场景和平时从 Inbox 中安排任务很类似,造成任务完不成,积压,是因为没有安排好任务的情景关系,何时、何地、如何去做这件事情,仅仅就是目标意图,我要做这些事情。\n没有对每个任务细致的思考前,无法把握任务的工作量,适合完成的场景,也就无法把控今天安排的任务是否都能完成。但刚刚开始接触这套方法的人,难免会因为对工作细节不熟悉导致工作量评估不准确的情况。\n「执行意图」消除了在何时何地做某事的模糊性。 例如,询问选民如何前往投票站等简单的问题,已经被证明可以提高当天的投票率。\n在这个例子当中,被问的选民其实受到了引导,增加去投票这件事情的确定性。我们也可以利用同样的方法,明确任务,顺着 if...then... 的顺序去完成任务。\n回顾 @mikhails5v 在 tweet thread 中提到了他在使用 Things 的 1.5 年中,对于没有完成的任务,在每晚回顾时,手动的去把今天的任务清空,已完成的任务会让他很有成就感;未完成的任务则在思考后移到后面适合的时候,或者重新放回 Inbox,甚至删掉它们。确保了每日的任务都是清空的状态,没有负罪感。即便是没有完成的任务,在每晚也会重新回顾,思考后续是否有合适的情景去完成它,或者干脆觉得没有执行的必要删除掉。\n阶段性思考 note-taking and knowledge creation will always require large amounts of brain work. On the other hand, GTD makes task management a simple, effortless process.\nGTD 中 Capture 是最不需要思考的,有什么东西直接记录到 Inbox 当中,清空当前的灵感,专注眼前的事情。例如,Things 中快速呼出 Inbox 的输入框;Org-roam 中写在 .org 文件中的 To-Do 都会集中在 Agenda 中;Logseq 中写在 Journals 中的 To-Do 也可以在 Pages 中按照自己的需求定制查询(复杂的查询有些门槛)。后续处理,按照执行意图的方法去评估 To-Do 适合的情景;较大的任务需要进行拆分成若干小任务,甚至形成一个专门的项目分为不同阶段去完成。\nNote-Taking 相反,从阅读就开始思考,随后各种灵感迸发,记录下来进行整理,去芜存菁后继续扩展,定期回顾后形成自己的知识并输出,这个时候的写作反而是最不需要动脑的部分了。以下的 Heat Map 表示不同阶段思考的程度。\nFigure 3: gtd_note_taking_dark\nTo-Do 是否需要独立管理 至于 To-Do 和 Note-Taking 是否分开,我的看法是逻辑上分开,物理上并不一定分开。\n一天 24 小时是由不同阶段组成的,工作、生活、娱乐等等,都会产生 To-Do。那么管理 To-Do 也可以分为不同的场景。\n工作 工作上做好日志记录,是个好习惯,可以方便查阅当时的想法,解决问题的思路,以及相关人员是谁,就跟航海日志一样。这个时候产生的 To-Do 就是与日志息息相关的,上下文联系紧密。加上 Capture 的原则是尽快记录,并且不打断现有的思路,最好的方法就是记录在笔记当中(比如Logseq 的 Journals)。\n汇总的话就更加简单了,Logseq 通过 Clojure 的查询语句就可以完成汇总,不会这门语言也没有关系,Discord 中有专门的 Channel 可以提问或讨论。每天上班的第一件事情就是回顾汇总,结合工作进度安排今天的工作。\n生活 生活中琐事居多,场景也非常繁杂,这个时候完全可以用单独的 App 来进行管理。Things 来收集拆分任务,Fantastical 来进行一天时间的分配以及未来几天需要提醒的任务,Due 来提醒重复任务。\nConclusion 警惕 All-In-One 的想法,并不是所有场景都有统一的解决方案。工作和生活分开管理 To-Do 还有个好处,休假的时候你完全可以屏蔽掉工作的内容。\n另外不管工作和生活中,长期计划用甘特图来管理还是非常不错的,确立里程碑,控制进度,把握长期计划的走向。\n","permalink":"https://sheerwill.xyz/posts/article/20220503143427-gtd/","summary":"ID: 1d28a46a-0a67-4633-863a-a0acf49b17c0 Introduction 2013年时,第一次接触到 GTD 是因为 Omnifocus 的美观,为此我还花了 $9.99 买了 iOS 的客户端。但从那个时候起,我一直没有用好 GTD 这套理论和各种工具,例如 Om","title":"Implementation Intention GTD"}]