├── 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 |
--------------------------------------------------------------------------------