diff --git a/package-lock.json b/package-lock.json index cb38f560a..224c00276 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "dependencies": { "@types/glob": { "version": "5.0.30", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-5.0.30.tgz", + "resolved": "http://registry.npmjs.org/@types/glob/-/glob-5.0.30.tgz", "integrity": "sha1-ECZAnFYlqGiQdGAoCNCCsoZ7ilE=", "dev": true, "requires": { @@ -14,6 +14,21 @@ "@types/node": "*" } }, + "@types/lodash": { + "version": "4.14.146", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.146.tgz", + "integrity": "sha512-JzJcmQ/ikHSv7pbvrVNKJU5j9jL9VLf3/gqs048CEnBVVVEv4kve3vLxoPHGvclutS+Il4SBIuQQ087m1eHffw==", + "dev": true + }, + "@types/lodash.findindex": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/@types/lodash.findindex/-/lodash.findindex-4.6.6.tgz", + "integrity": "sha512-quPh7tw70yhryaubH6wBvgIQgeU1PFjdoT4eaW6WCKzjIlxgImLKIv4bvJhMTUlRkMgf5VAfECKKXKuB8cexgw==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -33,9 +48,9 @@ "dev": true }, "@types/vscode": { - "version": "1.36.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.36.0.tgz", - "integrity": "sha512-SbHR3Q5g/C3N+Ila3KrRf1rSZiyHxWdOZ7X3yFHXzw6HrvRLuVZrxnwEX0lTBMRpH9LkwZdqRTgXW+D075jxkg==", + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.40.0.tgz", + "integrity": "sha512-5kEIxL3qVRkwhlMerxO7XuMffa+0LBl+iG2TcRa0NsdoeSFLkt/9hJ02jsi/Kvc6y8OVF2N2P2IHP5S4lWf/5w==", "dev": true }, "@types/winston": { @@ -281,7 +296,7 @@ }, "ansi-colors": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", "dev": true, "requires": { @@ -382,7 +397,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", "dev": true }, "arr-map": { @@ -867,7 +882,7 @@ }, "buffer": { "version": "3.6.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-3.6.0.tgz", + "resolved": "http://registry.npmjs.org/buffer/-/buffer-3.6.0.tgz", "integrity": "sha1-pyyTb3e5a/UvX357RnGAYoVR3vs=", "dev": true, "requires": { @@ -995,7 +1010,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -1473,7 +1488,7 @@ "decompress-tar": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", - "integrity": "sha1-cYy9P8sWIJcW5womuE57pFkuWvE=", + "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", "dev": true, "requires": { "file-type": "^5.2.0", @@ -1492,7 +1507,7 @@ "decompress-tarbz2": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", - "integrity": "sha1-MIKluIDqQEOBY0nzeLVsUWvho5s=", + "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", "dev": true, "requires": { "decompress-tar": "^4.1.0", @@ -1505,7 +1520,7 @@ "file-type": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", - "integrity": "sha1-5QzXXTVv/tTjBtxPW89Sp5kDqRk=", + "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", "dev": true } } @@ -1513,7 +1528,7 @@ "decompress-targz": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", - "integrity": "sha1-wJvDXE0R894J8tLaU+neI+fOHu4=", + "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", "dev": true, "requires": { "decompress-tar": "^4.1.1", @@ -2155,7 +2170,7 @@ }, "file-type": { "version": "3.9.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=", "dev": true }, @@ -3213,7 +3228,7 @@ }, "through": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, @@ -3628,7 +3643,7 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, "is-builtin-module": { @@ -3744,7 +3759,7 @@ "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { "isobject": "^3.0.1" @@ -4013,9 +4028,9 @@ } }, "lodash": { - "version": "4.17.14", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", - "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==" + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, "lodash._basecopy": { "version": "3.0.1", @@ -4086,6 +4101,12 @@ "lodash._root": "^3.0.0" } }, + "lodash.findindex": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.findindex/-/lodash.findindex-4.6.0.tgz", + "integrity": "sha1-oyRd7mH7m24GJLU1ElYku2nBEQY=", + "dev": true + }, "lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", @@ -4336,14 +4357,14 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -4400,7 +4421,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { @@ -4409,7 +4430,7 @@ "dependencies": { "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true } @@ -4824,7 +4845,7 @@ }, "os-tmpdir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "p-defer": { @@ -4956,7 +4977,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-key": { @@ -5024,7 +5045,7 @@ }, "pify": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true }, @@ -5071,7 +5092,7 @@ }, "pretty-hrtime": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "resolved": "http://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", "dev": true }, @@ -5917,7 +5938,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -5936,7 +5957,7 @@ "strip-dirs": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", - "integrity": "sha1-SYdzYmT8NEzyD2w0rKnRPR1O1sU=", + "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", "dev": true, "requires": { "is-natural-number": "^4.0.1" diff --git a/package.json b/package.json index b952a1c06..07296c5f3 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "preview": true, "enableProposedApi": false, "engines": { - "vscode": "^1.36.0" + "vscode": "^1.40.0" }, "repository": { "type": "git", @@ -452,6 +452,11 @@ "command": "java.project.listSourcePaths", "title": "List all Java source paths", "category": "Java" + }, + { + "command": "java.show.server.task.status", + "title": "Show Build Job Status", + "category": "Java" } ], "keybindings": [ @@ -541,13 +546,15 @@ }, "devDependencies": { "@types/glob": "5.0.30", + "@types/lodash.findindex": "^4.6.6", "@types/mocha": "^5.2.5", "@types/node": "^6.0.40", - "@types/vscode": "^1.36.0", + "@types/vscode": "^1.40.0", "@types/winston": "^2.4.4", "gulp": "^4.0.0", "gulp-decompress": "2.0.1", "gulp-download": "0.0.1", + "lodash.findindex": "^4.6.0", "mocha": "^5.2.0", "ts-loader": "^5.3.1", "tslint": "^5.11.0", diff --git a/src/commands.ts b/src/commands.ts index 832cbbb46..458ddb390 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -168,4 +168,8 @@ export namespace Commands { * Navigate To Super Method Command. */ export const NAVIGATE_TO_SUPER_IMPLEMENTATION_COMMAND = 'java.action.navigateToSuperImplementation'; + /** + * Show server task status + */ + export const SHOW_SERVER_TASK_STATUS = 'java.show.server.task.status'; } diff --git a/src/extension.ts b/src/extension.ts index e2ab74295..a1f38abbf 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -24,8 +24,9 @@ import { getJavaConfiguration } from './utils'; import { onConfigurationChange, excludeProjectSettingsFiles } from './settings'; import { logger, initializeLogFile } from './log'; import glob = require('glob'); +import { serverTasks } from './serverTasks'; +import { serverTaskPresenter } from './serverTaskPresenter'; -let lastStatus; let languageClient: LanguageClient; const jdtEventEmitter = new EventEmitter(); const cleanWorkspaceFileName = '.cleanWorkspace'; @@ -179,8 +180,9 @@ export function activate(context: ExtensionContext): Promise { const item = window.createStatusBarItem(StatusBarAlignment.Right, Number.MIN_VALUE); item.text = '$(sync~spin)'; - item.command = Commands.OPEN_OUTPUT; - const progressBar = window.createStatusBarItem(StatusBarAlignment.Left, Number.MIN_VALUE + 1); + item.command = Commands.SHOW_SERVER_TASK_STATUS; + + commands.executeCommand(Commands.SHOW_SERVER_TASK_STATUS); let serverOptions; const port = process.env['SERVER_PORT']; @@ -213,8 +215,6 @@ export function activate(context: ExtensionContext): Promise { switch (report.type) { case 'Started': item.text = '$(thumbsup)'; - p.report({ message: 'Finished' }); - lastStatus = item.text; commands.executeCommand('setContext', 'javaLSReady', true); resolve({ apiVersion: '0.2', @@ -225,8 +225,6 @@ export function activate(context: ExtensionContext): Promise { break; case 'Error': item.text = '$(thumbsdown)'; - lastStatus = item.text; - p.report({ message: 'Finished with Error' }); toggleItem(window.activeTextEditor, item); resolve({ apiVersion: '0.2', @@ -236,22 +234,15 @@ export function activate(context: ExtensionContext): Promise { }); break; case 'Starting': - p.report({ message: report.message }); - break; case 'Message': - item.text = report.message; - setTimeout(() => { item.text = lastStatus; }, 3000); + // message goes to progress report instead break; } item.tooltip = report.message; toggleItem(window.activeTextEditor, item); }); languageClient.onNotification(ProgressReportNotification.type, (progress) => { - progressBar.show(); - progressBar.text = progress.status; - if (progress.complete) { - setTimeout(() => { progressBar.hide(); }, 500); - } + serverTasks.updateServerTask(progress); }); languageClient.onNotification(ActionableNotification.type, (notification) => { let show = null; @@ -437,6 +428,8 @@ export function activate(context: ExtensionContext): Promise { context.subscriptions.push(commands.registerCommand(Commands.CLEAN_WORKSPACE, () => cleanWorkspace(workspacePath))); + context.subscriptions.push(commands.registerCommand(Commands.SHOW_SERVER_TASK_STATUS, () => serverTaskPresenter.presentServerTaskView())); + context.subscriptions.push(onConfigurationChange(languageClient, context)); toggleItem(window.activeTextEditor, item); }); diff --git a/src/serverTaskPresenter.ts b/src/serverTaskPresenter.ts new file mode 100644 index 000000000..e15559c4a --- /dev/null +++ b/src/serverTaskPresenter.ts @@ -0,0 +1,91 @@ +import { tasks, Task, TaskScope, Pseudoterminal, CustomExecution, TaskExecution, TaskRevealKind, TaskPanelKind, EventEmitter, Event, TerminalDimensions } from "vscode"; +import * as vscode from "vscode"; +import { serverTasks } from "./serverTasks"; +import { Disposable } from "vscode-languageclient"; +import { ProgressReport } from "./protocol"; + +const JAVA_SERVER_TASK_PRESENTER_TASK_NAME = "Java Build Status"; + +export namespace serverTaskPresenter { + export async function presentServerTaskView() { + const execution = await getPresenterTaskExecution(); + const terminals = vscode.window.terminals; + const presenterTerminals = terminals.filter(terminal => terminal.name.endsWith(execution.task.name)); + if (presenterTerminals.length > 0) { + presenterTerminals[0].show(); + } + } +} + +let presenterTaskExecution: TaskExecution = null; + +async function getPresenterTaskExecution(): Promise { + if (!!presenterTaskExecution) { + return Promise.resolve(presenterTaskExecution); + } + + const presenterTask = new Task({ type: "Java" }, TaskScope.Workspace, JAVA_SERVER_TASK_PRESENTER_TASK_NAME, "Java", new CustomExecution(async () => { + return new ServerTaskTerminal(); + })); + + presenterTask.presentationOptions = { + reveal: TaskRevealKind.Always, + panel: TaskPanelKind.Dedicated, + clear: true, + echo: false, + focus: false + }; + + return presenterTaskExecution = await tasks.executeTask(presenterTask); +} + +class ServerTaskTerminal implements Pseudoterminal { + private _onDidWriteEvent = new EventEmitter(); + private _onDidCloseEvent = new EventEmitter(); + private _subscription: Disposable = null; + private _rows: number = 1; + + onDidWrite: Event = this._onDidWriteEvent.event; + onDidClose?: Event = this._onDidCloseEvent.event; + + constructor() { + this._subscription = serverTasks.onDidUpdateServerTask(serverTasks => { + this.printTasks(serverTasks); + }); + } + + private printTasks(tasks: ProgressReport[]) { + this.clearScreen(); + tasks.forEach(task => this.printTask(task)); + } + + private clearScreen() { + this._onDidWriteEvent.fire("\u001Bc"); + } + + private printTask(report: ProgressReport) { + if (report.complete) { + this._onDidWriteEvent.fire(`${report.id.slice(0, 8)} ${report.task} [Done]\r\n`); + return; + } + + this._onDidWriteEvent.fire(`${report.id.slice(0, 8)} ${report.task}: ${report.status} [${report.workDone}/${report.totalWork}]\r\n`); + } + + open(initialDimensions: TerminalDimensions): void { + serverTasks.suggestTaskEntrySize(initialDimensions.rows - 1); + const tasks = serverTasks.getHistory(); + this.printTasks(tasks); + } + + close(): void { + presenterTaskExecution.terminate(); + presenterTaskExecution = null; + this._subscription.dispose(); + this._onDidCloseEvent.fire(); + } + + setDimensions(dimensions: TerminalDimensions) { + serverTasks.suggestTaskEntrySize(dimensions.rows - 1); + } +} diff --git a/src/serverTasks.ts b/src/serverTasks.ts new file mode 100644 index 000000000..251ac450c --- /dev/null +++ b/src/serverTasks.ts @@ -0,0 +1,70 @@ +import { EventEmitter, Progress } from "vscode"; +import { ProgressReport } from "./protocol"; + +const findIndex = require("lodash.findindex"); + +let tasks: ProgressReport[] = []; + +const emitter = new EventEmitter(); +let suggestedTaskEntrySize = 10; + +export namespace serverTasks { + export const onDidUpdateServerTask = emitter.event; + + export function updateServerTask(report: ProgressReport) { + applyReport(report); + emitter.fire(tasks); + } + + export function suggestTaskEntrySize(size: number) { + if (size > suggestedTaskEntrySize) { + suggestedTaskEntrySize = size; + } + } + + export function getHistory(): ProgressReport[] { + return tasks; + } +} + +function organizeTasks() { + let newArray = tasks; + if (tasks.length > suggestedTaskEntrySize) { + newArray = recycleTasks(tasks, suggestedTaskEntrySize); + } + + // make sure in-progress items are always at the end + newArray.sort((a, b) => { + const ai = a.complete ? 0 : 1, bi = a.complete ? 0 : 1; + return ai - bi; + }); + + tasks = newArray; +} + +function recycleTasks(tasks: ProgressReport[], length: number) { + const newArray = [], delta = tasks.length - length; + let skipped = 0; + + tasks.forEach(task => { + if (skipped < delta && task.complete) { + skipped++; + return; + } + + newArray.push(task); + }); + + return newArray; +} + +function applyReport(report: ProgressReport) { + const index = findIndex(tasks, task => task.id === report.id); + if (index === -1) { + tasks.push(report); + } else { + tasks[index] = report; + } + + organizeTasks(); +} diff --git a/test/extension.test.ts b/test/extension.test.ts index 731f7b826..b8248306b 100644 --- a/test/extension.test.ts +++ b/test/extension.test.ts @@ -55,7 +55,8 @@ suite('Java Language Extension', () => { Commands.APPLY_REFACTORING_COMMAND, Commands.RENAME_COMMAND, Commands.NAVIGATE_TO_SUPER_IMPLEMENTATION_COMMAND, - Commands.CLIPBOARD_ONPASTE + Commands.CLIPBOARD_ONPASTE, + Commands.SHOW_SERVER_TASK_STATUS, ]; const foundJavaCommands = commands.filter((value) => { return JAVA_COMMANDS.indexOf(value)>=0 || value.startsWith('java.');