From 606982f19d45f54dde2716ed5867ebc2f2e4de80 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Mon, 28 Oct 2024 15:09:09 -0400 Subject: [PATCH] Show multi-line lsp progress details (#600) * Show multi-line lsp progress details * fix failing tests --- package-lock.json | 366 +++++++++++++++++++++++++----- src/LanguageServerManager.spec.ts | 33 +++ src/LanguageServerManager.ts | 60 ++++- 3 files changed, 395 insertions(+), 64 deletions(-) diff --git a/package-lock.json b/package-lock.json index e3db0ea7..925683fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -107,6 +107,276 @@ "fsevents": "~2.3.2" } }, + "../brighterscript": { + "version": "0.67.7", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@rokucommunity/bslib": "^0.1.1", + "@rokucommunity/logger": "^0.3.9", + "@xml-tools/parser": "^1.0.7", + "array-flat-polyfill": "^1.0.1", + "chalk": "^2.4.2", + "chevrotain": "^7.0.1", + "chokidar": "^3.5.1", + "clear": "^0.1.0", + "cross-platform-clear-console": "^2.3.0", + "debounce-promise": "^3.1.0", + "eventemitter3": "^4.0.0", + "fast-glob": "^3.2.12", + "file-url": "^3.0.0", + "fs-extra": "^8.1.0", + "ignore": "^5.3.1", + "jsonc-parser": "^2.3.0", + "lodash.isequal": "^4.5.0", + "long": "^3.2.0", + "luxon": "^2.5.2", + "micromatch": "^4.0.4", + "minimatch": "^3.0.4", + "moment": "^2.23.0", + "p-settle": "^2.1.0", + "parse-ms": "^2.1.0", + "readline": "^1.3.0", + "require-relative": "^0.8.7", + "roku-deploy": "^3.12.1", + "safe-json-stringify": "^1.2.0", + "serialize-error": "^7.0.1", + "source-map": "^0.7.4", + "thenby": "^1.3.4", + "vscode-languageserver": "^9.0.1", + "vscode-languageserver-protocol": "^3.17.5", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-languageserver-types": "^3.17.5", + "vscode-uri": "^3.0.8", + "xml2js": "^0.5.0", + "yargs": "^16.2.0" + }, + "bin": { + "bsc": "dist/cli.js" + }, + "devDependencies": { + "@guyplusplus/turndown-plugin-gfm": "^1.0.7", + "@types/ajv": "^1.0.0", + "@types/benchmark": "^1.0.31", + "@types/chai": "^4.1.2", + "@types/clone": "^2.1.4", + "@types/command-line-args": "^5.0.0", + "@types/command-line-usage": "^5.0.1", + "@types/debounce-promise": "^3.1.1", + "@types/fs-extra": "^8.0.0", + "@types/lodash.isequal": "^4.5.8", + "@types/marked": "^4.0.3", + "@types/micromatch": "^4.0.2", + "@types/mocha": "^5.2.5", + "@types/node": "^11.9.0", + "@types/require-relative": "^0.8.0", + "@types/safe-json-stringify": "^1.1.5", + "@types/sinon": "^9.0.4", + "@types/turndown": "^5.0.1", + "@types/yargs": "^15.0.5", + "@typescript-eslint/eslint-plugin": "^5.27.0", + "@typescript-eslint/parser": "^5.27.0", + "ajv": "^8.14.0", + "benchmark": "^2.1.4", + "chai": "^4.2.0", + "chai-files": "^1.4.0", + "clipboardy": "^2.3.0", + "coveralls-next": "^4.2.0", + "deepmerge": "^4.2.2", + "eslint": "^8.16.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-jsdoc": "^39.3.6", + "eslint-plugin-no-only-tests": "2.6.0", + "he": "^1.2.0", + "madge": "^4.0.2", + "marked": "^4.0.12", + "mocha": "^9.1.3", + "node-html-markdown": "^1.2.0", + "node-run-cmd": "^1.0.1", + "nyc": "^15.1.0", + "object.pick": "^1.3.0", + "phin": "^3.7.1", + "rimraf": "^2.7.1", + "semver-extra": "^3.0.0", + "sinon": "^9.0.2", + "source-map-support": "^0.5.21", + "sync-request": "^6.1.0", + "testdouble": "^3.5.2", + "thenby": "^1.3.4", + "ts-node": "^10.9.2", + "turndown": "^7.1.1", + "turndown-plugin-gfm": "^1.0.2", + "typescript": "^4.7.2", + "typescript-formatter": "^7.2.2", + "undent": "^0.1.0", + "vscode-jsonrpc": "^5.0.1" + } + }, + "../brighterscript-formatter": { + "version": "1.7.5", + "extraneous": true, + "license": "MIT", + "dependencies": { + "brighterscript": "file:../brighterscript", + "glob-all": "^3.3.0", + "jsonc-parser": "^3.0.0", + "source-map": "^0.7.3", + "yargs": "^17.2.1" + }, + "bin": { + "brighterscript-formatter": "dist/cli.js", + "bsfmt": "dist/cli.js" + }, + "devDependencies": { + "@types/chai": "^4.2.22", + "@types/fs-extra": "^9.0.13", + "@types/glob": "^7.2.0", + "@types/mocha": "^9.0.0", + "@types/node": "^20.12.12", + "@types/sinon": "^10.0.6", + "@types/yargs": "^17.0.4", + "@typescript-eslint/eslint-plugin": "^5.27.0", + "@typescript-eslint/parser": "^5.27.0", + "chai": "^4.3.4", + "child-process-promise": "^2.2.1", + "coveralls-next": "^4.2.0", + "eslint": "^8.1.0", + "eslint-plugin-no-only-tests": "^2.6.0", + "fs-extra": "^10.0.0", + "glob-promise": "^4.2.2", + "mocha": "^9.1.3", + "nyc": "^15.1.0", + "rimraf": "^3.0.2", + "sinon": "^11.1.2", + "source-map-support": "^0.5.20", + "ts-node": "^10.4.0", + "typescript": "^4.7.2", + "undent": "^0.1.0" + } + }, + "../roku-debug": { + "version": "0.21.11", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@rokucommunity/logger": "^0.3.9", + "@types/request": "^2.48.8", + "brighterscript": "file:../brighterscript", + "dateformat": "^4.6.3", + "debounce": "^1.2.1", + "eol": "^0.9.1", + "eventemitter3": "^4.0.7", + "fast-glob": "^3.2.11", + "find-in-files": "^0.5.0", + "fs-extra": "^10.0.0", + "natural-orderby": "^2.0.3", + "portfinder": "^1.0.32", + "postman-request": "^2.88.1-postman.32", + "replace-in-file": "^6.3.2", + "replace-last": "^1.2.6", + "roku-deploy": "file:../roku-deploy", + "semver": "^7.5.4", + "serialize-error": "^8.1.0", + "smart-buffer": "^4.2.0", + "source-map": "^0.7.4", + "telnet-client": "^1.4.9", + "vscode-debugadapter": "^1.49.0", + "vscode-debugprotocol": "^1.49.0", + "vscode-languageserver": "^6.1.1", + "xml2js": "^0.5.0" + }, + "bin": { + "roku-debug": "dist/cli.js" + }, + "devDependencies": { + "@types/chai": "^4.2.22", + "@types/dateformat": "~3", + "@types/debounce": "^1.2.1", + "@types/decompress": "^4.2.4", + "@types/dedent": "^0.7.0", + "@types/find-in-files": "^0.5.1", + "@types/fs-extra": "^9.0.13", + "@types/glob": "^7.2.0", + "@types/mocha": "^9.0.0", + "@types/node": "^16.11.6", + "@types/request": "^2.48.8", + "@types/semver": "^7.3.9", + "@types/sinon": "^10.0.6", + "@types/vscode": "^1.61.0", + "@typescript-eslint/eslint-plugin": "^5.27.0", + "@typescript-eslint/parser": "^5.27.0", + "chai": "^4.3.4", + "coveralls-next": "^4.2.0", + "decompress": "^4.2.1", + "dedent": "^0.7.0", + "eslint": "^8.1.0", + "eslint-plugin-no-only-tests": "^2.6.0", + "get-port": "^5.1.1", + "mocha": "^9.1.3", + "nyc": "^15.1.0", + "p-defer": "^4.0.0", + "rimraf": "^3.0.2", + "rmfr": "^2.0.0", + "rxjs": "^7.4.0", + "sinon": "^11.1.2", + "source-map-support": "^0.5.20", + "ts-node": "^10.4.0", + "typescript": "^4.7.2" + } + }, + "../roku-deploy": { + "version": "3.12.1", + "extraneous": true, + "license": "MIT", + "dependencies": { + "chalk": "^2.4.2", + "dateformat": "^3.0.3", + "dayjs": "^1.11.0", + "fast-glob": "^3.2.12", + "fs-extra": "^7.0.1", + "is-glob": "^4.0.3", + "jsonc-parser": "^2.3.0", + "jszip": "^3.6.0", + "lodash": "^4.17.21", + "micromatch": "^4.0.4", + "moment": "^2.29.1", + "parse-ms": "^2.1.0", + "postman-request": "^2.88.1-postman.32", + "temp-dir": "^2.0.0", + "xml2js": "^0.5.0" + }, + "bin": { + "roku-deploy": "dist/cli.js" + }, + "devDependencies": { + "@types/chai": "^4.2.22", + "@types/fs-extra": "^5.0.1", + "@types/is-glob": "^4.0.2", + "@types/lodash": "^4.14.200", + "@types/micromatch": "^4.0.2", + "@types/mocha": "^9.0.0", + "@types/node": "^16.11.3", + "@types/q": "^1.5.8", + "@types/request": "^2.47.0", + "@types/sinon": "^10.0.4", + "@types/xml2js": "^0.4.5", + "@typescript-eslint/eslint-plugin": "5.1.0", + "@typescript-eslint/parser": "5.1.0", + "chai": "^4.3.4", + "coveralls-next": "^4.2.0", + "dedent": "^0.7.0", + "eslint": "8.0.1", + "mocha": "^9.1.3", + "nyc": "^15.1.0", + "q": "^1.5.1", + "rimraf": "^2.6.2", + "sinon": "^11.1.2", + "source-map-support": "^0.5.13", + "ts-node": "^10.3.1", + "typescript": "^4.4.4" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -1209,20 +1479,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/@rokucommunity/logger/node_modules/serialize-error": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", - "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@rokucommunity/logger/node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -2752,6 +3008,20 @@ "node": ">=4" } }, + "node_modules/brighterscript/node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/brighterscript/node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -2763,6 +3033,17 @@ "node": ">=4" } }, + "node_modules/brighterscript/node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/brighterscript/node_modules/vscode-jsonrpc": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", @@ -3359,9 +3640,9 @@ "dev": true }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "engines": { "node": ">= 0.6" } @@ -3938,9 +4219,12 @@ "dev": true }, "node_modules/emitter-component": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/emitter-component/-/emitter-component-1.1.1.tgz", - "integrity": "sha512-G+mpdiAySMuB7kesVRLuyvYRqDmshB7ReKEVuyBPkzQlmiDiLrt7hHHIy4Aff552bgknVN7B2/d3lzhGO5dvpQ==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/emitter-component/-/emitter-component-1.1.2.tgz", + "integrity": "sha512-QdXO3nXOzZB4pAjM0n6ZE+R9/+kPpECA/XSELIcc54NeYVnBqIk+4DFiBgK+8QbV3mdvTG6nedl7dTYgO+5wDw==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/emoji-regex": { "version": "9.2.2", @@ -4755,16 +5039,16 @@ } }, "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -8615,6 +8899,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", "engines": { "node": ">=0.6.0", "teleport": ">=0.2.0" @@ -9065,20 +9350,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/roku-debug/node_modules/serialize-error": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", - "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/roku-debug/node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -9353,23 +9624,12 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/serialize-error": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", - "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", + "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", "dependencies": { - "type-fest": "^0.13.1" - }, - "engines": { - "node": ">=10" + "type-fest": "^0.20.2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/serialize-error/node_modules/type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", "engines": { "node": ">=10" }, diff --git a/src/LanguageServerManager.spec.ts b/src/LanguageServerManager.spec.ts index 2c27fcd2..97060011 100644 --- a/src/LanguageServerManager.spec.ts +++ b/src/LanguageServerManager.spec.ts @@ -19,6 +19,7 @@ import { util } from './util'; import { GlobalStateManager } from './GlobalStateManager'; import { LocalPackageManager } from './managers/LocalPackageManager'; import { expectThrowsAsync } from './testHelpers.spec'; +import undent from 'undent'; const Module = require('module'); const sinon = createSandbox(); @@ -492,4 +493,36 @@ describe('LanguageServerManager', () => { expect(stub.called).to.be.true; }); }); + + describe('getActiveRunsTooltipText', () => { + it('returns empty string when no active runs', () => { + expect( + languageServerManager['getActiveRunsTooltipText']([]) + ).to.eql(''); + expect( + languageServerManager['getActiveRunsTooltipText'](undefined as any) + ).to.eql(''); + expect( + languageServerManager['getActiveRunsTooltipText']('' as any) + ).to.eql(''); + }); + + it('prints unscoped events at top', () => { + let date = Date.now(); + let dateText = new Date(date).toLocaleTimeString(); + expect( + undent( + languageServerManager['getActiveRunsTooltipText']([ + { label: 'scoped-validate', startTime: Date.now(), scope: 'prj1' }, + { label: 'validate', startTime: Date.now() } + ]) + ) + ).to.eql(undent` + general: + \tvalidate (since ${dateText}) + prj1: + \tscoped-validate (since ${dateText}) + `); + }); + }); }); diff --git a/src/LanguageServerManager.ts b/src/LanguageServerManager.ts index 1fcda1fe..a9fcaf2c 100644 --- a/src/LanguageServerManager.ts +++ b/src/LanguageServerManager.ts @@ -20,6 +20,7 @@ import * as fsExtra from 'fs-extra'; import { EventEmitter } from 'eventemitter3'; import * as dayjs from 'dayjs'; import type { LocalPackageManager, ParsedVersionInfo } from './managers/LocalPackageManager'; +import { firstBy } from 'thenby'; /** * Tracks the running/stopped state of the language server. When the lsp crashes, vscode will restart it. After the 5th crash, they'll leave it permanently crashed. @@ -283,7 +284,8 @@ export class LanguageServerManager { const logger = new Logger(); this.client.onNotification(NotificationName.busyStatus, (event: any) => { - this.setBusyStatus(event.status); + console.log(event); + this.updateStatusbar(event.status === BusyStatus.busy, event.activeRuns); //if the busy status takes too long, write a lsp log entry with details of what's still pending if (event.status === BusyStatus.busy) { @@ -300,25 +302,56 @@ export class LanguageServerManager { } - private setBusyStatus(status: BusyStatus) { - if (status === BusyStatus.busy) { - this.updateStatusbar(true); - } else { - this.updateStatusbar(false); - } - } - /** * Enable/disable the loading spinner on the statusbar item */ - private updateStatusbar(isLoading: boolean) { + private updateStatusbar(isLoading: boolean, activeRuns?: ActiveRun[]) { //do nothing if we don't have a statusbar if (!this.statusbarItem) { return; } const icon = isLoading ? '$(sync~spin)' : '$(flame)'; this.statusbarItem.text = `${icon} bsc-${this.selectedBscInfo.version}`; - this.statusbarItem.tooltip = `BrightScript Language Server: running`; + let tooltip = `BrightScript Language Server: ${isLoading ? 'working' : 'idle'}`; + + //print any acdtive runs so devs know what is taking so long + if (activeRuns?.length > 0) { + tooltip += '\n' + this.getActiveRunsTooltipText(activeRuns); + } + this.statusbarItem.tooltip = tooltip; + } + + private getActiveRunsTooltipText(activeRuns: ActiveRun[]) { + let tooltip = ''; + + //sort the runs so they're more consistent + const groups = activeRuns?.sort?.( + firstBy('scope').thenBy('label').thenBy('startTime') + ).reduce((groups, item) => { + if (!groups.has(item.scope)) { + groups.set(item.scope, []); + } + groups.get(item.scope).push(item); + return groups; + }, new Map>()); + + for (let [groupName, runsForGroup] of groups ?? []) { + if (!groupName || groupName?.trim() === 'undefined') { + groupName = 'general'; + } + + if (groupName) { + tooltip += `\n${groupName}:`; + } + + for (const run of runsForGroup ?? []) { + let line = '\n\t'; + + line += `${run.label} (since ${new Date(run.startTime)?.toLocaleTimeString()})`; + tooltip += line; + } + } + return tooltip; } /** @@ -617,3 +650,8 @@ function OneAtATime(options: { timeout?: number }) { }; }; } +interface ActiveRun { + label: string; + startTime: number; + scope?: string; +}