diff --git a/package.json b/package.json index 08a2a8e..18deb60 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "lib/index.js", "publisher": "chemzqm", "engines": { - "coc": "^0.0.35" + "coc": "^0.0.65" }, "keywords": [ "coc.nvim", @@ -446,7 +446,7 @@ "@types/fast-diff": "^1.2.0", "@types/find-up": "^2.1.1", "@types/node": "^11.13.0", - "coc.nvim": "0.0.62", + "coc.nvim": "^0.0.65", "rimraf": "^2.6.3", "tslint": "^5.15.0" }, diff --git a/src/server/features/watchBuild.ts b/src/server/features/watchBuild.ts index dd31635..20b844d 100644 --- a/src/server/features/watchBuild.ts +++ b/src/server/features/watchBuild.ts @@ -1,5 +1,5 @@ import { ChildProcess, spawn } from 'child_process' -import { disposeAll, StatusBarItem, workspace } from 'coc.nvim' +import { disposeAll, StatusBarItem, workspace, TaskOptions } from 'coc.nvim' import { Command, CommandManager } from 'coc.nvim/lib/commands' import findUp from 'find-up' import fs from 'fs' @@ -9,6 +9,7 @@ import { Disposable, Location } from 'vscode-languageserver-protocol' import Uri from 'vscode-uri' import which from 'which' import { resolveRoot } from '../utils/fs' +import Task from 'coc.nvim/lib/model/task' const TSC = './node_modules/.bin/tsc' const countRegex = /Found\s+(\d+)\s+error/ @@ -27,20 +28,64 @@ enum TscStatus { ERROR, } -class WatchCommand implements Command { - public readonly id: string = 'tsserver.watchBuild' +export default class WatchProject implements Disposable { + private disposables: Disposable[] = [] + public static readonly id: string = 'tsserver.watchBuild' + public static readonly startTexts: string[] = ['Starting compilation in watch mode', 'Starting incremental compilation'] private statusItem: StatusBarItem private isRunning = false - private process: ChildProcess + private task: Task + private options: TaskOptions - constructor() { + public constructor( + commandManager: CommandManager + ) { this.statusItem = workspace.createStatusBarItem(1, { progress: true }) + let task = this.task = workspace.createTask('TSC') + this.options = this.getOptions() + this.disposables.push(commandManager.registerCommand(WatchProject.id, async () => { + await this.start(this.options) + })) + task.onExit(code => { + if (code != 0) { + workspace.showMessage(`TSC exit with code ${code}`, 'warning') + } + this.onStop() + }) + task.onStdout(lines => { + for (let line of lines) { + this.onLine(line) + } + }) + task.onStderr(lines => { + workspace.showMessage(`TSC error: ` + lines.join('\n'), 'error') + }) + this.disposables.push(Disposable.create(() => { + task.dispose() + })) + this.check().catch(_e => { + // noop + }) + } + + private async check(): Promise { + let running = await this.task.running + if (running) { + this.statusItem.isProgress = false + this.statusItem.text = '?' + this.statusItem.show() + } else { + this.onStop() + } + } + + private async start(options: TaskOptions): Promise { + await this.task.start(options) } private onStop(): void { let { nvim } = workspace this.isRunning = false - nvim.setVar('Tsc_running', 0, true) this.statusItem.hide() } @@ -51,60 +96,43 @@ class WatchCommand implements Command { workspace.nvim.call('setqflist', [[], 'r'], true) } - private async start(cmd: string, args: string[], cwd: string): Promise { - if (this.isRunning) { - this.process.kill() - await wait(200) + private onLine(line: string): void { + if (countRegex.test(line)) { + let ms = line.match(countRegex) + this.statusItem.text = ms[1] == '0' ? '✓' : '✗' + this.statusItem.isProgress = false + } else if (WatchProject.startTexts.findIndex(s => line.indexOf(s) !== -1) != -1) { + this.onStart() + } else { + let ms = line.match(errorRegex) + if (!ms) return + let fullpath = path.join(this.options.cwd, ms[1]) + let uri = Uri.file(fullpath).toString() + let doc = workspace.getDocument(uri) + let bufnr = doc ? doc.bufnr : null + let item = { + filename: fullpath, + lnum: Number(ms[2]), + col: Number(ms[3]), + text: `[tsc ${ms[5]}] ${ms[6]}`, + type: /error/i.test(ms[4]) ? 'E' : 'W' + } as any + if (bufnr) item.bufnr = bufnr + workspace.nvim.call('setqflist', [[item], 'a']) } - this.isRunning = true - workspace.nvim.setVar('Tsc_running', 1, true) - this.process = spawn(cmd, args, { cwd }) - this.process.on('error', e => { - workspace.showMessage(e.message, 'error') - }) - const rl = readline.createInterface(this.process.stdout) - this.process.on('exit', () => { - this.onStop() - rl.close() - }) - this.process.stderr.on('data', chunk => { - workspace.showMessage(chunk.toString('utf8'), 'error') - }) - const startTexts = ['Starting compilation in watch mode', 'Starting incremental compilation'] - rl.on('line', line => { - if (countRegex.test(line)) { - let ms = line.match(countRegex) - this.statusItem.text = ms[1] == '0' ? '✓' : '✗' - this.statusItem.isProgress = false - } else if (startTexts.findIndex(s => line.indexOf(s) !== -1) != -1) { - this.onStart() - } else { - let ms = line.match(errorRegex) - if (!ms) return - let fullpath = path.join(cwd, ms[1]) - let uri = Uri.file(fullpath).toString() - let doc = workspace.getDocument(uri) - let bufnr = doc ? doc.bufnr : null - let item = { - filename: fullpath, - lnum: Number(ms[2]), - col: Number(ms[3]), - text: `[tsc ${ms[5]}] ${ms[6]}`, - type: /error/i.test(ms[4]) ? 'E' : 'W' - } as any - if (bufnr) item.bufnr = bufnr - workspace.nvim.call('setqflist', [[item], 'a']) - } - }) } - public async execute(): Promise { + public getOptions(): TaskOptions { let docs = workspace.documents let idx = docs.findIndex(doc => doc.uri.indexOf(TSC) !== -1) if (idx !== -1) return - let document = await workspace.document - let fsPath = Uri.parse(document.uri).fsPath - let cwd = path.dirname(fsPath) + let doc = workspace.getDocument(workspace.bufnr) + let cwd: string + if (doc && doc.schema == 'file') { + cwd = path.dirname(Uri.parse(doc.uri).fsPath) + } else { + cwd = workspace.cwd + } let res = findUp.sync(['node_modules'], { cwd }) let cmd: string let root: string @@ -130,26 +158,11 @@ class WatchCommand implements Command { return } let configPath = path.relative(root, path.join(configRoot, 'tsconfig.json')) - this.start(cmd, ['-p', configPath, '--watch', 'true', '--pretty', 'false'], root) - } -} - -export default class WatchProject implements Disposable { - private disposables: Disposable[] = [] - - public constructor( - commandManager: CommandManager - ) { - let cmd = new WatchCommand() - commandManager.register(cmd) - let { nvim } = workspace - nvim.getVar('Tsc_running').then(running => { - if (running) { - cmd.execute().catch(e => { - workspace.showMessage('TSC:' + e.message, 'error') - }) - } - }) + return { + cmd, + args: ['-p', configPath, '--watch', 'true', '--pretty', 'false'], + cwd: root + } } public dispose(): void { diff --git a/yarn.lock b/yarn.lock index 4695f79..9a2ac15 100644 --- a/yarn.lock +++ b/yarn.lock @@ -85,6 +85,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +binary-search@1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/binary-search/-/binary-search-1.3.5.tgz#479ad009589e0273cf54e5d74ab1546c489078ce" + integrity sha512-RHFP0AdU6KAB0CCZsRMU2CJTk2EpL8GLURT+4gilpjr1f/7M91FgUMnXuQLmf3OKLet34gjuNFwO7e4agdX5pw== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -125,22 +130,23 @@ chalk@^2.3.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -coc.nvim@0.0.62: - version "0.0.62" - resolved "https://registry.yarnpkg.com/coc.nvim/-/coc.nvim-0.0.62.tgz#49557e9bc95ce3627df66914b787be982374d986" - integrity sha512-DlaLXlRELXwu39DkXvZ+kfZdKImLiq9mRkFOnzzwMD/xftN/Ytj2UlEndMr9gl2hmM5Z6VzZC1wvn6JYU9Zj7A== +coc.nvim@^0.0.65: + version "0.0.65" + resolved "https://registry.yarnpkg.com/coc.nvim/-/coc.nvim-0.0.65.tgz#6c802a30beb63b02a71f2bdd07db50542c77a46f" + integrity sha512-CPvc4g0PJMlo/0A8kXvIAOWGqaDJKOpe6OvMhY4SJhWZRL03AExjByiLLGcBD+tOONBJNyDpPBnfMUqkOD0Vmg== dependencies: "@chemzqm/neovim" "4.4.1" + binary-search "1.3.5" debounce "^1.2.0" fast-diff "^1.2.0" fb-watchman "^2.0.0" find-up "^3.0.0" glob "^7.1.3" isuri "^2.0.3" - jsonc-parser "^2.0.3" - log4js "^4.0.2" + jsonc-parser "^2.1.0" + log4js "^4.1.0" minimatch "^3.0.4" - semver "^5.6.0" + semver "^6.0.0" tslib "^1.9.3" uuid "^3.3.2" vscode-languageserver-protocol "^3.15.0-next.1" @@ -339,7 +345,7 @@ js-yaml@^3.13.0: argparse "^1.0.7" esprima "^4.0.0" -jsonc-parser@^2.0.3: +jsonc-parser@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.1.0.tgz#eb0d0c7a3c33048524ce3574c57c7278fb2f1bf3" integrity sha512-n9GrT8rrr2fhvBbANa1g+xFmgGK5X91KFeDwlKQ3+SJfmH5+tKv/M/kahx/TXOMflfWHKGKqKyfHQaLKTNzJ6w== @@ -364,7 +370,7 @@ lodash@^4.17.10, lodash@^4.17.11: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== -log4js@^4.0.2: +log4js@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log4js/-/log4js-4.1.0.tgz#57983c6a443546a8c8607e9cb045d2a117c27644" integrity sha512-eDa+zZPeVEeK6QGJAePyXM6pg4P3n3TO5rX9iZMVY48JshsTyLJZLIL5HipI1kQ2qLsSyOpUqNND/C5H4WhhiA== @@ -484,11 +490,6 @@ semver@^5.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" integrity sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw== -semver@^5.6.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" - integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== - semver@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.0.0.tgz#05e359ee571e5ad7ed641a6eec1e547ba52dea65"