📦 Out of Context リポジトリ

リポジトリ / emssr / independent_common_compiler

r2
r2 emssr1
// Common scope の VS Code 拡張。
r2 emssr2
//
r2 emssr3
// web 版と同じ server.py を使う。拡張が足すのは三つだけになる。
r2 emssr4
//
r2 emssr5
//   1. server.py の起動と停止
r2 emssr6
//   2. notebook を webview で開く
r2 emssr7
//   3. 編集中の .cmn を hub へ送り、開いている全パネルを更新する
r2 emssr8
//
r2 emssr9
// 計算は拡張の中に無い。平文は server の /state へ渡り、各ウィンドウの
r2 emssr10
// kernel がそれぞれ実行する。lane の作業領域は交わらない(20 式 1.2)ので、
r2 emssr11
// 何枚開いても state が混ざらない。
r2 emssr12
r2 emssr13
const vscode = require('vscode');
r2 emssr14
const cp = require('child_process');
r2 emssr15
const fs = require('fs');
r2 emssr16
const path = require('path');
r2 emssr17
const http = require('http');
r2 emssr18
r2 emssr19
let server = null;      // 起動した server.py の process
r2 emssr20
let out = null;         // その標準出力を写す channel
r2 emssr21
r2 emssr22
function cfg(key) {
r2 emssr23
  return vscode.workspace.getConfiguration('commonScope').get(key);
r2 emssr24
}
r2 emssr25
r2 emssr26
// ===== server.py の場所 =====
r2 emssr27
//
r2 emssr28
// 設定が空なら、開いている folder の下から common_scope_notebook/server.py を探す。
r2 emssr29
r2 emssr30
function findRoot() {
r2 emssr31
  const set = cfg('root');
r2 emssr32
  if (set) return set;
r2 emssr33
  const rel = path.join('continuous_republic', 'common_apps', 'common_scope_notebook');
r2 emssr34
  for (const f of vscode.workspace.workspaceFolders || []) {
r2 emssr35
    for (const c of [path.join(f.uri.fsPath, rel), f.uri.fsPath]) {
r2 emssr36
      if (fs.existsSync(path.join(c, 'server.py'))) return c;
r2 emssr37
    }
r2 emssr38
  }
r2 emssr39
  return null;
r2 emssr40
}
r2 emssr41
r2 emssr42
function url(pathname) {
r2 emssr43
  return `http://127.0.0.1:${cfg('port')}${pathname || '/'}`;
r2 emssr44
}
r2 emssr45
r2 emssr46
function alive() {
r2 emssr47
  return new Promise(ok => {
r2 emssr48
    const req = http.get(url('/api/state'), r => { r.resume(); ok(r.statusCode === 200); });
r2 emssr49
    req.on('error', () => ok(false));
r2 emssr50
    req.setTimeout(400, () => { req.destroy(); ok(false); });
r2 emssr51
  });
r2 emssr52
}
r2 emssr53
r2 emssr54
async function ensureServer() {
r2 emssr55
  if (await alive()) return true;
r2 emssr56
  const root = findRoot();
r2 emssr57
  if (!root) {
r2 emssr58
    vscode.window.showErrorMessage(
r2 emssr59
      'common_scope_notebook が見つかりません。設定 commonScope.root にフォルダを書いてください。');
r2 emssr60
    return false;
r2 emssr61
  }
r2 emssr62
  out = out || vscode.window.createOutputChannel('Common scope');
r2 emssr63
  server = cp.spawn(cfg('python'),
r2 emssr64
    [path.join(root, 'server.py'), '--port', String(cfg('port')), '--no-browser'],
r2 emssr65
    { cwd: root });
r2 emssr66
  server.stdout.on('data', d => out.append(d.toString()));
r2 emssr67
  server.stderr.on('data', d => out.append(d.toString()));
r2 emssr68
  server.on('exit', c => { out.appendLine(`server 終了 (${c})`); server = null; });
r2 emssr69
r2 emssr70
  for (let k = 0; k < 40; k++) {
r2 emssr71
    await new Promise(r => setTimeout(r, 250));
r2 emssr72
    if (await alive()) return true;
r2 emssr73
  }
r2 emssr74
  out.show(true);
r2 emssr75
  vscode.window.showErrorMessage('server.py が起動しません。出力を確認してください。');
r2 emssr76
  return false;
r2 emssr77
}
r2 emssr78
r2 emssr79
// ===== hub へ送る =====
r2 emssr80
r2 emssr81
function post(body) {
r2 emssr82
  return new Promise((ok, no) => {
r2 emssr83
    const data = Buffer.from(JSON.stringify(body), 'utf8');
r2 emssr84
    const req = http.request(url('/state'),
r2 emssr85
      { method: 'POST', headers: { 'Content-Length': data.length } },
r2 emssr86
      r => { r.resume(); ok(r.statusCode); });
r2 emssr87
    req.on('error', no);
r2 emssr88
    req.end(data);
r2 emssr89
  });
r2 emssr90
}
r2 emssr91
r2 emssr92
// 同じフォルダに .dic があれば採用語彙として一緒に送る
r2 emssr93
function dicBeside(file) {
r2 emssr94
  const dir = path.dirname(file);
r2 emssr95
  const own = path.join(dir, path.basename(file, '.cmn') + '.dic');
r2 emssr96
  if (fs.existsSync(own)) return fs.readFileSync(own, 'utf8');
r2 emssr97
  for (const f of fs.readdirSync(dir)) {
r2 emssr98
    if (f.endsWith('.dic')) return fs.readFileSync(path.join(dir, f), 'utf8');
r2 emssr99
  }
r2 emssr100
  return null;
r2 emssr101
}
r2 emssr102
r2 emssr103
async function send() {
r2 emssr104
  const ed = vscode.window.activeTextEditor;
r2 emssr105
  if (!ed || !ed.document.fileName.endsWith('.cmn')) {
r2 emssr106
    vscode.window.showWarningMessage('.cmn を開いてから実行してください。');
r2 emssr107
    return;
r2 emssr108
  }
r2 emssr109
  if (!(await ensureServer())) return;
r2 emssr110
  await post({
r2 emssr111
    notebook: path.basename(ed.document.fileName),
r2 emssr112
    spec: { source: ed.document.getText(), dic: dicBeside(ed.document.fileName),
r2 emssr113
            seeds: null, words: null, budget: null },
r2 emssr114
    view: { inputs: null, cut: null, out: 0 },
r2 emssr115
  });
r2 emssr116
}
r2 emssr117
r2 emssr118
// ===== webview =====
r2 emssr119
r2 emssr120
function panel(title, pathname) {
r2 emssr121
  const p = vscode.window.createWebviewPanel('commonScope', title,
r2 emssr122
    vscode.ViewColumn.Beside, { enableScripts: true, retainContextWhenHidden: true });
r2 emssr123
  const src = url(pathname);
r2 emssr124
  p.webview.html = `<!DOCTYPE html><html><head><meta charset="utf-8">
r2 emssr125
<style>html,body,iframe{margin:0;padding:0;height:100%;width:100%;border:0}</style>
r2 emssr126
</head><body><iframe src="${src}"></iframe></body></html>`;
r2 emssr127
  return p;
r2 emssr128
}
r2 emssr129
r2 emssr130
const PANES = {
r2 emssr131
  '層と机': 'chamber', '箱の DAG': 'dag', 'ツイ数平面': 'tsui',
r2 emssr132
  'lane64': 'lane', '接合秩': 'joint', '音節と箱': 'text',
r2 emssr133
};
r2 emssr134
r2 emssr135
function activate(ctx) {
r2 emssr136
  ctx.subscriptions.push(
r2 emssr137
    vscode.commands.registerCommand('commonScope.open', async () => {
r2 emssr138
      if (await ensureServer()) panel('Common scope notebook', '/');
r2 emssr139
    }),
r2 emssr140
    vscode.commands.registerCommand('commonScope.send', send),
r2 emssr141
    vscode.commands.registerCommand('commonScope.pane', async () => {
r2 emssr142
      const pick = await vscode.window.showQuickPick(Object.keys(PANES),
r2 emssr143
        { placeHolder: '開くパネル' });
r2 emssr144
      if (!pick) return;
r2 emssr145
      if (await ensureServer()) panel(pick, '/?pane=' + PANES[pick]);
r2 emssr146
    }),
r2 emssr147
    vscode.commands.registerCommand('commonScope.stop', () => {
r2 emssr148
      if (server) { server.kill(); server = null; }
r2 emssr149
    }));
r2 emssr150
}
r2 emssr151
r2 emssr152
function deactivate() {
r2 emssr153
  if (server) server.kill();
r2 emssr154
}
r2 emssr155
r2 emssr156
module.exports = { activate, deactivate };