chrome-jig 0.1.0 → 0.2.0
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.
- package/SKILL.md +1 -0
- package/dist/chrome/connection.js +2 -2
- package/dist/chrome/connection.js.map +1 -1
- package/dist/cli.js +51 -0
- package/dist/cli.js.map +1 -1
- package/dist/cljs/compiler.d.ts.map +1 -1
- package/dist/cljs/compiler.js +1 -0
- package/dist/cljs/compiler.js.map +1 -1
- package/dist/commands/install-nvim.d.ts +21 -0
- package/dist/commands/install-nvim.d.ts.map +1 -0
- package/dist/commands/install-nvim.js +127 -0
- package/dist/commands/install-nvim.js.map +1 -0
- package/dist/commands/serve.d.ts +16 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/commands/serve.js +22 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/repl/repl.d.ts +8 -8
- package/dist/repl/repl.d.ts.map +1 -1
- package/dist/repl/repl.js +45 -131
- package/dist/repl/repl.js.map +1 -1
- package/dist/session/jsonrpc-protocol.d.ts +18 -0
- package/dist/session/jsonrpc-protocol.d.ts.map +1 -0
- package/dist/session/jsonrpc-protocol.js +59 -0
- package/dist/session/jsonrpc-protocol.js.map +1 -0
- package/dist/session/protocol.d.ts +47 -0
- package/dist/session/protocol.d.ts.map +1 -0
- package/dist/session/protocol.js +10 -0
- package/dist/session/protocol.js.map +1 -0
- package/dist/session/repl-protocol.d.ts +16 -0
- package/dist/session/repl-protocol.d.ts.map +1 -0
- package/dist/session/repl-protocol.js +101 -0
- package/dist/session/repl-protocol.js.map +1 -0
- package/dist/session/session.d.ts +44 -0
- package/dist/session/session.d.ts.map +1 -0
- package/dist/session/session.js +195 -0
- package/dist/session/session.js.map +1 -0
- package/editor/nvim/README.md +134 -0
- package/editor/nvim/lua/cjig/init.lua +226 -0
- package/editor/nvim/lua/cjig/rpc.lua +177 -0
- package/editor/nvim/lua/cjig/ui.lua +88 -0
- package/package.json +2 -1
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session core — shared method dispatch between all protocol adapters.
|
|
3
|
+
*
|
|
4
|
+
* Owns the readline loop, method routing, and connection reference.
|
|
5
|
+
* Protocol adapters own parsing and formatting.
|
|
6
|
+
*/
|
|
7
|
+
import * as readline from 'node:readline';
|
|
8
|
+
import { evaluate } from '../commands/eval.js';
|
|
9
|
+
import { evaluateCljs } from '../commands/cljs-eval.js';
|
|
10
|
+
import { listTabs, selectTab } from '../commands/tabs.js';
|
|
11
|
+
import { inject } from '../commands/inject.js';
|
|
12
|
+
import { isProtocolError } from './protocol.js';
|
|
13
|
+
/** Built-in method handlers */
|
|
14
|
+
export const builtinMethods = {
|
|
15
|
+
async eval(params, connection) {
|
|
16
|
+
const code = String(params.code ?? '');
|
|
17
|
+
if (!code)
|
|
18
|
+
throw new Error('Missing param: code');
|
|
19
|
+
const lang = String(params.lang ?? 'js');
|
|
20
|
+
if (lang === 'cljs') {
|
|
21
|
+
const result = await evaluateCljs(connection, code);
|
|
22
|
+
if (!result.success)
|
|
23
|
+
throw new Error(result.error);
|
|
24
|
+
return result.value;
|
|
25
|
+
}
|
|
26
|
+
if (lang !== 'js') {
|
|
27
|
+
throw new Error(`Unknown lang: ${lang}. Supported: js, cljs`);
|
|
28
|
+
}
|
|
29
|
+
const result = await evaluate(connection, code);
|
|
30
|
+
if (!result.success)
|
|
31
|
+
throw new Error(result.error);
|
|
32
|
+
return result.value;
|
|
33
|
+
},
|
|
34
|
+
async 'cljs-eval'(params, connection, config) {
|
|
35
|
+
return builtinMethods.eval({ ...params, lang: 'cljs' }, connection, config);
|
|
36
|
+
},
|
|
37
|
+
async tabs(_params, connection) {
|
|
38
|
+
const tabs = await listTabs(connection);
|
|
39
|
+
return { tabs, formatted: formatTabs(tabs) };
|
|
40
|
+
},
|
|
41
|
+
async selectTab(params, connection) {
|
|
42
|
+
const pattern = String(params.pattern ?? '');
|
|
43
|
+
if (!pattern)
|
|
44
|
+
throw new Error('Missing param: pattern');
|
|
45
|
+
const tab = await selectTab(connection, pattern);
|
|
46
|
+
if (!tab)
|
|
47
|
+
throw new Error(`No tab matching: ${pattern}`);
|
|
48
|
+
return { tab, formatted: `Switched to: ${tab.title}\n ${tab.url}` };
|
|
49
|
+
},
|
|
50
|
+
async inject(params, connection, config) {
|
|
51
|
+
const ref = String(params.ref ?? '');
|
|
52
|
+
if (!ref)
|
|
53
|
+
throw new Error('Missing param: ref');
|
|
54
|
+
const result = await inject(connection, config, ref);
|
|
55
|
+
if (!result.success)
|
|
56
|
+
throw new Error(result.error);
|
|
57
|
+
const lines = [`Injected: ${result.url}`];
|
|
58
|
+
if (result.windowApi)
|
|
59
|
+
lines.push(`API available as: window.${result.windowApi}`);
|
|
60
|
+
if (result.quickStart)
|
|
61
|
+
lines.push(`Try: ${result.quickStart}`);
|
|
62
|
+
return { ...result, formatted: lines.join('\n') };
|
|
63
|
+
},
|
|
64
|
+
async reload(_params, connection) {
|
|
65
|
+
await connection.reload();
|
|
66
|
+
return { formatted: 'Reloaded' };
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
function formatTabs(tabs) {
|
|
70
|
+
if (tabs.length === 0)
|
|
71
|
+
return 'No tabs open';
|
|
72
|
+
const lines = ['\nOpen tabs:\n'];
|
|
73
|
+
for (const tab of tabs) {
|
|
74
|
+
const marker = tab.isCurrent ? '→ ' : ' ';
|
|
75
|
+
lines.push(`${marker}[${tab.index}] ${tab.title}`);
|
|
76
|
+
lines.push(` ${tab.url}\n`);
|
|
77
|
+
}
|
|
78
|
+
return lines.join('\n');
|
|
79
|
+
}
|
|
80
|
+
export class Session {
|
|
81
|
+
options;
|
|
82
|
+
rl = null;
|
|
83
|
+
running = false;
|
|
84
|
+
methods;
|
|
85
|
+
pending = 0;
|
|
86
|
+
drainResolve = null;
|
|
87
|
+
constructor(options) {
|
|
88
|
+
this.options = options;
|
|
89
|
+
this.methods = { ...builtinMethods };
|
|
90
|
+
}
|
|
91
|
+
/** Register a custom method handler */
|
|
92
|
+
register(method, handler) {
|
|
93
|
+
this.methods[method] = handler;
|
|
94
|
+
}
|
|
95
|
+
async start() {
|
|
96
|
+
this.running = true;
|
|
97
|
+
const input = this.options.input ?? process.stdin;
|
|
98
|
+
const output = this.options.output ?? process.stdout;
|
|
99
|
+
const rlOptions = {
|
|
100
|
+
input: input,
|
|
101
|
+
output: output,
|
|
102
|
+
terminal: !!this.options.prompt,
|
|
103
|
+
};
|
|
104
|
+
if (this.options.prompt)
|
|
105
|
+
rlOptions.prompt = this.options.prompt;
|
|
106
|
+
if (this.options.completer)
|
|
107
|
+
rlOptions.completer = this.options.completer;
|
|
108
|
+
this.rl = readline.createInterface(rlOptions);
|
|
109
|
+
if (this.options.prompt)
|
|
110
|
+
this.rl.prompt();
|
|
111
|
+
this.rl.on('line', async (line) => {
|
|
112
|
+
this.pending++;
|
|
113
|
+
try {
|
|
114
|
+
await this.handleLine(line, output);
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
this.pending--;
|
|
118
|
+
if (this.pending === 0 && this.drainResolve) {
|
|
119
|
+
this.drainResolve();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (this.running && this.options.prompt) {
|
|
123
|
+
this.rl?.prompt();
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
if (this.options.prompt) {
|
|
127
|
+
this.rl.on('SIGINT', () => {
|
|
128
|
+
this.write(output, '\n(Use .exit or Ctrl+D to quit)');
|
|
129
|
+
this.rl?.prompt();
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return new Promise((resolve) => {
|
|
133
|
+
this.rl?.on('close', async () => {
|
|
134
|
+
if (this.pending > 0) {
|
|
135
|
+
await new Promise(r => { this.drainResolve = r; });
|
|
136
|
+
}
|
|
137
|
+
this.running = false;
|
|
138
|
+
this.options.onExit?.();
|
|
139
|
+
resolve();
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
stop() {
|
|
144
|
+
this.running = false;
|
|
145
|
+
this.rl?.close();
|
|
146
|
+
}
|
|
147
|
+
/** Access the readline interface (for REPL prompt control) */
|
|
148
|
+
getReadline() {
|
|
149
|
+
return this.rl;
|
|
150
|
+
}
|
|
151
|
+
async handleLine(line, output) {
|
|
152
|
+
const { protocol, connection, config } = this.options;
|
|
153
|
+
// Try protocol-local handling first (REPL: .help, .watch, etc.)
|
|
154
|
+
if (protocol.handleLocal && this.options.localContext) {
|
|
155
|
+
const localCtx = {
|
|
156
|
+
...this.options.localContext,
|
|
157
|
+
print: (msg) => this.write(output, msg),
|
|
158
|
+
};
|
|
159
|
+
const localResult = await protocol.handleLocal(line, localCtx);
|
|
160
|
+
if (localResult !== null) {
|
|
161
|
+
if (localResult)
|
|
162
|
+
this.write(output, localResult);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Parse the input
|
|
167
|
+
const parsed = protocol.parse(line);
|
|
168
|
+
if (parsed === null)
|
|
169
|
+
return; // blank line or skip
|
|
170
|
+
if (isProtocolError(parsed)) {
|
|
171
|
+
this.write(output, protocol.formatError(null, parsed.code, parsed.message));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
// Dispatch to method handler
|
|
175
|
+
const handler = this.methods[parsed.method];
|
|
176
|
+
if (!handler) {
|
|
177
|
+
this.write(output, protocol.formatError(parsed, -32601, `Unknown method: ${parsed.method}`));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
const result = await handler(parsed.params, connection, config);
|
|
182
|
+
const formatted = protocol.formatResult(parsed, result);
|
|
183
|
+
if (formatted)
|
|
184
|
+
this.write(output, formatted);
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
188
|
+
this.write(output, protocol.formatError(parsed, -32603, message));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
write(output, data) {
|
|
192
|
+
output.write(data + '\n');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/session/session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# cjig.nvim
|
|
2
|
+
|
|
3
|
+
Neovim plugin for evaluating JavaScript/ClojureScript in Chrome via `cjig serve --stdio`.
|
|
4
|
+
|
|
5
|
+
Select code, eval in browser, see result in a floating window.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- Neovim >= 0.9
|
|
10
|
+
- `cjig` installed and on `$PATH`
|
|
11
|
+
- Chrome running with debugging enabled (`cjig launch`)
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
Run once (and again after upgrading cjig or switching Node versions):
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
cjig install-nvim
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This creates a stable symlink at `$XDG_DATA_HOME/cjig/editors/nvim` (default: `~/.local/share/cjig/editors/nvim`) pointing to the plugin inside the installed package. The command prints setup snippets — add the appropriate one to your Neovim config.
|
|
22
|
+
|
|
23
|
+
### lazy.nvim
|
|
24
|
+
|
|
25
|
+
Create `lua/plugins/cjig.lua`:
|
|
26
|
+
|
|
27
|
+
```lua
|
|
28
|
+
return { dir = "~/.local/share/cjig/editors/nvim" }
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### packer
|
|
32
|
+
|
|
33
|
+
```lua
|
|
34
|
+
use { "~/.local/share/cjig/editors/nvim" }
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Manual (init.lua)
|
|
38
|
+
|
|
39
|
+
```lua
|
|
40
|
+
vim.opt.runtimepath:prepend(vim.fn.expand("~/.local/share/cjig/editors/nvim"))
|
|
41
|
+
require("cjig").setup()
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Uninstall
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
cjig uninstall-nvim
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Then remove the corresponding line from your Neovim config.
|
|
51
|
+
|
|
52
|
+
## Commands
|
|
53
|
+
|
|
54
|
+
| Command | Mode | Description |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| `:CjigConnect` | n | Spawn `cjig serve --stdio` subprocess |
|
|
57
|
+
| `:CjigDisconnect` | n | Kill the subprocess |
|
|
58
|
+
| `:CjigEval` | v | Eval visual selection in browser |
|
|
59
|
+
| `:CjigEvalForm` | n | Eval treesitter form at cursor |
|
|
60
|
+
| `:CjigTabs` | n | List open browser tabs |
|
|
61
|
+
| `:CjigInject <name>` | n | Inject a harness script by name |
|
|
62
|
+
| `:CjigReload` | n | Reload current browser tab |
|
|
63
|
+
|
|
64
|
+
## Default Keymaps
|
|
65
|
+
|
|
66
|
+
All under `<leader>c` (configurable via `opts.leader`):
|
|
67
|
+
|
|
68
|
+
| Key | Mode | Action |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| `<leader>ce` | v | Eval selection |
|
|
71
|
+
| `<leader>cf` | n | Eval form at cursor |
|
|
72
|
+
| `<leader>ct` | n | List tabs |
|
|
73
|
+
| `<leader>cr` | n | Reload tab |
|
|
74
|
+
| `<leader>cc` | n | Connect |
|
|
75
|
+
| `<leader>cd` | n | Disconnect |
|
|
76
|
+
|
|
77
|
+
Disable with `require("cjig").setup({ keymaps = false })`.
|
|
78
|
+
|
|
79
|
+
## Options
|
|
80
|
+
|
|
81
|
+
```lua
|
|
82
|
+
require("cjig").setup({
|
|
83
|
+
keymaps = true, -- set false to skip default keymaps
|
|
84
|
+
leader = "<leader>c", -- keymap prefix
|
|
85
|
+
})
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Auto-connect
|
|
89
|
+
|
|
90
|
+
`:CjigEval` and `:CjigEvalForm` auto-connect on first use.
|
|
91
|
+
Explicit `:CjigConnect` is optional.
|
|
92
|
+
|
|
93
|
+
## Language Detection
|
|
94
|
+
|
|
95
|
+
- `clojure` filetype -> sends `lang: "cljs"` (compiled via squint-cljs)
|
|
96
|
+
- Everything else -> `lang: "js"`
|
|
97
|
+
|
|
98
|
+
## Treesitter Form Selection
|
|
99
|
+
|
|
100
|
+
`:CjigEvalForm` walks up the treesitter tree from the cursor to find the nearest evaluable node:
|
|
101
|
+
|
|
102
|
+
- `expression_statement` (trailing `;` stripped)
|
|
103
|
+
- `variable_declaration` / `lexical_declaration`
|
|
104
|
+
- `function_declaration`
|
|
105
|
+
- `call_expression`
|
|
106
|
+
- `assignment_expression`
|
|
107
|
+
|
|
108
|
+
## Walkthrough
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Terminal: launch Chrome and serve examples
|
|
112
|
+
cjig launch
|
|
113
|
+
pnpm serve:examples
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
In Neovim:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
:CjigConnect " connects to Chrome on localhost:9222
|
|
120
|
+
:CjigTabs " floating window lists tabs
|
|
121
|
+
|
|
122
|
+
" Open a scratch .js file, type:
|
|
123
|
+
" document.title
|
|
124
|
+
" Visual select the line, then:
|
|
125
|
+
:'<,'>CjigEval " floating window shows page title
|
|
126
|
+
|
|
127
|
+
" Type: FX.burst(50)
|
|
128
|
+
" Place cursor on it:
|
|
129
|
+
:CjigEvalForm " particles appear in browser
|
|
130
|
+
|
|
131
|
+
:CjigInject fancy-demo " injects harness
|
|
132
|
+
:CjigReload " reloads tab
|
|
133
|
+
:CjigDisconnect " kills subprocess
|
|
134
|
+
```
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
--- cjig.nvim — eval-from-editor via JSON-RPC over stdio.
|
|
2
|
+
---
|
|
3
|
+
--- Commands: CjigConnect, CjigDisconnect, CjigEval, CjigEvalForm,
|
|
4
|
+
--- CjigTabs, CjigInject, CjigReload
|
|
5
|
+
|
|
6
|
+
local rpc = require("cjig.rpc")
|
|
7
|
+
local ui = require("cjig.ui")
|
|
8
|
+
|
|
9
|
+
local M = {}
|
|
10
|
+
|
|
11
|
+
-- Evaluable treesitter node types
|
|
12
|
+
local EVAL_NODES = {
|
|
13
|
+
expression_statement = true,
|
|
14
|
+
variable_declaration = true,
|
|
15
|
+
lexical_declaration = true,
|
|
16
|
+
function_declaration = true,
|
|
17
|
+
call_expression = true,
|
|
18
|
+
assignment_expression = true,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
--- Detect language from filetype.
|
|
22
|
+
--- @return string "js" or "cljs"
|
|
23
|
+
local function detect_lang()
|
|
24
|
+
if vim.bo.filetype == "clojure" then
|
|
25
|
+
return "cljs"
|
|
26
|
+
end
|
|
27
|
+
return "js"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
--- Get visual selection text.
|
|
31
|
+
--- @return string
|
|
32
|
+
local function get_visual_selection()
|
|
33
|
+
local _, srow, scol, _ = unpack(vim.fn.getpos("'<"))
|
|
34
|
+
local _, erow, ecol, _ = unpack(vim.fn.getpos("'>"))
|
|
35
|
+
|
|
36
|
+
if srow > erow or (srow == erow and scol > ecol) then
|
|
37
|
+
srow, erow = erow, srow
|
|
38
|
+
scol, ecol = ecol, scol
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
local lines = vim.api.nvim_buf_get_lines(0, srow - 1, erow, false)
|
|
42
|
+
if #lines == 0 then return "" end
|
|
43
|
+
|
|
44
|
+
if #lines == 1 then
|
|
45
|
+
lines[1] = lines[1]:sub(scol, ecol)
|
|
46
|
+
else
|
|
47
|
+
lines[1] = lines[1]:sub(scol)
|
|
48
|
+
lines[#lines] = lines[#lines]:sub(1, ecol)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
return table.concat(lines, "\n")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
--- Walk up treesitter tree to find nearest evaluable node.
|
|
55
|
+
--- @return string|nil
|
|
56
|
+
local function get_treesitter_form()
|
|
57
|
+
local ok, ts_utils = pcall(require, "nvim-treesitter.ts_utils")
|
|
58
|
+
local node
|
|
59
|
+
|
|
60
|
+
if ok then
|
|
61
|
+
node = ts_utils.get_node_at_cursor()
|
|
62
|
+
else
|
|
63
|
+
-- Fallback: use built-in treesitter
|
|
64
|
+
node = vim.treesitter.get_node()
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if not node then return nil end
|
|
68
|
+
|
|
69
|
+
-- Walk up to nearest evaluable node
|
|
70
|
+
while node do
|
|
71
|
+
if EVAL_NODES[node:type()] then
|
|
72
|
+
local sr, sc, er, ec = node:range()
|
|
73
|
+
local lines = vim.api.nvim_buf_get_text(0, sr, sc, er, ec, {})
|
|
74
|
+
local text = table.concat(lines, "\n")
|
|
75
|
+
|
|
76
|
+
-- Strip trailing semicolon from expression statements
|
|
77
|
+
if node:type() == "expression_statement" then
|
|
78
|
+
text = text:gsub(";%s*$", "")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
return text
|
|
82
|
+
end
|
|
83
|
+
node = node:parent()
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
return nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
--- Eval code string in browser, show result in float.
|
|
90
|
+
--- @param code string
|
|
91
|
+
local function eval_and_show(code)
|
|
92
|
+
if code == "" then
|
|
93
|
+
ui.show_error("No code to evaluate")
|
|
94
|
+
return
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
local lang = detect_lang()
|
|
98
|
+
|
|
99
|
+
rpc.ensure_connected(function()
|
|
100
|
+
vim.schedule(function()
|
|
101
|
+
rpc.request("eval", { code = code, lang = lang }, function(result)
|
|
102
|
+
vim.schedule(function()
|
|
103
|
+
ui.show_result(result)
|
|
104
|
+
end)
|
|
105
|
+
end, function(err)
|
|
106
|
+
vim.schedule(function()
|
|
107
|
+
ui.show_error(err)
|
|
108
|
+
end)
|
|
109
|
+
end)
|
|
110
|
+
end)
|
|
111
|
+
end)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
--- Register all user commands.
|
|
115
|
+
local function register_commands()
|
|
116
|
+
vim.api.nvim_create_user_command("CjigConnect", function()
|
|
117
|
+
rpc.connect()
|
|
118
|
+
end, { desc = "Connect to Chrome via cjig serve" })
|
|
119
|
+
|
|
120
|
+
vim.api.nvim_create_user_command("CjigDisconnect", function()
|
|
121
|
+
rpc.disconnect()
|
|
122
|
+
end, { desc = "Disconnect from cjig" })
|
|
123
|
+
|
|
124
|
+
vim.api.nvim_create_user_command("CjigEval", function()
|
|
125
|
+
local code = get_visual_selection()
|
|
126
|
+
eval_and_show(code)
|
|
127
|
+
end, { range = true, desc = "Eval visual selection in browser" })
|
|
128
|
+
|
|
129
|
+
vim.api.nvim_create_user_command("CjigEvalForm", function()
|
|
130
|
+
local code = get_treesitter_form()
|
|
131
|
+
if not code then
|
|
132
|
+
ui.show_error("No evaluable form at cursor")
|
|
133
|
+
return
|
|
134
|
+
end
|
|
135
|
+
eval_and_show(code)
|
|
136
|
+
end, { desc = "Eval treesitter form at cursor" })
|
|
137
|
+
|
|
138
|
+
vim.api.nvim_create_user_command("CjigTabs", function()
|
|
139
|
+
rpc.ensure_connected(function()
|
|
140
|
+
vim.schedule(function()
|
|
141
|
+
rpc.request("tabs", {}, function(result)
|
|
142
|
+
vim.schedule(function()
|
|
143
|
+
if result and result.formatted then
|
|
144
|
+
ui.show_result(result.formatted)
|
|
145
|
+
else
|
|
146
|
+
ui.show_result(result)
|
|
147
|
+
end
|
|
148
|
+
end)
|
|
149
|
+
end, function(err)
|
|
150
|
+
vim.schedule(function()
|
|
151
|
+
ui.show_error(err)
|
|
152
|
+
end)
|
|
153
|
+
end)
|
|
154
|
+
end)
|
|
155
|
+
end)
|
|
156
|
+
end, { desc = "List browser tabs" })
|
|
157
|
+
|
|
158
|
+
vim.api.nvim_create_user_command("CjigInject", function(opts)
|
|
159
|
+
local ref = opts.args
|
|
160
|
+
if ref == "" then
|
|
161
|
+
ui.show_error("Usage: :CjigInject <name>")
|
|
162
|
+
return
|
|
163
|
+
end
|
|
164
|
+
rpc.ensure_connected(function()
|
|
165
|
+
vim.schedule(function()
|
|
166
|
+
rpc.request("inject", { ref = ref }, function(result)
|
|
167
|
+
vim.schedule(function()
|
|
168
|
+
if result and result.formatted then
|
|
169
|
+
vim.notify("cjig: " .. result.formatted, vim.log.levels.INFO)
|
|
170
|
+
else
|
|
171
|
+
ui.show_result(result)
|
|
172
|
+
end
|
|
173
|
+
end)
|
|
174
|
+
end, function(err)
|
|
175
|
+
vim.schedule(function()
|
|
176
|
+
ui.show_error(err)
|
|
177
|
+
end)
|
|
178
|
+
end)
|
|
179
|
+
end)
|
|
180
|
+
end)
|
|
181
|
+
end, { nargs = 1, desc = "Inject a harness script" })
|
|
182
|
+
|
|
183
|
+
vim.api.nvim_create_user_command("CjigReload", function()
|
|
184
|
+
rpc.ensure_connected(function()
|
|
185
|
+
vim.schedule(function()
|
|
186
|
+
rpc.request("reload", {}, function(_)
|
|
187
|
+
vim.schedule(function()
|
|
188
|
+
vim.notify("cjig: Reloaded", vim.log.levels.INFO)
|
|
189
|
+
end)
|
|
190
|
+
end, function(err)
|
|
191
|
+
vim.schedule(function()
|
|
192
|
+
ui.show_error(err)
|
|
193
|
+
end)
|
|
194
|
+
end)
|
|
195
|
+
end)
|
|
196
|
+
end)
|
|
197
|
+
end, { desc = "Reload current browser tab" })
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
--- Setup the plugin with optional config.
|
|
201
|
+
--- @param opts? table
|
|
202
|
+
function M.setup(opts)
|
|
203
|
+
opts = opts or {}
|
|
204
|
+
|
|
205
|
+
register_commands()
|
|
206
|
+
|
|
207
|
+
-- Default keymaps (disable with opts.keymaps = false)
|
|
208
|
+
if opts.keymaps ~= false then
|
|
209
|
+
local leader = opts.leader or "<leader>c"
|
|
210
|
+
|
|
211
|
+
vim.keymap.set("v", leader .. "e", ":<C-u>CjigEval<CR>",
|
|
212
|
+
{ silent = true, desc = "cjig: eval selection" })
|
|
213
|
+
vim.keymap.set("n", leader .. "f", "<cmd>CjigEvalForm<CR>",
|
|
214
|
+
{ silent = true, desc = "cjig: eval form" })
|
|
215
|
+
vim.keymap.set("n", leader .. "t", "<cmd>CjigTabs<CR>",
|
|
216
|
+
{ silent = true, desc = "cjig: list tabs" })
|
|
217
|
+
vim.keymap.set("n", leader .. "r", "<cmd>CjigReload<CR>",
|
|
218
|
+
{ silent = true, desc = "cjig: reload tab" })
|
|
219
|
+
vim.keymap.set("n", leader .. "c", "<cmd>CjigConnect<CR>",
|
|
220
|
+
{ silent = true, desc = "cjig: connect" })
|
|
221
|
+
vim.keymap.set("n", leader .. "d", "<cmd>CjigDisconnect<CR>",
|
|
222
|
+
{ silent = true, desc = "cjig: disconnect" })
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
return M
|