├── .eslintrc.js ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── bin ├── nextword.wasm └── wasm_exec.js ├── esbuild.js ├── package.json ├── src ├── ctx.ts └── index.ts ├── tsconfig.json └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | node: true 4 | }, 5 | parser: '@typescript-eslint/parser', 6 | extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', 'prettier/@typescript-eslint'], 7 | rules: { 8 | '@typescript-eslint/ban-ts-ignore': 'off', 9 | '@typescript-eslint/no-explicit-any': 'off', 10 | '@typescript-eslint/no-non-null-assertion': 'off', 11 | '@typescript-eslint/no-namespace': 'off', 12 | '@typescript-eslint/no-empty-function': 'off', 13 | '@typescript-eslint/explicit-function-return-type': 'off' 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | node-version: [13] 19 | 20 | env: 21 | NODE_ENV: test 22 | 23 | steps: 24 | - uses: actions/checkout@v1 25 | - name: Use Node.js ${{ matrix.node-version }} 26 | uses: actions/setup-node@v1 27 | with: 28 | node-version: ${{ matrix.node-version }} 29 | - name: Install yarn 30 | run: | 31 | curl --compressed -o- -L https://yarnpkg.com/install.sh | bash 32 | - name: yarn build 33 | run: | 34 | yarn 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | node_modules 3 | tsconfig.json 4 | *.map 5 | .tags 6 | .DS_Store 7 | esbuild.js 8 | webpack.config.js 9 | yarn.lock 10 | yarn-error.log 11 | .github 12 | .eslintrc.js 13 | .prettierrc 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Heyward Fann 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **NEW PREDICTION ENGINE [MOCWORD](https://github.com/high-moctane/mocword) IS AVAILABLE, TRY [coc-mocword](https://github.com/fannheyward/coc-mocword)**. 2 | 3 | # coc-nextword 4 | 5 | [nextword][] extension for coc.nvim 6 | 7 | ![](https://user-images.githubusercontent.com/345274/109935135-38bc3180-7d08-11eb-8d04-3e46b8df1fa4.mov) 8 | 9 | ## Install 10 | 11 | 1. Install [nextword][]. **Note**: you can use bundled `nextword.wasm` instead, see `nextword.wasm`. 12 | 1. Download [nextword dataset](https://github.com/high-moctane/nextword-data) 13 | 1. Set `$NEXTWORD_DATA_PATH`: `export NEXTWORD_DATA_PATH=/path/to/nextword-data` 14 | 1. `:CocInstall coc-nextword` 15 | 16 | ## Configurations 17 | 18 | - `nextword.filetypes`: filetypes that enable nextword, defaults `["text", "help", "markdown", "gitcommit"]` 19 | - `nextword.dataPath`: custom path of nextword dataset, will override `$NEXTWORD_DATA_PATH` 20 | - `nextword.count`: max candidates word number, defaults `10` 21 | - `nextword.greedy`: show as many results as possible, defaults `true` 22 | - `nextword.wasm`: use bundled WebAssembly version `nextword`, this means you don't need to install `nextword` by Go, defaults `false`. In my tests, `nextword.go` takes 20-30ms to do suggesting, `nextword.wasm` takes 80-100ms. 23 | 24 | ## License 25 | 26 | MIT 27 | 28 | --- 29 | 30 | > This extension is created by [create-coc-extension](https://github.com/fannheyward/create-coc-extension) 31 | 32 | [nextword]: https://github.com/high-moctane/nextword 33 | -------------------------------------------------------------------------------- /bin/nextword.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fannheyward/coc-nextword/00cc065393e84c0f7a9318d9d00ed808150dded7/bin/nextword.wasm -------------------------------------------------------------------------------- /bin/wasm_exec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | (() => { 6 | // Map multiple JavaScript environments to a single common API, 7 | // preferring web standards over Node.js API. 8 | // 9 | // Environments considered: 10 | // - Browsers 11 | // - Node.js 12 | // - Electron 13 | // - Parcel 14 | // - Webpack 15 | 16 | if (typeof global !== "undefined") { 17 | // global already exists 18 | } else if (typeof window !== "undefined") { 19 | window.global = window; 20 | } else if (typeof self !== "undefined") { 21 | self.global = self; 22 | } else { 23 | throw new Error("cannot export Go (neither global, window nor self is defined)"); 24 | } 25 | 26 | if (!global.require && typeof require !== "undefined") { 27 | global.require = require; 28 | } 29 | 30 | if (!global.fs && global.require) { 31 | const fs = require("fs"); 32 | if (typeof fs === "object" && fs !== null && Object.keys(fs).length !== 0) { 33 | global.fs = fs; 34 | } 35 | } 36 | 37 | const enosys = () => { 38 | const err = new Error("not implemented"); 39 | err.code = "ENOSYS"; 40 | return err; 41 | }; 42 | 43 | if (!global.fs) { 44 | let outputBuf = ""; 45 | global.fs = { 46 | constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused 47 | writeSync(fd, buf) { 48 | outputBuf += decoder.decode(buf); 49 | const nl = outputBuf.lastIndexOf("\n"); 50 | if (nl != -1) { 51 | console.log(outputBuf.substr(0, nl)); 52 | outputBuf = outputBuf.substr(nl + 1); 53 | } 54 | return buf.length; 55 | }, 56 | write(fd, buf, offset, length, position, callback) { 57 | if (offset !== 0 || length !== buf.length || position !== null) { 58 | callback(enosys()); 59 | return; 60 | } 61 | const n = this.writeSync(fd, buf); 62 | callback(null, n); 63 | }, 64 | chmod(path, mode, callback) { callback(enosys()); }, 65 | chown(path, uid, gid, callback) { callback(enosys()); }, 66 | close(fd, callback) { callback(enosys()); }, 67 | fchmod(fd, mode, callback) { callback(enosys()); }, 68 | fchown(fd, uid, gid, callback) { callback(enosys()); }, 69 | fstat(fd, callback) { callback(enosys()); }, 70 | fsync(fd, callback) { callback(null); }, 71 | ftruncate(fd, length, callback) { callback(enosys()); }, 72 | lchown(path, uid, gid, callback) { callback(enosys()); }, 73 | link(path, link, callback) { callback(enosys()); }, 74 | lstat(path, callback) { callback(enosys()); }, 75 | mkdir(path, perm, callback) { callback(enosys()); }, 76 | open(path, flags, mode, callback) { callback(enosys()); }, 77 | read(fd, buffer, offset, length, position, callback) { callback(enosys()); }, 78 | readdir(path, callback) { callback(enosys()); }, 79 | readlink(path, callback) { callback(enosys()); }, 80 | rename(from, to, callback) { callback(enosys()); }, 81 | rmdir(path, callback) { callback(enosys()); }, 82 | stat(path, callback) { callback(enosys()); }, 83 | symlink(path, link, callback) { callback(enosys()); }, 84 | truncate(path, length, callback) { callback(enosys()); }, 85 | unlink(path, callback) { callback(enosys()); }, 86 | utimes(path, atime, mtime, callback) { callback(enosys()); }, 87 | }; 88 | } 89 | 90 | if (!global.process) { 91 | global.process = { 92 | getuid() { return -1; }, 93 | getgid() { return -1; }, 94 | geteuid() { return -1; }, 95 | getegid() { return -1; }, 96 | getgroups() { throw enosys(); }, 97 | pid: -1, 98 | ppid: -1, 99 | umask() { throw enosys(); }, 100 | cwd() { throw enosys(); }, 101 | chdir() { throw enosys(); }, 102 | } 103 | } 104 | 105 | if (!global.crypto && global.require) { 106 | const nodeCrypto = require("crypto"); 107 | global.crypto = { 108 | getRandomValues(b) { 109 | nodeCrypto.randomFillSync(b); 110 | }, 111 | }; 112 | } 113 | if (!global.crypto) { 114 | throw new Error("global.crypto is not available, polyfill required (getRandomValues only)"); 115 | } 116 | 117 | if (!global.performance) { 118 | global.performance = { 119 | now() { 120 | const [sec, nsec] = process.hrtime(); 121 | return sec * 1000 + nsec / 1000000; 122 | }, 123 | }; 124 | } 125 | 126 | if (!global.TextEncoder && global.require) { 127 | global.TextEncoder = require("util").TextEncoder; 128 | } 129 | if (!global.TextEncoder) { 130 | throw new Error("global.TextEncoder is not available, polyfill required"); 131 | } 132 | 133 | if (!global.TextDecoder && global.require) { 134 | global.TextDecoder = require("util").TextDecoder; 135 | } 136 | if (!global.TextDecoder) { 137 | throw new Error("global.TextDecoder is not available, polyfill required"); 138 | } 139 | 140 | // End of polyfills for common API. 141 | 142 | const encoder = new TextEncoder("utf-8"); 143 | const decoder = new TextDecoder("utf-8"); 144 | 145 | global.Go = class { 146 | constructor() { 147 | this.argv = ["js"]; 148 | this.env = {}; 149 | this.exit = (code) => { 150 | if (code !== 0) { 151 | console.warn("exit code:", code); 152 | } 153 | }; 154 | this._exitPromise = new Promise((resolve) => { 155 | this._resolveExitPromise = resolve; 156 | }); 157 | this._pendingEvent = null; 158 | this._scheduledTimeouts = new Map(); 159 | this._nextCallbackTimeoutID = 1; 160 | 161 | const setInt64 = (addr, v) => { 162 | this.mem.setUint32(addr + 0, v, true); 163 | this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true); 164 | } 165 | 166 | const getInt64 = (addr) => { 167 | const low = this.mem.getUint32(addr + 0, true); 168 | const high = this.mem.getInt32(addr + 4, true); 169 | return low + high * 4294967296; 170 | } 171 | 172 | const loadValue = (addr) => { 173 | const f = this.mem.getFloat64(addr, true); 174 | if (f === 0) { 175 | return undefined; 176 | } 177 | if (!isNaN(f)) { 178 | return f; 179 | } 180 | 181 | const id = this.mem.getUint32(addr, true); 182 | return this._values[id]; 183 | } 184 | 185 | const storeValue = (addr, v) => { 186 | const nanHead = 0x7FF80000; 187 | 188 | if (typeof v === "number" && v !== 0) { 189 | if (isNaN(v)) { 190 | this.mem.setUint32(addr + 4, nanHead, true); 191 | this.mem.setUint32(addr, 0, true); 192 | return; 193 | } 194 | this.mem.setFloat64(addr, v, true); 195 | return; 196 | } 197 | 198 | if (v === undefined) { 199 | this.mem.setFloat64(addr, 0, true); 200 | return; 201 | } 202 | 203 | let id = this._ids.get(v); 204 | if (id === undefined) { 205 | id = this._idPool.pop(); 206 | if (id === undefined) { 207 | id = this._values.length; 208 | } 209 | this._values[id] = v; 210 | this._goRefCounts[id] = 0; 211 | this._ids.set(v, id); 212 | } 213 | this._goRefCounts[id]++; 214 | let typeFlag = 0; 215 | switch (typeof v) { 216 | case "object": 217 | if (v !== null) { 218 | typeFlag = 1; 219 | } 220 | break; 221 | case "string": 222 | typeFlag = 2; 223 | break; 224 | case "symbol": 225 | typeFlag = 3; 226 | break; 227 | case "function": 228 | typeFlag = 4; 229 | break; 230 | } 231 | this.mem.setUint32(addr + 4, nanHead | typeFlag, true); 232 | this.mem.setUint32(addr, id, true); 233 | } 234 | 235 | const loadSlice = (addr) => { 236 | const array = getInt64(addr + 0); 237 | const len = getInt64(addr + 8); 238 | return new Uint8Array(this._inst.exports.mem.buffer, array, len); 239 | } 240 | 241 | const loadSliceOfValues = (addr) => { 242 | const array = getInt64(addr + 0); 243 | const len = getInt64(addr + 8); 244 | const a = new Array(len); 245 | for (let i = 0; i < len; i++) { 246 | a[i] = loadValue(array + i * 8); 247 | } 248 | return a; 249 | } 250 | 251 | const loadString = (addr) => { 252 | const saddr = getInt64(addr + 0); 253 | const len = getInt64(addr + 8); 254 | return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); 255 | } 256 | 257 | const timeOrigin = Date.now() - performance.now(); 258 | this.importObject = { 259 | go: { 260 | // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) 261 | // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported 262 | // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). 263 | // This changes the SP, thus we have to update the SP used by the imported function. 264 | 265 | // func wasmExit(code int32) 266 | "runtime.wasmExit": (sp) => { 267 | sp >>>= 0; 268 | const code = this.mem.getInt32(sp + 8, true); 269 | this.exited = true; 270 | delete this._inst; 271 | delete this._values; 272 | delete this._goRefCounts; 273 | delete this._ids; 274 | delete this._idPool; 275 | this.exit(code); 276 | }, 277 | 278 | // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) 279 | "runtime.wasmWrite": (sp) => { 280 | sp >>>= 0; 281 | const fd = getInt64(sp + 8); 282 | const p = getInt64(sp + 16); 283 | const n = this.mem.getInt32(sp + 24, true); 284 | fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); 285 | }, 286 | 287 | // func resetMemoryDataView() 288 | "runtime.resetMemoryDataView": (sp) => { 289 | sp >>>= 0; 290 | this.mem = new DataView(this._inst.exports.mem.buffer); 291 | }, 292 | 293 | // func nanotime1() int64 294 | "runtime.nanotime1": (sp) => { 295 | sp >>>= 0; 296 | setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); 297 | }, 298 | 299 | // func walltime1() (sec int64, nsec int32) 300 | "runtime.walltime1": (sp) => { 301 | sp >>>= 0; 302 | const msec = (new Date).getTime(); 303 | setInt64(sp + 8, msec / 1000); 304 | this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); 305 | }, 306 | 307 | // func scheduleTimeoutEvent(delay int64) int32 308 | "runtime.scheduleTimeoutEvent": (sp) => { 309 | sp >>>= 0; 310 | const id = this._nextCallbackTimeoutID; 311 | this._nextCallbackTimeoutID++; 312 | this._scheduledTimeouts.set(id, setTimeout( 313 | () => { 314 | this._resume(); 315 | while (this._scheduledTimeouts.has(id)) { 316 | // for some reason Go failed to register the timeout event, log and try again 317 | // (temporary workaround for https://github.com/golang/go/issues/28975) 318 | console.warn("scheduleTimeoutEvent: missed timeout event"); 319 | this._resume(); 320 | } 321 | }, 322 | getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early 323 | )); 324 | this.mem.setInt32(sp + 16, id, true); 325 | }, 326 | 327 | // func clearTimeoutEvent(id int32) 328 | "runtime.clearTimeoutEvent": (sp) => { 329 | sp >>>= 0; 330 | const id = this.mem.getInt32(sp + 8, true); 331 | clearTimeout(this._scheduledTimeouts.get(id)); 332 | this._scheduledTimeouts.delete(id); 333 | }, 334 | 335 | // func getRandomData(r []byte) 336 | "runtime.getRandomData": (sp) => { 337 | sp >>>= 0; 338 | crypto.getRandomValues(loadSlice(sp + 8)); 339 | }, 340 | 341 | // func finalizeRef(v ref) 342 | "syscall/js.finalizeRef": (sp) => { 343 | sp >>>= 0; 344 | const id = this.mem.getUint32(sp + 8, true); 345 | this._goRefCounts[id]--; 346 | if (this._goRefCounts[id] === 0) { 347 | const v = this._values[id]; 348 | this._values[id] = null; 349 | this._ids.delete(v); 350 | this._idPool.push(id); 351 | } 352 | }, 353 | 354 | // func stringVal(value string) ref 355 | "syscall/js.stringVal": (sp) => { 356 | sp >>>= 0; 357 | storeValue(sp + 24, loadString(sp + 8)); 358 | }, 359 | 360 | // func valueGet(v ref, p string) ref 361 | "syscall/js.valueGet": (sp) => { 362 | sp >>>= 0; 363 | const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); 364 | sp = this._inst.exports.getsp() >>> 0; // see comment above 365 | storeValue(sp + 32, result); 366 | }, 367 | 368 | // func valueSet(v ref, p string, x ref) 369 | "syscall/js.valueSet": (sp) => { 370 | sp >>>= 0; 371 | Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); 372 | }, 373 | 374 | // func valueDelete(v ref, p string) 375 | "syscall/js.valueDelete": (sp) => { 376 | sp >>>= 0; 377 | Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)); 378 | }, 379 | 380 | // func valueIndex(v ref, i int) ref 381 | "syscall/js.valueIndex": (sp) => { 382 | sp >>>= 0; 383 | storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); 384 | }, 385 | 386 | // valueSetIndex(v ref, i int, x ref) 387 | "syscall/js.valueSetIndex": (sp) => { 388 | sp >>>= 0; 389 | Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); 390 | }, 391 | 392 | // func valueCall(v ref, m string, args []ref) (ref, bool) 393 | "syscall/js.valueCall": (sp) => { 394 | sp >>>= 0; 395 | try { 396 | const v = loadValue(sp + 8); 397 | const m = Reflect.get(v, loadString(sp + 16)); 398 | const args = loadSliceOfValues(sp + 32); 399 | const result = Reflect.apply(m, v, args); 400 | sp = this._inst.exports.getsp() >>> 0; // see comment above 401 | storeValue(sp + 56, result); 402 | this.mem.setUint8(sp + 64, 1); 403 | } catch (err) { 404 | storeValue(sp + 56, err); 405 | this.mem.setUint8(sp + 64, 0); 406 | } 407 | }, 408 | 409 | // func valueInvoke(v ref, args []ref) (ref, bool) 410 | "syscall/js.valueInvoke": (sp) => { 411 | sp >>>= 0; 412 | try { 413 | const v = loadValue(sp + 8); 414 | const args = loadSliceOfValues(sp + 16); 415 | const result = Reflect.apply(v, undefined, args); 416 | sp = this._inst.exports.getsp() >>> 0; // see comment above 417 | storeValue(sp + 40, result); 418 | this.mem.setUint8(sp + 48, 1); 419 | } catch (err) { 420 | storeValue(sp + 40, err); 421 | this.mem.setUint8(sp + 48, 0); 422 | } 423 | }, 424 | 425 | // func valueNew(v ref, args []ref) (ref, bool) 426 | "syscall/js.valueNew": (sp) => { 427 | sp >>>= 0; 428 | try { 429 | const v = loadValue(sp + 8); 430 | const args = loadSliceOfValues(sp + 16); 431 | const result = Reflect.construct(v, args); 432 | sp = this._inst.exports.getsp() >>> 0; // see comment above 433 | storeValue(sp + 40, result); 434 | this.mem.setUint8(sp + 48, 1); 435 | } catch (err) { 436 | storeValue(sp + 40, err); 437 | this.mem.setUint8(sp + 48, 0); 438 | } 439 | }, 440 | 441 | // func valueLength(v ref) int 442 | "syscall/js.valueLength": (sp) => { 443 | sp >>>= 0; 444 | setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); 445 | }, 446 | 447 | // valuePrepareString(v ref) (ref, int) 448 | "syscall/js.valuePrepareString": (sp) => { 449 | sp >>>= 0; 450 | const str = encoder.encode(String(loadValue(sp + 8))); 451 | storeValue(sp + 16, str); 452 | setInt64(sp + 24, str.length); 453 | }, 454 | 455 | // valueLoadString(v ref, b []byte) 456 | "syscall/js.valueLoadString": (sp) => { 457 | sp >>>= 0; 458 | const str = loadValue(sp + 8); 459 | loadSlice(sp + 16).set(str); 460 | }, 461 | 462 | // func valueInstanceOf(v ref, t ref) bool 463 | "syscall/js.valueInstanceOf": (sp) => { 464 | sp >>>= 0; 465 | this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0); 466 | }, 467 | 468 | // func copyBytesToGo(dst []byte, src ref) (int, bool) 469 | "syscall/js.copyBytesToGo": (sp) => { 470 | sp >>>= 0; 471 | const dst = loadSlice(sp + 8); 472 | const src = loadValue(sp + 32); 473 | if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { 474 | this.mem.setUint8(sp + 48, 0); 475 | return; 476 | } 477 | const toCopy = src.subarray(0, dst.length); 478 | dst.set(toCopy); 479 | setInt64(sp + 40, toCopy.length); 480 | this.mem.setUint8(sp + 48, 1); 481 | }, 482 | 483 | // func copyBytesToJS(dst ref, src []byte) (int, bool) 484 | "syscall/js.copyBytesToJS": (sp) => { 485 | sp >>>= 0; 486 | const dst = loadValue(sp + 8); 487 | const src = loadSlice(sp + 16); 488 | if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { 489 | this.mem.setUint8(sp + 48, 0); 490 | return; 491 | } 492 | const toCopy = src.subarray(0, dst.length); 493 | dst.set(toCopy); 494 | setInt64(sp + 40, toCopy.length); 495 | this.mem.setUint8(sp + 48, 1); 496 | }, 497 | 498 | "debug": (value) => { 499 | console.log(value); 500 | }, 501 | } 502 | }; 503 | } 504 | 505 | async run(instance) { 506 | if (!(instance instanceof WebAssembly.Instance)) { 507 | throw new Error("Go.run: WebAssembly.Instance expected"); 508 | } 509 | this._inst = instance; 510 | this.mem = new DataView(this._inst.exports.mem.buffer); 511 | this._values = [ // JS values that Go currently has references to, indexed by reference id 512 | NaN, 513 | 0, 514 | null, 515 | true, 516 | false, 517 | global, 518 | this, 519 | ]; 520 | this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id 521 | this._ids = new Map([ // mapping from JS values to reference ids 522 | [0, 1], 523 | [null, 2], 524 | [true, 3], 525 | [false, 4], 526 | [global, 5], 527 | [this, 6], 528 | ]); 529 | this._idPool = []; // unused ids that have been garbage collected 530 | this.exited = false; // whether the Go program has exited 531 | 532 | // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. 533 | let offset = 4096; 534 | 535 | const strPtr = (str) => { 536 | const ptr = offset; 537 | const bytes = encoder.encode(str + "\0"); 538 | new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes); 539 | offset += bytes.length; 540 | if (offset % 8 !== 0) { 541 | offset += 8 - (offset % 8); 542 | } 543 | return ptr; 544 | }; 545 | 546 | const argc = this.argv.length; 547 | 548 | const argvPtrs = []; 549 | this.argv.forEach((arg) => { 550 | argvPtrs.push(strPtr(arg)); 551 | }); 552 | argvPtrs.push(0); 553 | 554 | const keys = Object.keys(this.env).sort(); 555 | keys.forEach((key) => { 556 | argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); 557 | }); 558 | argvPtrs.push(0); 559 | 560 | const argv = offset; 561 | argvPtrs.forEach((ptr) => { 562 | this.mem.setUint32(offset, ptr, true); 563 | this.mem.setUint32(offset + 4, 0, true); 564 | offset += 8; 565 | }); 566 | 567 | this._inst.exports.run(argc, argv); 568 | if (this.exited) { 569 | this._resolveExitPromise(); 570 | } 571 | await this._exitPromise; 572 | } 573 | 574 | _resume() { 575 | if (this.exited) { 576 | throw new Error("Go program has already exited"); 577 | } 578 | this._inst.exports.resume(); 579 | if (this.exited) { 580 | this._resolveExitPromise(); 581 | } 582 | } 583 | 584 | _makeFuncWrapper(id) { 585 | const go = this; 586 | return function () { 587 | const event = { id: id, this: this, args: arguments }; 588 | go._pendingEvent = event; 589 | go._resume(); 590 | return event.result; 591 | }; 592 | } 593 | } 594 | 595 | if ( 596 | typeof module !== "undefined" && 597 | global.require && 598 | global.require.main === module && 599 | global.process && 600 | global.process.versions && 601 | !global.process.versions.electron 602 | ) { 603 | if (process.argv.length < 3) { 604 | console.error("usage: go_js_wasm_exec [wasm binary] [arguments]"); 605 | process.exit(1); 606 | } 607 | 608 | const go = new Go(); 609 | go.argv = process.argv.slice(2); 610 | go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env); 611 | go.exit = process.exit; 612 | WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { 613 | process.on("exit", (code) => { // Node.js exits if no event handler is pending 614 | if (code === 0 && !go.exited) { 615 | // deadlock, make Go print error and stack traces 616 | go._pendingEvent = { id: 0 }; 617 | go._resume(); 618 | } 619 | }); 620 | return go.run(result.instance); 621 | }).catch((err) => { 622 | console.error(err); 623 | process.exit(1); 624 | }); 625 | } 626 | })(); 627 | -------------------------------------------------------------------------------- /esbuild.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | async function start(watch) { 3 | await require('esbuild').build({ 4 | entryPoints: ['src/index.ts'], 5 | bundle: true, 6 | watch, 7 | minify: process.env.NODE_ENV === 'production', 8 | sourcemap: process.env.NODE_ENV === 'development', 9 | mainFields: ['module', 'main'], 10 | external: ['coc.nvim'], 11 | platform: 'node', 12 | target: 'node10.12', 13 | outfile: 'lib/index.js', 14 | }); 15 | } 16 | 17 | let watch = false; 18 | if (process.argv.length > 2 && process.argv[2] === '--watch') { 19 | console.log('watching...'); 20 | watch = { 21 | onRebuild(error) { 22 | if (error) { 23 | console.error('watch build failed:', error); 24 | } else { 25 | console.log('watch build succeeded'); 26 | } 27 | }, 28 | }; 29 | } 30 | 31 | start(watch).catch((e) => { 32 | console.error(e); 33 | }); 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coc-nextword", 3 | "version": "0.5.0", 4 | "description": "nextword extension for coc.nvim", 5 | "author": "Heyward Fann ", 6 | "license": "MIT", 7 | "main": "lib/index.js", 8 | "keywords": [ 9 | "coc.nvim" 10 | ], 11 | "engines": { 12 | "coc": "^0.0.80" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/fannheyward/coc-nextword" 17 | }, 18 | "scripts": { 19 | "build": "node esbuild.js", 20 | "prepare": "node esbuild.js" 21 | }, 22 | "devDependencies": { 23 | "@types/node": "^16.4.10", 24 | "@types/which": "^2.0.0", 25 | "coc.nvim": "^0.0.80", 26 | "esbuild": "^0.12.5", 27 | "typescript": "^4.0.2", 28 | "which": "^2.0.2" 29 | }, 30 | "prettier": { 31 | "singleQuote": true, 32 | "printWidth": 180, 33 | "semi": true 34 | }, 35 | "activationEvents": [ 36 | "*" 37 | ], 38 | "contributes": { 39 | "configuration": { 40 | "type": "object", 41 | "title": "coc-nextword configuration", 42 | "properties": { 43 | "nextword.enable": { 44 | "type": "boolean", 45 | "default": true 46 | }, 47 | "nextword.filetypes": { 48 | "type": "array", 49 | "default": [ 50 | "text", 51 | "help", 52 | "markdown", 53 | "gitcommit" 54 | ], 55 | "description": "Enable nextword for these filetypes, `[\"*\"]` for all, and empty to disable" 56 | }, 57 | "nextword.dataPath": { 58 | "type": "string", 59 | "default": "", 60 | "description": "Data path of nextword dataset. Use $NEXTWORD_DATA_PATH by default" 61 | }, 62 | "nextword.count": { 63 | "type": "number", 64 | "default": 10, 65 | "description": "Max candidates word number" 66 | }, 67 | "nextword.greedy": { 68 | "type": "boolean", 69 | "default": true, 70 | "description": "Show as many result as possible" 71 | }, 72 | "nextword.wasm": { 73 | "type": "boolean", 74 | "default": false, 75 | "description": "Use bundled nextword.wasm" 76 | } 77 | } 78 | }, 79 | "commands": [] 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/ctx.ts: -------------------------------------------------------------------------------- 1 | import { ChildProcess, spawn } from 'child_process'; 2 | import { CompletionItem, CompletionItemProvider, ExtensionContext, workspace, WorkspaceConfiguration } from 'coc.nvim'; 3 | import path from 'path'; 4 | import which from 'which'; 5 | 6 | export class Nextword implements CompletionItemProvider { 7 | private cfg: WorkspaceConfiguration; 8 | private proc: ChildProcess | null = null; 9 | 10 | constructor(private readonly context: ExtensionContext) { 11 | this.cfg = workspace.getConfiguration('nextword'); 12 | } 13 | 14 | get enable() { 15 | return this.cfg.get('enable') as boolean; 16 | } 17 | 18 | get filetypes() { 19 | return this.cfg.get('filetypes') as string[]; 20 | } 21 | 22 | get dataPath() { 23 | const dataPath = this.cfg.get('dataPath', ''); 24 | return dataPath ? dataPath : process.env.NEXTWORD_DATA_PATH; 25 | } 26 | 27 | get count() { 28 | return this.cfg.get('count') as string; 29 | } 30 | 31 | get greedy() { 32 | return this.cfg.get('greedy') as boolean; 33 | } 34 | 35 | get wasm() { 36 | return this.cfg.get('wasm') as boolean; 37 | } 38 | 39 | get bin(): string | null { 40 | if (this.wasm) return process.platform === 'win32' ? 'node.exe' : 'node'; 41 | 42 | const cmd = process.platform === 'win32' ? 'nextword.exe' : 'nextword'; 43 | return which.sync(cmd, { nothrow: true }); 44 | } 45 | 46 | async provideCompletionItems(): Promise { 47 | if (!this.proc) { 48 | let args: string[] = []; 49 | if (this.wasm) { 50 | args.push(path.join(this.context.extensionPath, 'bin', 'wasm_exec.js')); 51 | args.push(path.join(this.context.extensionPath, 'bin', 'nextword.wasm')); 52 | } 53 | if (this.greedy) args.push('-g'); 54 | args = args.concat(['-c', this.count]); 55 | if (this.dataPath) args = args.concat(['-d', this.dataPath]); 56 | this.proc = spawn(this.bin!, args); 57 | } 58 | 59 | if (!this.proc) return []; 60 | 61 | const line = await workspace.nvim.line; 62 | const parts = line.split(/[.?!]/); 63 | const last = parts[parts.length - 1]; 64 | if (!last) return []; 65 | this.proc.stdin?.write(last + '\n'); 66 | 67 | return new Promise((resolve) => { 68 | const items: CompletionItem[] = []; 69 | this.proc?.stdout?.on('data', (chunk) => { 70 | for (const word of (chunk.toString() as string).split(' ')) { 71 | const label = word.trimRight(); 72 | if (!label) continue; 73 | items.push({ label }) 74 | } 75 | 76 | resolve(items); 77 | }); 78 | 79 | this.proc?.on('error', () => resolve(items)); 80 | }); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext, languages, window } from 'coc.nvim'; 2 | import { Nextword } from './ctx'; 3 | 4 | export async function activate(context: ExtensionContext): Promise { 5 | const ctx = new Nextword(context); 6 | if (!ctx.enable) return; 7 | if (!ctx.bin) { 8 | window.showMessage(`nextword is not found, you need to install first: https://github.com/high-moctane/nextword`, 'warning'); 9 | return; 10 | } 11 | 12 | if (!ctx.dataPath) { 13 | window.showMessage(`No nextword dataset found, you can set with nextword.dataPath or $NEXTWORD_DATA_PATH in env`, 'warning'); 14 | return; 15 | } 16 | 17 | const letters = 'abcdefghijklmnopqrstuvwxyz'; 18 | const characters = (letters + letters.toUpperCase() + ' ').split(''); 19 | const provider = new Nextword(context); 20 | context.subscriptions.push(languages.registerCompletionItemProvider('nextword', 'Next', ctx.filetypes, provider, characters)); 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "lib": ["es2017", "es2018"], 5 | "module": "commonjs", 6 | "declaration": false, 7 | "sourceMap": true, 8 | "outDir": "lib", 9 | "strict": true, 10 | "moduleResolution": "node", 11 | "noImplicitAny": false, 12 | "esModuleInterop": true 13 | }, 14 | "include": ["src"] 15 | } 16 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/node@^16.4.10": 6 | version "16.4.10" 7 | resolved "https://registry.yarnpkg.com/@types/node/-/node-16.4.10.tgz#e57e2a54fc6da58da94b3571b1cb456d39f88597" 8 | integrity sha512-TmVHsm43br64js9BqHWqiDZA+xMtbUpI1MBIA0EyiBmoV9pcEYFOSdj5fr6enZNfh4fChh+AGOLIzGwJnkshyQ== 9 | 10 | "@types/which@^2.0.0": 11 | version "2.0.1" 12 | resolved "https://registry.yarnpkg.com/@types/which/-/which-2.0.1.tgz#27ecd67f915b7c3d6ba552135bb1eecd66e63501" 13 | integrity sha512-Jjakcv8Roqtio6w1gr0D7y6twbhx6gGgFGF5BLwajPpnOIOxFkakFhCq+LmyyeAz7BX6ULrjBOxdKaCDy+4+dQ== 14 | 15 | coc.nvim@^0.0.80: 16 | version "0.0.80" 17 | resolved "https://registry.yarnpkg.com/coc.nvim/-/coc.nvim-0.0.80.tgz#785145c382660db03f517f9b497900d95cbd0e4f" 18 | integrity sha512-/3vTcnofoAYMrdENrlQmADTzfXX4+PZ0fiM10a39UA37dTR2dpIGi9O469kcIksuunLjToqWG8S45AGx/9wV7g== 19 | 20 | esbuild@^0.12.5: 21 | version "0.12.17" 22 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.17.tgz#5816f905c2905de0ebbc658860df7b5b48afbcd3" 23 | integrity sha512-GshKJyVYUnlSXIZj/NheC2O0Kblh42CS7P1wJyTbbIHevTG4jYMS9NNw8EOd8dDWD0dzydYHS01MpZoUcQXB4g== 24 | 25 | isexe@^2.0.0: 26 | version "2.0.0" 27 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 28 | integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= 29 | 30 | typescript@^4.0.2: 31 | version "4.3.5" 32 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" 33 | integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== 34 | 35 | which@^2.0.2: 36 | version "2.0.2" 37 | resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" 38 | integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== 39 | dependencies: 40 | isexe "^2.0.0" 41 | --------------------------------------------------------------------------------