diff --git a/build/azure-pipelines/distro-build.yml b/build/azure-pipelines/distro-build.yml index 62ee67ad1c..74fddcc55a 100644 --- a/build/azure-pipelines/distro-build.yml +++ b/build/azure-pipelines/distro-build.yml @@ -1,3 +1,6 @@ +pool: + vmImage: 'Ubuntu-16.04' + trigger: branches: include: ['master', 'release/*'] diff --git a/build/azure-pipelines/exploration-build.yml b/build/azure-pipelines/exploration-build.yml index 1fbc9eaa9c..797c4b5fce 100644 --- a/build/azure-pipelines/exploration-build.yml +++ b/build/azure-pipelines/exploration-build.yml @@ -1,3 +1,6 @@ +pool: + vmImage: 'Ubuntu-16.04' + trigger: branches: include: ['master'] @@ -10,20 +13,10 @@ steps: inputs: versionSpec: "10.15.1" -- task: AzureKeyVault@1 - displayName: 'Azure Key Vault: Get Secrets' - inputs: - azureSubscription: 'vscode-builds-subscription' - KeyVaultName: vscode - - script: | set -e cat << EOF > ~/.netrc - machine github.com - login vscode - password $(github-distro-mixin-password) - EOF git config user.email "vscode@microsoft.com" git config user.name "VSCode" diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index f38f431182..a2fdc9e4ec 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -143,8 +143,8 @@ trigger: none pr: none schedules: -- cron: "0 5 * * Mon-Fri" - displayName: Mon-Fri at 7:00 +- cron: "10 5 * * Mon-Fri" + displayName: Mon-Fri at 7:10 branches: include: - master diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index 686aa2f6b4..8ab4e7954f 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -252,7 +252,7 @@ gulp.task('tslint', () => { .pipe(filter(tslintExtensionsFilter)) .pipe(gulptslint.default({ rulesDirectory: 'build/lib/tslint' })) .pipe(gulptslint.default.report({ emitError: true })) - ]).pipe(es.through()); // {{SQL CARBON EDIT}} fix issue + ]).pipe(es.through()); }); function hygiene(some) { diff --git a/build/lib/tslint/noDOMGlobalsRule.js b/build/lib/tslint/noDomGlobalsRule.js similarity index 100% rename from build/lib/tslint/noDOMGlobalsRule.js rename to build/lib/tslint/noDomGlobalsRule.js diff --git a/build/lib/tslint/noDOMGlobalsRule.ts b/build/lib/tslint/noDomGlobalsRule.ts similarity index 100% rename from build/lib/tslint/noDOMGlobalsRule.ts rename to build/lib/tslint/noDomGlobalsRule.ts diff --git a/build/lib/tslint/noNodeJSGlobalsRule.js b/build/lib/tslint/noNodejsGlobalsRule.js similarity index 98% rename from build/lib/tslint/noNodeJSGlobalsRule.js rename to build/lib/tslint/noNodejsGlobalsRule.js index 315bf38e54..7189d8f21d 100644 --- a/build/lib/tslint/noNodeJSGlobalsRule.js +++ b/build/lib/tslint/noNodejsGlobalsRule.js @@ -26,6 +26,7 @@ class NoNodejsGlobalsRuleWalker extends abstractGlobalsRule_1.AbstractGlobalsRul getDisallowedGlobals() { // https://nodejs.org/api/globals.html#globals_global_objects return [ + "NodeJS", "Buffer", "__dirname", "__filename", diff --git a/build/lib/tslint/noNodeJSGlobalsRule.ts b/build/lib/tslint/noNodejsGlobalsRule.ts similarity index 99% rename from build/lib/tslint/noNodeJSGlobalsRule.ts rename to build/lib/tslint/noNodejsGlobalsRule.ts index be6798421d..0ac0e74511 100644 --- a/build/lib/tslint/noNodeJSGlobalsRule.ts +++ b/build/lib/tslint/noNodejsGlobalsRule.ts @@ -37,6 +37,7 @@ class NoNodejsGlobalsRuleWalker extends AbstractGlobalsRuleWalker { getDisallowedGlobals(): string[] { // https://nodejs.org/api/globals.html#globals_global_objects return [ + "NodeJS", "Buffer", "__dirname", "__filename", diff --git a/package.json b/package.json index 667f1e9153..fd41814a48 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "vscode-ripgrep": "^1.5.6", "vscode-sqlite3": "4.0.8", "vscode-textmate": "^4.2.2", - "xterm": "3.15.0-beta99", + "xterm": "3.15.0-beta101", "xterm-addon-search": "0.2.0-beta5", "xterm-addon-web-links": "0.1.0-beta10", "yauzl": "^2.9.2", @@ -87,6 +87,7 @@ "devDependencies": { "7zip": "0.0.6", "@types/chart.js": "^2.7.31", + "@types/cookie": "^0.3.3", "@types/htmlparser2": "^3.7.31", "@types/keytar": "^4.4.0", "@types/mocha": "2.2.39", diff --git a/remote/package.json b/remote/package.json index 2e8ceed080..9fa3881681 100644 --- a/remote/package.json +++ b/remote/package.json @@ -21,7 +21,7 @@ "vscode-proxy-agent": "0.4.0", "vscode-ripgrep": "^1.5.6", "vscode-textmate": "^4.2.2", - "xterm": "3.15.0-beta99", + "xterm": "3.15.0-beta101", "xterm-addon-search": "0.2.0-beta5", "xterm-addon-web-links": "0.1.0-beta10", "yauzl": "^2.9.2", diff --git a/remote/yarn.lock b/remote/yarn.lock index dca0e2501f..308bf9634b 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -1225,10 +1225,10 @@ xterm-addon-web-links@0.1.0-beta10: resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.1.0-beta10.tgz#610fa9773a2a5ccd41c1c83ba0e2dd2c9eb66a23" integrity sha512-xfpjy0V6bB4BR44qIgZQPoCMVakxb65gMscPkHpO//QxvUxKzabV3dxOsIbeZRFkUGsWTFlvz2OoaBLoNtv5gg== -xterm@3.15.0-beta99: - version "3.15.0-beta99" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta99.tgz#0010a7ea5d56cbb08a1e3a525b353c96a158e7a0" - integrity sha512-Vm0ZWToWwO4uk/28Kqvqt9L92h5EU2z4WR9I6xcQaPIBmkJPINIARU4LWQnvaOfgFhRbpwBMveTfh8/jM97lPg== +xterm@3.15.0-beta101: + version "3.15.0-beta101" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta101.tgz#38ffa0df5a3e9bdcb1818e74fe59b2f98b0fff69" + integrity sha512-HRa7+FDqQ8iWBTvb1Ni+uMGILnu6k9mF7JHMHRHfWxFoQlSoGYCyfdyXlJjk68YN8GsEQREmrII6cPLiQizdEQ== yauzl@^2.9.2: version "2.10.0" diff --git a/src/sql/base/browser/ui/table/table.ts b/src/sql/base/browser/ui/table/table.ts index 9aa86e5e71..d5d62021d2 100644 --- a/src/sql/base/browser/ui/table/table.ts +++ b/src/sql/base/browser/ui/table/table.ts @@ -41,7 +41,7 @@ export class Table extends Widget implements IDisposa private _container: HTMLElement; private _tableContainer: HTMLElement; - private _classChangeTimeout: NodeJS.Timer; + private _classChangeTimeout: any; private _onContextMenu = new Emitter(); public readonly onContextMenu: Event = this._onContextMenu.event; diff --git a/src/sql/platform/tasks/common/tasksService.ts b/src/sql/platform/tasks/common/tasksService.ts index 1161c1d12e..ee11a303ec 100644 --- a/src/sql/platform/tasks/common/tasksService.ts +++ b/src/sql/platform/tasks/common/tasksService.ts @@ -162,7 +162,7 @@ export class TaskService implements ITaskService { this.dialogService.show(Severity.Warning, message, options).then(choice => { switch (choice) { case 0: - let timeout: NodeJS.Timer; + let timeout: any; let isTimeout = false; this.cancelAllTasks().then(() => { clearTimeout(timeout); diff --git a/src/sql/workbench/parts/editData/browser/editData.component.ts b/src/sql/workbench/parts/editData/browser/editData.component.ts index 5047ec5895..c530f8cdb1 100644 --- a/src/sql/workbench/parts/editData/browser/editData.component.ts +++ b/src/sql/workbench/parts/editData/browser/editData.component.ts @@ -45,7 +45,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On private refreshGridTimeoutInMs = 200; // The timeout handle for the refresh grid task - private refreshGridTimeoutHandle: NodeJS.Timer; + private refreshGridTimeoutHandle: any; // Optimized for the edit top 200 rows scenario, only need to retrieve the data once // to make the scroll experience smoother diff --git a/src/sql/workbench/parts/objectExplorer/browser/treeSelectionHandler.ts b/src/sql/workbench/parts/objectExplorer/browser/treeSelectionHandler.ts index 8589f33280..0b98ed304f 100644 --- a/src/sql/workbench/parts/objectExplorer/browser/treeSelectionHandler.ts +++ b/src/sql/workbench/parts/objectExplorer/browser/treeSelectionHandler.ts @@ -16,7 +16,7 @@ export class TreeSelectionHandler { // progressRunner: IProgressRunner; private _lastClicked: any[]; - private _clickTimer: NodeJS.Timer = undefined; + private _clickTimer: any = undefined; // constructor(@IProgressService private _progressService: IProgressService) { diff --git a/src/sql/workbench/parts/profiler/browser/profilerFindWidget.ts b/src/sql/workbench/parts/profiler/browser/profilerFindWidget.ts index 9fbede46b0..0d7fd6bd1f 100644 --- a/src/sql/workbench/parts/profiler/browser/profilerFindWidget.ts +++ b/src/sql/workbench/parts/profiler/browser/profilerFindWidget.ts @@ -83,7 +83,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas private _resizeSash: Sash; - private searchTimeoutHandle: NodeJS.Timer; + private searchTimeoutHandle: any; constructor( tableController: ITableController, diff --git a/src/tsconfig.base.json b/src/tsconfig.base.json index aceed7e420..b3ef78e4a9 100644 --- a/src/tsconfig.base.json +++ b/src/tsconfig.base.json @@ -23,13 +23,6 @@ "sqltest/*": [ "./sqltest/*" ] - }, - "types": [ - "keytar", - "mocha", - "semver", - "sinon", - "winreg" - ] + } } } diff --git a/src/tsconfig.json b/src/tsconfig.json index 2e95f74519..eb94959e88 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -11,6 +11,13 @@ "es5", "es2015.iterable", "webworker" + ], + "types": [ + "keytar", + "mocha", + "semver", + "sinon", + "winreg" ] }, "include": [ diff --git a/src/typings/xterm.d.ts b/src/typings/xterm.d.ts index e02926afc7..3f1aecf853 100644 --- a/src/typings/xterm.d.ts +++ b/src/typings/xterm.d.ts @@ -207,47 +207,47 @@ declare module 'xterm' { */ export interface ITheme { /** The default foreground color */ - foreground?: string, + foreground?: string; /** The default background color */ - background?: string, + background?: string; /** The cursor color */ - cursor?: string, + cursor?: string; /** The accent color of the cursor (fg color for a block cursor) */ - cursorAccent?: string, + cursorAccent?: string; /** The selection background color (can be transparent) */ - selection?: string, + selection?: string; /** ANSI black (eg. `\x1b[30m`) */ - black?: string, + black?: string; /** ANSI red (eg. `\x1b[31m`) */ - red?: string, + red?: string; /** ANSI green (eg. `\x1b[32m`) */ - green?: string, + green?: string; /** ANSI yellow (eg. `\x1b[33m`) */ - yellow?: string, + yellow?: string; /** ANSI blue (eg. `\x1b[34m`) */ - blue?: string, + blue?: string; /** ANSI magenta (eg. `\x1b[35m`) */ - magenta?: string, + magenta?: string; /** ANSI cyan (eg. `\x1b[36m`) */ - cyan?: string, + cyan?: string; /** ANSI white (eg. `\x1b[37m`) */ - white?: string, + white?: string; /** ANSI bright black (eg. `\x1b[1;30m`) */ - brightBlack?: string, + brightBlack?: string; /** ANSI bright red (eg. `\x1b[1;31m`) */ - brightRed?: string, + brightRed?: string; /** ANSI bright green (eg. `\x1b[1;32m`) */ - brightGreen?: string, + brightGreen?: string; /** ANSI bright yellow (eg. `\x1b[1;33m`) */ - brightYellow?: string, + brightYellow?: string; /** ANSI bright blue (eg. `\x1b[1;34m`) */ - brightBlue?: string, + brightBlue?: string; /** ANSI bright magenta (eg. `\x1b[1;35m`) */ - brightMagenta?: string, + brightMagenta?: string; /** ANSI bright cyan (eg. `\x1b[1;36m`) */ - brightCyan?: string, + brightCyan?: string; /** ANSI bright white (eg. `\x1b[1;37m`) */ - brightWhite?: string + brightWhite?: string; } /** @@ -386,6 +386,12 @@ declare module 'xterm' { */ readonly markers: ReadonlyArray; + /** + * (EXPERIMENTAL) Get the parser interface to register + * custom escape sequence handlers. + */ + readonly parser: IParser; + /** * Natural language strings that can be localized. */ @@ -500,32 +506,6 @@ declare module 'xterm' { */ attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void; - /** - * (EXPERIMENTAL) Adds a handler for CSI escape sequences. - * @param flag The flag should be one-character string, which specifies the - * final character (e.g "m" for SGR) of the CSI sequence. - * @param callback The function to handle the escape sequence. The callback - * is called with the numerical params, as well as the special characters - * (e.g. "$" for DECSCPP). If the sequence has subparams the array will - * contain subarrays with their numercial values. - * Return true if the sequence was handled; false if - * we should try a previous handler (set by addCsiHandler or setCsiHandler). - * The most recently-added handler is tried first. - * @return An IDisposable you can call to remove this handler. - */ - addCsiHandler(flag: string, callback: (params: (number | number[])[], collect: string) => boolean): IDisposable; - - /** - * (EXPERIMENTAL) Adds a handler for OSC escape sequences. - * @param ident The number (first parameter) of the sequence. - * @param callback The function to handle the escape sequence. The callback - * is called with OSC data string. Return true if the sequence was handled; - * false if we should try a previous handler (set by addOscHandler or - * setOscHandler). The most recently-added handler is tried first. - * @return An IDisposable you can call to remove this handler. - */ - addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; - /** * (EXPERIMENTAL) Registers a link matcher, allowing custom link patterns to * be matched and handled. @@ -689,6 +669,12 @@ declare module 'xterm' { */ writeUtf8(data: Uint8Array): void; + /** + * Writes text to the terminal, performing the necessary transformations for pasted text. + * @param data The text to write to the terminal. + */ + paste(data: string): void; + /** * Retrieves an option's value from the terminal. * @param key The option key. @@ -804,18 +790,10 @@ declare module 'xterm' { /** * Perform a full reset (RIS, aka '\x1bc'). */ - reset(): void + reset(): void; /** - * Applies an addon to the Terminal prototype, making it available to all - * newly created Terminals. - * @param addon The addon to apply. - * @deprecated Use the new loadAddon API/addon format. - */ - static applyAddon(addon: any): void; - - /** - * (EXPERIMENTAL) Loads an addon into this instance of xterm.js. + * Loads an addon into this instance of xterm.js. * @param addon The addon to load. */ loadAddon(addon: ITerminalAddon): void; @@ -951,6 +929,119 @@ declare module 'xterm' { */ readonly width: number; } + + /** + * (EXPERIMENTAL) Data type to register a CSI, DCS or ESC callback in the parser + * in the form: + * ESC I..I F + * CSI Prefix P..P I..I F + * DCS Prefix P..P I..I F data_bytes ST + * + * with these rules/restrictions: + * - prefix can only be used with CSI and DCS + * - only one leading prefix byte is recognized by the parser + * before any other parameter bytes (P..P) + * - intermediate bytes are recognized up to 2 + * + * For custom sequences make sure to read ECMA-48 and the resources at + * vt100.net to not clash with existing sequences or reserved address space. + * General recommendations: + * - use private address space (see ECMA-48) + * - use max one intermediate byte (technically not limited by the spec, + * in practice there are no sequences with more than one intermediate byte, + * thus parsers might get confused with more intermediates) + * - test against other common emulators to check whether they escape/ignore + * the sequence correctly + * + * Notes: OSC command registration is handled differently (see addOscHandler) + * APC, PM or SOS is currently not supported. + */ + export interface IFunctionIdentifier { + /** + * Optional prefix byte, must be in range \x3c .. \x3f. + * Usable in CSI and DCS. + */ + prefix?: string; + /** + * Optional intermediate bytes, must be in range \x20 .. \x2f. + * Usable in CSI, DCS and ESC. + */ + intermediates?: string; + /** + * Final byte, must be in range \x40 .. \x7e for CSI and DCS, + * \x30 .. \x7e for ESC. + */ + final: string; + } + + /** + * (EXPERIMENTAL) Parser interface. + */ + export interface IParser { + /** + * Adds a handler for CSI escape sequences. + * @param id Specifies the function identifier under which the callback + * gets registered, e.g. {final: 'm'} for SGR. + * @param callback The function to handle the sequence. The callback is + * called with the numerical params. If the sequence has subparams the + * array will contain subarrays with their numercial values. + * Return true if the sequence was handled; false if we should try + * a previous handler (set by addCsiHandler or setCsiHandler). + * The most recently-added handler is tried first. + * @return An IDisposable you can call to remove this handler. + */ + addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable; + + /** + * Adds a handler for DCS escape sequences. + * @param id Specifies the function identifier under which the callback + * gets registered, e.g. {intermediates: '$' final: 'q'} for DECRQSS. + * @param callback The function to handle the sequence. Note that the + * function will only be called once if the sequence finished sucessfully. + * There is currently no way to intercept smaller data chunks, data chunks + * will be stored up until the sequence is finished. Since DCS sequences + * are not limited by the amount of data this might impose a problem for + * big payloads. Currently xterm.js limits DCS payload to 10 MB + * which should give enough room for most use cases. + * The function gets the payload and numerical parameters as arguments. + * Return true if the sequence was handled; false if we should try + * a previous handler (set by addDcsHandler or setDcsHandler). + * The most recently-added handler is tried first. + * @return An IDisposable you can call to remove this handler. + */ + addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable; + + /** + * Adds a handler for ESC escape sequences. + * @param id Specifies the function identifier under which the callback + * gets registered, e.g. {intermediates: '%' final: 'G'} for + * default charset selection. + * @param callback The function to handle the sequence. + * Return true if the sequence was handled; false if we should try + * a previous handler (set by addEscHandler or setEscHandler). + * The most recently-added handler is tried first. + * @return An IDisposable you can call to remove this handler. + */ + addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable; + + /** + * Adds a handler for OSC escape sequences. + * @param ident The number (first parameter) of the sequence. + * @param callback The function to handle the sequence. Note that the + * function will only be called once if the sequence finished sucessfully. + * There is currently no way to intercept smaller data chunks, data chunks + * will be stored up until the sequence is finished. Since OSC sequences + * are not limited by the amount of data this might impose a problem for + * big payloads. Currently xterm.js limits OSC payload to 10 MB + * which should give enough room for most use cases. + * The callback is called with OSC data string. + * Return true if the sequence was handled; false if we should try + * a previous handler (set by addOscHandler or setOscHandler). + * The most recently-added handler is tried first. + * @return An IDisposable you can call to remove this handler. + */ + addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; + } } @@ -986,4 +1077,4 @@ declare module 'xterm' { interface Terminal { _core: TerminalCore; } -} \ No newline at end of file +} diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 48483d8a6c..282581451f 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -1202,5 +1202,8 @@ export function asDomUri(uri: URI): URI { * returns url('...') */ export function asCSSUrl(uri: URI): string { + if (!uri) { + return `url('')`; + } return `url('${asDomUri(uri).toString(true).replace(/'/g, '%27')}')`; } diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 81150addc0..cd96a8feb2 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -7,6 +7,15 @@ import { URI } from 'vs/base/common/uri'; import { CharCode } from 'vs/base/common/charCode'; import { Iterator, IteratorResult, FIN } from './iterator'; + +export function fromArray(array: readonly T[]): Set { + const result = new Set(); + for (const element of array) { + result.add(element); + } + return result; +} + export function values(set: Set): V[]; export function values(map: Map): V[]; export function values(forEachable: { forEach(callback: (value: V, ...more: any[]) => any): void }): V[] { diff --git a/src/vs/base/common/search.ts b/src/vs/base/common/search.ts index 8d5a91edf7..6fb11a17c0 100644 --- a/src/vs/base/common/search.ts +++ b/src/vs/base/common/search.ts @@ -12,7 +12,11 @@ export function buildReplaceStringWithCasePreserved(matches: string[] | null, pa } else if (matches[0].toLowerCase() === matches[0]) { return pattern.toLowerCase(); } else if (strings.containsUppercaseCharacter(matches[0][0])) { - return pattern[0].toUpperCase() + pattern.substr(1); + if (validateSpecificSpecialCharacter(matches, pattern, '-')) { + return buildReplaceStringForSpecificSpecialCharacter(matches, pattern, '-'); + } else { + return pattern[0].toUpperCase() + pattern.substr(1); + } } else { // we don't understand its pattern yet. return pattern; @@ -21,3 +25,19 @@ export function buildReplaceStringWithCasePreserved(matches: string[] | null, pa return pattern; } } + +function validateSpecificSpecialCharacter(matches: string[], pattern: string, specialCharacter: string): boolean { + const doesConatinSpecialCharacter = matches[0].indexOf(specialCharacter) !== -1 && pattern.indexOf(specialCharacter) !== -1; + return doesConatinSpecialCharacter && matches[0].split(specialCharacter).length === pattern.split(specialCharacter).length; +} + +function buildReplaceStringForSpecificSpecialCharacter(matches: string[], pattern: string, specialCharacter: string): string { + const splitPatternAtSpecialCharacter = pattern.split(specialCharacter); + const splitMatchAtSpecialCharacter = matches[0].split(specialCharacter); + let replaceString: string = ''; + splitPatternAtSpecialCharacter.forEach((splitValue, index) => { + replaceString += buildReplaceStringWithCasePreserved([splitMatchAtSpecialCharacter[index]], splitValue) + specialCharacter; + }); + + return replaceString.slice(0, -1); +} diff --git a/src/vs/base/test/node/keytar.test.ts b/src/vs/base/test/node/keytar.test.ts index e1e37536e2..1dd5ca3a33 100644 --- a/src/vs/base/test/node/keytar.test.ts +++ b/src/vs/base/test/node/keytar.test.ts @@ -32,4 +32,4 @@ suite('Keytar', () => { } })().then(done, done); }); -}); \ No newline at end of file +}); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 0bda375a38..091c63054f 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -70,7 +70,7 @@ import { startsWith } from 'vs/base/common/strings'; import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService'; import { IBackupMainService } from 'vs/platform/backup/common/backup'; import { HistoryMainService } from 'vs/platform/history/electron-main/historyMainService'; -import { URLService } from 'vs/platform/url/common/urlService'; +import { URLService } from 'vs/platform/url/node/urlService'; import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { statSync } from 'fs'; import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsIpc'; diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 9b60e7e626..7d1614ffb9 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -404,7 +404,7 @@ export interface CompletionItem { * A modifier to the `kind` which affect how the item * is rendered, e.g. Deprecated is rendered with a strikeout */ - kindModifier?: CompletionItemKindModifier; + kindModifier?: Set; /** * A human-readable string with additional information * about this item, like type or symbol information. @@ -867,6 +867,9 @@ export const enum SymbolKind { TypeParameter = 25 } +export const enum SymbolKindTag { + Deprecated = 1, +} /** * @internal @@ -910,6 +913,7 @@ export interface DocumentSymbol { name: string; detail: string; kind: SymbolKind; + kindTags: SymbolKindTag[]; containerName?: string; range: IRange; selectionRange: IRange; diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 9b06b322e4..67cc65ca13 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -660,4 +660,8 @@ export enum SymbolKind { Event = 23, Operator = 24, TypeParameter = 25 +} + +export enum SymbolKindTag { + Deprecated = 1 } \ No newline at end of file diff --git a/src/vs/editor/contrib/documentSymbols/media/outlineTree.css b/src/vs/editor/contrib/documentSymbols/media/outlineTree.css index eaee13302b..b8a1ce12bf 100644 --- a/src/vs/editor/contrib/documentSymbols/media/outlineTree.css +++ b/src/vs/editor/contrib/documentSymbols/media/outlineTree.css @@ -20,6 +20,11 @@ color: var(--outline-element-color); } +.monaco-list .outline-element .deprecated { + text-decoration: line-through; + opacity: 0.66; +} + .monaco-tree .monaco-tree-row.focused .outline-element .outline-element-detail { visibility: inherit; } diff --git a/src/vs/editor/contrib/documentSymbols/outlineTree.ts b/src/vs/editor/contrib/documentSymbols/outlineTree.ts index 4a35ac08d4..cde6226532 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineTree.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineTree.ts @@ -12,7 +12,7 @@ import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import 'vs/css!./media/outlineTree'; import 'vs/css!./media/symbol-icons'; import { Range } from 'vs/editor/common/core/range'; -import { SymbolKind, symbolKindToCssClass } from 'vs/editor/common/modes'; +import { SymbolKind, symbolKindToCssClass, SymbolKindTag } from 'vs/editor/common/modes'; import { OutlineElement, OutlineGroup, OutlineModel } from 'vs/editor/contrib/documentSymbols/outlineModel'; import { localize } from 'vs/nls'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; @@ -127,6 +127,10 @@ export class OutlineElementRenderer implements ITreeRenderer= 0) { + options.extraClasses.push(`deprecated`); + options.matches = []; + } template.iconLabel.setLabel(element.symbol.name, element.symbol.detail, options); this._renderMarkerInfo(element, template); } diff --git a/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts b/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts index 6dc986eb4d..faa5f0955b 100644 --- a/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts +++ b/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts @@ -76,6 +76,7 @@ suite('OutlineModel', function () { name, detail: 'fake', kind: SymbolKind.Boolean, + kindTags: [], selectionRange: range, range: range }; diff --git a/src/vs/editor/contrib/find/test/replacePattern.test.ts b/src/vs/editor/contrib/find/test/replacePattern.test.ts index 04da0bda2d..9ff703c4a4 100644 --- a/src/vs/editor/contrib/find/test/replacePattern.test.ts +++ b/src/vs/editor/contrib/find/test/replacePattern.test.ts @@ -176,6 +176,13 @@ suite('Replace Pattern test', () => { assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'Def'); actual = ['aBC']; assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'Def'); + + actual = ['Foo-Bar']; + assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar'), 'Newfoo-Newbar'); + actual = ['Foo-Bar-Abc']; + assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar-newabc'), 'Newfoo-Newbar-Newabc'); + actual = ['Foo-Bar-abc']; + assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar'), 'Newfoo-newbar'); }); test('preserve case', () => { @@ -198,5 +205,17 @@ suite('Replace Pattern test', () => { assert.equal(actual, 'Def'); actual = replacePattern.buildReplaceString(['aBC'], true); assert.equal(actual, 'Def'); + + replacePattern = parseReplaceString('newfoo-newbar'); + actual = replacePattern.buildReplaceString(['Foo-Bar'], true); + assert.equal(actual, 'Newfoo-Newbar'); + + replacePattern = parseReplaceString('newfoo-newbar-newabc'); + actual = replacePattern.buildReplaceString(['Foo-Bar-Abc'], true); + assert.equal(actual, 'Newfoo-Newbar-Newabc'); + + replacePattern = parseReplaceString('newfoo-newbar'); + actual = replacePattern.buildReplaceString(['Foo-Bar-abc'], true); + assert.equal(actual, 'Newfoo-newbar'); }); }); diff --git a/src/vs/editor/contrib/quickOpen/quickOpen.ts b/src/vs/editor/contrib/quickOpen/quickOpen.ts index b392f8d180..4aae25223c 100644 --- a/src/vs/editor/contrib/quickOpen/quickOpen.ts +++ b/src/vs/editor/contrib/quickOpen/quickOpen.ts @@ -51,6 +51,7 @@ function flatten(bucket: DocumentSymbol[], entries: DocumentSymbol[], overrideCo for (let entry of entries) { bucket.push({ kind: entry.kind, + kindTags: [], name: entry.name, detail: entry.detail, containerName: entry.containerName || overrideContainerLabel, diff --git a/src/vs/editor/contrib/suggest/media/suggest.css b/src/vs/editor/contrib/suggest/media/suggest.css index 86ec9f94fb..d38a4465b5 100644 --- a/src/vs/editor/contrib/suggest/media/suggest.css +++ b/src/vs/editor/contrib/suggest/media/suggest.css @@ -97,10 +97,6 @@ font-weight: bold; } -.monaco-editor .suggest-widget-deprecated span { - text-decoration: line-through; -} - /** Icon styles **/ .monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .header > .close, @@ -115,8 +111,8 @@ .monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .header > .close { background-image: url('./close-light.svg'); position: absolute; - top: 0px; - right: 0px; + top: 0; + right: 0; margin-right: 5px; } @@ -159,9 +155,16 @@ } /** Styles for each row in the list **/ + +.monaco-editor .suggest-widget .monaco-list .monaco-list-row .monaco-icon-label.deprecated { + opacity: 0.66; +} +.monaco-editor .suggest-widget .monaco-list .monaco-list-row .monaco-icon-label.deprecated > .monaco-icon-label-description-container { + text-decoration: line-through; +} + .monaco-editor .suggest-widget .monaco-list .monaco-list-row .monaco-icon-label::before { height: 100%; - } .monaco-editor .suggest-widget .monaco-list .monaco-list-row .icon { @@ -257,7 +260,7 @@ text-overflow: ellipsis; opacity: 0.7; word-break: break-all; - margin: 0px 24px 0 0; + margin: 0 24px 0 0; padding: 4px 0 12px 5px; } diff --git a/src/vs/editor/contrib/suggest/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts index 4c5b089455..078bf09208 100644 --- a/src/vs/editor/contrib/suggest/suggestController.ts +++ b/src/vs/editor/contrib/suggest/suggestController.ts @@ -34,6 +34,8 @@ import { IdleValue } from 'vs/base/common/async'; import { isObject } from 'vs/base/common/types'; import { CommitCharacterController } from './suggestCommitCharacters'; +const _sticky = false; // for development purposes only + export class SuggestController implements IEditorContribution { private static readonly ID: string = 'editor.contrib.suggestController'; @@ -47,7 +49,6 @@ export class SuggestController implements IEditorContribution { private readonly _alternatives: IdleValue; private readonly _toDispose = new DisposableStore(); - private readonly _sticky = false; // for development purposes only constructor( private _editor: ICodeEditor, @@ -127,7 +128,7 @@ export class SuggestController implements IEditorContribution { } })); this._toDispose.add(this._editor.onDidBlurEditorWidget(() => { - if (!this._sticky) { + if (!_sticky) { this._model.cancel(); this._model.clear(); } diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 94797e2281..ea9b05696e 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -193,8 +193,9 @@ class Renderer implements IListRenderer ]; } - if (suggestion.kindModifier && suggestion.kindModifier & CompletionItemKindModifier.Deprecated) { - labelOptions.extraClasses = (labelOptions.extraClasses || []).concat(['suggest-widget-deprecated']); + if (suggestion.kindModifier && suggestion.kindModifier.has(CompletionItemKindModifier.Deprecated)) { + labelOptions.extraClasses = (labelOptions.extraClasses || []).concat(['deprecated']); + labelOptions.matches = []; } data.iconLabel.setLabel(suggestion.label, undefined, labelOptions); diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index 809174a6ac..3859d719a7 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -565,6 +565,7 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { CompletionItemKindModifier: standaloneEnums.CompletionItemKindModifier, CompletionItemInsertTextRule: standaloneEnums.CompletionItemInsertTextRule, SymbolKind: standaloneEnums.SymbolKind, + SymbolKindTag: standaloneEnums.SymbolKindTag, IndentAction: standaloneEnums.IndentAction, CompletionTriggerKind: standaloneEnums.CompletionTriggerKind, SignatureHelpTriggerKind: standaloneEnums.SignatureHelpTriggerKind, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 9c17fcde9d..ee5e515281 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4826,7 +4826,7 @@ declare namespace monaco.languages { * A modifier to the `kind` which affect how the item * is rendered, e.g. Deprecated is rendered with a strikeout */ - kindModifier?: CompletionItemKindModifier; + kindModifier?: Set; /** * A human-readable string with additional information * about this item, like type or symbol information. @@ -5236,10 +5236,15 @@ declare namespace monaco.languages { TypeParameter = 25 } + export enum SymbolKindTag { + Deprecated = 1 + } + export interface DocumentSymbol { name: string; detail: string; kind: SymbolKind; + kindTags: SymbolKindTag[]; containerName?: string; range: IRange; selectionRange: IRange; diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index 20625c64a6..dcf4d7ca42 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -50,6 +50,7 @@ export interface IProgressOptions { source?: string; total?: number; cancellable?: boolean; + buttons?: string[]; } export interface IProgressNotificationOptions extends IProgressOptions { diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index 45ae5a21f8..e1b2407f32 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -12,6 +12,7 @@ import { Emitter } from 'vs/base/common/event'; import { RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { ISignService } from 'vs/platform/sign/common/sign'; +import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; export const enum ConnectionType { Management = 1, @@ -245,9 +246,15 @@ export async function connectRemoteAgentTunnel(options: IConnectionOptions, tunn return protocol; } -function sleep(seconds: number): Promise { - return new Promise((resolve, reject) => { - setTimeout(resolve, seconds * 1000); +function sleep(seconds: number): CancelablePromise { + return createCancelablePromise(token => { + return new Promise((resolve, reject) => { + const timeout = setTimeout(resolve, seconds * 1000); + token.onCancellationRequested(() => { + clearTimeout(timeout); + resolve(); + }); + }); }); } @@ -264,8 +271,13 @@ export class ConnectionLostEvent { export class ReconnectionWaitEvent { public readonly type = PersistentConnectionEventType.ReconnectionWait; constructor( - public readonly durationSeconds: number + public readonly durationSeconds: number, + private readonly cancellableTimer: CancelablePromise ) { } + + public skipWait(): void { + this.cancellableTimer.cancel(); + } } export class ReconnectionRunningEvent { public readonly type = PersistentConnectionEventType.ReconnectionRunning; @@ -330,8 +342,12 @@ abstract class PersistentConnection extends Disposable { attempt++; const waitTime = (attempt < TIMES.length ? TIMES[attempt] : TIMES[TIMES.length - 1]); try { - this._onDidStateChange.fire(new ReconnectionWaitEvent(waitTime)); - await sleep(waitTime); + const sleepPromise = sleep(waitTime); + this._onDidStateChange.fire(new ReconnectionWaitEvent(waitTime, sleepPromise)); + + try { + await sleepPromise; + } catch { } // User canceled timer // connection was lost, let's try to re-establish it this._onDidStateChange.fire(new ReconnectionRunningEvent()); diff --git a/src/vs/platform/url/common/url.ts b/src/vs/platform/url/common/url.ts index 1fb0b28736..4a560ee5a0 100644 --- a/src/vs/platform/url/common/url.ts +++ b/src/vs/platform/url/common/url.ts @@ -3,8 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI } from 'vs/base/common/uri'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable } from 'vs/base/common/lifecycle'; export const IURLService = createDecorator('urlService'); @@ -14,8 +14,17 @@ export interface IURLHandler { } export interface IURLService { - _serviceBrand: any; + + _serviceBrand: ServiceIdentifier; + + /** + * Create a URL that can be called to trigger IURLhandlers. + * The URL that gets passed to the IURLHandlers carries over + * any of the provided IURLCreateOption values. + */ + create(options?: Partial): URI; open(url: URI): Promise; + registerHandler(handler: IURLHandler): IDisposable; } diff --git a/src/vs/platform/url/common/urlService.ts b/src/vs/platform/url/common/urlService.ts index a6eb375558..db77ad2e40 100644 --- a/src/vs/platform/url/common/urlService.ts +++ b/src/vs/platform/url/common/urlService.ts @@ -4,18 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; -import { URI } from 'vs/base/common/uri'; -import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { first } from 'vs/base/common/async'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { values } from 'vs/base/common/map'; +import { first } from 'vs/base/common/async'; +import { toDisposable, IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; -export class URLService implements IURLService { +export abstract class AbstractURLService extends Disposable implements IURLService { _serviceBrand!: ServiceIdentifier; private handlers = new Set(); + abstract create(options?: Partial): URI; + open(uri: URI): Promise { const handlers = values(this.handlers); return first(handlers.map(h => () => h.handleURL(uri)), undefined, false).then(val => val || false); diff --git a/src/vs/platform/url/node/urlIpc.ts b/src/vs/platform/url/node/urlIpc.ts index f3a5ba8c24..94ed13bec2 100644 --- a/src/vs/platform/url/node/urlIpc.ts +++ b/src/vs/platform/url/node/urlIpc.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { URI } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; @@ -39,6 +39,10 @@ export class URLServiceChannelClient implements IURLService { registerHandler(handler: IURLHandler): IDisposable { throw new Error('Not implemented.'); } + + create(_options?: Partial): URI { + throw new Error('Method not implemented.'); + } } export class URLHandlerChannel implements IServerChannel { diff --git a/src/vs/platform/url/node/urlService.ts b/src/vs/platform/url/node/urlService.ts new file mode 100644 index 0000000000..c9a5b41ac2 --- /dev/null +++ b/src/vs/platform/url/node/urlService.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI, UriComponents } from 'vs/base/common/uri'; +import product from 'vs/platform/product/node/product'; +import { AbstractURLService } from 'vs/platform/url/common/urlService'; + +export class URLService extends AbstractURLService { + + create(options?: Partial): URI { + const { authority, path, query, fragment } = options ? options : { authority: undefined, path: undefined, query: undefined, fragment: undefined }; + + return URI.from({ scheme: product.urlProtocol, authority, path, query, fragment }); + } +} diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index e6e647346b..374b44d3bf 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -6882,6 +6882,13 @@ declare module 'vscode' { * Whether to show collapse all action or not. */ showCollapseAll?: boolean; + + /** + * Whether the tree supports multi-select. When the tree supports multi-select and a command is executed from the tree, + * the first argument to the command is the tree item that the command was executed on and the second argument is an + * array containing the other selected tree items. + */ + canSelectMany?: boolean; } /** diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 18924a8570..5482def506 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -881,6 +881,11 @@ declare module 'vscode' { /** * An event that when fired will signal that the pty is closed and dispose of the terminal. * + * A number can be used to provide an exit code for the terminal. Exit codes must be + * positive and a non-zero exit codes signals failure which shows a notification for a + * regular terminal and allows dependent tasks to proceed when used with the + * `CustomExecution2` API. + * * **Example:** Exit the terminal when "y" is pressed, otherwise show a notification. * ```typescript * const writeEmitter = new vscode.EventEmitter(); @@ -899,7 +904,7 @@ declare module 'vscode' { * }; * vscode.window.createTerminal({ name: 'Exit example', pty }); */ - onDidClose?: Event; + onDidClose?: Event; /** * Implement to handle when the pty is open and ready to start firing events. @@ -1030,15 +1035,6 @@ declare module 'vscode' { */ constructor(label: TreeItemLabel, collapsibleState?: TreeItemCollapsibleState); } - - export interface TreeViewOptions2 extends TreeViewOptions { - /** - * Whether the tree supports multi-select. When the tree supports multi-select and a command is executed from the tree, - * the first argument to the command is the tree item that the command was executed on and the second argument is an - * array containing the other selected tree items. - */ - canSelectMany?: boolean; - } //#endregion //#region CustomExecution @@ -1142,13 +1138,64 @@ declare module 'vscode' { //#endregion - //#region Deprecated support + //#region Joh - CompletionItemKindModifier, https://github.com/microsoft/vscode/issues/23927 + + export enum CompletionItemKindModifier { + Deprecated = 1 + } export interface CompletionItem { + /** - * Indicates if this item is deprecated. + * */ - deprecated?: boolean; + kind2?: CompletionItemKind | { base: CompletionItemKind, modifier: ReadonlyArray }; + } + + //#endregion + + // #region Ben - extension auth flow (desktop+web) + + export interface AppUriOptions { + payload?: { + path?: string; + query?: string; + fragment?: string; + }; + } + + export namespace env { + + /** + * Creates a Uri that - if opened in a browser - will result in a + * registered [UriHandler](#UriHandler) to fire. The handler's + * Uri will be configured with the path, query and fragment of + * [AppUriOptions](#AppUriOptions) if provided, otherwise it will be empty. + * + * Extensions should not make any assumptions about the resulting + * Uri and should not alter it in anyway. Rather, extensions can e.g. + * use this Uri in an authentication flow, by adding the Uri as + * callback query argument to the server to authenticate to. + * + * Note: If the server decides to add additional query parameters to the Uri + * (e.g. a token or secret), it will appear in the Uri that is passed + * to the [UriHandler](#UriHandler). + * + * **Example** of an authentication flow: + * ```typescript + * vscode.window.registerUriHandler({ + * handleUri(uri: vscode.Uri): vscode.ProviderResult { + * if (uri.path === '/did-authenticate') { + * console.log(uri.toString()); + * } + * } + * }); + * + * const callableUri = await vscode.env.createAppUri({ payload: { path: '/did-authenticate' } }); + * await vscode.env.openExternal(callableUri); + * ``` + */ + export function createAppUri(options?: AppUriOptions): Thenable; } //#endregion diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 0de8da8538..9f3b74b223 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -21,6 +21,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { mixin } from 'vs/base/common/objects'; +import { fromArray } from 'vs/base/common/map'; @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape { @@ -330,7 +331,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha return { label: data.a, kind: data.b, - kindModifier: data.n ? modes.CompletionItemKindModifier.Deprecated : undefined, + kindModifier: data.n && fromArray(data.n), detail: data.c, documentation: data.d, sortText: data.e, diff --git a/src/vs/workbench/api/browser/mainThreadUrls.ts b/src/vs/workbench/api/browser/mainThreadUrls.ts index 9c7ab132f0..a3795d9b6a 100644 --- a/src/vs/workbench/api/browser/mainThreadUrls.ts +++ b/src/vs/workbench/api/browser/mainThreadUrls.ts @@ -6,7 +6,7 @@ import { ExtHostContext, IExtHostContext, MainContext, MainThreadUrlsShape, ExtHostUrlsShape } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from '../common/extHostCustomers'; import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; -import { URI } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IExtensionUrlHandler } from 'vs/workbench/services/extensions/common/inactiveExtensionUrlHandler'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @@ -68,6 +68,16 @@ export class MainThreadUrls implements MainThreadUrlsShape { return Promise.resolve(undefined); } + async $createAppUri(extensionId: ExtensionIdentifier, options?: { payload?: Partial }): Promise { + const payload: Partial = options && options.payload ? options.payload : Object.create(null); + + // we define the authority to be the extension ID to ensure + // that the Uri gets routed back to the extension properly. + payload.authority = extensionId.value; + + return this.urlService.create(payload); + } + dispose(): void { this.handlers.forEach(({ disposable }) => disposable.dispose()); this.handlers.clear(); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index fd09c640e5..809988add1 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -73,14 +73,6 @@ export interface IExtensionApiFactory { (extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode; } -function proposedApiFunction(extension: IExtensionDescription, fn: T): T { - if (extension.enableProposedApi) { - return fn; - } else { - return throwProposedApiError.bind(null, extension) as any as T; - } -} - /** * This method instantiates and returns the extension API surface */ @@ -208,7 +200,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }); }); }, - registerDiffInformationCommand: proposedApiFunction(extension, (id: string, callback: (diff: vscode.LineChange[], ...args: any[]) => any, thisArg?: any): vscode.Disposable => { + registerDiffInformationCommand: (id: string, callback: (diff: vscode.LineChange[], ...args: any[]) => any, thisArg?: any): vscode.Disposable => { + checkProposedApiEnabled(extension); return extHostCommands.registerCommand(true, id, async (...args: any[]): Promise => { const activeTextEditor = extHostEditors.getActiveTextEditor(); if (!activeTextEditor) { @@ -219,7 +212,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const diff = await extHostEditors.getDiffInformation(activeTextEditor.id); callback.apply(thisArg, [diff, ...args]); }); - }), + }, executeCommand(id: string, ...args: any[]): Thenable { return extHostCommands.executeCommand(id, ...args); }, @@ -236,6 +229,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I get appName() { return initData.environment.appName; }, get appRoot() { return initData.environment.appRoot!.fsPath; }, get uriScheme() { return initData.environment.appUriScheme; }, + createAppUri(options?) { + checkProposedApiEnabled(extension); + return extHostUrls.createAppUri(extension.identifier, options); + }, get logLevel() { checkProposedApiEnabled(extension); return typeConverters.LogLevel.to(extHostLogService.getLevel()); @@ -531,9 +528,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerWebviewPanelSerializer: (viewType: string, serializer: vscode.WebviewPanelSerializer) => { return extHostWebviews.registerWebviewPanelSerializer(viewType, serializer); }, - registerDecorationProvider: proposedApiFunction(extension, (provider: vscode.DecorationProvider) => { + registerDecorationProvider(provider: vscode.DecorationProvider) { + checkProposedApiEnabled(extension); return extHostDecorations.registerDecorationProvider(provider, extension.identifier); - }), + }, registerUriHandler(handler: vscode.UriHandler) { return extHostUrls.registerUriHandler(extension.identifier, handler); }, @@ -548,6 +546,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // namespace: workspace const workspace: typeof vscode.workspace = { get rootPath() { + console.warn(`[Deprecation Warning] 'workspace.rootPath' is deprecated and should no longer be used. Please use 'workspace.workspaceFolders' instead. (${extension.publisher}.${extension.name})`); return extHostWorkspace.getPath(); }, set rootPath(value) { @@ -668,24 +667,30 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I get fs() { return extHostFileSystem.fileSystem; }, - registerFileSearchProvider: proposedApiFunction(extension, (scheme: string, provider: vscode.FileSearchProvider) => { + registerFileSearchProvider: (scheme: string, provider: vscode.FileSearchProvider) => { + checkProposedApiEnabled(extension); return extHostSearch.registerFileSearchProvider(scheme, provider); - }), - registerTextSearchProvider: proposedApiFunction(extension, (scheme: string, provider: vscode.TextSearchProvider) => { + }, + registerTextSearchProvider: (scheme: string, provider: vscode.TextSearchProvider) => { + checkProposedApiEnabled(extension); return extHostSearch.registerTextSearchProvider(scheme, provider); - }), - registerRemoteAuthorityResolver: proposedApiFunction(extension, (authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver) => { + }, + registerRemoteAuthorityResolver: (authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver) => { + checkProposedApiEnabled(extension); return extensionService.registerRemoteAuthorityResolver(authorityPrefix, resolver); - }), - registerResourceLabelFormatter: proposedApiFunction(extension, (formatter: vscode.ResourceLabelFormatter) => { + }, + registerResourceLabelFormatter: (formatter: vscode.ResourceLabelFormatter) => { + checkProposedApiEnabled(extension); return extHostLabelService.$registerResourceLabelFormatter(formatter); - }), - onDidRenameFile: proposedApiFunction(extension, (listener: (e: vscode.FileRenameEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { + }, + onDidRenameFile: (listener: (e: vscode.FileRenameEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { + checkProposedApiEnabled(extension); return extHostFileSystemEvent.onDidRenameFile(listener, thisArg, disposables); - }), - onWillRenameFile: proposedApiFunction(extension, (listener: (e: vscode.FileWillRenameEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { + }, + onWillRenameFile: (listener: (e: vscode.FileWillRenameEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { + checkProposedApiEnabled(extension); return extHostFileSystemEvent.getOnWillRenameFileEvent(extension)(listener, thisArg, disposables); - }) + } }; // namespace: scm @@ -810,6 +815,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I CommentMode: extHostTypes.CommentMode, CompletionItem: extHostTypes.CompletionItem, CompletionItemKind: extHostTypes.CompletionItemKind, + CompletionItemKindModifier: extHostTypes.CompletionItemKindModifier, CompletionList: extHostTypes.CompletionList, CompletionTriggerKind: extHostTypes.CompletionTriggerKind, ConfigurationTarget: extHostTypes.ConfigurationTarget, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index ae40dc7973..d96d7133aa 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -571,6 +571,7 @@ export interface ExtHostWebviewsShape { export interface MainThreadUrlsShape extends IDisposable { $registerUriHandler(handle: number, extensionId: ExtensionIdentifier): Promise; $unregisterUriHandler(handle: number): Promise; + $createAppUri(extensionId: ExtensionIdentifier, options?: { payload?: Partial }): Promise; } export interface ExtHostUrlsShape { @@ -939,7 +940,7 @@ export interface ISuggestDataDto { k/* commitCharacters */?: string[]; l/* additionalTextEdits */?: ISingleEditOperation[]; m/* command */?: modes.Command; - n/* deprecated */?: boolean; + n/* kindModifier */?: modes.CompletionItemKindModifier[]; // not-standard x?: ChainedCacheId; } diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index ffc06816f2..4ace0760c7 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -70,6 +70,7 @@ class DocumentSymbolAdapter { const element = { name: info.name || '!!MISSING: name!!', kind: typeConvert.SymbolKind.from(info.kind), + kindTags: [], detail: undefined!, // Strict null override — avoid changing behavior containerName: info.containerName, range: typeConvert.Range.from(info.location.range), @@ -736,9 +737,14 @@ class SuggestAdapter { k: item.commitCharacters, l: item.additionalTextEdits && item.additionalTextEdits.map(typeConvert.TextEdit.from), m: this._commands.toInternal(item.command, disposables), - n: item.deprecated }; + // kind2 + if (typeof item.kind2 === 'object') { + result.b = typeConvert.CompletionItemKind.from(item.kind2.base); + result.n = item.kind2.modifier.map(typeConvert.CompletionItemKindModifier.from); + } + // 'insertText'-logic if (item.textEdit) { result.h = item.textEdit.newText; diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index bc8588334e..dcff183259 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -198,7 +198,7 @@ export class ExtHostTreeView extends Disposable { private refreshPromise: Promise = Promise.resolve(); private refreshQueue: Promise = Promise.resolve(); - constructor(private viewId: string, options: vscode.TreeViewOptions2, private proxy: MainThreadTreeViewsShape, private commands: CommandsConverter, private logService: ILogService, private extension: IExtensionDescription) { + constructor(private viewId: string, options: vscode.TreeViewOptions, private proxy: MainThreadTreeViewsShape, private commands: CommandsConverter, private logService: ILogService, private extension: IExtensionDescription) { super(); this.dataProvider = options.treeDataProvider; // {{SQL CARBON EDIT}} diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 08b22c4e62..dca646dc85 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -582,7 +582,8 @@ export namespace DocumentSymbol { detail: info.detail, range: Range.from(info.range), selectionRange: Range.from(info.selectionRange), - kind: SymbolKind.from(info.kind) + kind: SymbolKind.from(info.kind), + kindTags: [] }; if (info.children) { result.children = info.children.map(from); @@ -681,6 +682,21 @@ export namespace CompletionContext { } } +export namespace CompletionItemKindModifier { + + export function from(kind: types.CompletionItemKindModifier): modes.CompletionItemKindModifier { + switch (kind) { + case types.CompletionItemKindModifier.Deprecated: return modes.CompletionItemKindModifier.Deprecated; + } + } + + export function to(kind: modes.CompletionItemKindModifier): types.CompletionItemKindModifier { + switch (kind) { + case modes.CompletionItemKindModifier.Deprecated: return types.CompletionItemKindModifier.Deprecated; + } + } +} + export namespace CompletionItemKind { export function from(kind: types.CompletionItemKind | undefined): modes.CompletionItemKind { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 5d863ad87d..ebc275eac0 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -1308,11 +1308,16 @@ export enum CompletionItemKind { TypeParameter = 24 } +export enum CompletionItemKindModifier { + Deprecated = 1, +} + @es5ClassCompat export class CompletionItem implements vscode.CompletionItem { label: string; kind?: CompletionItemKind; + kind2?: CompletionItemKind | { base: CompletionItemKind, modifier: CompletionItemKindModifier[] }; detail?: string; documentation?: string | MarkdownString; sortText?: string; diff --git a/src/vs/workbench/api/common/extHostUrls.ts b/src/vs/workbench/api/common/extHostUrls.ts index 930d05087b..ad5d57893a 100644 --- a/src/vs/workbench/api/common/extHostUrls.ts +++ b/src/vs/workbench/api/common/extHostUrls.ts @@ -55,4 +55,8 @@ export class ExtHostUrls implements ExtHostUrlsShape { return Promise.resolve(undefined); } + + async createAppUri(extensionId: ExtensionIdentifier, options?: vscode.AppUriOptions): Promise { + return URI.revive(await this._proxy.$createAppUri(extensionId, options)); + } } diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 7fa1f5c156..8fb99a7f8c 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -738,7 +738,7 @@ class ExtHostPseudoterminal implements ITerminalChildProcess { // Attach the listeners this._pty.onDidWrite(e => this._onProcessData.fire(e)); if (this._pty.onDidClose) { - this._pty.onDidClose(e => this._onProcessExit.fire(0)); + this._pty.onDidClose(e => this._onProcessExit.fire(e || 0)); } if (this._pty.onDidOverrideDimensions) { this._pty.onDidOverrideDimensions(e => this._onProcessOverrideDimensions.fire(e ? { cols: e.columns, rows: e.rows } : undefined)); // {{SQL CARBON EDIT}} strict-null-checks diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index df2e809994..b40b454c74 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -617,10 +617,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // To properly reset line numbers we need to read the configuration for each editor respecting it's uri. if (!lineNumbers && isCodeEditor(editor) && editor.hasModel()) { const model = editor.getModel(); - this.configurationService.getValue('editor.lineNumbers', { resource: model.uri }); - } else { - editor.updateOptions({ lineNumbers }); + lineNumbers = this.configurationService.getValue('editor.lineNumbers', { resource: model.uri }); } + if (!lineNumbers) { + lineNumbers = this.configurationService.getValue('editor.lineNumbers'); + } + + editor.updateOptions({ lineNumbers }); }); // Check if zen mode transitioned to full screen and if now we are out of zen mode @@ -1292,4 +1295,3 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.disposed = true; } } - diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 9fb8fbc654..55c5fab68c 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -999,7 +999,7 @@ export class ChangeModeAction extends Action { private configureFileAssociation(resource: URI): void { const extension = extname(resource); const base = basename(resource); - const currentAssociation = this.modeService.getModeIdByFilepathOrFirstLine(resource.with({ path: base })); + const currentAssociation = this.modeService.getModeIdByFilepathOrFirstLine(URI.file(base)); const languages = this.modeService.getRegisteredLanguageNames(); const picks: IQuickPickItem[] = languages.sort().map((lang, index) => { diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 7366d7a392..245a684391 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -6,7 +6,7 @@ import { mark } from 'vs/base/common/performance'; import { domContentLoaded, addDisposableListener, EventType, addClass } from 'vs/base/browser/dom'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { ILogService } from 'vs/platform/log/common/log'; +import { ILogService, ConsoleLogService, MultiplexLogService } from 'vs/platform/log/common/log'; import { Disposable } from 'vs/base/common/lifecycle'; import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { Workbench } from 'vs/workbench/browser/workbench'; @@ -44,7 +44,8 @@ import { StaticExtensionsService, IStaticExtensionsService } from 'vs/workbench/ import { BufferLogService } from 'vs/platform/log/common/bufferLog'; import { FileLogService } from 'vs/platform/log/common/fileLogService'; import { toLocalISOString } from 'vs/base/common/date'; -import { INDEXEDDB_LOG_SCHEME, IndexedDBLogProvider } from 'vs/workbench/services/log/browser/indexedDBLogProvider'; +import { IndexedDBLogProvider } from 'vs/workbench/services/log/browser/indexedDBLogProvider'; +import { InMemoryLogProvider } from 'vs/workbench/services/log/common/inMemoryLogProvider'; class CodeRendererMain extends Disposable { @@ -120,7 +121,7 @@ class CodeRendererMain extends Disposable { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // Log - const logsPath = URI.file(toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')).with({ scheme: INDEXEDDB_LOG_SCHEME }); + const logsPath = URI.file(toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')).with({ scheme: 'vscode-log' }); const logService = new BufferLogService(); serviceCollection.set(ILogService, logService); @@ -151,8 +152,19 @@ class CodeRendererMain extends Disposable { serviceCollection.set(IFileService, fileService); // Logger - fileService.registerProvider(INDEXEDDB_LOG_SCHEME, new IndexedDBLogProvider()); - logService.logger = new FileLogService('window', environmentService.logFile, logService.getLevel(), fileService); + const indexedDBLogProvider = new IndexedDBLogProvider(logsPath.scheme); + indexedDBLogProvider.database.then( + () => fileService.registerProvider(logsPath.scheme, indexedDBLogProvider), + e => { + (logService).info('Error while creating indexedDB log provider. Falling back to in-memory log provider.'); + (logService).error(e); + fileService.registerProvider(logsPath.scheme, new InMemoryLogProvider(logsPath.scheme)); + } + ).then(() => { + const consoleLogService = new ConsoleLogService(logService.getLevel()); + const fileLogService = new FileLogService('window', environmentService.logFile, logService.getLevel(), fileService); + logService.logger = new MultiplexLogService([consoleLogService, fileLogService]); + }); // Static Extensions const staticExtensions = new StaticExtensionsService(this.configuration.staticExtensions || []); diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index 2eb195d725..9341fb5860 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -7,11 +7,8 @@ import { URI } from 'vs/base/common/uri'; import * as browser from 'vs/base/browser/browser'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Event } from 'vs/base/common/event'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { IURLHandler, IURLService } from 'vs/platform/url/common/url'; import { ILogService } from 'vs/platform/log/common/log'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IUpdateService, State } from 'vs/platform/update/common/update'; import { IWindowService, INativeOpenDialogOptions, IEnterWorkspaceResult, IURIToOpen, IMessageBoxResult, IWindowsService, IOpenSettings, IWindowSettings } from 'vs/platform/windows/common/windows'; @@ -20,7 +17,6 @@ import { IRecentlyOpened, IRecent, isRecentFile, isRecentFolder } from 'vs/platf import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; -// tslint:disable-next-line: import-patterns import { IWorkspaceContextService, WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { addDisposableListener, EventType, windowOpenNoOpener } from 'vs/base/browser/dom'; import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; @@ -38,29 +34,6 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService // tslint:disable-next-line: import-patterns import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/common/workspaceStats'; -//#region Extension URL Handler - -export const IExtensionUrlHandler = createDecorator('inactiveExtensionUrlHandler'); - -export interface IExtensionUrlHandler { - readonly _serviceBrand: any; - registerExtensionHandler(extensionId: ExtensionIdentifier, handler: IURLHandler): void; - unregisterExtensionHandler(extensionId: ExtensionIdentifier): void; -} - -export class SimpleExtensionURLHandler implements IExtensionUrlHandler { - - _serviceBrand: any; - - registerExtensionHandler(extensionId: ExtensionIdentifier, handler: IURLHandler): void { } - - unregisterExtensionHandler(extensionId: ExtensionIdentifier): void { } -} - -registerSingleton(IExtensionUrlHandler, SimpleExtensionURLHandler, true); - -//#endregion - //#region Update export class SimpleUpdateService implements IUpdateService { @@ -95,24 +68,6 @@ registerSingleton(IUpdateService, SimpleUpdateService); //#endregion -//#region URL - -export class SimpleURLService implements IURLService { - _serviceBrand: any; - - open(url: URI): Promise { - return Promise.resolve(false); - } - - registerHandler(handler: IURLHandler): IDisposable { - return Disposable.None; - } -} - -registerSingleton(IURLService, SimpleURLService); - -//#endregion - //#region Window export class SimpleWindowService extends Disposable implements IWindowService { diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 088ae604b7..ad187549ed 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -590,7 +590,12 @@ class CallStackDataSource implements IAsyncDataSource 1) || (threads.length === 1 && threads[0].stopped) || (this.debugService.getModel().getSessions().filter(s => s.parentSession === element).length > 0); + } + + return isDebugModel(element) || (element instanceof Thread && element.stopped); } async getChildren(element: IDebugModel | CallStackItem): Promise { diff --git a/src/vs/workbench/contrib/debug/common/debugger.ts b/src/vs/workbench/contrib/debug/common/debugger.ts index a9dd2d0c29..501923a17a 100644 --- a/src/vs/workbench/contrib/debug/common/debugger.ts +++ b/src/vs/workbench/contrib/debug/common/debugger.ts @@ -236,16 +236,18 @@ export class Debugger implements IDebugger { }; properties['preLaunchTask'] = { anyOf: [taskSchema, { - type: ['string', 'null'], + type: ['string'] }], default: '', + defaultSnippets: [{ body: { task: '', type: '' } }], description: nls.localize('debugPrelaunchTask', "Task to run before debug session starts.") }; properties['postDebugTask'] = { anyOf: [taskSchema, { - type: ['string', 'null'], + type: ['string'], }], default: '', + defaultSnippets: [{ body: { task: '', type: '' } }], description: nls.localize('debugPostDebugTask', "Task to run after debug session ends.") }; properties['internalConsoleOptions'] = INTERNAL_CONSOLE_OPTIONS_SCHEMA; diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts index 23207c76dd..d8e7eda424 100644 --- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts @@ -15,7 +15,7 @@ import { IExtensionEnablementService } from 'vs/workbench/services/extensionMana import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { Emitter } from 'vs/base/common/event'; import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test'; -import { URLService } from 'vs/platform/url/common/urlService'; +import { URLService } from 'vs/platform/url/node/urlService'; import { IURLService } from 'vs/platform/url/common/url'; import { ITelemetryService, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index d99cf61728..73ab83dfd3 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -31,7 +31,7 @@ import { TestContextService, TestWindowService, TestSharedProcessService } from import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IWindowService } from 'vs/platform/windows/common/windows'; -import { URLService } from 'vs/platform/url/common/urlService'; +import { URLService } from 'vs/platform/url/node/urlService'; import { URI } from 'vs/base/common/uri'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts index 5f9cc135cd..a327d6183d 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -40,7 +40,7 @@ import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { INotificationService, Severity, IPromptChoice, IPromptOptions } from 'vs/platform/notification/common/notification'; -import { URLService } from 'vs/platform/url/common/urlService'; +import { URLService } from 'vs/platform/url/node/urlService'; import { IExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService'; import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index d23416f9e2..5a38560825 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -31,7 +31,7 @@ import { TestContextService, TestWindowService, TestSharedProcessService } from import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IWindowService } from 'vs/platform/windows/common/windows'; -import { URLService } from 'vs/platform/url/common/urlService'; +import { URLService } from 'vs/platform/url/node/urlService'; import { URI } from 'vs/base/common/uri'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { SinonStub } from 'sinon'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index dd802c3d54..056b33497c 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -34,7 +34,7 @@ import { IWindowService } from 'vs/platform/windows/common/windows'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { ProgressService } from 'vs/workbench/services/progress/browser/progressService'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { URLService } from 'vs/platform/url/common/urlService'; +import { URLService } from 'vs/platform/url/node/urlService'; import { URI } from 'vs/base/common/uri'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; diff --git a/src/vs/workbench/contrib/logs/common/logsDataCleaner.ts b/src/vs/workbench/contrib/logs/common/logsDataCleaner.ts index c49b48da75..561415b9c2 100644 --- a/src/vs/workbench/contrib/logs/common/logsDataCleaner.ts +++ b/src/vs/workbench/contrib/logs/common/logsDataCleaner.ts @@ -22,7 +22,7 @@ export class LogsDataCleaner extends Disposable { } private cleanUpOldLogsSoon(): void { - let handle: NodeJS.Timeout | undefined = setTimeout(async () => { + let handle: any = setTimeout(async () => { handle = undefined; const logsPath = URI.file(this.environmentService.logsPath).with({ scheme: this.environmentService.logFile.scheme }); const stat = await this.fileService.resolve(dirname(logsPath)); diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index 217e75d4cb..e2f2bc6f0e 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -369,6 +369,10 @@ margin-top: -1px; z-index: 1; } +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-item-contents.invalid-input .setting-item-validation-message { + position: static; + margin-top: 1rem; +} .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-text .setting-item-validation-message { width: 500px; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 71c234b6ce..3cc54981b0 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -224,8 +224,9 @@ interface ISettingComplexItemTemplate extends ISettingItemTemplate { button: Button; } -interface ISettingListItemTemplate extends ISettingItemTemplate { +interface ISettingListItemTemplate extends ISettingItemTemplate { listWidget: ListSettingWidget; + validationErrorMessageElement: HTMLElement; } interface ISettingExcludeItemTemplate extends ISettingItemTemplate { @@ -679,6 +680,9 @@ export class SettingArrayRenderer extends AbstractSettingRenderer implements ITr renderTemplate(container: HTMLElement): ISettingListItemTemplate { const common = this.renderCommonTemplate(null, container, 'list'); + const descriptionElement = common.containerElement.querySelector('.setting-item-description')!; + const validationErrorMessageElement = $('.setting-item-validation-message'); + descriptionElement.after(validationErrorMessageElement); const listWidget = this._instantiationService.createInstance(ListSettingWidget, common.controlElement); listWidget.domNode.classList.add(AbstractSettingRenderer.CONTROL_CLASS); @@ -686,19 +690,40 @@ export class SettingArrayRenderer extends AbstractSettingRenderer implements ITr const template: ISettingListItemTemplate = { ...common, - listWidget + listWidget, + validationErrorMessageElement }; this.addSettingElementFocusHandler(template); - common.toDispose.push(listWidget.onDidChangeList(e => this.onDidChangeList(template, e))); + common.toDispose.push( + listWidget.onDidChangeList(e => { + const newList = this.computeNewList(template, e); + this.onDidChangeList(template, newList); + if (newList !== null && template.onChange) { + template.onChange(newList); + } + }) + ); return template; } - private onDidChangeList(template: ISettingListItemTemplate, e: IListChangeEvent): void { + private onDidChangeList(template: ISettingListItemTemplate, newList: string[] | undefined | null): void { + if (!template.context || newList === null) { + return; + } + + this._onDidChangeSetting.fire({ + key: template.context.setting.key, + value: newList, + type: template.context.valueType + }); + } + + private computeNewList(template: ISettingListItemTemplate, e: IListChangeEvent): string[] | undefined | null { if (template.context) { - let newValue: any[] = []; + let newValue: string[] = []; if (isArray(template.context.scopeValue)) { newValue = [...template.context.scopeValue]; } else if (isArray(template.context.value)) { @@ -732,29 +757,30 @@ export class SettingArrayRenderer extends AbstractSettingRenderer implements ITr template.context.defaultValue.length === newValue.length && template.context.defaultValue.join() === newValue.join() ) { - return this._onDidChangeSetting.fire({ - key: template.context.setting.key, - value: undefined, // reset setting - type: template.context.valueType - }); + return undefined; } - this._onDidChangeSetting.fire({ - key: template.context.setting.key, - value: newValue, - type: template.context.valueType - }); + return newValue; } + + return undefined; } renderElement(element: ITreeNode, index: number, templateData: ISettingListItemTemplate): void { super.renderSettingElement(element, index, templateData); } - protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingListItemTemplate, onChange: (value: string) => void): void { + protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingListItemTemplate, onChange: (value: string[] | undefined) => void): void { const value = getListDisplayValue(dataElement); template.listWidget.setValue(value); template.context = dataElement; + + template.onChange = (v) => { + onChange(v); + renderArrayValidations(dataElement, template, v, false); + }; + + renderArrayValidations(dataElement, template, value.map(v => v.value), true); } } @@ -1237,6 +1263,29 @@ function renderValidations(dataElement: SettingsTreeSettingElement, template: IS DOM.removeClass(template.containerElement, 'invalid-input'); } +function renderArrayValidations( + dataElement: SettingsTreeSettingElement, + template: ISettingListItemTemplate, + value: string[] | undefined, + calledOnStartup: boolean +) { + DOM.addClass(template.containerElement, 'invalid-input'); + if (dataElement.setting.validator) { + const errMsg = dataElement.setting.validator(value); + if (errMsg && errMsg !== '') { + DOM.addClass(template.containerElement, 'invalid-input'); + template.validationErrorMessageElement.innerText = errMsg; + const validationError = localize('validationError', "Validation Error."); + template.containerElement.setAttribute('aria-label', [dataElement.setting.key, validationError, errMsg].join(' ')); + if (!calledOnStartup) { ariaAlert(validationError + ' ' + errMsg); } + return; + } else { + template.containerElement.setAttribute('aria-label', dataElement.setting.key); + DOM.removeClass(template.containerElement, 'invalid-input'); + } + } +} + function cleanRenderedMarkdown(element: Node): void { for (let i = 0; i < element.childNodes.length; i++) { const child = element.childNodes.item(i); diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index b8db7a5b09..c88ef93653 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -30,7 +30,7 @@ import { ipcRenderer as ipc } from 'electron'; import { IDiagnosticInfoOptions, IRemoteDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IProgressService, IProgress, IProgressStep, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection'; +import { PersistentConnectionEventType, ReconnectionWaitEvent } from 'vs/platform/remote/common/remoteAgentConnection'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import Severity from 'vs/base/common/severity'; @@ -315,7 +315,58 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { if (connection) { let currentProgressPromiseResolve: (() => void) | null = null; let progressReporter: ProgressReporter | null = null; + let lastLocation: ProgressLocation | null = null; let currentTimer: ReconnectionTimer | null = null; + let reconnectWaitEvent: ReconnectionWaitEvent | null = null; + + function showProgress(location: ProgressLocation, buttons?: string[]) { + if (currentProgressPromiseResolve) { + currentProgressPromiseResolve(); + } + + const promise = new Promise((resolve) => currentProgressPromiseResolve = resolve); + lastLocation = location; + + if (location === ProgressLocation.Dialog) { + // Show dialog + progressService!.withProgress( + { location: ProgressLocation.Dialog, buttons }, + (progress) => { progressReporter = new ProgressReporter(progress); return promise; }, + (choice?) => { + // Handle choice from dialog + if (choice === 0 && buttons && reconnectWaitEvent) { + reconnectWaitEvent.skipWait(); + } else { + showProgress(ProgressLocation.Notification, buttons); + } + + progressReporter!.report(); + }); + } else { + // Show notification + progressService!.withProgress( + { location: ProgressLocation.Notification, buttons }, + (progress) => { if (progressReporter) { progressReporter.currentProgress = progress; } return promise; }, + (choice?) => { + // Handle choice from notification + if (choice === 0 && buttons && reconnectWaitEvent) { + reconnectWaitEvent.skipWait(); + progressReporter!.report(); + } else { + hideProgress(); + } + }); + } + } + + function hideProgress() { + if (currentProgressPromiseResolve) { + currentProgressPromiseResolve(); + } + + currentProgressPromiseResolve = null; + progressReporter = null; + } connection.onDidStateChange((e) => { if (currentTimer) { @@ -325,31 +376,24 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { switch (e.type) { case PersistentConnectionEventType.ConnectionLost: if (!currentProgressPromiseResolve) { - let promise = new Promise((resolve) => currentProgressPromiseResolve = resolve); - progressService!.withProgress( - { location: ProgressLocation.Dialog }, - (progress: IProgress | null) => { progressReporter = new ProgressReporter(progress!); return promise; }, - () => { - currentProgressPromiseResolve!(); - promise = new Promise((resolve) => currentProgressPromiseResolve = resolve); - progressService!.withProgress({ location: ProgressLocation.Notification }, (progress) => { if (progressReporter) { progressReporter.currentProgress = progress; } return promise; }); - progressReporter!.report(); - } - ); + showProgress(ProgressLocation.Dialog, [nls.localize('reconnectNow', "Reconnect Now")]); } progressReporter!.report(nls.localize('connectionLost', "Connection Lost")); break; case PersistentConnectionEventType.ReconnectionWait: + hideProgress(); + reconnectWaitEvent = e; + showProgress(lastLocation || ProgressLocation.Notification, [nls.localize('reconnectNow', "Reconnect Now")]); currentTimer = new ReconnectionTimer(progressReporter!, Date.now() + 1000 * e.durationSeconds); break; case PersistentConnectionEventType.ReconnectionRunning: + hideProgress(); + showProgress(lastLocation || ProgressLocation.Notification); progressReporter!.report(nls.localize('reconnectionRunning', "Attempting to reconnect...")); break; case PersistentConnectionEventType.ReconnectionPermanentFailure: - currentProgressPromiseResolve!(); - currentProgressPromiseResolve = null; - progressReporter = null; + hideProgress(); dialogService.show(Severity.Error, nls.localize('reconnectionPermanentFailure', "Cannot reconnect. Please reload the window."), [nls.localize('reloadWindow', "Reload Window"), nls.localize('cancel', "Cancel")], { cancelId: 1 }).then(choice => { // Reload the window @@ -359,9 +403,7 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { }); break; case PersistentConnectionEventType.ConnectionGain: - currentProgressPromiseResolve!(); - currentProgressPromiseResolve = null; - progressReporter = null; + hideProgress(); break; } }); diff --git a/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts b/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts index f63024a048..10d9bb432f 100644 --- a/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts +++ b/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts @@ -147,7 +147,9 @@ export class WorkspaceStats implements IWorkbenchContribution { @ISharedProcessService private readonly sharedProcessService: ISharedProcessService, @IWorkspaceStatsService private readonly workspaceStatsService: IWorkspaceStatsService ) { - this.report(); + if (this.telemetryService.isOptedIn) { + this.report(); + } } private report(): void { diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 783c79ce85..e09f9f5397 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -44,6 +44,7 @@ import { Schemas } from 'vs/base/common/network'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { env as processEnv, cwd as processCwd } from 'vs/base/common/process'; interface TerminalData { terminal: ITerminalInstance; @@ -339,7 +340,9 @@ export class TerminalTaskSystem implements ITaskSystem { private async executeTask(task: Task, resolver: ITaskResolver, trigger: string): Promise { let promises: Promise[] = []; if (task.configurationProperties.dependsOn) { - for (const dependency of task.configurationProperties.dependsOn) { //{{SQL CARBON EDIT}} change to for of for linting + // tslint:disable-next-line: no-for-in-array + for (let index in task.configurationProperties.dependsOn) { + const dependency = task.configurationProperties.dependsOn[index]; let dependencyTask = resolver.resolve(dependency.workspaceFolder, dependency.task!); if (dependencyTask) { let key = dependencyTask.getMapKey(); @@ -1374,7 +1377,7 @@ export class TerminalTaskSystem implements ITaskSystem { return command; } if (cwd === undefined) { - cwd = process.cwd(); + cwd = processCwd(); } const dir = path.dirname(command); if (dir !== '.') { @@ -1382,8 +1385,8 @@ export class TerminalTaskSystem implements ITaskSystem { // to the current working directory. return path.join(cwd, command); } - if (paths === undefined && Types.isString(process.env.PATH)) { - paths = process.env.PATH.split(path.delimiter); + if (paths === undefined && Types.isString(processEnv.PATH)) { + paths = processEnv.PATH.split(path.delimiter); } // No PATH environment. Make path absolute to the cwd. if (paths === undefined || paths.length === 0) { diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index f04586f48e..35f3b1ca30 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -985,14 +985,15 @@ export namespace KeyedTaskIdentifier { function sortedStringify(literal: any): string { const keys = Object.keys(literal).sort(); let result: string = ''; - for (const position of keys) { // {{SQL CARBON EDIT}} change to of for linting - let stringified = literal[position]; + // tslint:disable-next-line: no-for-in-array + for (let position in keys) { + let stringified = literal[keys[position]]; if (stringified instanceof Object) { stringified = sortedStringify(stringified); } else if (typeof stringified === 'string') { stringified = stringified.replace(/,/g, ',,'); } - result += position + ',' + stringified + ','; + result += keys[position] + ',' + stringified + ','; } return result; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 8b9bd012d5..f46baeb075 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -72,17 +72,17 @@ configurationRegistry.registerConfiguration({ type: 'object', properties: { 'terminal.integrated.automationShell.linux': { - markdownDescription: nls.localize('terminal.integrated.automationShell.linux', "A path that when set will override {0} and ignore {1} and {2} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.linux`', '`shellArgs`', '`env`'), + markdownDescription: nls.localize('terminal.integrated.automationShell.linux', "A path that when set will override {0} and ignore {1} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.linux`', '`shellArgs`'), type: ['string', 'null'], default: null }, 'terminal.integrated.automationShell.osx': { - markdownDescription: nls.localize('terminal.integrated.automationShell.osx', "A path that when set will override {0} and ignore {1} and {2} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.osx`', '`shellArgs`', '`env`'), + markdownDescription: nls.localize('terminal.integrated.automationShell.osx', "A path that when set will override {0} and ignore {1} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.osx`', '`shellArgs`'), type: ['string', 'null'], default: null }, 'terminal.integrated.automationShell.windows': { - markdownDescription: nls.localize('terminal.integrated.automationShell.windows', "A path that when set will override {0} and ignore {1} and {2} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.windows`', '`shellArgs`', '`env`'), + markdownDescription: nls.localize('terminal.integrated.automationShell.windows', "A path that when set will override {0} and ignore {1} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.windows`', '`shellArgs`'), type: ['string', 'null'], default: null }, @@ -530,6 +530,7 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindNext, FindNe }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Find next', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindNext, FindNext.ID, FindNext.LABEL, { primary: KeyCode.F3, + secondary: [KeyMod.Shift | KeyCode.Enter], mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_G, secondary: [KeyCode.F3, KeyMod.Shift | KeyCode.Enter] } }, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Find next'); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindPrevious, FindPrevious.ID_TERMINAL_FOCUS, FindPrevious.LABEL, { @@ -538,6 +539,7 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindPrevious, Fi }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Find previous', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindPrevious, FindPrevious.ID, FindPrevious.LABEL, { primary: KeyMod.Shift | KeyCode.F3, + secondary: [KeyCode.Enter], mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_G, secondary: [KeyMod.Shift | KeyCode.F3, KeyCode.Enter] }, }, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Find previous'); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 43be816bce..4ee386ffc6 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -500,7 +500,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Force line data to be sent when the cursor is moved, the main purpose for // this is because ConPTY will often not do a line feed but instead move the // cursor, in which case we still want to send the current line's data to tasks. - xterm.addCsiHandler('H', () => { + xterm.parser.addCsiHandler({ final: 'H' }, () => { this._onCursorMove(); return false; }); @@ -865,7 +865,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return; } this.focus(); - this._xterm._core._coreService.triggerDataEvent(await this._clipboardService.readText(), true); + this._xterm.paste(await this._clipboardService.readText()); } public write(text: string): void { diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index a1a738b1b9..e5fde473ef 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -248,6 +248,7 @@ export interface ITerminalService { /** * Creates a raw terminal instance, this should not be used outside of the terminal part. */ + // tslint:disable-next-line: no-dom-globals createInstance(container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance; getInstanceFromId(terminalId: number): ITerminalInstance | undefined; getInstanceFromIndex(terminalIndex: number): ITerminalInstance; @@ -279,6 +280,7 @@ export interface ITerminalService { selectDefaultWindowsShell(): Promise; + // tslint:disable-next-line: no-dom-globals setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void; manageWorkspaceShellPermissions(): void; @@ -337,6 +339,7 @@ export interface ITerminalTab { focusNextPane(): void; resizePane(direction: Direction): void; setActiveInstanceByIndex(index: number): void; + // tslint:disable-next-line: no-dom-globals attachToElement(element: HTMLElement): void; setVisible(visible: boolean): void; layout(width: number, height: number): void; @@ -611,6 +614,7 @@ export interface ITerminalInstance { * * @param container The element to attach the terminal instance to. */ + // tslint:disable-next-line: no-dom-globals attachToElement(container: HTMLElement): void; /** diff --git a/src/vs/workbench/contrib/terminal/common/terminalService.ts b/src/vs/workbench/contrib/terminal/common/terminalService.ts index a06d3a6b1d..ac31ebbc6e 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalService.ts @@ -122,7 +122,9 @@ export abstract class TerminalService implements ITerminalService { protected abstract _showBackgroundTerminal(instance: ITerminalInstance): void; public abstract createTerminal(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance; + // tslint:disable-next-line: no-dom-globals public abstract createInstance(container: HTMLElement, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance; + // tslint:disable-next-line: no-dom-globals public abstract setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void; public getActiveOrCreateInstance(wasNewTerminalAction?: boolean): ITerminalInstance { diff --git a/src/vs/workbench/contrib/url/common/url.contribution.ts b/src/vs/workbench/contrib/url/common/url.contribution.ts index e743113254..6f8dc3b908 100644 --- a/src/vs/workbench/contrib/url/common/url.contribution.ts +++ b/src/vs/workbench/contrib/url/common/url.contribution.ts @@ -50,14 +50,17 @@ Registry.as(ActionExtensions.WorkbenchActions).registe localize('developer', 'Developer') ); -const VSCODE_DOMAIN = 'https://code.visualstudio.com'; +const DEAFULT_TRUSTED_DOMAINS = [ + 'https://code.visualstudio.com', + 'https://go.microsoft.com' +]; const configureTrustedDomainsHandler = async ( quickInputService: IQuickInputService, storageService: IStorageService, domainToConfigure?: string ) => { - let trustedDomains: string[] = [VSCODE_DOMAIN]; + let trustedDomains: string[] = DEAFULT_TRUSTED_DOMAINS; try { const trustedDomainsSrc = storageService.get('http.trustedDomains', StorageScope.GLOBAL); @@ -158,7 +161,7 @@ class OpenerValidatorContributions implements IWorkbenchContribution { return true; } - let trustedDomains: string[] = [VSCODE_DOMAIN]; + let trustedDomains: string[] = DEAFULT_TRUSTED_DOMAINS; try { const trustedDomainsSrc = this._storageService.get('http.trustedDomains', StorageScope.GLOBAL); if (trustedDomainsSrc) { diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index c88880ca4b..71741a7f44 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -283,17 +283,30 @@ export class EditorService extends Disposable implements EditorServiceImpl { } // Respect option to reveal an editor if it is open (not necessarily visible) + // Still prefer to reveal an editor in a group where the editor is active though. if (!targetGroup) { if ((options && options.revealIfOpened) || this.configurationService.getValue('workbench.editor.revealIfOpen')) { + let groupWithInputActive: IEditorGroup | undefined = undefined; + let groupWithInputOpened: IEditorGroup | undefined = undefined; + for (const group of groupsByLastActive) { - if (group.isOpened(input) && group.isActive(input)) { - targetGroup = group; - break; + if (group.isOpened(input)) { + if (!groupWithInputOpened) { + groupWithInputOpened = group; + } + + if (!groupWithInputActive && group.isActive(input)) { + groupWithInputActive = group; + } } - if (group.isOpened(input) && !targetGroup) { - targetGroup = group; + + if (groupWithInputOpened && groupWithInputActive) { + break; // we found all groups we wanted } } + + // Prefer a target group where the input is visible + targetGroup = groupWithInputActive || groupWithInputOpened; } } } diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 0709fb7f32..5109c2b008 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -34,7 +34,6 @@ import { IWindowService } from 'vs/platform/windows/common/windows'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { MenuRegistry } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -// tslint:disable-next-line: import-patterns import { commandsExtensionPoint } from 'vs/workbench/api/common/menusExtensionPoint'; import { Disposable } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; diff --git a/src/vs/workbench/services/log/browser/indexedDBLogProvider.ts b/src/vs/workbench/services/log/browser/indexedDBLogProvider.ts index 323f900c0a..89f70d611b 100644 --- a/src/vs/workbench/services/log/browser/indexedDBLogProvider.ts +++ b/src/vs/workbench/services/log/browser/indexedDBLogProvider.ts @@ -5,23 +5,21 @@ import { KeyValueLogProvider } from 'vs/workbench/services/log/common/keyValueLogProvider'; -export const INDEXEDDB_LOG_SCHEME = 'vscode-logs-indexedbd'; -export const INDEXEDDB_LOGS_DB = 'vscode-logs-db'; +export const INDEXEDDB_VSCODE_DB = 'vscode-web-db'; export const INDEXEDDB_LOGS_OBJECT_STORE = 'vscode-logs-store'; export class IndexedDBLogProvider extends KeyValueLogProvider { - private readonly database: Promise; + readonly database: Promise; - constructor( - ) { - super(INDEXEDDB_LOG_SCHEME); + constructor(scheme: string) { + super(scheme); this.database = this.openDatabase(1); } private openDatabase(version: number): Promise { return new Promise((c, e) => { - const request = window.indexedDB.open(INDEXEDDB_LOGS_DB, version); + const request = window.indexedDB.open(INDEXEDDB_VSCODE_DB, version); request.onerror = (err) => e(request.error); request.onsuccess = () => { const db = request.result; diff --git a/src/vs/workbench/services/log/common/inMemoryLogProvider.ts b/src/vs/workbench/services/log/common/inMemoryLogProvider.ts index 7138d7ec11..4e25f2f686 100644 --- a/src/vs/workbench/services/log/common/inMemoryLogProvider.ts +++ b/src/vs/workbench/services/log/common/inMemoryLogProvider.ts @@ -6,17 +6,10 @@ import { KeyValueLogProvider } from 'vs/workbench/services/log/common/keyValueLogProvider'; import { keys } from 'vs/base/common/map'; -export const INMEMORY_LOG_SCHEME = 'vscode-logs-inmemory'; - export class InMemoryLogProvider extends KeyValueLogProvider { private readonly logs: Map = new Map(); - constructor( - ) { - super(INMEMORY_LOG_SCHEME); - } - protected async getAllKeys(): Promise { return keys(this.logs); } diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 81bc747ebd..0a13080f8c 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -1028,6 +1028,67 @@ class SettingsContentBuilder { } export function createValidator(prop: IConfigurationPropertySchema): (value: any) => (string | null) { + // Only for array of string + if (prop.type === 'array' && prop.items && !isArray(prop.items) && prop.items.type === 'string') { + const propItems = prop.items; + if (propItems && !isArray(propItems) && propItems.type === 'string') { + const withQuotes = (s: string) => `'` + s + `'`; + + return value => { + if (!value) { + return null; + } + + let message = ''; + + const stringArrayValue = value as string[]; + + if (prop.minItems && stringArrayValue.length < prop.minItems) { + message += nls.localize('validations.stringArrayMinItem', 'Array must have at least {0} items', prop.minItems); + message += '\n'; + } + + if (prop.maxItems && stringArrayValue.length > prop.maxItems) { + message += nls.localize('validations.stringArrayMaxItem', 'Array must have less than {0} items', prop.maxItems); + message += '\n'; + } + + if (typeof propItems.pattern === 'string') { + const patternRegex = new RegExp(propItems.pattern); + stringArrayValue.forEach(v => { + if (!patternRegex.test(v)) { + message += + propItems.patternErrorMessage || + nls.localize( + 'validations.stringArrayItemPattern', + 'Value {0} must match regex {1}.', + withQuotes(v), + withQuotes(propItems.pattern!) + ); + } + }); + } + + const propItemsEnum = propItems.enum; + if (propItemsEnum) { + stringArrayValue.forEach(v => { + if (propItemsEnum.indexOf(v) === -1) { + message += nls.localize( + 'validations.stringArrayItemEnum', + 'Value {0} is not one of {1}', + withQuotes(v), + '[' + propItemsEnum.map(withQuotes).join(', ') + ']' + ); + message += '\n'; + } + }); + } + + return message; + }; + } + } + return value => { let exclusiveMax: number | undefined; let exclusiveMin: number | undefined; diff --git a/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts b/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts index ef335092af..624cfca1bd 100644 --- a/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts +++ b/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts @@ -250,4 +250,82 @@ suite('Preferences Model test', () => { withMessage.rejects(' ').withMessage('always error!'); withMessage.rejects('1').withMessage('always error!'); }); -}); \ No newline at end of file + + class ArrayTester { + private validator: (value: any) => string | null; + + constructor(private settings: IConfigurationPropertySchema) { + this.validator = createValidator(settings)!; + } + + public accepts(input: string[]) { + assert.equal(this.validator(input), '', `Expected ${JSON.stringify(this.settings)} to accept \`${JSON.stringify(input)}\`. Got ${this.validator(input)}.`); + } + + public rejects(input: any[]) { + assert.notEqual(this.validator(input), '', `Expected ${JSON.stringify(this.settings)} to reject \`${JSON.stringify(input)}\`.`); + return { + withMessage: + (message: string) => { + const actual = this.validator(input); + assert.ok(actual); + assert(actual!.indexOf(message) > -1, + `Expected error of ${JSON.stringify(this.settings)} on \`${input}\` to contain ${message}. Got ${this.validator(input)}.`); + } + }; + } + } + + test('simple array', () => { + { + const arr = new ArrayTester({ type: 'array', items: { type: 'string' } }); + arr.accepts([]); + arr.accepts(['foo']); + arr.accepts(['foo', 'bar']); + } + }); + + test('min-max items array', () => { + { + const arr = new ArrayTester({ type: 'array', items: { type: 'string' }, minItems: 1, maxItems: 2 }); + arr.rejects([]).withMessage('Array must have at least 1 items'); + arr.accepts(['a']); + arr.accepts(['a', 'a']); + arr.rejects(['a', 'a', 'a']).withMessage('Array must have less than 2 items'); + } + }); + + test('array of enums', () => { + { + const arr = new ArrayTester({ type: 'array', items: { type: 'string', enum: ['a', 'b'] } }); + arr.accepts(['a']); + arr.accepts(['a', 'b']); + + arr.rejects(['c']).withMessage(`Value 'c' is not one of`); + arr.rejects(['a', 'c']).withMessage(`Value 'c' is not one of`); + + arr.rejects(['c', 'd']).withMessage(`Value 'c' is not one of`); + arr.rejects(['c', 'd']).withMessage(`Value 'd' is not one of`); + } + }); + + test('min-max and enum', () => { + const arr = new ArrayTester({ type: 'array', items: { type: 'string', enum: ['a', 'b'] }, minItems: 1, maxItems: 2 }); + + arr.rejects(['a', 'b', 'c']).withMessage('Array must have less than 2 items'); + arr.rejects(['a', 'b', 'c']).withMessage(`Value 'c' is not one of`); + }); + + test('pattern', () => { + const arr = new ArrayTester({ type: 'array', items: { type: 'string', pattern: '^(hello)*$' } }); + + arr.accepts(['hello']); + arr.rejects(['a']).withMessage(`Value 'a' must match regex`); + }); + + test('pattern with error message', () => { + const arr = new ArrayTester({ type: 'array', items: { type: 'string', pattern: '^(hello)*$', patternErrorMessage: 'err: must be friendly' } }); + + arr.rejects(['a']).withMessage(`err: must be friendly`); + }); +}); diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index 5172a1a44f..060471a418 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -46,7 +46,7 @@ export class ProgressService extends Disposable implements IProgressService { super(); } - withProgress(options: IProgressOptions, task: (progress: IProgress) => Promise, onDidCancel?: () => void): Promise { + withProgress(options: IProgressOptions, task: (progress: IProgress) => Promise, onDidCancel?: (choice?: number) => void): Promise { const { location } = options; if (typeof location === 'string') { if (this.viewletService.getProgressIndicator(location)) { @@ -142,7 +142,7 @@ export class ProgressService extends Disposable implements IProgressService { } } - private withNotificationProgress

, R = unknown>(options: IProgressNotificationOptions, callback: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: () => void): P { + private withNotificationProgress

, R = unknown>(options: IProgressNotificationOptions, callback: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: (choice?: number) => void): P { const toDispose = new DisposableStore(); const createNotification = (message: string | undefined, increment?: number): INotificationHandle | undefined => { @@ -152,6 +152,29 @@ export class ProgressService extends Disposable implements IProgressService { const primaryActions = options.primaryActions ? Array.from(options.primaryActions) : []; const secondaryActions = options.secondaryActions ? Array.from(options.secondaryActions) : []; + + if (options.buttons) { + options.buttons.forEach((button, index) => { + const buttonAction = new class extends Action { + constructor() { + super(`progress.button.${button}`, button, undefined, true); + } + + run(): Promise { + if (typeof onDidCancel === 'function') { + onDidCancel(index); + } + + return Promise.resolve(undefined); + } + }; + + toDispose.add(buttonAction); + + primaryActions.push(buttonAction); + }); + } + if (options.cancellable) { const cancelAction = new class extends Action { constructor() { @@ -182,6 +205,10 @@ export class ProgressService extends Disposable implements IProgressService { updateProgress(handle, increment); Event.once(handle.onDidClose)(() => { + if (typeof onDidCancel === 'function') { + onDidCancel(); + } + toDispose.dispose(); }); @@ -317,7 +344,7 @@ export class ProgressService extends Disposable implements IProgressService { return promise; } - private withDialogProgress

, R = unknown>(options: IProgressOptions, task: (progress: IProgress) => P, onDidCancel?: () => void): P { + private withDialogProgress

, R = unknown>(options: IProgressOptions, task: (progress: IProgress) => P, onDidCancel?: (choice?: number) => void): P { const disposables = new DisposableStore(); const allowableCommands = [ 'workbench.action.quit', @@ -327,12 +354,17 @@ export class ProgressService extends Disposable implements IProgressService { let dialog: Dialog; const createDialog = (message: string) => { + + const buttons = options.buttons || []; + buttons.push(options.cancellable ? localize('cancel', "Cancel") : localize('dismiss', "Dismiss")); + dialog = new Dialog( this.layoutService.container, message, - [options.cancellable ? localize('cancel', "Cancel") : localize('dismiss', "Dismiss")], + buttons, { type: 'pending', + cancelId: buttons.length - 1, keyEventProcessor: (event: StandardKeyboardEvent) => { const resolved = this.keybindingService.softDispatch(event, this.layoutService.container); if (resolved && resolved.commandId) { @@ -347,9 +379,9 @@ export class ProgressService extends Disposable implements IProgressService { disposables.add(dialog); disposables.add(attachDialogStyler(dialog, this.themeService)); - dialog.show().then(() => { + dialog.show().then((dialogResult) => { if (typeof onDidCancel === 'function') { - onDidCancel(); + onDidCancel(dialogResult.button); } dispose(dialog); diff --git a/src/vs/workbench/services/url/browser/urlService.ts b/src/vs/workbench/services/url/browser/urlService.ts new file mode 100644 index 0000000000..05399205cb --- /dev/null +++ b/src/vs/workbench/services/url/browser/urlService.ts @@ -0,0 +1,177 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IURLService } from 'vs/platform/url/common/url'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { ServiceIdentifier, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { AbstractURLService } from 'vs/platform/url/common/urlService'; +import { Event, Emitter } from 'vs/base/common/event'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IRequestService } from 'vs/platform/request/common/request'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { streamToBuffer } from 'vs/base/common/buffer'; +import { ILogService } from 'vs/platform/log/common/log'; +import { generateUuid } from 'vs/base/common/uuid'; + +export interface IURLCallbackProvider { + + /** + * Indicates that a Uri has been opened outside of VSCode. The Uri + * will be forwarded to all installed Uri handlers in the system. + */ + readonly onCallback: Event; + + /** + * Creates a Uri that - if opened in a browser - must result in + * the `onCallback` to fire. + * + * The optional `Partial` must be properly restored for + * the Uri passed to the `onCallback` handler. + * + * For example: if a Uri is to be created with `scheme:"vscode"`, + * `authority:"foo"` and `path:"bar"` the `onCallback` should fire + * with a Uri `vscode://foo/bar`. + * + * If there are additional `query` values in the Uri, they should + * be added to the list of provided `query` arguments from the + * `Partial`. + */ + create(options?: Partial): URI; +} + +export class BrowserURLService extends AbstractURLService { + + _serviceBrand!: ServiceIdentifier; + + private provider: IURLCallbackProvider; + + constructor( + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IInstantiationService instantiationService: IInstantiationService + ) { + super(); + + this.provider = environmentService.options && environmentService.options.urlCallbackProvider ? environmentService.options.urlCallbackProvider : instantiationService.createInstance(SelfhostURLCallbackProvider); + + this.registerListeners(); + } + + private registerListeners(): void { + this._register(this.provider.onCallback(uri => this.open(uri))); + } + + create(options?: Partial): URI { + return this.provider.create(options); + } +} + +class SelfhostURLCallbackProvider extends Disposable implements IURLCallbackProvider { + + static FETCH_INTERVAL = 500; // fetch every 500ms + static FETCH_TIMEOUT = 5 * 60 * 1000; // ...but stop after 5min + + static QUERY_KEYS = { + REQUEST_ID: 'vscode-requestId', + SCHEME: 'vscode-scheme', + AUTHORITY: 'vscode-authority', + PATH: 'vscode-path', + QUERY: 'vscode-query', + FRAGMENT: 'vscode-fragment' + }; + + private readonly _onCallback: Emitter = this._register(new Emitter()); + readonly onCallback: Event = this._onCallback.event; + + constructor( + @IRequestService private readonly requestService: IRequestService, + @ILogService private readonly logService: ILogService + ) { + super(); + } + + create(options?: Partial): URI { + const queryValues: Map = new Map(); + + const requestId = generateUuid(); + queryValues.set(SelfhostURLCallbackProvider.QUERY_KEYS.REQUEST_ID, requestId); + + const { scheme, authority, path, query, fragment } = options ? options : { scheme: undefined, authority: undefined, path: undefined, query: undefined, fragment: undefined }; + + if (scheme) { + queryValues.set(SelfhostURLCallbackProvider.QUERY_KEYS.SCHEME, scheme); + } + + if (authority) { + queryValues.set(SelfhostURLCallbackProvider.QUERY_KEYS.AUTHORITY, authority); + } + + if (path) { + queryValues.set(SelfhostURLCallbackProvider.QUERY_KEYS.PATH, path); + } + + if (query) { + queryValues.set(SelfhostURLCallbackProvider.QUERY_KEYS.QUERY, query); + } + + if (fragment) { + queryValues.set(SelfhostURLCallbackProvider.QUERY_KEYS.FRAGMENT, fragment); + } + + // Start to poll on the callback being fired + this.periodicFetchCallback(requestId, Date.now()); + + return this.doCreateUri('/callback', queryValues); + } + + private async periodicFetchCallback(requestId: string, startTime: number): Promise { + + // Ask server for callback results + const queryValues: Map = new Map(); + queryValues.set(SelfhostURLCallbackProvider.QUERY_KEYS.REQUEST_ID, requestId); + + const result = await this.requestService.request({ + url: this.doCreateUri('/fetch-callback', queryValues).toString(true) + }, CancellationToken.None); + + // Check for callback results + const content = await streamToBuffer(result.stream); + if (content.byteLength > 0) { + try { + this._onCallback.fire(URI.revive(JSON.parse(content.toString()))); + } catch (error) { + this.logService.error(error); + } + + return; // done + } + + // Continue fetching unless we hit the timeout + if (Date.now() - startTime < SelfhostURLCallbackProvider.FETCH_TIMEOUT) { + setTimeout(() => this.periodicFetchCallback(requestId, startTime), SelfhostURLCallbackProvider.FETCH_INTERVAL); + } + } + + private doCreateUri(path: string, queryValues: Map): URI { + let query: string | undefined = undefined; + + if (queryValues) { + let index = 0; + queryValues.forEach((value, key) => { + if (!query) { + query = ''; + } + + const prefix = (index++ === 0) ? '' : '&'; + query += `${prefix}${key}=${encodeURIComponent(value)}`; + }); + } + + return URI.parse(window.location.href).with({ path, query }); + } +} + +registerSingleton(IURLService, BrowserURLService, true); diff --git a/src/vs/platform/url/electron-browser/urlService.ts b/src/vs/workbench/services/url/electron-browser/urlService.ts similarity index 89% rename from src/vs/platform/url/electron-browser/urlService.ts rename to src/vs/workbench/services/url/electron-browser/urlService.ts index 9648f39901..bbf124801d 100644 --- a/src/vs/platform/url/electron-browser/urlService.ts +++ b/src/vs/workbench/services/url/electron-browser/urlService.ts @@ -7,9 +7,10 @@ import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; import { URI } from 'vs/base/common/uri'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { URLServiceChannelClient, URLHandlerChannel } from 'vs/platform/url/node/urlIpc'; -import { URLService } from 'vs/platform/url/common/urlService'; +import { URLService } from 'vs/platform/url/node/urlService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import product from 'vs/platform/product/node/product'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class RelayURLService extends URLService implements IURLHandler { @@ -43,3 +44,5 @@ export class RelayURLService extends URLService implements IURLHandler { return super.open(uri); } } + +registerSingleton(IURLService, RelayURLService); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index cf370a8a7a..8963f04728 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -53,6 +53,7 @@ import 'vs/workbench/browser/parts/views/views'; //#region --- workbench services +import 'vs/workbench/services/extensions/common/inactiveExtensionUrlHandler'; import 'vs/workbench/services/bulkEdit/browser/bulkEditService'; import 'vs/workbench/services/keybinding/common/keybindingEditing'; import 'vs/workbench/services/decorations/browser/decorationsService'; diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 1def2a9181..c991548a05 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -31,7 +31,6 @@ import 'vs/workbench/electron-browser/desktop.main'; import 'vs/workbench/services/integrity/node/integrityService'; import 'vs/workbench/services/textMate/electron-browser/textMateService'; import 'vs/workbench/services/workspace/electron-browser/workspaceEditingService'; -import 'vs/workbench/services/extensions/common/inactiveExtensionUrlHandler'; import 'vs/workbench/services/search/node/searchService'; import 'vs/workbench/services/output/node/outputChannelModelService'; import 'vs/workbench/services/textfile/node/textFileService'; @@ -50,6 +49,7 @@ import 'vs/workbench/services/accessibility/node/accessibilityService'; import 'vs/workbench/services/remote/node/tunnelService'; import 'vs/workbench/services/backup/node/backupFileService'; import 'vs/workbench/services/credentials/node/credentialsService'; +import 'vs/workbench/services/url/electron-browser/urlService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -71,8 +71,6 @@ import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { WorkspacesService } from 'vs/platform/workspaces/electron-browser/workspacesService'; import { IMenubarService } from 'vs/platform/menubar/node/menubar'; import { MenubarService } from 'vs/platform/menubar/electron-browser/menubarService'; -import { IURLService } from 'vs/platform/url/common/url'; -import { RelayURLService } from 'vs/platform/url/electron-browser/urlService'; import { StaticExtensionsService, IStaticExtensionsService } from 'vs/workbench/services/extensions/common/staticExtensions'; registerSingleton(IClipboardService, ClipboardService, true); @@ -85,7 +83,6 @@ registerSingleton(IUpdateService, UpdateService); registerSingleton(IIssueService, IssueService); registerSingleton(IWorkspacesService, WorkspacesService); registerSingleton(IMenubarService, MenubarService); -registerSingleton(IURLService, RelayURLService); registerSingleton(IStaticExtensionsService, class extends StaticExtensionsService { constructor() { super([]); } }); //#endregion diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 94ca71746b..e2b88e544f 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -10,6 +10,7 @@ import { IFileSystemProvider } from 'vs/platform/files/common/files'; import { IWebSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory'; import { ICredentialsProvider } from 'vs/workbench/services/credentials/browser/credentialsService'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { IURLCallbackProvider } from 'vs/workbench/services/url/browser/urlService'; export interface IWorkbenchConstructionOptions { @@ -65,6 +66,11 @@ export interface IWorkbenchConstructionOptions { * Experimental: Add static extensions that cannot be uninstalled but only be disabled. */ staticExtensions?: { packageJSON: IExtensionManifest, extensionLocation: UriComponents }[]; + + /** + * Experimental: Support for URL callbacks. + */ + urlCallbackProvider?: IURLCallbackProvider; } /** diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 85a92e6565..4f77cbf01f 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -37,6 +37,7 @@ import 'vs/workbench/services/extensionManagement/common/extensionManagementServ import 'vs/workbench/services/telemetry/browser/telemetryService'; import 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; import 'vs/workbench/services/credentials/browser/credentialsService'; +import 'vs/workbench/services/url/browser/urlService'; import 'vs/workbench/browser/web.simpleservices'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; diff --git a/yarn.lock b/yarn.lock index 588f3fbeb9..a236ffbb94 100644 --- a/yarn.lock +++ b/yarn.lock @@ -220,6 +220,11 @@ dependencies: commander "*" +"@types/cookie@^0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.3.3.tgz#85bc74ba782fb7aa3a514d11767832b0e3bc6803" + integrity sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow== + "@types/d3@^3": version "3.5.42" resolved "https://registry.yarnpkg.com/@types/d3/-/d3-3.5.42.tgz#6a782b44bb7f5c48165cb166886b5d53cb84455f" @@ -10338,10 +10343,10 @@ xterm-addon-web-links@0.1.0-beta10: resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.1.0-beta10.tgz#610fa9773a2a5ccd41c1c83ba0e2dd2c9eb66a23" integrity sha512-xfpjy0V6bB4BR44qIgZQPoCMVakxb65gMscPkHpO//QxvUxKzabV3dxOsIbeZRFkUGsWTFlvz2OoaBLoNtv5gg== -xterm@3.15.0-beta99: - version "3.15.0-beta99" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta99.tgz#0010a7ea5d56cbb08a1e3a525b353c96a158e7a0" - integrity sha512-Vm0ZWToWwO4uk/28Kqvqt9L92h5EU2z4WR9I6xcQaPIBmkJPINIARU4LWQnvaOfgFhRbpwBMveTfh8/jM97lPg== +xterm@3.15.0-beta101: + version "3.15.0-beta101" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta101.tgz#38ffa0df5a3e9bdcb1818e74fe59b2f98b0fff69" + integrity sha512-HRa7+FDqQ8iWBTvb1Ni+uMGILnu6k9mF7JHMHRHfWxFoQlSoGYCyfdyXlJjk68YN8GsEQREmrII6cPLiQizdEQ== y18n@^3.2.1: version "3.2.1"