リポジトリ / emssr / independent_common_compiler
r2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | // Common scope の VS Code 拡張。 // // web 版と同じ server.py を使う。拡張が足すのは三つだけになる。 // // 1. server.py の起動と停止 // 2. notebook を webview で開く // 3. 編集中の .cmn を hub へ送り、開いている全パネルを更新する // // 計算は拡張の中に無い。平文は server の /state へ渡り、各ウィンドウの // kernel がそれぞれ実行する。lane の作業領域は交わらない(20 式 1.2)ので、 // 何枚開いても state が混ざらない。 const vscode = require('vscode'); const cp = require('child_process'); const fs = require('fs'); const path = require('path'); const http = require('http'); let server = null; // 起動した server.py の process let out = null; // その標準出力を写す channel function cfg(key) { return vscode.workspace.getConfiguration('commonScope').get(key); } // ===== server.py の場所 ===== // // 設定が空なら、開いている folder の下から common_scope_notebook/server.py を探す。 function findRoot() { const set = cfg('root'); if (set) return set; const rel = path.join('continuous_republic', 'common_apps', 'common_scope_notebook'); for (const f of vscode.workspace.workspaceFolders || []) { for (const c of [path.join(f.uri.fsPath, rel), f.uri.fsPath]) { if (fs.existsSync(path.join(c, 'server.py'))) return c; } } return null; } function url(pathname) { return `http://127.0.0.1:${cfg('port')}${pathname || '/'}`; } function alive() { return new Promise(ok => { const req = http.get(url('/api/state'), r => { r.resume(); ok(r.statusCode === 200); }); req.on('error', () => ok(false)); req.setTimeout(400, () => { req.destroy(); ok(false); }); }); } async function ensureServer() { if (await alive()) return true; const root = findRoot(); if (!root) { vscode.window.showErrorMessage( 'common_scope_notebook が見つかりません。設定 commonScope.root にフォルダを書いてください。'); return false; } out = out || vscode.window.createOutputChannel('Common scope'); server = cp.spawn(cfg('python'), [path.join(root, 'server.py'), '--port', String(cfg('port')), '--no-browser'], { cwd: root }); server.stdout.on('data', d => out.append(d.toString())); server.stderr.on('data', d => out.append(d.toString())); server.on('exit', c => { out.appendLine(`server 終了 (${c})`); server = null; }); for (let k = 0; k < 40; k++) { await new Promise(r => setTimeout(r, 250)); if (await alive()) return true; } out.show(true); vscode.window.showErrorMessage('server.py が起動しません。出力を確認してください。'); return false; } // ===== hub へ送る ===== function post(body) { return new Promise((ok, no) => { const data = Buffer.from(JSON.stringify(body), 'utf8'); const req = http.request(url('/state'), { method: 'POST', headers: { 'Content-Length': data.length } }, r => { r.resume(); ok(r.statusCode); }); req.on('error', no); req.end(data); }); } // 同じフォルダに .dic があれば採用語彙として一緒に送る function dicBeside(file) { const dir = path.dirname(file); const own = path.join(dir, path.basename(file, '.cmn') + '.dic'); if (fs.existsSync(own)) return fs.readFileSync(own, 'utf8'); for (const f of fs.readdirSync(dir)) { if (f.endsWith('.dic')) return fs.readFileSync(path.join(dir, f), 'utf8'); } return null; } async function send() { const ed = vscode.window.activeTextEditor; if (!ed || !ed.document.fileName.endsWith('.cmn')) { vscode.window.showWarningMessage('.cmn を開いてから実行してください。'); return; } if (!(await ensureServer())) return; await post({ notebook: path.basename(ed.document.fileName), spec: { source: ed.document.getText(), dic: dicBeside(ed.document.fileName), seeds: null, words: null, budget: null }, view: { inputs: null, cut: null, out: 0 }, }); } // ===== webview ===== function panel(title, pathname) { const p = vscode.window.createWebviewPanel('commonScope', title, vscode.ViewColumn.Beside, { enableScripts: true, retainContextWhenHidden: true }); const src = url(pathname); p.webview.html = `<!DOCTYPE html><html><head><meta charset="utf-8"> <style>html,body,iframe{margin:0;padding:0;height:100%;width:100%;border:0}</style> </head><body><iframe src="${src}"></iframe></body></html>`; return p; } const PANES = { '層と机': 'chamber', '箱の DAG': 'dag', 'ツイ数平面': 'tsui', 'lane64': 'lane', '接合秩': 'joint', '音節と箱': 'text', }; function activate(ctx) { ctx.subscriptions.push( vscode.commands.registerCommand('commonScope.open', async () => { if (await ensureServer()) panel('Common scope notebook', '/'); }), vscode.commands.registerCommand('commonScope.send', send), vscode.commands.registerCommand('commonScope.pane', async () => { const pick = await vscode.window.showQuickPick(Object.keys(PANES), { placeHolder: '開くパネル' }); if (!pick) return; if (await ensureServer()) panel(pick, '/?pane=' + PANES[pick]); }), vscode.commands.registerCommand('commonScope.stop', () => { if (server) { server.kill(); server = null; } })); } function deactivate() { if (server) server.kill(); } module.exports = { activate, deactivate }; |