@xinleibird/bridge-opencode 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @xinleibird/bridge-opencode might be problematic. Click here for more details.
- package/.cargo/config.toml +3 -0
- package/Cargo.lock +386 -0
- package/Cargo.toml +26 -0
- package/LICENSE +21 -0
- package/README.md +39 -0
- package/bridge-opencode.darwin-arm64.node +0 -0
- package/bridge.ts +128 -0
- package/build.rs +5 -0
- package/package.json +35 -0
- package/src/action/neovim/buffer.rs +90 -0
- package/src/action/neovim/connection.rs +53 -0
- package/src/action/neovim/lua.rs +74 -0
- package/src/action/neovim.rs +74 -0
- package/src/action.rs +23 -0
- package/src/constants.rs +3 -0
- package/src/handler.rs +26 -0
- package/src/lib.rs +88 -0
- package/src/utils.rs +11 -0
package/Cargo.lock
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 4
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "aho-corasick"
|
|
7
|
+
version = "1.1.4"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"memchr",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[[package]]
|
|
15
|
+
name = "anyhow"
|
|
16
|
+
version = "1.0.100"
|
|
17
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
18
|
+
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
|
19
|
+
|
|
20
|
+
[[package]]
|
|
21
|
+
name = "autocfg"
|
|
22
|
+
version = "1.5.1"
|
|
23
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
24
|
+
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
|
25
|
+
|
|
26
|
+
[[package]]
|
|
27
|
+
name = "bitflags"
|
|
28
|
+
version = "2.11.1"
|
|
29
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
30
|
+
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
|
31
|
+
|
|
32
|
+
[[package]]
|
|
33
|
+
name = "bridge-opencode"
|
|
34
|
+
version = "0.0.1"
|
|
35
|
+
dependencies = [
|
|
36
|
+
"anyhow",
|
|
37
|
+
"glob",
|
|
38
|
+
"napi",
|
|
39
|
+
"napi-build",
|
|
40
|
+
"napi-derive",
|
|
41
|
+
"neovim-lib",
|
|
42
|
+
"rmp",
|
|
43
|
+
"serde",
|
|
44
|
+
"serde_json",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[[package]]
|
|
48
|
+
name = "byteorder"
|
|
49
|
+
version = "1.5.0"
|
|
50
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
51
|
+
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|
52
|
+
|
|
53
|
+
[[package]]
|
|
54
|
+
name = "cfg-if"
|
|
55
|
+
version = "0.1.10"
|
|
56
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
57
|
+
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
|
58
|
+
|
|
59
|
+
[[package]]
|
|
60
|
+
name = "cfg-if"
|
|
61
|
+
version = "1.0.4"
|
|
62
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
63
|
+
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
64
|
+
|
|
65
|
+
[[package]]
|
|
66
|
+
name = "convert_case"
|
|
67
|
+
version = "0.6.0"
|
|
68
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
69
|
+
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
|
|
70
|
+
dependencies = [
|
|
71
|
+
"unicode-segmentation",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
[[package]]
|
|
75
|
+
name = "ctor"
|
|
76
|
+
version = "0.2.9"
|
|
77
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
78
|
+
checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
|
|
79
|
+
dependencies = [
|
|
80
|
+
"quote",
|
|
81
|
+
"syn",
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
[[package]]
|
|
85
|
+
name = "glob"
|
|
86
|
+
version = "0.3.3"
|
|
87
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
88
|
+
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
|
89
|
+
|
|
90
|
+
[[package]]
|
|
91
|
+
name = "itoa"
|
|
92
|
+
version = "1.0.18"
|
|
93
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
94
|
+
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
|
95
|
+
|
|
96
|
+
[[package]]
|
|
97
|
+
name = "libc"
|
|
98
|
+
version = "0.2.186"
|
|
99
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
100
|
+
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
|
101
|
+
|
|
102
|
+
[[package]]
|
|
103
|
+
name = "libloading"
|
|
104
|
+
version = "0.8.9"
|
|
105
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
106
|
+
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
|
107
|
+
dependencies = [
|
|
108
|
+
"cfg-if 1.0.4",
|
|
109
|
+
"windows-link",
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
[[package]]
|
|
113
|
+
name = "log"
|
|
114
|
+
version = "0.4.29"
|
|
115
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
116
|
+
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
|
117
|
+
|
|
118
|
+
[[package]]
|
|
119
|
+
name = "memchr"
|
|
120
|
+
version = "2.8.0"
|
|
121
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
122
|
+
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
|
123
|
+
|
|
124
|
+
[[package]]
|
|
125
|
+
name = "napi"
|
|
126
|
+
version = "2.16.17"
|
|
127
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
128
|
+
checksum = "55740c4ae1d8696773c78fdafd5d0e5fe9bc9f1b071c7ba493ba5c413a9184f3"
|
|
129
|
+
dependencies = [
|
|
130
|
+
"bitflags",
|
|
131
|
+
"ctor",
|
|
132
|
+
"napi-derive",
|
|
133
|
+
"napi-sys",
|
|
134
|
+
"once_cell",
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
[[package]]
|
|
138
|
+
name = "napi-build"
|
|
139
|
+
version = "2.3.2"
|
|
140
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
141
|
+
checksum = "c9c366d2c8c60b86fa632df75f745509b52f9128f91a6bad4c796e44abb505e1"
|
|
142
|
+
|
|
143
|
+
[[package]]
|
|
144
|
+
name = "napi-derive"
|
|
145
|
+
version = "2.16.13"
|
|
146
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
147
|
+
checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c"
|
|
148
|
+
dependencies = [
|
|
149
|
+
"cfg-if 1.0.4",
|
|
150
|
+
"convert_case",
|
|
151
|
+
"napi-derive-backend",
|
|
152
|
+
"proc-macro2",
|
|
153
|
+
"quote",
|
|
154
|
+
"syn",
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
[[package]]
|
|
158
|
+
name = "napi-derive-backend"
|
|
159
|
+
version = "1.0.75"
|
|
160
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
161
|
+
checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf"
|
|
162
|
+
dependencies = [
|
|
163
|
+
"convert_case",
|
|
164
|
+
"once_cell",
|
|
165
|
+
"proc-macro2",
|
|
166
|
+
"quote",
|
|
167
|
+
"regex",
|
|
168
|
+
"semver",
|
|
169
|
+
"syn",
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
[[package]]
|
|
173
|
+
name = "napi-sys"
|
|
174
|
+
version = "2.4.0"
|
|
175
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
176
|
+
checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3"
|
|
177
|
+
dependencies = [
|
|
178
|
+
"libloading",
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
[[package]]
|
|
182
|
+
name = "neovim-lib"
|
|
183
|
+
version = "0.6.1"
|
|
184
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
185
|
+
checksum = "d6a8f5a1e1be160ce2b669c2c495a34ade6f3a525d4afafd7370c1792070f587"
|
|
186
|
+
dependencies = [
|
|
187
|
+
"log",
|
|
188
|
+
"rmp",
|
|
189
|
+
"rmpv",
|
|
190
|
+
"unix_socket",
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
[[package]]
|
|
194
|
+
name = "num-traits"
|
|
195
|
+
version = "0.2.19"
|
|
196
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
197
|
+
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
|
198
|
+
dependencies = [
|
|
199
|
+
"autocfg",
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
[[package]]
|
|
203
|
+
name = "once_cell"
|
|
204
|
+
version = "1.21.4"
|
|
205
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
206
|
+
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
207
|
+
|
|
208
|
+
[[package]]
|
|
209
|
+
name = "paste"
|
|
210
|
+
version = "1.0.15"
|
|
211
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
212
|
+
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
|
213
|
+
|
|
214
|
+
[[package]]
|
|
215
|
+
name = "proc-macro2"
|
|
216
|
+
version = "1.0.105"
|
|
217
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
218
|
+
checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
|
|
219
|
+
dependencies = [
|
|
220
|
+
"unicode-ident",
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
[[package]]
|
|
224
|
+
name = "quote"
|
|
225
|
+
version = "1.0.45"
|
|
226
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
227
|
+
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
|
228
|
+
dependencies = [
|
|
229
|
+
"proc-macro2",
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
[[package]]
|
|
233
|
+
name = "regex"
|
|
234
|
+
version = "1.12.3"
|
|
235
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
236
|
+
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
|
237
|
+
dependencies = [
|
|
238
|
+
"aho-corasick",
|
|
239
|
+
"memchr",
|
|
240
|
+
"regex-automata",
|
|
241
|
+
"regex-syntax",
|
|
242
|
+
]
|
|
243
|
+
|
|
244
|
+
[[package]]
|
|
245
|
+
name = "regex-automata"
|
|
246
|
+
version = "0.4.14"
|
|
247
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
248
|
+
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
|
249
|
+
dependencies = [
|
|
250
|
+
"aho-corasick",
|
|
251
|
+
"memchr",
|
|
252
|
+
"regex-syntax",
|
|
253
|
+
]
|
|
254
|
+
|
|
255
|
+
[[package]]
|
|
256
|
+
name = "regex-syntax"
|
|
257
|
+
version = "0.8.10"
|
|
258
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
259
|
+
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
|
260
|
+
|
|
261
|
+
[[package]]
|
|
262
|
+
name = "rmp"
|
|
263
|
+
version = "0.8.14"
|
|
264
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
265
|
+
checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4"
|
|
266
|
+
dependencies = [
|
|
267
|
+
"byteorder",
|
|
268
|
+
"num-traits",
|
|
269
|
+
"paste",
|
|
270
|
+
]
|
|
271
|
+
|
|
272
|
+
[[package]]
|
|
273
|
+
name = "rmpv"
|
|
274
|
+
version = "0.4.7"
|
|
275
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
276
|
+
checksum = "7c760afe11955e16121e36485b6b828326c3f0eaff1c31758d96dbeb5cf09fd5"
|
|
277
|
+
dependencies = [
|
|
278
|
+
"num-traits",
|
|
279
|
+
"rmp",
|
|
280
|
+
"serde",
|
|
281
|
+
"serde_bytes",
|
|
282
|
+
]
|
|
283
|
+
|
|
284
|
+
[[package]]
|
|
285
|
+
name = "semver"
|
|
286
|
+
version = "1.0.28"
|
|
287
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
288
|
+
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
|
289
|
+
|
|
290
|
+
[[package]]
|
|
291
|
+
name = "serde"
|
|
292
|
+
version = "1.0.228"
|
|
293
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
294
|
+
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
|
295
|
+
dependencies = [
|
|
296
|
+
"serde_core",
|
|
297
|
+
"serde_derive",
|
|
298
|
+
]
|
|
299
|
+
|
|
300
|
+
[[package]]
|
|
301
|
+
name = "serde_bytes"
|
|
302
|
+
version = "0.11.19"
|
|
303
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
304
|
+
checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8"
|
|
305
|
+
dependencies = [
|
|
306
|
+
"serde",
|
|
307
|
+
"serde_core",
|
|
308
|
+
]
|
|
309
|
+
|
|
310
|
+
[[package]]
|
|
311
|
+
name = "serde_core"
|
|
312
|
+
version = "1.0.228"
|
|
313
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
314
|
+
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
|
315
|
+
dependencies = [
|
|
316
|
+
"serde_derive",
|
|
317
|
+
]
|
|
318
|
+
|
|
319
|
+
[[package]]
|
|
320
|
+
name = "serde_derive"
|
|
321
|
+
version = "1.0.228"
|
|
322
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
323
|
+
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
|
324
|
+
dependencies = [
|
|
325
|
+
"proc-macro2",
|
|
326
|
+
"quote",
|
|
327
|
+
"syn",
|
|
328
|
+
]
|
|
329
|
+
|
|
330
|
+
[[package]]
|
|
331
|
+
name = "serde_json"
|
|
332
|
+
version = "1.0.150"
|
|
333
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
334
|
+
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
|
335
|
+
dependencies = [
|
|
336
|
+
"itoa",
|
|
337
|
+
"memchr",
|
|
338
|
+
"serde",
|
|
339
|
+
"serde_core",
|
|
340
|
+
"zmij",
|
|
341
|
+
]
|
|
342
|
+
|
|
343
|
+
[[package]]
|
|
344
|
+
name = "syn"
|
|
345
|
+
version = "2.0.114"
|
|
346
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
347
|
+
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
|
348
|
+
dependencies = [
|
|
349
|
+
"proc-macro2",
|
|
350
|
+
"quote",
|
|
351
|
+
"unicode-ident",
|
|
352
|
+
]
|
|
353
|
+
|
|
354
|
+
[[package]]
|
|
355
|
+
name = "unicode-ident"
|
|
356
|
+
version = "1.0.22"
|
|
357
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
358
|
+
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
|
359
|
+
|
|
360
|
+
[[package]]
|
|
361
|
+
name = "unicode-segmentation"
|
|
362
|
+
version = "1.13.2"
|
|
363
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
364
|
+
checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
|
|
365
|
+
|
|
366
|
+
[[package]]
|
|
367
|
+
name = "unix_socket"
|
|
368
|
+
version = "0.5.0"
|
|
369
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
370
|
+
checksum = "6aa2700417c405c38f5e6902d699345241c28c0b7ade4abaad71e35a87eb1564"
|
|
371
|
+
dependencies = [
|
|
372
|
+
"cfg-if 0.1.10",
|
|
373
|
+
"libc",
|
|
374
|
+
]
|
|
375
|
+
|
|
376
|
+
[[package]]
|
|
377
|
+
name = "windows-link"
|
|
378
|
+
version = "0.2.1"
|
|
379
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
380
|
+
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
|
381
|
+
|
|
382
|
+
[[package]]
|
|
383
|
+
name = "zmij"
|
|
384
|
+
version = "1.0.21"
|
|
385
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
386
|
+
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
package/Cargo.toml
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "bridge-opencode"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
edition = "2024"
|
|
5
|
+
|
|
6
|
+
[lib]
|
|
7
|
+
crate-type = ["cdylib", "lib"]
|
|
8
|
+
|
|
9
|
+
[build-dependencies]
|
|
10
|
+
napi-build = "2"
|
|
11
|
+
|
|
12
|
+
[features]
|
|
13
|
+
default = []
|
|
14
|
+
napi = ["dep:napi", "dep:napi-derive"]
|
|
15
|
+
|
|
16
|
+
[dependencies]
|
|
17
|
+
serde = { version = "1.0", features = ["derive"] }
|
|
18
|
+
serde_json = "1.0"
|
|
19
|
+
anyhow = "1.0"
|
|
20
|
+
neovim-lib = "0.6"
|
|
21
|
+
glob = "0.3"
|
|
22
|
+
napi = { version = "2", default-features = false, features = ["napi6"], optional = true }
|
|
23
|
+
napi-derive = { version = "2", optional = true }
|
|
24
|
+
|
|
25
|
+
# Pin rmp to avoid breaking changes in 0.8.15 that break rmpv 0.4.7 (used by neovim-lib)
|
|
26
|
+
rmp = "=0.8.14"
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Nishant Joshi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# bridge-opencode
|
|
2
|
+
|
|
3
|
+
Bridge between opencode and Neovim.
|
|
4
|
+
|
|
5
|
+
Inspired by / forked from [sidekick](https://github.com/NishantJoshi00/sidekick) — thanks [@NishantJoshi00](https://github.com/NishantJoshi00).
|
|
6
|
+
|
|
7
|
+
## Structure
|
|
8
|
+
|
|
9
|
+
- `plugin/bridge.lua` — Neovim plugin, starts msgpack-rpc socket
|
|
10
|
+
- `bridge.ts` — opencode plugin entry (npm package main)
|
|
11
|
+
- `src/` — Rust source (napi-rs native addon)
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
### Neovim plugin (lazy.nvim)
|
|
16
|
+
|
|
17
|
+
```lua
|
|
18
|
+
{
|
|
19
|
+
"xinleibird/bridge-opencode",
|
|
20
|
+
priority = 1000,
|
|
21
|
+
lazy = false,
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`plugin/bridge.lua` auto-starts the RPC socket when Neovim launches.
|
|
26
|
+
|
|
27
|
+
### opencode plugin
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
cd ~/.config/opencode && npm install @xinleibird/bridge-opencode
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"plugin": ["@xinleibird/bridge-opencode"]
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The `postinstall` script compiles the Rust native addon via napi-rs.
|
|
Binary file
|
package/bridge.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
2
|
+
import { isAbsolute, join } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
checkBuffer,
|
|
5
|
+
refreshBuffer,
|
|
6
|
+
getVisualSelections,
|
|
7
|
+
} from "./bridge-opencode.js";
|
|
8
|
+
|
|
9
|
+
type ToolName = "Edit" | "Write";
|
|
10
|
+
|
|
11
|
+
const TOOL_MAP: Record<string, ToolName> = {
|
|
12
|
+
edit: "Edit",
|
|
13
|
+
write: "Write",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const PATCH_FILE_MARKERS = [
|
|
17
|
+
"*** Add File:",
|
|
18
|
+
"*** Update File:",
|
|
19
|
+
"*** Delete File:",
|
|
20
|
+
"*** Move to:",
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
function pickFilePath(args: unknown): string | null {
|
|
24
|
+
if (!args || typeof args !== "object") return null;
|
|
25
|
+
const candidate = (args as Record<string, unknown>).filePath;
|
|
26
|
+
return typeof candidate === "string" && candidate.length > 0
|
|
27
|
+
? candidate
|
|
28
|
+
: null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function pickPatchPaths(args: unknown): string[] {
|
|
32
|
+
if (!args || typeof args !== "object") return [];
|
|
33
|
+
const patch = (args as Record<string, unknown>).patchText;
|
|
34
|
+
if (typeof patch !== "string") return [];
|
|
35
|
+
|
|
36
|
+
const paths: string[] = [];
|
|
37
|
+
for (const line of patch.split("\n")) {
|
|
38
|
+
const trimmed = line.trimStart();
|
|
39
|
+
for (const marker of PATCH_FILE_MARKERS) {
|
|
40
|
+
if (trimmed.startsWith(marker)) {
|
|
41
|
+
const candidate = trimmed.slice(marker.length).trim();
|
|
42
|
+
if (candidate) paths.push(candidate);
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return paths;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function resolveCall(
|
|
51
|
+
tool: string,
|
|
52
|
+
args: unknown,
|
|
53
|
+
cwd: string,
|
|
54
|
+
): { filePaths: string[] } | null {
|
|
55
|
+
const abs = (p: string) => (isAbsolute(p) ? p : join(cwd, p));
|
|
56
|
+
|
|
57
|
+
if (tool === "apply_patch") {
|
|
58
|
+
return { filePaths: pickPatchPaths(args).map(abs) };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const toolName = TOOL_MAP[tool];
|
|
62
|
+
if (!toolName) return null;
|
|
63
|
+
|
|
64
|
+
const raw = pickFilePath(args);
|
|
65
|
+
return { filePaths: raw ? [abs(raw)] : [] };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const BridgePlugin: Plugin = async ({ directory }) => {
|
|
69
|
+
const cwd = directory ?? process.cwd();
|
|
70
|
+
const pendingByCallID = new Map<string, { filePaths: string[] }>();
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
"tool.execute.before": async (input, output) => {
|
|
74
|
+
const call = resolveCall(input.tool, output.args, cwd);
|
|
75
|
+
if (!call || call.filePaths.length === 0) return;
|
|
76
|
+
|
|
77
|
+
for (const filePath of call.filePaths) {
|
|
78
|
+
const status = await checkBuffer(filePath);
|
|
79
|
+
if (status.hasUnsavedChanges && status.isCurrent) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
"bridge: file has unsaved changes in Neovim",
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
pendingByCallID.set(input.callID, call);
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
"tool.execute.after": async (input) => {
|
|
90
|
+
const pending = pendingByCallID.get(input.callID);
|
|
91
|
+
if (!pending) return;
|
|
92
|
+
pendingByCallID.delete(input.callID);
|
|
93
|
+
|
|
94
|
+
for (const filePath of pending.filePaths) {
|
|
95
|
+
await refreshBuffer(filePath);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
"chat.message": async (_input, output) => {
|
|
100
|
+
let selections;
|
|
101
|
+
try {
|
|
102
|
+
selections = await getVisualSelections();
|
|
103
|
+
} catch {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (!selections || selections.length === 0) return;
|
|
107
|
+
|
|
108
|
+
const filteredSelections = selections.filter(
|
|
109
|
+
(s) => !s.cwd || s.cwd === cwd,
|
|
110
|
+
);
|
|
111
|
+
if (filteredSelections.length === 0) return;
|
|
112
|
+
|
|
113
|
+
const textPart = output.parts.find((p: any) => p.type === "text") as any;
|
|
114
|
+
if (!textPart || typeof textPart.text !== "string") return;
|
|
115
|
+
|
|
116
|
+
const lines = filteredSelections.map((s) => {
|
|
117
|
+
const path = s.filePath.startsWith(cwd + "/")
|
|
118
|
+
? "@" + s.filePath.slice(cwd.length + 1)
|
|
119
|
+
: s.filePath;
|
|
120
|
+
return `${path}:${s.startLine}-${s.endLine}`;
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
textPart.text = `${lines.join("\n")}\n\n${textPart.text}`;
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export default BridgePlugin;
|
package/build.rs
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xinleibird/bridge-opencode",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "bridge.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./bridge.ts",
|
|
12
|
+
"default": "./bridge.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "napi build --platform --release",
|
|
17
|
+
"postinstall": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"napi": {
|
|
20
|
+
"name": "bridge-opencode",
|
|
21
|
+
"triples": {
|
|
22
|
+
"defaults": false,
|
|
23
|
+
"additional": [
|
|
24
|
+
"aarch64-apple-darwin",
|
|
25
|
+
"x86_64-apple-darwin"
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@napi-rs/cli": "^2.18.0"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@opencode-ai/plugin": ">=1.0.0"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
use super::lua;
|
|
2
|
+
use crate::action::{BufferStatus, EditorContext};
|
|
3
|
+
use anyhow::{Context, Result};
|
|
4
|
+
use neovim_lib::{Neovim, NeovimApi, neovim_api::Buffer};
|
|
5
|
+
use std::path::PathBuf;
|
|
6
|
+
|
|
7
|
+
pub fn find_buffer(nvim: &mut Neovim, file_path: &str) -> Result<Buffer> {
|
|
8
|
+
let buffers = nvim.list_bufs().context("couldn't list buffers")?;
|
|
9
|
+
|
|
10
|
+
let target_path = PathBuf::from(file_path)
|
|
11
|
+
.canonicalize()
|
|
12
|
+
.unwrap_or_else(|_| PathBuf::from(file_path));
|
|
13
|
+
|
|
14
|
+
for buffer in buffers {
|
|
15
|
+
let buf_name = buffer.get_name(nvim).context("couldn't read buffer name")?;
|
|
16
|
+
|
|
17
|
+
if buf_name.is_empty() {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let buf_path = PathBuf::from(&buf_name)
|
|
22
|
+
.canonicalize()
|
|
23
|
+
.unwrap_or_else(|_| PathBuf::from(&buf_name));
|
|
24
|
+
|
|
25
|
+
if buf_path == target_path {
|
|
26
|
+
return Ok(buffer);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
anyhow::bail!("file not open in Neovim: {}", file_path)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
pub fn get_buffer_status(nvim: &mut Neovim, file_path: &str) -> Result<BufferStatus> {
|
|
34
|
+
let buffer = find_buffer(nvim, file_path)?;
|
|
35
|
+
let current_buf = nvim.get_current_buf()?;
|
|
36
|
+
let is_current = buffer == current_buf;
|
|
37
|
+
|
|
38
|
+
let modified = buffer.get_option(nvim, "modified")?;
|
|
39
|
+
let has_unsaved_changes = modified.as_bool().unwrap_or(false);
|
|
40
|
+
|
|
41
|
+
Ok(BufferStatus {
|
|
42
|
+
is_current,
|
|
43
|
+
has_unsaved_changes,
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
pub fn refresh_buffer(nvim: &mut Neovim, file_path: &str) -> Result<()> {
|
|
48
|
+
let buffer = find_buffer(nvim, file_path)?;
|
|
49
|
+
let buf_number = buffer.get_number(nvim)?;
|
|
50
|
+
|
|
51
|
+
let lua_code = lua::refresh_buffer_lua(buf_number);
|
|
52
|
+
|
|
53
|
+
nvim.execute_lua(&lua_code, vec![])
|
|
54
|
+
.map(|_| ())
|
|
55
|
+
.context("couldn't reload buffer")
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
pub fn get_visual_selection(nvim: &mut Neovim) -> Result<Option<EditorContext>> {
|
|
59
|
+
let lua_code = lua::get_visual_selection_lua();
|
|
60
|
+
|
|
61
|
+
let result = nvim
|
|
62
|
+
.execute_lua(lua_code, vec![])
|
|
63
|
+
.context("couldn't read visual selection")?;
|
|
64
|
+
|
|
65
|
+
if result.is_nil() {
|
|
66
|
+
return Ok(None);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let json_str = result.as_str().context("unexpected response from Neovim")?;
|
|
70
|
+
|
|
71
|
+
#[derive(serde::Deserialize)]
|
|
72
|
+
struct SelectionData {
|
|
73
|
+
file_path: String,
|
|
74
|
+
start_line: u32,
|
|
75
|
+
end_line: u32,
|
|
76
|
+
cwd: String,
|
|
77
|
+
content: String,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let data: SelectionData =
|
|
81
|
+
serde_json::from_str(json_str).context("couldn't parse visual selection")?;
|
|
82
|
+
|
|
83
|
+
Ok(Some(EditorContext {
|
|
84
|
+
file_path: data.file_path,
|
|
85
|
+
start_line: data.start_line,
|
|
86
|
+
end_line: data.end_line,
|
|
87
|
+
cwd: data.cwd,
|
|
88
|
+
content: data.content,
|
|
89
|
+
}))
|
|
90
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
use crate::constants::NEOVIM_RPC_TIMEOUT;
|
|
2
|
+
use anyhow::{Context, Result};
|
|
3
|
+
use neovim_lib::{Neovim, Session};
|
|
4
|
+
use std::path::PathBuf;
|
|
5
|
+
|
|
6
|
+
pub fn connect(socket_path: &PathBuf) -> Result<Neovim> {
|
|
7
|
+
let mut session =
|
|
8
|
+
Session::new_unix_socket(socket_path).context("couldn't connect to Neovim")?;
|
|
9
|
+
session.set_timeout(NEOVIM_RPC_TIMEOUT);
|
|
10
|
+
session.start_event_loop();
|
|
11
|
+
Ok(Neovim::new(session))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
pub fn for_each_instance<F>(socket_paths: &[PathBuf], mut f: F) -> bool
|
|
15
|
+
where
|
|
16
|
+
F: FnMut(&mut Neovim) -> Result<()>,
|
|
17
|
+
{
|
|
18
|
+
socket_paths
|
|
19
|
+
.iter()
|
|
20
|
+
.filter_map(|path| connect(path).ok())
|
|
21
|
+
.any(|mut nvim| f(&mut nvim).is_ok())
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
pub fn try_fold_instances<T, F>(socket_paths: &[PathBuf], init: T, mut f: F) -> Option<T>
|
|
25
|
+
where
|
|
26
|
+
F: FnMut(&mut T, &mut Neovim) -> Result<bool>,
|
|
27
|
+
{
|
|
28
|
+
let mut any_processed = false;
|
|
29
|
+
|
|
30
|
+
let result = socket_paths
|
|
31
|
+
.iter()
|
|
32
|
+
.filter_map(|path| connect(path).ok())
|
|
33
|
+
.try_fold(init, |mut acc, mut nvim| match f(&mut acc, &mut nvim) {
|
|
34
|
+
Ok(should_continue) => {
|
|
35
|
+
any_processed = true;
|
|
36
|
+
if should_continue { Ok(acc) } else { Err(acc) }
|
|
37
|
+
}
|
|
38
|
+
Err(_) => Ok(acc),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
any_processed.then(|| result.unwrap_or_else(|acc| acc))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
pub fn collect_all<T, F>(socket_paths: &[PathBuf], mut f: F) -> Vec<T>
|
|
45
|
+
where
|
|
46
|
+
F: FnMut(&mut Neovim) -> Result<Option<T>>,
|
|
47
|
+
{
|
|
48
|
+
socket_paths
|
|
49
|
+
.iter()
|
|
50
|
+
.filter_map(|path| connect(path).ok())
|
|
51
|
+
.filter_map(|mut nvim| f(&mut nvim).ok().flatten())
|
|
52
|
+
.collect()
|
|
53
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
pub fn refresh_buffer_lua(buf_number: i64) -> String {
|
|
2
|
+
format!(
|
|
3
|
+
r#"
|
|
4
|
+
local buf = {}
|
|
5
|
+
local cursor_positions = {{}}
|
|
6
|
+
local is_current_buf = vim.api.nvim_get_current_buf() == buf
|
|
7
|
+
|
|
8
|
+
for _, win in ipairs(vim.api.nvim_list_wins()) do
|
|
9
|
+
if vim.api.nvim_win_get_buf(win) == buf then
|
|
10
|
+
cursor_positions[win] = vim.api.nvim_win_get_cursor(win)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
vim.api.nvim_buf_call(buf, function()
|
|
15
|
+
vim.cmd('checktime')
|
|
16
|
+
vim.cmd('edit')
|
|
17
|
+
end)
|
|
18
|
+
|
|
19
|
+
for win, pos in pairs(cursor_positions) do
|
|
20
|
+
if vim.api.nvim_win_is_valid(win) then
|
|
21
|
+
pcall(vim.api.nvim_win_set_cursor, win, pos)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
if is_current_buf then
|
|
26
|
+
vim.cmd('redraw')
|
|
27
|
+
end
|
|
28
|
+
"#,
|
|
29
|
+
buf_number
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
pub fn send_notification_lua(message: &str) -> String {
|
|
34
|
+
format!(
|
|
35
|
+
r#"vim.notify("{}", vim.log.levels.WARN)"#,
|
|
36
|
+
message.replace('"', r#"\""#)
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
pub fn get_visual_selection_lua() -> &'static str {
|
|
41
|
+
r#"
|
|
42
|
+
local mode = vim.fn.mode()
|
|
43
|
+
if not mode:match('[vV\22]') then
|
|
44
|
+
return nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
local start_pos = vim.fn.getpos("v")
|
|
48
|
+
local end_pos = vim.fn.getpos(".")
|
|
49
|
+
local sel_type = mode:sub(1, 1)
|
|
50
|
+
|
|
51
|
+
if start_pos[2] == 0 or end_pos[2] == 0 then
|
|
52
|
+
return nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
local file_path = vim.api.nvim_buf_get_name(0)
|
|
56
|
+
if file_path == "" then
|
|
57
|
+
return nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
local lines = vim.fn.getregion(start_pos, end_pos, { type = sel_type })
|
|
61
|
+
local content = table.concat(lines, "\n")
|
|
62
|
+
|
|
63
|
+
local start_line = math.min(start_pos[2], end_pos[2])
|
|
64
|
+
local end_line = math.max(start_pos[2], end_pos[2])
|
|
65
|
+
|
|
66
|
+
return vim.fn.json_encode({
|
|
67
|
+
file_path = file_path,
|
|
68
|
+
start_line = start_line,
|
|
69
|
+
end_line = end_line,
|
|
70
|
+
content = content,
|
|
71
|
+
cwd = vim.fn.getcwd()
|
|
72
|
+
})
|
|
73
|
+
"#
|
|
74
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
mod buffer;
|
|
2
|
+
mod connection;
|
|
3
|
+
mod lua;
|
|
4
|
+
|
|
5
|
+
use crate::action::{Action, BufferStatus, EditorContext};
|
|
6
|
+
use anyhow::Result;
|
|
7
|
+
use neovim_lib::NeovimApi;
|
|
8
|
+
use std::path::PathBuf;
|
|
9
|
+
|
|
10
|
+
pub struct NeovimAction {
|
|
11
|
+
socket_paths: Vec<PathBuf>,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
impl NeovimAction {
|
|
15
|
+
pub fn new(socket_paths: Vec<PathBuf>) -> Self {
|
|
16
|
+
Self { socket_paths }
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
impl Action for NeovimAction {
|
|
21
|
+
fn buffer_status(&self, file_path: &str) -> Result<BufferStatus> {
|
|
22
|
+
let status = connection::try_fold_instances(
|
|
23
|
+
&self.socket_paths,
|
|
24
|
+
(false, false),
|
|
25
|
+
|(is_current_acc, unsaved_acc), nvim| {
|
|
26
|
+
let status = buffer::get_buffer_status(nvim, file_path)?;
|
|
27
|
+
|
|
28
|
+
*is_current_acc = *is_current_acc || status.is_current;
|
|
29
|
+
*unsaved_acc = *unsaved_acc || status.has_unsaved_changes;
|
|
30
|
+
|
|
31
|
+
Ok(!*unsaved_acc)
|
|
32
|
+
},
|
|
33
|
+
)
|
|
34
|
+
.unwrap_or((false, false));
|
|
35
|
+
|
|
36
|
+
Ok(BufferStatus {
|
|
37
|
+
is_current: status.0,
|
|
38
|
+
has_unsaved_changes: status.1,
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fn refresh_buffer(&self, file_path: &str) -> Result<()> {
|
|
43
|
+
let any_success = connection::for_each_instance(&self.socket_paths, |nvim| {
|
|
44
|
+
buffer::refresh_buffer(nvim, file_path)
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if any_success {
|
|
48
|
+
Ok(())
|
|
49
|
+
} else {
|
|
50
|
+
anyhow::bail!("couldn't refresh Neovim")
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
fn send_message(&self, message: &str) -> Result<()> {
|
|
55
|
+
let lua_code = lua::send_notification_lua(message);
|
|
56
|
+
let any_success = connection::for_each_instance(&self.socket_paths, |nvim| {
|
|
57
|
+
nvim.execute_lua(&lua_code, vec![])
|
|
58
|
+
.map(|_| ())
|
|
59
|
+
.map_err(|e| anyhow::anyhow!("couldn't send to Neovim: {}", e))
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if any_success {
|
|
63
|
+
Ok(())
|
|
64
|
+
} else {
|
|
65
|
+
anyhow::bail!("couldn't send to Neovim")
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fn get_visual_selections(&self) -> Result<Vec<EditorContext>> {
|
|
70
|
+
Ok(connection::collect_all(&self.socket_paths, |nvim| {
|
|
71
|
+
buffer::get_visual_selection(nvim)
|
|
72
|
+
}))
|
|
73
|
+
}
|
|
74
|
+
}
|
package/src/action.rs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
pub mod neovim;
|
|
2
|
+
|
|
3
|
+
#[derive(Debug, Clone)]
|
|
4
|
+
pub struct BufferStatus {
|
|
5
|
+
pub is_current: bool,
|
|
6
|
+
pub has_unsaved_changes: bool,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
#[derive(Debug, Clone)]
|
|
10
|
+
pub struct EditorContext {
|
|
11
|
+
pub file_path: String,
|
|
12
|
+
pub start_line: u32,
|
|
13
|
+
pub end_line: u32,
|
|
14
|
+
pub cwd: String,
|
|
15
|
+
pub content: String,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
pub trait Action {
|
|
19
|
+
fn buffer_status(&self, file_path: &str) -> anyhow::Result<BufferStatus>;
|
|
20
|
+
fn refresh_buffer(&self, file_path: &str) -> anyhow::Result<()>;
|
|
21
|
+
fn send_message(&self, message: &str) -> anyhow::Result<()>;
|
|
22
|
+
fn get_visual_selections(&self) -> anyhow::Result<Vec<EditorContext>>;
|
|
23
|
+
}
|
package/src/constants.rs
ADDED
package/src/handler.rs
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
use crate::action::{BufferStatus, EditorContext, Action, neovim::NeovimAction};
|
|
2
|
+
use crate::utils;
|
|
3
|
+
|
|
4
|
+
pub fn connect() -> anyhow::Result<NeovimAction> {
|
|
5
|
+
let socket_paths = utils::find_matching_sockets()?;
|
|
6
|
+
if socket_paths.is_empty() {
|
|
7
|
+
anyhow::bail!("no Neovim instances found");
|
|
8
|
+
}
|
|
9
|
+
Ok(NeovimAction::new(socket_paths))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
pub fn check_buffer(action: &NeovimAction, file_path: &str) -> anyhow::Result<BufferStatus> {
|
|
13
|
+
action.buffer_status(file_path)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
pub fn refresh_buffer(action: &NeovimAction, file_path: &str) -> anyhow::Result<()> {
|
|
17
|
+
action.refresh_buffer(file_path)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
pub fn get_visual_selections(action: &NeovimAction) -> anyhow::Result<Vec<EditorContext>> {
|
|
21
|
+
action.get_visual_selections()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
pub fn send_message(action: &NeovimAction, message: &str) -> anyhow::Result<()> {
|
|
25
|
+
action.send_message(message)
|
|
26
|
+
}
|
package/src/lib.rs
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
pub mod action;
|
|
2
|
+
pub mod constants;
|
|
3
|
+
pub mod handler;
|
|
4
|
+
pub mod utils;
|
|
5
|
+
|
|
6
|
+
#[cfg(feature = "napi")]
|
|
7
|
+
mod bindings {
|
|
8
|
+
use crate::action::neovim::NeovimAction;
|
|
9
|
+
use crate::action::EditorContext as InternalEditorContext;
|
|
10
|
+
use crate::handler;
|
|
11
|
+
use napi::bindgen_prelude::*;
|
|
12
|
+
use napi_derive::napi;
|
|
13
|
+
|
|
14
|
+
fn connect_nvim() -> Result<NeovimAction> {
|
|
15
|
+
handler::connect().map_err(|e| Error::from_reason(e.to_string()))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
fn with_action<F, T>(f: F) -> Result<T>
|
|
19
|
+
where
|
|
20
|
+
F: FnOnce(&NeovimAction) -> anyhow::Result<T>,
|
|
21
|
+
{
|
|
22
|
+
let action = connect_nvim()?;
|
|
23
|
+
f(&action).map_err(|e| Error::from_reason(e.to_string()))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
#[napi(object)]
|
|
27
|
+
#[derive(Clone)]
|
|
28
|
+
pub struct BufferStatus {
|
|
29
|
+
pub is_current: bool,
|
|
30
|
+
pub has_unsaved_changes: bool,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
impl From<crate::action::BufferStatus> for BufferStatus {
|
|
34
|
+
fn from(s: crate::action::BufferStatus) -> Self {
|
|
35
|
+
Self {
|
|
36
|
+
is_current: s.is_current,
|
|
37
|
+
has_unsaved_changes: s.has_unsaved_changes,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#[napi(object)]
|
|
43
|
+
#[derive(Clone)]
|
|
44
|
+
pub struct EditorContext {
|
|
45
|
+
pub file_path: String,
|
|
46
|
+
pub start_line: u32,
|
|
47
|
+
pub end_line: u32,
|
|
48
|
+
pub cwd: String,
|
|
49
|
+
pub content: String,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
impl From<InternalEditorContext> for EditorContext {
|
|
53
|
+
fn from(ctx: InternalEditorContext) -> Self {
|
|
54
|
+
Self {
|
|
55
|
+
file_path: ctx.file_path,
|
|
56
|
+
start_line: ctx.start_line,
|
|
57
|
+
end_line: ctx.end_line,
|
|
58
|
+
cwd: ctx.cwd,
|
|
59
|
+
content: ctx.content,
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#[napi]
|
|
65
|
+
pub fn check_buffer(file_path: String) -> Result<BufferStatus> {
|
|
66
|
+
with_action(|action| {
|
|
67
|
+
handler::check_buffer(action, &file_path).map(BufferStatus::from)
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#[napi]
|
|
72
|
+
pub fn refresh_buffer(file_path: String) -> Result<()> {
|
|
73
|
+
with_action(|action| handler::refresh_buffer(action, &file_path))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#[napi]
|
|
77
|
+
pub fn get_visual_selections() -> Result<Vec<EditorContext>> {
|
|
78
|
+
with_action(|action| {
|
|
79
|
+
handler::get_visual_selections(action)
|
|
80
|
+
.map(|v| v.into_iter().map(EditorContext::from).collect())
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#[napi]
|
|
85
|
+
pub fn send_message(message: String) -> Result<()> {
|
|
86
|
+
with_action(|action| handler::send_message(action, &message))
|
|
87
|
+
}
|
|
88
|
+
}
|
package/src/utils.rs
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
use anyhow::Context;
|
|
2
|
+
use std::path::PathBuf;
|
|
3
|
+
|
|
4
|
+
pub fn find_matching_sockets() -> anyhow::Result<Vec<PathBuf>> {
|
|
5
|
+
let pattern = "/tmp/bridge-*.sock";
|
|
6
|
+
Ok(glob::glob(pattern)
|
|
7
|
+
.context("couldn't search for Neovim sockets")?
|
|
8
|
+
.filter_map(Result::ok)
|
|
9
|
+
.filter(|path| path.exists())
|
|
10
|
+
.collect())
|
|
11
|
+
}
|