├── README.md ├── README.zh-CN.md ├── deno-bridge.el └── example ├── deno-bridge-demo.el └── deno-bridge-demo.ts /README.md: -------------------------------------------------------------------------------- 1 | English | [简体中文](./README.zh-CN.md) 2 | 3 |

4 |
A free/libre framework that build bridge between Emacs and Deno runtime.
Allows execution of JavaScript and Typescript within Emacs. 5 |

6 | 7 | ## Vision 8 | With deno-bridge, we can execution of JavaScript and Typescript within Emacs and don't need change source code of Emacs. It's bringing TypeScript ecosystem of powerful tools and approaches that Emacs just doesn't have currently: 9 | 10 | 1. TypeScript offers an extremely flexible typing system, that allows to user to have compile time control of their scripting, with the flexibility of types "getting out of the way" when not needed. 11 | 2. Deno uses Google's v8 JavaScript engine, which features an extremely powerful JIT and world-class garbage collector. 12 | 3. Usage of modern Async I/O utilizing Rust's Tokio library. 13 | 4. WebWorker support, meaning that multiple JavaScript engines can be running in parallel within the editor. The only restriction is that only the 'main' JS Engine can directly call lisp functions. 14 | 5. WebAssembly support, compile your C module as WebAsm and distribute it to the world. Don't worry about packaging shared libraries or changing module interfaces, everything can be handled and customized by you the user, at the scripting layer. No need to be dependent on native implementation details. 15 | 6. Performance, v8's world-class JIT offers the potential for large performance gains. Async I/O from Deno, WebWorkers, and WebAsm, gives you the tools to make Emacs a smoother and faster experience without having to install additional tools to launch as background processes or worry about shared library versions. 16 | 17 | ## Install 18 | 19 | #### 1. Download deno-bridge 20 | 21 | ```Bash 22 | git clone --depth=1 -b master https://github.com/manateelazycat/deno-bridge ~/.emacs.d/site-lisp/deno-bridge/ 23 | ``` 24 | 25 | #### 2. Install Dependences 26 | 27 | 1. [Deno](https://github.com/denoland/deno_install) 28 | 2. [Websocket](https://github.com/ahyatt/emacs-websocket) 29 | 30 | #### 3. Add to ~/.emacs 31 | 32 | From here on, you can add the full path to the deno-bridge installation directory to your Emacs ```load-path```, then add the following to `init.el`: 33 | 34 | ```Elisp 35 | (add-to-list 'load-path "~/.emacs.d/site-lisp/deno-bridge/") 36 | (require 'deno-bridge) 37 | ``` 38 | 39 | ## Example 40 | 41 | I write a demo to show you how simple write app base on deno-brige: 42 | 43 | #### Elisp (deno-bridge-demo.el) 44 | 45 | ```elisp 46 | (require 'deno-bridge) 47 | (setq deno-bridge-demo-ts-path (concat (file-name-directory load-file-name) "deno-bridge-demo.ts")) 48 | (deno-bridge-start "demo" deno-bridge-demo-ts-path) 49 | (deno-bridge-call "demo" "ping" "Hello from Emacs.") 50 | ``` 51 | 52 | 1. Start Deno process: `(deno-bridge-start "demo" deno-bridge-demo-ts-path)` 53 | 2. Call TypeScript function from Emacs: `(deno-bridge-call "demo" "ping" "Hello from Emacs.")` 54 | 3. Clean Deno process: execute command `deno-bridge-exit` and select application name 55 | 56 | #### TypeScript (deno-bridge-demo.ts) 57 | 58 | ```typescript 59 | import { DenoBridge } from "https://deno.land/x/denobridge@0.0.1/mod.ts" 60 | 61 | const bridge = new DenoBridge(Deno.args[0], Deno.args[1], Deno.args[2], messageDispatcher) 62 | 63 | async function messageDispatcher(message: string) { 64 | const [funcName, funcArgs] = JSON.parse(message)[1] 65 | 66 | if (funcName == "ping") { 67 | console.log("Emacs message: ", funcArgs) 68 | 69 | const emacsVar = await bridge.getEmacsVar("deno-bridge-app-list") 70 | console.log("Emacs var 'deno-bridge-app-list': ", emacsVar) 71 | 72 | bridge.messageToEmacs("Hi from TypeScript") 73 | 74 | bridge.evalInEmacs('(message \"Eval from TypeScript\")') 75 | } 76 | } 77 | ``` 78 | 79 | 1. Create DenoBridge object to communicate with Emacs 80 | 2. Get Emacs variable value: `await bridge.getEmacsVar(emacs-var-name)` 81 | 3. Show message in Emacs minibuffer: `bridge.messageToEmacs("message")` 82 | 4. Eval Elisp code in TypeScript: `bridge.evalInEmacs('(message \"Eval from TypeScript\")')` 83 | 84 | **That's all story about deno-bridge.** 85 | 86 | ## Project base on deno-bridge 87 | 88 | * [deno-bridge-jieba](https://github.com/ginqi7/deno-bridge-jieba) 89 | * [emmet2-mode](https://github.com/P233/emmet2-mode) 90 | * [insert-translated-name](https://github.com/manateelazycat/insert-translated-name) 91 | * [deno-bridge-echo](https://github.com/nailuoGG/deno-bridge-echo) 92 | 93 | ## Contributor 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | [English](./README.md) | 简体中文 2 | 3 |

4 |
一款基于 Deno 运行时的编程框架, 使得 Emacs 可以使用 TypeScript/JavaScript 编写 Emacs 插件
5 |

6 | 7 | ## 愿景 8 | 基于 deno-bridge, 我们可以在 Emacs 中执行 JavaScript 或 TypeScript 代码并且不需要修改 Emacs 的源代码。 它给 Emacs 带来了 强大的 TypeScript 生态工具和 Emacs 并不具备的编程能力: 9 | 10 | 1. TypeScript 提供了一个非常灵活的类型系统,允许用户在编译时控制他们的脚本 11 | 2. Deno 使用 Google 的 V8 JavaScript 引擎,具有极其强大的 JIT 和世界级的垃圾收集器 12 | 3. 提供现代的异步 IO 库, 底层基于 Rust 实现的 Tokio 13 | 4. WebWorker 支持,这意味着多个 JavaScript 引擎可以在编辑器中并行运行, 唯一的限制是只有 'main' JS Engine 可以直接调用 lisp 函数 14 | 5. WebAssembly 支持,将你的 C 模块编译为 WebAsm 并分发给全世界, 不用担心打包共享库或更改模块接口,一切都可以由用户在脚本层处理和定制, 无需依赖本机实现细节 15 | 6. 性能,V8 的世界级 JIT 提供了大幅提升性能的潜力, 来自 Deno、WebWorkers 和 WebAsm 的异步 I/O 使 Emacs 体验更流畅,而无需安装额外的工具来作为后台进程启动或担心共享库版本 16 | 17 | ## 安装 18 | 19 | #### 1. 下载 deno-bridge 20 | 21 | ```Bash 22 | git clone --depth=1 -b master https://github.com/manateelazycat/deno-bridge ~/.emacs.d/site-lisp/deno-bridge/ 23 | ``` 24 | 25 | #### 2. 下载依赖 26 | 27 | 1. [Deno](https://github.com/denoland/deno_install) 28 | 2. [Websocket](https://github.com/ahyatt/emacs-websocket) 29 | 30 | #### 3. 添加配置 ~/.emacs 31 | 32 | 添加以下配置到你的 ~/.emacs, 注意路径要使用你下载 deno-bridge 时存放的目录 33 | 34 | ```Elisp 35 | (add-to-list 'load-path "~/.emacs.d/site-lisp/deno-bridge/") 36 | (require 'deno-bridge) 37 | ``` 38 | 39 | ## 示例 40 | 41 | 我将演示一下基于 deno-bridge 开发应用有多么简单! 42 | 43 | #### Elisp (deno-bridge-demo.el) 44 | 45 | ```elisp 46 | (require 'deno-bridge) 47 | (setq deno-bridge-demo-ts-path (concat (file-name-directory load-file-name) "deno-bridge-demo.ts")) 48 | (deno-bridge-start "demo" deno-bridge-demo-ts-path) 49 | (deno-bridge-call "demo" "ping" "Hello from Emacs.") 50 | ``` 51 | 52 | 1. 启动 Deno 进程: `(deno-bridge-start "demo" deno-bridge-demo-ts-path)` 53 | 2. 在 Emacs 中调用 TypeScript 函数: `(deno-bridge-call "demo" "ping" "Hello from Emacs.")` 54 | 3. 退出 Deno 进程: 执行命令 `deno-bridge-exit` 并选择想要推出的应用程序名称 55 | 56 | #### TypeScript (deno-bridge-demo.ts) 57 | 58 | ```typescript 59 | import { DenoBridge } from "https://deno.land/x/denobridge@0.0.1/mod.ts" 60 | 61 | const bridge = new DenoBridge(Deno.args[0], Deno.args[1], Deno.args[2], messageDispatcher) 62 | 63 | async function messageDispatcher(message: string) { 64 | const [funcName, funcArgs] = JSON.parse(message)[1] 65 | 66 | if (funcName == "ping") { 67 | console.log("Emacs message: ", funcArgs) 68 | 69 | const emacsVar = await bridge.getEmacsVar("deno-bridge-app-list") 70 | console.log("Emacs var 'deno-bridge-app-list': ", emacsVar) 71 | 72 | bridge.messageToEmacs("Hi from TypeScript") 73 | 74 | bridge.evalInEmacs('(message \"Eval from TypeScript\")') 75 | } 76 | } 77 | ``` 78 | 79 | 1. 创建对象 DenoBridge, 以和 Emacs 进行 WebSocekt 通讯 80 | 2. 在 TypeScript 中获取 Emacs 的变量值: `await bridge.getEmacsVar(emacs-var-name)` 81 | 3. 发送消息给 Emacs: `bridge.messageToEmacs("message")` 82 | 4. 在 TypeScript 中执行 Emacs Elisp 代码: `bridge.evalInEmacs('(message \"Eval from TypeScript\")')` 83 | 84 | **上面就是你编写 deno-bridge 插件的全部 API, 简单吧?** 85 | 86 | ## 基于 deno-bridge 的社区项目 87 | 88 | * [deno-bridge-jieba](https://github.com/ginqi7/deno-bridge-jieba) 89 | * [emmet2-mode](https://github.com/P233/emmet2-mode) 90 | * [insert-translated-name](https://github.com/manateelazycat/insert-translated-name) 91 | * [deno-bridge-echo](https://github.com/nailuoGG/deno-bridge-echo) 92 | 93 | ## 贡献者 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /deno-bridge.el: -------------------------------------------------------------------------------- 1 | ;;; deno-bridge.el --- Bridge between Emacs and Deno -*- lexical-binding: t; -*- 2 | 3 | ;; Filename: deno-bridge.el 4 | ;; Description: Bridge between Emacs and Deno 5 | ;; Author: Andy Stewart 6 | ;; Maintainer: Andy Stewart 7 | ;; Copyright (C) 2022, Andy Stewart, all rights reserved. 8 | ;; Created: 2022-10-07 12:22:01 9 | ;; Version: 0.1 10 | ;; Last-Updated: 2022-10-07 12:22:01 11 | ;; By: Andy Stewart 12 | ;; URL: https://www.github.org/manateelazycat/deno-bridge 13 | ;; Keywords: 14 | ;; Compatibility: GNU Emacs 28.2 15 | ;; 16 | ;; Features that might be required by this library: 17 | ;; 18 | ;; 19 | ;; 20 | 21 | ;;; This file is NOT part of GNU Emacs 22 | 23 | ;;; License 24 | ;; 25 | ;; This program is free software; you can redistribute it and/or modify 26 | ;; it under the terms of the GNU General Public License as published by 27 | ;; the Free Software Foundation; either version 3, or (at your option) 28 | ;; any later version. 29 | 30 | ;; This program is distributed in the hope that it will be useful, 31 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 32 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 33 | ;; GNU General Public License for more details. 34 | 35 | ;; You should have received a copy of the GNU General Public License 36 | ;; along with this program; see the file COPYING. If not, write to 37 | ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth 38 | ;; Floor, Boston, MA 02110-1301, USA. 39 | 40 | ;;; Commentary: 41 | ;; 42 | ;; Bridge between Emacs and Deno 43 | ;; 44 | 45 | ;;; Installation: 46 | ;; 47 | ;; Put deno-bridge.el to your load-path. 48 | ;; The load-path is usually ~/elisp/. 49 | ;; It's set in your ~/.emacs like this: 50 | ;; (add-to-list 'load-path (expand-file-name "~/elisp")) 51 | ;; 52 | ;; And the following to your ~/.emacs startup file. 53 | ;; 54 | ;; (require 'deno-bridge) 55 | ;; 56 | ;; No need more. 57 | 58 | ;;; Customize: 59 | ;; 60 | ;; 61 | ;; 62 | ;; All of the above can customize by: 63 | ;; M-x customize-group RET deno-bridge RET 64 | ;; 65 | 66 | ;;; Change log: 67 | ;; 68 | ;; 2022/10/07 69 | ;; * First released. 70 | ;; 71 | 72 | ;;; Acknowledgements: 73 | ;; 74 | ;; 75 | ;; 76 | 77 | ;;; TODO 78 | ;; 79 | ;; 80 | ;; 81 | 82 | ;;; Require 83 | (require 'websocket) 84 | (require 'ansi-color) 85 | 86 | ;;; Code: 87 | 88 | (defvar deno-bridge-app-list (list)) 89 | 90 | (defun deno-bridge-get-free-port () 91 | (save-excursion 92 | (let* ((process-buffer " *temp*") 93 | (process (make-network-process 94 | :name process-buffer 95 | :buffer process-buffer 96 | :family 'ipv4 97 | :server t 98 | :host "127.0.0.1" 99 | :service t)) 100 | port) 101 | (setq port (process-contact process)) 102 | (delete-process process) 103 | (kill-buffer process-buffer) 104 | (format "%s" (cadr port))))) 105 | 106 | (cl-defmacro deno-bridge-start (app-name ts-path) 107 | (if (member app-name deno-bridge-app-list) 108 | (message "[DenoBridge] Application %s has start." app-name) 109 | (let* ((deno-port (deno-bridge-get-free-port)) 110 | (emacs-port (deno-bridge-get-free-port)) 111 | (server (intern (format "deno-bridge-server-%s" app-name))) 112 | (process (intern (format "deno-bridge-process-%s" app-name))) 113 | (process-buffer (format " *deno-bridge-app-%s*" app-name)) 114 | (client (intern (format "deno-bridge-client-%s" app-name)))) 115 | `(let ((process-environment (cons "NO_COLOR=true" process-environment))) 116 | (defvar ,process nil) 117 | (defvar ,server nil) 118 | (defvar ,client nil) 119 | 120 | (setq ,server 121 | (websocket-server 122 | ,emacs-port 123 | :host 'local 124 | :on-message (lambda (_websocket frame) 125 | (let ((text (websocket-frame-text frame)) 126 | (opcode (websocket-frame-opcode frame))) 127 | 128 | ;; Only process text frames 129 | (when (eq opcode 'text) 130 | (condition-case err 131 | (let* ((info (json-parse-string text)) 132 | (info-type (gethash "type" info nil))) 133 | (pcase info-type 134 | ("show-message" (message (gethash "content" info nil))) 135 | ("eval-code" (eval (read (gethash "content" info nil)))) 136 | ("fetch-var" (websocket-send-text _websocket (json-encode (eval (read (gethash "content" info nil)))))))) 137 | (json-parse-error 138 | (when emacs-conductor-enable-debug 139 | (message "Received malformed JSON in text frame: %S" text))))))) 140 | 141 | :on-open (lambda (_websocket) 142 | (setq ,client (websocket-open (format "ws://127.0.0.1:%s" ,deno-port)))) 143 | :on-close (lambda (_websocket)))) 144 | ;; Start Deno process. 145 | (setq ,process 146 | (start-process ,app-name ,process-buffer "deno" "run" "--allow-all",ts-path ,app-name ,deno-port ,emacs-port)) 147 | 148 | ;; Make sure ANSI color render correctly. 149 | (set-process-sentinel 150 | ,process 151 | (lambda (p _m) 152 | (when (eq 0 (process-exit-status p)) 153 | (with-current-buffer (process-buffer p) 154 | (ansi-color-apply-on-region (point-min) (point-max)))))) 155 | 156 | (add-to-list 'deno-bridge-app-list ,app-name t))))) 157 | 158 | (defun deno-bridge-exit () 159 | (interactive) 160 | (let* ((app-name (completing-read "[DenoBridge] Exit application: " deno-bridge-app-list))) 161 | (if (member app-name deno-bridge-app-list) 162 | (let* ((server (intern-soft (format "deno-bridge-server-%s" app-name))) 163 | (process (intern-soft (format "deno-bridge-process-%s" app-name))) 164 | (process-buffer (format " *deno-bridge-app-%s*" app-name)) 165 | (client (intern-soft (format "deno-bridge-client-%s" app-name)))) 166 | (when server 167 | (when (symbol-value server) 168 | (websocket-server-close (symbol-value server))) 169 | (makunbound server)) 170 | 171 | (when client 172 | (when (symbol-value client) 173 | (websocket-close (symbol-value client))) 174 | (makunbound client)) 175 | 176 | (let ((old-kill-buffer-query-functions kill-buffer-query-functions)) 177 | (when process 178 | (let ((kill-buffer-query-functions nil)) ; Disable process check temporarily 179 | (kill-buffer process-buffer) 180 | (makunbound process)) 181 | (setq kill-buffer-query-functions old-kill-buffer-query-functions))) 182 | 183 | (setq deno-bridge-app-list (delete app-name deno-bridge-app-list))) 184 | (message "[DenoBridge] Application %s has exited." app-name)))) 185 | 186 | (defun deno-bridge-call (app-name &rest func-args) 187 | "Call Deno TypeScript function from Emacs." 188 | (if (member app-name deno-bridge-app-list) 189 | (websocket-send-text (symbol-value (intern-soft (format "deno-bridge-client-%s" app-name))) 190 | (json-encode (list "data" func-args))) 191 | (message "[DenoBridge] Application %s has exited." app-name))) 192 | 193 | (provide 'deno-bridge) 194 | 195 | ;;; deno-bridge.el ends here 196 | -------------------------------------------------------------------------------- /example/deno-bridge-demo.el: -------------------------------------------------------------------------------- 1 | ;;; deno-bridge-demo.el --- Demo for deno-bridge 2 | 3 | ;; Filename: deno-bridge-demo.el 4 | ;; Description: Demo for deno-bridge 5 | ;; Author: Andy Stewart 6 | ;; Maintainer: Andy Stewart 7 | ;; Copyright (C) 2022, Andy Stewart, all rights reserved. 8 | ;; Created: 2022-10-08 23:38:27 9 | ;; Version: 0.1 10 | ;; Last-Updated: 2022-10-08 23:38:27 11 | ;; By: Andy Stewart 12 | ;; URL: https://www.github.org/manateelazycat/deno-bridge-demo 13 | ;; Keywords: 14 | ;; Compatibility: GNU Emacs 28.2 15 | ;; 16 | ;; Features that might be required by this library: 17 | ;; 18 | ;; 19 | ;; 20 | 21 | ;;; This file is NOT part of GNU Emacs 22 | 23 | ;;; License 24 | ;; 25 | ;; This program is free software; you can redistribute it and/or modify 26 | ;; it under the terms of the GNU General Public License as published by 27 | ;; the Free Software Foundation; either version 3, or (at your option) 28 | ;; any later version. 29 | 30 | ;; This program is distributed in the hope that it will be useful, 31 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 32 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 33 | ;; GNU General Public License for more details. 34 | 35 | ;; You should have received a copy of the GNU General Public License 36 | ;; along with this program; see the file COPYING. If not, write to 37 | ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth 38 | ;; Floor, Boston, MA 02110-1301, USA. 39 | 40 | ;;; Commentary: 41 | ;; 42 | ;; Demo for deno-bridge 43 | ;; 44 | 45 | ;;; Installation: 46 | ;; 47 | ;; Put deno-bridge-demo.el to your load-path. 48 | ;; The load-path is usually ~/elisp/. 49 | ;; It's set in your ~/.emacs like this: 50 | ;; (add-to-list 'load-path (expand-file-name "~/elisp")) 51 | ;; 52 | ;; And the following to your ~/.emacs startup file. 53 | ;; 54 | ;; (require 'deno-bridge-demo) 55 | ;; 56 | ;; No need more. 57 | 58 | ;;; Customize: 59 | ;; 60 | ;; 61 | ;; 62 | ;; All of the above can customize by: 63 | ;; M-x customize-group RET deno-bridge-demo RET 64 | ;; 65 | 66 | ;;; Change log: 67 | ;; 68 | ;; 2022/10/08 69 | ;; * First released. 70 | ;; 71 | 72 | ;;; Acknowledgements: 73 | ;; 74 | ;; 75 | ;; 76 | 77 | ;;; TODO 78 | ;; 79 | ;; 80 | ;; 81 | 82 | ;;; Require 83 | (require 'deno-bridge) 84 | 85 | ;;; Code: 86 | 87 | (setq deno-bridge-demo-ts-path (concat (file-name-directory load-file-name) "deno-bridge-demo.ts")) 88 | (deno-bridge-start "demo" deno-bridge-demo-ts-path) 89 | 90 | ;; (deno-bridge-call "demo" "ping" "Hello from Emacs.") 91 | 92 | (provide 'deno-bridge-demo) 93 | 94 | ;;; deno-bridge-demo.el ends here 95 | -------------------------------------------------------------------------------- /example/deno-bridge-demo.ts: -------------------------------------------------------------------------------- 1 | import { DenoBridge } from "https://deno.land/x/denobridge@0.0.1/mod.ts" 2 | 3 | const bridge = new DenoBridge(Deno.args[0], Deno.args[1], Deno.args[2], messageDispatcher) 4 | 5 | async function messageDispatcher(message: string) { 6 | const [funcName, funcArgs] = JSON.parse(message)[1] 7 | 8 | if (funcName == "ping") { 9 | console.log("Emacs message: ", funcArgs) 10 | 11 | const emacsVar = await bridge.getEmacsVar("deno-bridge-app-list") 12 | console.log("Emacs var 'deno-bridge-app-list': ", emacsVar) 13 | 14 | bridge.messageToEmacs("Hi from TypeScript") 15 | 16 | bridge.evalInEmacs('(message \"Eval from TypeScript\")') 17 | } 18 | } 19 | --------------------------------------------------------------------------------