diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 8b200ee04ae..a3478121f28 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -475,6 +475,8 @@ const apiToken = process.env.TRANSIFEX_API_TOKEN; gulp.task(task.define( 'vscode-translations-push', task.series( + compileBuildTask, + compileExtensionsBuildTask, optimizeVSCodeTask, function () { const pathToMetadata = './out-vscode/nls.metadata.json'; @@ -494,6 +496,8 @@ gulp.task(task.define( gulp.task(task.define( 'vscode-translations-export', task.series( + compileBuildTask, + compileExtensionsBuildTask, optimizeVSCodeTask, function () { const pathToMetadata = './out-vscode/nls.metadata.json'; diff --git a/extensions/typescript-language-features/src/features/task.ts b/extensions/typescript-language-features/src/features/task.ts index ae530bcdffe..d5fa55ba87a 100644 --- a/extensions/typescript-language-features/src/features/task.ts +++ b/extensions/typescript-language-features/src/features/task.ts @@ -72,6 +72,12 @@ class TscTaskProvider implements vscode.TaskProvider { } public resolveTask(_task: vscode.Task): vscode.Task | undefined { + const definition = _task.definition; + const badTsconfig = '\\tsconfig.json'; + if ((definition.tsconfig.length > badTsconfig.length) && (definition.tsconfig.substring(definition.tsconfig.length - badTsconfig.length, definition.tsconfig.length) === badTsconfig)) { + // Warn that the task has the wrong slash type + vscode.window.showWarningMessage(localize('badTsConfig', "Typescript Task in tasks.json contains \"\\\\\". Typescript tasks must use \"/\"")); + } return undefined; } diff --git a/package.json b/package.json index f600630a339..66eae3982c9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.37.0", - "distro": "3c319811459f451676c3ccb148f96c49d342dbdc", + "distro": "40f61fd7e131c62473197b6505a3168a6bce1bc6", "author": { "name": "Microsoft Corporation" }, diff --git a/src/tsconfig.base.json b/src/tsconfig.base.json index 627491de597..cfd4b390a62 100644 --- a/src/tsconfig.base.json +++ b/src/tsconfig.base.json @@ -3,7 +3,6 @@ "module": "amd", "moduleResolution": "node", "noImplicitAny": true, - "suppressImplicitAnyIndexErrors": true, "target": "es5", "experimentalDecorators": true, "noImplicitReturns": true, diff --git a/src/vs/base/browser/htmlContentRenderer.ts b/src/vs/base/browser/htmlContentRenderer.ts index 68fe5d89e22..4506912e178 100644 --- a/src/vs/base/browser/htmlContentRenderer.ts +++ b/src/vs/base/browser/htmlContentRenderer.ts @@ -6,7 +6,7 @@ import * as DOM from 'vs/base/browser/dom'; import { defaultGenerator } from 'vs/base/common/idGenerator'; import { escape } from 'vs/base/common/strings'; -import { removeMarkdownEscapes, IMarkdownString } from 'vs/base/common/htmlContent'; +import { removeMarkdownEscapes, IMarkdownString, parseHrefAndDimensions } from 'vs/base/common/htmlContent'; import * as marked from 'vs/base/common/marked/marked'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -100,29 +100,11 @@ export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions const renderer = new marked.Renderer(); renderer.image = (href: string, title: string, text: string) => { - href = _href(href, true); let dimensions: string[] = []; - if (href) { - const splitted = href.split('|').map(s => s.trim()); - href = splitted[0]; - const parameters = splitted[1]; - if (parameters) { - const heightFromParams = /height=(\d+)/.exec(parameters); - const widthFromParams = /width=(\d+)/.exec(parameters); - const height = heightFromParams ? heightFromParams[1] : ''; - const width = widthFromParams ? widthFromParams[1] : ''; - const widthIsFinite = isFinite(parseInt(width)); - const heightIsFinite = isFinite(parseInt(height)); - if (widthIsFinite) { - dimensions.push(`width="${width}"`); - } - if (heightIsFinite) { - dimensions.push(`height="${height}"`); - } - } - } let attributes: string[] = []; if (href) { + ({ href, dimensions } = parseHrefAndDimensions(href)); + href = _href(href, true); attributes.push(`src="${href}"`); } if (text) { diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index bdbce4e9e30..13e4b9c34be 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -57,7 +57,7 @@ export interface IBreadcrumbsItemEvent { export class BreadcrumbsWidget { - private readonly _disposables = new Array(); + private readonly _disposables = new DisposableStore(); private readonly _domNode: HTMLDivElement; private readonly _styleElement: HTMLStyleElement; private readonly _scrollable: DomScrollableElement; @@ -94,26 +94,25 @@ export class BreadcrumbsWidget { useShadows: false, scrollYToX: true }); - this._disposables.push(this._scrollable); - this._disposables.push(dom.addStandardDisposableListener(this._domNode, 'click', e => this._onClick(e))); + this._disposables.add(this._scrollable); + this._disposables.add(dom.addStandardDisposableListener(this._domNode, 'click', e => this._onClick(e))); container.appendChild(this._scrollable.getDomNode()); this._styleElement = dom.createStyleSheet(this._domNode); - let focusTracker = dom.trackFocus(this._domNode); - this._disposables.push(focusTracker); - this._disposables.push(focusTracker.onDidBlur(_ => this._onDidChangeFocus.fire(false))); - this._disposables.push(focusTracker.onDidFocus(_ => this._onDidChangeFocus.fire(true))); + const focusTracker = dom.trackFocus(this._domNode); + this._disposables.add(focusTracker); + this._disposables.add(focusTracker.onDidBlur(_ => this._onDidChangeFocus.fire(false))); + this._disposables.add(focusTracker.onDidFocus(_ => this._onDidChangeFocus.fire(true))); } dispose(): void { - dispose(this._disposables); + this._disposables.dispose(); dispose(this._pendingLayout); this._onDidSelectItem.dispose(); this._onDidFocusItem.dispose(); this._onDidChangeFocus.dispose(); this._domNode.remove(); - this._disposables.length = 0; this._nodes.length = 0; this._freeNodes.length = 0; } diff --git a/src/vs/base/common/buffer.ts b/src/vs/base/common/buffer.ts index 74f51565089..a21a626e2ef 100644 --- a/src/vs/base/common/buffer.ts +++ b/src/vs/base/common/buffer.ts @@ -141,18 +141,13 @@ export interface VSBufferReadable { read(): VSBuffer | null; } -/** - * A buffer readable stream emits data to listeners. The stream - * will only start emitting when the first data listener has - * been added or the resume() method has been called. - */ -export interface VSBufferReadableStream { +export interface ReadableStream { /** * The 'data' event is emitted whenever the stream is * relinquishing ownership of a chunk of data to a consumer. */ - on(event: 'data', callback: (chunk: VSBuffer) => void): void; + on(event: 'data', callback: (chunk: T) => void): void; /** * Emitted when any error occurs. @@ -188,6 +183,17 @@ export function isVSBufferReadableStream(obj: any): obj is VSBufferReadableStrea return candidate && [candidate.on, candidate.pause, candidate.resume, candidate.destroy].every(fn => typeof fn === 'function'); } +/** + * A readable stream that sends data via VSBuffer. + */ +export interface VSBufferReadableStream extends ReadableStream { } + +export function isVSBufferReadableStream(obj: any): obj is VSBufferReadableStream { + const candidate: VSBufferReadableStream = obj; + + return candidate && [candidate.on, candidate.pause, candidate.resume, candidate.destroy].every(fn => typeof fn === 'function'); +} + /** * Helper to fully read a VSBuffer readable into a single buffer. */ @@ -245,6 +251,19 @@ export function bufferToStream(buffer: VSBuffer): VSBufferReadableStream { return stream; } +/** + * Helper to create a VSBufferStream from a Uint8Array stream. + */ +export function toVSBufferReadableStream(stream: ReadableStream): VSBufferReadableStream { + const vsbufferStream = writeableBufferStream(); + + stream.on('data', data => vsbufferStream.write(VSBuffer.wrap(data))); + stream.on('end', () => vsbufferStream.end()); + stream.on('error', error => vsbufferStream.error(error)); + + return vsbufferStream; +} + /** * Helper to create a VSBufferStream that can be pushed * buffers to. Will only start to emit data when a listener diff --git a/src/vs/base/common/htmlContent.ts b/src/vs/base/common/htmlContent.ts index 201e378e11e..ac7c8ed3a91 100644 --- a/src/vs/base/common/htmlContent.ts +++ b/src/vs/base/common/htmlContent.ts @@ -92,3 +92,25 @@ export function removeMarkdownEscapes(text: string): string { } return text.replace(/\\([\\`*_{}[\]()#+\-.!])/g, '$1'); } + +export function parseHrefAndDimensions(href: string): { href: string, dimensions: string[] } { + const dimensions: string[] = []; + const splitted = href.split('|').map(s => s.trim()); + href = splitted[0]; + const parameters = splitted[1]; + if (parameters) { + const heightFromParams = /height=(\d+)/.exec(parameters); + const widthFromParams = /width=(\d+)/.exec(parameters); + const height = heightFromParams ? heightFromParams[1] : ''; + const width = widthFromParams ? widthFromParams[1] : ''; + const widthIsFinite = isFinite(parseInt(width)); + const heightIsFinite = isFinite(parseInt(height)); + if (widthIsFinite) { + dimensions.push(`width="${width}"`); + } + if (heightIsFinite) { + dimensions.push(`height="${height}"`); + } + } + return { href, dimensions }; +} diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index a0f2b7e79ca..66143bb94b7 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -81,7 +81,13 @@ export function combinedDisposable(...disposables: IDisposable[]): IDisposable { } export function toDisposable(fn: () => void): IDisposable { - return trackDisposable({ dispose: fn }); + const self = trackDisposable({ + dispose: () => { + markTracked(self); + fn(); + } + }); + return self; } export class DisposableStore implements IDisposable { diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index 093523af512..56d365091e9 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -170,9 +170,9 @@ export function getAllPropertyNames(obj: object): string[] { } export function getAllMethodNames(obj: object): string[] { - let methods: string[] = []; + const methods: string[] = []; for (const prop of getAllPropertyNames(obj)) { - if (typeof obj[prop] === 'function') { + if (typeof (obj as any)[prop] === 'function') { methods.push(prop); } } diff --git a/src/vs/base/common/worker/simpleWorker.ts b/src/vs/base/common/worker/simpleWorker.ts index 3772c910f41..ca6e174ce33 100644 --- a/src/vs/base/common/worker/simpleWorker.ts +++ b/src/vs/base/common/worker/simpleWorker.ts @@ -212,12 +212,12 @@ export class SimpleWorkerClient extends Disp this._worker.postMessage(msg); }, handleMessage: (method: string, args: any[]): Promise => { - if (typeof host[method] !== 'function') { + if (typeof (host as any)[method] !== 'function') { return Promise.reject(new Error('Missing method ' + method + ' on main thread host.')); } try { - return Promise.resolve(host[method].apply(host, args)); + return Promise.resolve((host as any)[method].apply(host, args)); } catch (e) { return Promise.reject(e); } diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts index 14dca3d5f49..1af9f028c70 100644 --- a/src/vs/base/node/pfs.ts +++ b/src/vs/base/node/pfs.ts @@ -138,6 +138,20 @@ export async function readdir(path: string): Promise { return handleDirectoryChildren(await promisify(fs.readdir)(path)); } +export async function readdirWithFileTypes(path: string): Promise { + const children = await promisify(fs.readdir)(path, { withFileTypes: true }); + + // Mac: uses NFD unicode form on disk, but we want NFC + // See also https://github.com/nodejs/node/issues/2165 + if (platform.isMacintosh) { + for (const child of children) { + child.name = normalizeNFC(child.name); + } + } + + return children; +} + export function readdirSync(path: string): string[] { return handleDirectoryChildren(fs.readdirSync(path)); } diff --git a/src/vs/base/test/node/pfs/pfs.test.ts b/src/vs/base/test/node/pfs/pfs.test.ts index 4f09ad2b56d..060913bbe72 100644 --- a/src/vs/base/test/node/pfs/pfs.test.ts +++ b/src/vs/base/test/node/pfs/pfs.test.ts @@ -16,6 +16,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { isWindows, isLinux } from 'vs/base/common/platform'; import { canNormalize } from 'vs/base/common/normalization'; import { VSBuffer } from 'vs/base/common/buffer'; +import { join } from 'path'; const chunkSize = 64 * 1024; const readError = 'Error while reading'; @@ -386,6 +387,31 @@ suite('PFS', () => { } }); + test('readdirWithFileTypes', async () => { + if (canNormalize && typeof process.versions['electron'] !== 'undefined' /* needs electron */) { + const id = uuid.generateUuid(); + const parentDir = path.join(os.tmpdir(), 'vsctests', id); + const testDir = join(parentDir, 'pfs', id); + + const newDir = path.join(testDir, 'öäü'); + await pfs.mkdirp(newDir, 493); + + await pfs.writeFile(join(testDir, 'somefile.txt'), 'contents'); + + assert.ok(fs.existsSync(newDir)); + + const children = await pfs.readdirWithFileTypes(testDir); + + assert.equal(children.some(n => n.name === 'öäü'), true); // Mac always converts to NFD, so + assert.equal(children.some(n => n.isDirectory()), true); + + assert.equal(children.some(n => n.name === 'somefile.txt'), true); + assert.equal(children.some(n => n.isFile()), true); + + await pfs.rimraf(parentDir); + } + }); + test('writeFile (string)', async () => { const smallData = 'Hello World'; const bigData = (new Array(100 * 1024)).join('Large String\n'); diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index decce9a2e02..a6bb5723f58 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -336,7 +336,7 @@ export class IssueReporter extends Disposable { this.render(); }); - ['includeSystemInfo', 'includeProcessInfo', 'includeWorkspaceInfo', 'includeExtensions', 'includeSearchedExtensions', 'includeSettingsSearchDetails'].forEach(elementId => { + (['includeSystemInfo', 'includeProcessInfo', 'includeWorkspaceInfo', 'includeExtensions', 'includeSearchedExtensions', 'includeSettingsSearchDetails'] as const).forEach(elementId => { this.addEventListener(elementId, 'click', (event: Event) => { event.stopPropagation(); this.issueReporterModel.update({ [elementId]: !this.issueReporterModel.getData()[elementId] }); diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index a1671f40d44..e6cb0ebb5e5 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -584,9 +584,10 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Config (combination of process.argv and window configuration) const environment = parseArgs(process.argv); const config = objects.assign(environment, windowConfiguration); - for (let key in config) { - if (config[key] === undefined || config[key] === null || config[key] === '' || config[key] === false) { - delete config[key]; // only send over properties that have a true value + for (const key in config) { + const configValue = (config as any)[key]; + if (configValue === undefined || configValue === null || configValue === '' || configValue === false) { + delete (config as any)[key]; // only send over properties that have a true value } } diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 211c04bf478..8184682f4ae 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -767,6 +767,7 @@ export class Minimap extends ViewPart { // Cache line offset data so that it is only read once per line let lineIndexToXOffset = lineOffsetMap.get(lineNumber); + const isFirstDecorationForLine = !lineIndexToXOffset; if (!lineIndexToXOffset) { const lineData = this._context.model.getLineContent(lineNumber); lineIndexToXOffset = [0]; @@ -796,12 +797,22 @@ export class Minimap extends ViewPart { this.renderDecoration(canvasContext, decoration.options.minimap, x, y, width, height); } + if (isFirstDecorationForLine) { + this.renderLineHighlight(canvasContext, decoration.options.minimap, y, height); + } + + } + + private renderLineHighlight(canvasContext: CanvasRenderingContext2D, minimapOptions: ModelDecorationMinimapOptions, y: number, height: number): void { + const decorationColor = minimapOptions.getColor(this._context.theme); + canvasContext.fillStyle = decorationColor && decorationColor.transparent(0.5).toString() || ''; + canvasContext.fillRect(0, y, canvasContext.canvas.width, height); } private renderDecoration(canvasContext: CanvasRenderingContext2D, minimapOptions: ModelDecorationMinimapOptions, x: number, y: number, width: number, height: number) { const decorationColor = minimapOptions.getColor(this._context.theme); - canvasContext.fillStyle = decorationColor; + canvasContext.fillStyle = decorationColor && decorationColor.toString() || ''; canvasContext.fillRect(x, y, width, height); } diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 6edd0f4ecfb..c8b89e889cf 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -33,6 +33,7 @@ import { ITheme, ThemeColor } from 'vs/platform/theme/common/themeService'; import { withUndefinedAsNull } from 'vs/base/common/types'; import { VSBufferReadableStream, VSBuffer } from 'vs/base/common/buffer'; import { TokensStore, MultilineTokens } from 'vs/editor/common/model/tokensStore'; +import { Color } from 'vs/base/common/color'; function createTextBufferBuilder() { return new PieceTreeTextBufferBuilder(); @@ -2635,12 +2636,22 @@ function cleanClassName(className: string): string { class DecorationOptions implements model.IDecorationOptions { readonly color: string | ThemeColor; readonly darkColor: string | ThemeColor; - private _resolvedColor: string | null; constructor(options: model.IDecorationOptions) { this.color = options.color || strings.empty; this.darkColor = options.darkColor || strings.empty; + + } +} + +export class ModelDecorationOverviewRulerOptions extends DecorationOptions { + readonly position: model.OverviewRulerLane; + private _resolvedColor: string | null; + + constructor(options: model.IModelDecorationOverviewRulerOptions) { + super(options); this._resolvedColor = null; + this.position = (typeof options.position === 'number' ? options.position : model.OverviewRulerLane.Center); } public getColor(theme: ITheme): string { @@ -2670,22 +2681,34 @@ class DecorationOptions implements model.IDecorationOptions { } } -export class ModelDecorationOverviewRulerOptions extends DecorationOptions { - readonly position: model.OverviewRulerLane; - - constructor(options: model.IModelDecorationOverviewRulerOptions) { - super(options); - this.position = (typeof options.position === 'number' ? options.position : model.OverviewRulerLane.Center); - } -} - export class ModelDecorationMinimapOptions extends DecorationOptions { readonly position: model.MinimapPosition; + private _resolvedColor: Color | undefined; + constructor(options: model.IModelDecorationMinimapOptions) { super(options); this.position = options.position; } + + public getColor(theme: ITheme): Color | undefined { + if (!this._resolvedColor) { + if (theme.type !== 'light' && this.darkColor) { + this._resolvedColor = this._resolveColor(this.darkColor, theme); + } else { + this._resolvedColor = this._resolveColor(this.color, theme); + } + } + + return this._resolvedColor; + } + + private _resolveColor(color: string | ThemeColor, theme: ITheme): Color | undefined { + if (typeof color === 'string') { + return Color.fromHex(color); + } + return theme.getColor(color.id); + } } export class ModelDecorationOptions implements model.IModelDecorationOptions { diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 0616ed0e7a6..f9caed90bae 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -17,8 +17,8 @@ import { TokenizationResult, TokenizationResult2 } from 'vs/editor/common/core/t import * as model from 'vs/editor/common/model'; import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureRegistry'; import { TokenizationRegistryImpl } from 'vs/editor/common/modes/tokenizationRegistry'; -import { IMarkerData } from 'vs/platform/markers/common/markers'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { IMarkerData } from 'vs/platform/markers/common/markers'; /** * Open ended enum at runtime @@ -1241,19 +1241,7 @@ export interface CommentThreadTemplate { export interface CommentInfo { extensionId?: string; threads: CommentThread[]; - commentingRanges?: (IRange[] | CommentingRanges); - reply?: Command; - draftMode?: DraftMode; - template?: CommentThreadTemplate; -} - -/** - * @internal - */ -export enum DraftMode { - NotSupported, - InDraft, - NotInDraft + commentingRanges: CommentingRanges; } /** @@ -1293,7 +1281,7 @@ export interface CommentInput { /** * @internal */ -export interface CommentThread2 { +export interface CommentThread { commentThreadHandle: number; controllerHandle: number; extensionId?: string; @@ -1307,11 +1295,6 @@ export interface CommentThread2 { collapsibleState?: CommentThreadCollapsibleState; input?: CommentInput; onDidChangeInput: Event; - acceptInputCommand?: Command; - additionalCommands?: Command[]; - deleteCommand?: Command; - onDidChangeAcceptInputCommand: Event; - onDidChangeAdditionalCommands: Event; onDidChangeRange: Event; onDidChangeLabel: Event; onDidChangeCollasibleState: Event; @@ -1325,30 +1308,6 @@ export interface CommentThread2 { export interface CommentingRanges { readonly resource: URI; ranges: IRange[]; - newCommentThreadCallback?: (uri: UriComponents, range: IRange) => Promise; -} - -/** - * @internal - */ -export interface CommentThread { - extensionId?: string; - threadId: string | null; - resource: string | null; - range: IRange; - comments: Comment[] | undefined; - collapsibleState?: CommentThreadCollapsibleState; - reply?: Command; - isDisposed?: boolean; - contextValue?: string; -} - -/** - * @internal - */ -export interface NewCommentAction { - ranges: IRange[]; - actions: Command[]; } /** @@ -1380,12 +1339,6 @@ export interface Comment { readonly userName: string; readonly userIconPath?: string; readonly contextValue?: string; - readonly canEdit?: boolean; - readonly canDelete?: boolean; - readonly selectCommand?: Command; - readonly editCommand?: Command; - readonly deleteCommand?: Command; - readonly isDraft?: boolean; readonly commentReactions?: CommentReaction[]; readonly label?: string; readonly mode?: CommentMode; @@ -1398,54 +1351,17 @@ export interface CommentThreadChangedEvent { /** * Added comment threads. */ - readonly added: (CommentThread | CommentThread2)[]; + readonly added: CommentThread[]; /** * Removed comment threads. */ - readonly removed: (CommentThread | CommentThread2)[]; + readonly removed: CommentThread[]; /** * Changed comment threads. */ - readonly changed: (CommentThread | CommentThread2)[]; - - /** - * changed draft mode. - */ - readonly draftMode?: DraftMode; -} - -/** - * @internal - */ -export interface DocumentCommentProvider { - provideDocumentComments(resource: URI, token: CancellationToken): Promise; - createNewCommentThread(resource: URI, range: Range, text: string, token: CancellationToken): Promise; - replyToCommentThread(resource: URI, range: Range, thread: CommentThread, text: string, token: CancellationToken): Promise; - editComment(resource: URI, comment: Comment, text: string, token: CancellationToken): Promise; - deleteComment(resource: URI, comment: Comment, token: CancellationToken): Promise; - startDraft?(resource: URI, token: CancellationToken): Promise; - deleteDraft?(resource: URI, token: CancellationToken): Promise; - finishDraft?(resource: URI, token: CancellationToken): Promise; - - startDraftLabel?: string; - deleteDraftLabel?: string; - finishDraftLabel?: string; - - addReaction?(resource: URI, comment: Comment, reaction: CommentReaction, token: CancellationToken): Promise; - deleteReaction?(resource: URI, comment: Comment, reaction: CommentReaction, token: CancellationToken): Promise; - reactionGroup?: CommentReaction[]; - - onDidChangeCommentThreads?(): Event; -} - -/** - * @internal - */ -export interface WorkspaceCommentProvider { - provideWorkspaceComments(token: CancellationToken): Promise; - onDidChangeCommentThreads(): Event; + readonly changed: CommentThread[]; } /** diff --git a/src/vs/editor/common/services/webWorker.ts b/src/vs/editor/common/services/webWorker.ts index 944be578f8e..0ae55dffaa0 100644 --- a/src/vs/editor/common/services/webWorker.ts +++ b/src/vs/editor/common/services/webWorker.ts @@ -52,13 +52,13 @@ export interface IWebWorkerOptions { /** * An object that can be used by the web worker to make calls back to the main thread. */ - host?: object; + host?: any; } class MonacoWebWorkerImpl extends EditorWorkerClient implements MonacoWebWorker { private readonly _foreignModuleId: string; - private readonly _foreignModuleHost: object | null; + private readonly _foreignModuleHost: { [method: string]: Function } | null; private _foreignModuleCreateData: any | null; private _foreignProxy: Promise | null; diff --git a/src/vs/editor/contrib/find/findDecorations.ts b/src/vs/editor/contrib/find/findDecorations.ts index 49a5527fd55..ee93bccc3cf 100644 --- a/src/vs/editor/contrib/find/findDecorations.ts +++ b/src/vs/editor/contrib/find/findDecorations.ts @@ -9,7 +9,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { FindMatch, IModelDecorationsChangeAccessor, IModelDeltaDecoration, OverviewRulerLane, TrackedRangeStickiness, MinimapPosition } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { overviewRulerFindMatchForeground } from 'vs/platform/theme/common/colorRegistry'; +import { overviewRulerFindMatchForeground, minimapFindMatch } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; export class FindDecorations implements IDisposable { @@ -271,7 +271,7 @@ export class FindDecorations implements IDisposable { position: OverviewRulerLane.Center }, minimap: { - color: themeColorFromId(overviewRulerFindMatchForeground), + color: themeColorFromId(minimapFindMatch), position: MinimapPosition.Inline } }); @@ -285,7 +285,7 @@ export class FindDecorations implements IDisposable { position: OverviewRulerLane.Center }, minimap: { - color: themeColorFromId(overviewRulerFindMatchForeground), + color: themeColorFromId(minimapFindMatch), position: MinimapPosition.Inline } }); diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 356cc653aff..80c7a0661c9 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -335,7 +335,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { let isEmptyHoverContent = true; let containColorPicker = false; - let markdownDisposeables: IDisposable[] = []; + const markdownDisposeables = new DisposableStore(); const markerMessages: MarkerHover[] = []; messages.forEach((msg) => { if (!msg.range) { @@ -426,7 +426,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this.updateContents(fragment); this._colorPicker.layout(); - this.renderDisposable.value = combinedDisposable(colorListener, colorChangeListener, widget, ...markdownDisposeables); + this.renderDisposable.value = combinedDisposable(colorListener, colorChangeListener, widget, markdownDisposeables); }); } else { if (msg instanceof MarkerHover) { @@ -438,15 +438,14 @@ export class ModesContentHoverWidget extends ContentHoverWidget { .forEach(contents => { const markdownHoverElement = $('div.hover-row.markdown-hover'); const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents')); - const renderer = new MarkdownRenderer(this._editor, this._modeService, this._openerService); - markdownDisposeables.push(renderer.onDidRenderCodeBlock(() => { + const renderer = markdownDisposeables.add(new MarkdownRenderer(this._editor, this._modeService, this._openerService)); + markdownDisposeables.add(renderer.onDidRenderCodeBlock(() => { hoverContentsElement.className = 'hover-contents code-hover-contents'; this.onContentsChange(); })); - const renderedContents = renderer.render(contents); + const renderedContents = markdownDisposeables.add(renderer.render(contents)); hoverContentsElement.appendChild(renderedContents.element); fragment.appendChild(markdownHoverElement); - markdownDisposeables.push(renderedContents); isEmptyHoverContent = false; }); } diff --git a/src/vs/editor/contrib/hover/modesGlyphHover.ts b/src/vs/editor/contrib/hover/modesGlyphHover.ts index 2d83df2cea3..b2b78438a79 100644 --- a/src/vs/editor/contrib/hover/modesGlyphHover.ts +++ b/src/vs/editor/contrib/hover/modesGlyphHover.ts @@ -5,7 +5,7 @@ import { $ } from 'vs/base/browser/dom'; import { IMarkdownString, isEmptyMarkdownString } from 'vs/base/common/htmlContent'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { HoverOperation, HoverStartMode, IHoverComputer } from 'vs/editor/contrib/hover/hoverOperation'; import { GlyphHoverWidget } from 'vs/editor/contrib/hover/hoverWidgets'; @@ -91,7 +91,7 @@ export class ModesGlyphHoverWidget extends GlyphHoverWidget { private readonly _markdownRenderer: MarkdownRenderer; private readonly _computer: MarginComputer; private readonly _hoverOperation: HoverOperation; - private _renderDisposeables: IDisposable[]; + private readonly _renderDisposeables = this._register(new DisposableStore()); constructor( editor: ICodeEditor, @@ -102,7 +102,7 @@ export class ModesGlyphHoverWidget extends GlyphHoverWidget { this._lastLineNumber = -1; - this._markdownRenderer = new MarkdownRenderer(this._editor, modeService, openerService); + this._markdownRenderer = this._register(new MarkdownRenderer(this._editor, modeService, openerService)); this._computer = new MarginComputer(this._editor); this._hoverOperation = new HoverOperation( @@ -116,7 +116,6 @@ export class ModesGlyphHoverWidget extends GlyphHoverWidget { } public dispose(): void { - this._renderDisposeables = dispose(this._renderDisposeables); this._hoverOperation.cancel(); super.dispose(); } @@ -163,16 +162,15 @@ export class ModesGlyphHoverWidget extends GlyphHoverWidget { } private _renderMessages(lineNumber: number, messages: IHoverMessage[]): void { - dispose(this._renderDisposeables); - this._renderDisposeables = []; + this._renderDisposeables.clear(); const fragment = document.createDocumentFragment(); - messages.forEach((msg) => { + for (const msg of messages) { const renderedContents = this._markdownRenderer.render(msg.value); - this._renderDisposeables.push(renderedContents); + this._renderDisposeables.add(renderedContents); fragment.appendChild($('div.hover-row', undefined, renderedContents.element)); - }); + } this.updateContents(fragment); this.showAt(lineNumber); diff --git a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts index abab5967188..3db02e45be4 100644 --- a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts @@ -274,7 +274,7 @@ suite('Snippet Variables Resolver', function () { let resolver: VariableResolver; const clipboardService = new class implements IClipboardService { _serviceBrand: any; - readText(): string { return readTextResult; } + async readText(): Promise { return readTextResult; } readTextSync(): any { return readTextResult; } _throw = () => { throw new Error(); }; writeText = this._throw; diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index fd6c81d0fac..9e02b37f673 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -492,7 +492,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate(obj: any): obj is Thenable { - if (typeof obj.then === 'function') { - return true; - } - return false; + return obj && typeof obj.then === 'function'; } /** diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 0cedc1d3428..945d10d7e14 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -989,7 +989,7 @@ declare namespace monaco.editor { /** * An object that can be used by the web worker to make calls back to the main thread. */ - host?: object; + host?: any; } /** diff --git a/src/vs/platform/clipboard/browser/clipboardService.ts b/src/vs/platform/clipboard/browser/clipboardService.ts new file mode 100644 index 00000000000..9502c4bdc97 --- /dev/null +++ b/src/vs/platform/clipboard/browser/clipboardService.ts @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { URI } from 'vs/base/common/uri'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; + +export class BrowserClipboardService implements IClipboardService { + + _serviceBrand: ServiceIdentifier; + + private _internalResourcesClipboard: URI[] | undefined; + + async writeText(text: string, type?: string): Promise { + return navigator.clipboard.writeText(text); + } + + async readText(type?: string): Promise { + return navigator.clipboard.readText(); + } + + readTextSync(): string | undefined { + return undefined; + } + + readFindText(): string { + // @ts-ignore + return undefined; + } + + writeFindText(text: string): void { } + + writeResources(resources: URI[]): void { + this._internalResourcesClipboard = resources; + } + + readResources(): URI[] { + return this._internalResourcesClipboard || []; + } + + hasResources(): boolean { + return this._internalResourcesClipboard !== undefined && this._internalResourcesClipboard.length > 0; + } +} + +registerSingleton(IClipboardService, BrowserClipboardService, true); \ No newline at end of file diff --git a/src/vs/platform/clipboard/common/clipboardService.ts b/src/vs/platform/clipboard/common/clipboardService.ts index 75ee8724b01..84018111136 100644 --- a/src/vs/platform/clipboard/common/clipboardService.ts +++ b/src/vs/platform/clipboard/common/clipboardService.ts @@ -15,12 +15,12 @@ export interface IClipboardService { /** * Writes text to the system clipboard. */ - writeText(text: string, type?: string): void; + writeText(text: string, type?: string): Promise; /** * Reads the content of the clipboard in plain text */ - readText(type?: string): string; + readText(type?: string): Promise; readTextSync(): string | undefined; diff --git a/src/vs/platform/clipboard/electron-browser/clipboardService.ts b/src/vs/platform/clipboard/electron-browser/clipboardService.ts index 6d000b9da04..4150430af7b 100644 --- a/src/vs/platform/clipboard/electron-browser/clipboardService.ts +++ b/src/vs/platform/clipboard/electron-browser/clipboardService.ts @@ -14,11 +14,11 @@ export class ClipboardService implements IClipboardService { _serviceBrand: any; - writeText(text: string, type?: 'selection' | 'clipboard'): void { + async writeText(text: string, type?: 'selection' | 'clipboard'): Promise { clipboard.writeText(text, type); } - readText(type?: 'selection' | 'clipboard'): string { + async readText(type?: 'selection' | 'clipboard'): Promise { return clipboard.readText(type); } diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index 38063562380..67dd28f9bbb 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -264,7 +264,7 @@ export class FileService extends Disposable implements IFileService { //#region File Reading/Writing - async createFile(resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable = VSBuffer.fromString(''), options?: ICreateFileOptions): Promise { + async createFile(resource: URI, bufferOrReadableOrStream: VSBuffer | VSBufferReadable | VSBufferReadableStream = VSBuffer.fromString(''), options?: ICreateFileOptions): Promise { // validate overwrite const overwrite = !!(options && options.overwrite); @@ -273,7 +273,7 @@ export class FileService extends Disposable implements IFileService { } // do write into file (this will create it too) - const fileStat = await this.writeFile(resource, bufferOrReadable); + const fileStat = await this.writeFile(resource, bufferOrReadableOrStream); // events this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.CREATE, fileStat)); @@ -885,17 +885,23 @@ export class FileService extends Disposable implements IFileService { let posInFile = 0; stream.on('data', async chunk => { + + // pause stream to perform async write operation stream.pause(); try { await this.doWriteBuffer(provider, handle, chunk, chunk.byteLength, posInFile, 0); } catch (error) { - reject(error); + return reject(error); } posInFile += chunk.byteLength; - stream.resume(); + // resume stream now that we have successfully written + // run this on the next tick to prevent increasing the + // execution stack because resume() may call the event + // handler again before finishing. + setTimeout(() => stream.resume()); }); stream.on('error', error => reject(error)); diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 4b576f6de98..adc1bf41767 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -105,7 +105,7 @@ export interface IFileService { /** * Updates the content replacing its previous value. */ - writeFile(resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: IWriteFileOptions): Promise; + writeFile(resource: URI, bufferOrReadableOrStream: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: IWriteFileOptions): Promise; /** * Moves the file/folder to a new path identified by the resource. @@ -127,7 +127,7 @@ export interface IFileService { * * The optional parameter content can be used as value to fill into the new file. */ - createFile(resource: URI, bufferOrReadable?: VSBuffer | VSBufferReadable, options?: ICreateFileOptions): Promise; + createFile(resource: URI, bufferOrReadableOrStream?: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: ICreateFileOptions): Promise; /** * Creates a new folder with the given path. The returned promise diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts index 5b37992ad63..1a7534ffe08 100644 --- a/src/vs/platform/files/node/diskFileSystemProvider.ts +++ b/src/vs/platform/files/node/diskFileSystemProvider.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { mkdir, open, close, read, write, fdatasync } from 'fs'; +import { mkdir, open, close, read, write, fdatasync, Dirent, Stats } from 'fs'; import { promisify } from 'util'; import { IDisposable, Disposable, toDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; import { IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, FileSystemProviderErrorCode, createFileSystemProviderError, FileSystemProviderError } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { isLinux, isWindows } from 'vs/base/common/platform'; -import { statLink, readdir, unlink, move, copy, readFile, truncate, rimraf, RimRafMode, exists } from 'vs/base/node/pfs'; +import { statLink, unlink, move, copy, readFile, truncate, rimraf, RimRafMode, exists, readdirWithFileTypes } from 'vs/base/node/pfs'; import { normalize, basename, dirname } from 'vs/base/common/path'; import { joinPath } from 'vs/base/common/resources'; import { isEqual } from 'vs/base/common/extpath'; @@ -62,15 +62,8 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro try { const { stat, isSymbolicLink } = await statLink(this.toFilePath(resource)); // cannot use fs.stat() here to support links properly - let type: number; - if (isSymbolicLink) { - type = FileType.SymbolicLink | (stat.isDirectory() ? FileType.Directory : FileType.File); - } else { - type = stat.isFile() ? FileType.File : stat.isDirectory() ? FileType.Directory : FileType.Unknown; - } - return { - type, + type: this.toType(stat, isSymbolicLink), ctime: stat.ctime.getTime(), mtime: stat.mtime.getTime(), size: stat.size @@ -82,13 +75,19 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro async readdir(resource: URI): Promise<[string, FileType][]> { try { - const children = await readdir(this.toFilePath(resource)); + const children = await readdirWithFileTypes(this.toFilePath(resource)); const result: [string, FileType][] = []; await Promise.all(children.map(async child => { try { - const stat = await this.stat(joinPath(resource, child)); - result.push([child, stat.type]); + let type: FileType; + if (child.isSymbolicLink()) { + type = (await this.stat(joinPath(resource, child.name))).type; // always resolve target the link points to if any + } else { + type = this.toType(child); + } + + result.push([child.name, type]); } catch (error) { this.logService.trace(error); // ignore errors for individual entries that can arise from permission denied } @@ -100,6 +99,14 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro } } + private toType(entry: Stats | Dirent, isSymbolicLink = entry.isSymbolicLink()): FileType { + if (isSymbolicLink) { + return FileType.SymbolicLink | (entry.isDirectory() ? FileType.Directory : FileType.File); + } + + return entry.isFile() ? FileType.File : entry.isDirectory() ? FileType.Directory : FileType.Unknown; + } + //#endregion //#region File Reading/Writing diff --git a/src/vs/platform/files/test/node/diskFileService.test.ts b/src/vs/platform/files/test/node/diskFileService.test.ts index e89581a3107..33b3a30717c 100644 --- a/src/vs/platform/files/test/node/diskFileService.test.ts +++ b/src/vs/platform/files/test/node/diskFileService.test.ts @@ -14,13 +14,13 @@ import { join, basename, dirname, posix } from 'vs/base/common/path'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { copy, rimraf, symlink, RimRafMode, rimrafSync } from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; -import { existsSync, statSync, readdirSync, readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync } from 'fs'; +import { existsSync, statSync, readdirSync, readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync, createReadStream } from 'fs'; import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, FileSystemProviderCapabilities, FileChangeType, IFileChange, FileChangesEvent, FileOperationError, etag, IStat, IFileStatWithMetadata } from 'vs/platform/files/common/files'; import { NullLogService } from 'vs/platform/log/common/log'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; -import { VSBuffer, VSBufferReadable, bufferToStream } from 'vs/base/common/buffer'; +import { VSBuffer, VSBufferReadable, toVSBufferReadableStream, VSBufferReadableStream, bufferToReadable, bufferToStream } from 'vs/base/common/buffer'; function getByName(root: IFileStat, name: string): IFileStat | null { if (root.children === undefined) { @@ -1326,12 +1326,24 @@ suite('Disk File Service', () => { }); test('createFile', async () => { + assertCreateFile(contents => VSBuffer.fromString(contents)); + }); + + test('createFile (readable)', async () => { + assertCreateFile(contents => bufferToReadable(VSBuffer.fromString(contents))); + }); + + test('createFile (stream)', async () => { + assertCreateFile(contents => bufferToStream(VSBuffer.fromString(contents))); + }); + + async function assertCreateFile(converter: (content: string) => VSBuffer | VSBufferReadable | VSBufferReadableStream): Promise { let event: FileOperationEvent; disposables.add(service.onAfterOperation(e => event = e)); const contents = 'Hello World'; const resource = URI.file(join(testDir, 'test.txt')); - const fileStat = await service.createFile(resource, VSBuffer.fromString(contents)); + const fileStat = await service.createFile(resource, converter(contents)); assert.equal(fileStat.name, 'test.txt'); assert.equal(existsSync(fileStat.resource.fsPath), true); assert.equal(readFileSync(fileStat.resource.fsPath), contents); @@ -1340,7 +1352,7 @@ suite('Disk File Service', () => { assert.equal(event!.resource.fsPath, resource.fsPath); assert.equal(event!.operation, FileOperation.CREATE); assert.equal(event!.target!.resource.fsPath, resource.fsPath); - }); + } test('createFile (does not overwrite by default)', async () => { const contents = 'Hello World'; @@ -1545,46 +1557,52 @@ suite('Disk File Service', () => { assert.equal(readFileSync(resource.fsPath), newContent); }); + test('writeFile (stream) - buffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + + const source = URI.file(join(testDir, 'small.txt')); + const target = URI.file(join(testDir, 'small-copy.txt')); + + const fileStat = await service.writeFile(target, toVSBufferReadableStream(createReadStream(source.fsPath))); + assert.equal(fileStat.name, 'small-copy.txt'); + + assert.equal(readFileSync(source.fsPath).toString(), readFileSync(target.fsPath).toString()); + }); + test('writeFile (large file - stream) - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - const resource = URI.file(join(testDir, 'lorem.txt')); + const source = URI.file(join(testDir, 'lorem.txt')); + const target = URI.file(join(testDir, 'lorem-copy.txt')); - const content = readFileSync(resource.fsPath); - const newContent = content.toString() + content.toString(); + const fileStat = await service.writeFile(target, toVSBufferReadableStream(createReadStream(source.fsPath))); + assert.equal(fileStat.name, 'lorem-copy.txt'); - const fileStat = await service.writeFile(resource, bufferToStream(VSBuffer.fromString(newContent))); - assert.equal(fileStat.name, 'lorem.txt'); - - assert.equal(readFileSync(resource.fsPath), newContent); + assert.equal(readFileSync(source.fsPath).toString(), readFileSync(target.fsPath).toString()); }); test('writeFile (stream) - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); - const resource = URI.file(join(testDir, 'small.txt')); + const source = URI.file(join(testDir, 'small.txt')); + const target = URI.file(join(testDir, 'small-copy.txt')); - const content = readFileSync(resource.fsPath); - assert.equal(content, 'Small File'); + const fileStat = await service.writeFile(target, toVSBufferReadableStream(createReadStream(source.fsPath))); + assert.equal(fileStat.name, 'small-copy.txt'); - const newContent = 'Updates to the small file'; - await service.writeFile(resource, bufferToStream(VSBuffer.fromString(newContent))); - - assert.equal(readFileSync(resource.fsPath), newContent); + assert.equal(readFileSync(source.fsPath).toString(), readFileSync(target.fsPath).toString()); }); test('writeFile (large file - stream) - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); - const resource = URI.file(join(testDir, 'lorem.txt')); + const source = URI.file(join(testDir, 'lorem.txt')); + const target = URI.file(join(testDir, 'lorem-copy.txt')); - const content = readFileSync(resource.fsPath); - const newContent = content.toString() + content.toString(); + const fileStat = await service.writeFile(target, toVSBufferReadableStream(createReadStream(source.fsPath))); + assert.equal(fileStat.name, 'lorem-copy.txt'); - const fileStat = await service.writeFile(resource, bufferToStream(VSBuffer.fromString(newContent))); - assert.equal(fileStat.name, 'lorem.txt'); - - assert.equal(readFileSync(resource.fsPath), newContent); + assert.equal(readFileSync(source.fsPath).toString(), readFileSync(target.fsPath).toString()); }); test('writeFile (file is created including parents)', async () => { diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index 7b330d9a35d..06c2c8ba0f4 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -222,10 +222,10 @@ export class InstantiationService implements IInstantiationService { const idle = new IdleValue(() => this._createInstance(ctor, args, _trace)); return new Proxy(Object.create(null), { get(_target: T, prop: PropertyKey): any { - return idle.getValue()[prop]; + return (idle.getValue() as any)[prop]; }, set(_target: T, p: PropertyKey, value: any): boolean { - idle.getValue()[p] = value; + (idle.getValue() as any)[p] = value; return true; } }); diff --git a/src/vs/platform/storage/browser/storageService.ts b/src/vs/platform/storage/browser/storageService.ts index 74f8629b401..734397a9f16 100644 --- a/src/vs/platform/storage/browser/storageService.ts +++ b/src/vs/platform/storage/browser/storageService.ts @@ -70,13 +70,17 @@ export class BrowserStorageService extends Disposable implements IStorageService private async doInitialize(payload: IWorkspaceInitializationPayload): Promise { + // Ensure state folder exists + const stateRoot = joinPath(this.environmentService.userRoamingDataHome, 'state'); + await this.fileService.createFolder(stateRoot); + // Workspace Storage - this.workspaceStorageFile = joinPath(this.environmentService.userRoamingDataHome, 'state', `${payload.id}.json`); + this.workspaceStorageFile = joinPath(stateRoot, `${payload.id}.json`); this.workspaceStorage = new Storage(this._register(new FileStorageDatabase(this.workspaceStorageFile, this.fileService))); this._register(this.workspaceStorage.onDidChangeStorage(key => this._onDidChangeStorage.fire({ key, scope: StorageScope.WORKSPACE }))); // Global Storage - this.globalStorageFile = joinPath(this.environmentService.userRoamingDataHome, 'state', 'global.json'); + this.globalStorageFile = joinPath(stateRoot, 'global.json'); this.globalStorage = new Storage(this._register(new FileStorageDatabase(this.globalStorageFile, this.fileService))); this._register(this.globalStorage.onDidChangeStorage(key => this._onDidChangeStorage.fire({ key, scope: StorageScope.GLOBAL }))); diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index ef39b5aacca..11cb7598f9c 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -391,6 +391,8 @@ export const overviewRulerFindMatchForeground = registerColor('editorOverviewRul export const overviewRulerSelectionHighlightForeground = registerColor('editorOverviewRuler.selectionHighlightForeground', { dark: '#A0A0A0CC', light: '#A0A0A0CC', hc: '#A0A0A0CC' }, nls.localize('overviewRulerSelectionHighlightForeground', 'Overview ruler marker color for selection highlights. The color must not be opaque so as not to hide underlying decorations.'), true); +export const minimapFindMatch = registerColor('minimap.findMatchHighlight', { light: '#d18616', dark: '#d18616', hc: '#AB5A00' }, nls.localize('minimapFindMatchHighlight', 'Minimap marker color for find matches.'), true); + // ----- color functions diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 33a93a228cd..217471a484d 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -713,121 +713,6 @@ declare module 'vscode' { //#endregion - //#region Comments - /** - * Comments provider related APIs are still in early stages, they may be changed significantly during our API experiments. - */ - - interface CommentInfo { - /** - * All of the comment threads associated with the document. - */ - threads: CommentThread[]; - - /** - * The ranges of the document which support commenting. - */ - commentingRanges?: Range[]; - - /** - * If it's in draft mode or not - */ - inDraftMode?: boolean; - } - - /** - * A comment is displayed within the editor or the Comments Panel, depending on how it is provided. - */ - export interface Comment { - /** - * The display name of the user who created the comment - */ - readonly userName: string; - - /** - * The icon path for the user who created the comment - */ - readonly userIconPath?: Uri; - - /** - * The id of the comment - * - * @deprecated Use Id instead - */ - readonly commentId: string; - - /** - * @deprecated Use userIconPath instead. The avatar src of the user who created the comment - */ - gravatar?: string; - - /** - * Whether the current user has permission to edit the comment. - * - * This will be treated as false if the comment is provided by a `WorkspaceCommentProvider`, or - * if it is provided by a `DocumentCommentProvider` and no `editComment` method is given. - * - * DEPRECATED, use editCommand - */ - canEdit?: boolean; - - /** - * Whether the current user has permission to delete the comment. - * - * This will be treated as false if the comment is provided by a `WorkspaceCommentProvider`, or - * if it is provided by a `DocumentCommentProvider` and no `deleteComment` method is given. - * - * DEPRECATED, use deleteCommand - */ - canDelete?: boolean; - - /** - * @deprecated - * The command to be executed if the comment is selected in the Comments Panel - */ - command?: Command; - - /** - * Deprecated - */ - isDraft?: boolean; - - /** - * The command to be executed when users try to delete the comment - */ - deleteCommand?: Command; - - /** - * Proposed Comment Reaction - */ - commentReactions?: CommentReaction[]; - } - - /** - * Deprecated - */ - export interface CommentThreadChangedEvent { - /** - * Added comment threads. - */ - readonly added: ReadonlyArray; - - /** - * Removed comment threads. - */ - readonly removed: ReadonlyArray; - - /** - * Changed comment threads. - */ - readonly changed: ReadonlyArray; - - /** - * Changed draft mode - */ - readonly inDraftMode: boolean; - } - /** * Comment Reactions * Stay in proposed. @@ -836,69 +721,6 @@ declare module 'vscode' { readonly hasReacted?: boolean; } - /** - * DEPRECATED - */ - interface DocumentCommentProvider { - /** - * Provide the commenting ranges and comment threads for the given document. The comments are displayed within the editor. - */ - provideDocumentComments(document: TextDocument, token: CancellationToken): Promise; - - /** - * Called when a user adds a new comment thread in the document at the specified range, with body text. - */ - createNewCommentThread(document: TextDocument, range: Range, text: string, token: CancellationToken): Promise; - - /** - * Called when a user replies to a new comment thread in the document at the specified range, with body text. - */ - replyToCommentThread(document: TextDocument, range: Range, commentThread: CommentThread, text: string, token: CancellationToken): Promise; - - /** - * Called when a user edits the comment body to the be new text. - */ - editComment?(document: TextDocument, comment: Comment, text: string, token: CancellationToken): Promise; - - /** - * Called when a user deletes the comment. - */ - deleteComment?(document: TextDocument, comment: Comment, token: CancellationToken): Promise; - - startDraft?(document: TextDocument, token: CancellationToken): Promise; - deleteDraft?(document: TextDocument, token: CancellationToken): Promise; - finishDraft?(document: TextDocument, token: CancellationToken): Promise; - - startDraftLabel?: string; - deleteDraftLabel?: string; - finishDraftLabel?: string; - - addReaction?(document: TextDocument, comment: Comment, reaction: CommentReaction): Promise; - deleteReaction?(document: TextDocument, comment: Comment, reaction: CommentReaction): Promise; - reactionGroup?: CommentReaction[]; - - /** - * Notify of updates to comment threads. - */ - onDidChangeCommentThreads: Event; - } - - /** - * DEPRECATED - */ - interface WorkspaceCommentProvider { - /** - * Provide all comments for the workspace. Comments are shown within the comments panel. Selecting a comment - * from the panel runs the comment's command. - */ - provideWorkspaceComments(token: CancellationToken): Promise; - - /** - * Notify of updates to comment threads. - */ - onDidChangeCommentThreads: Event; - } - /** * Stay in proposed */ @@ -907,26 +729,6 @@ declare module 'vscode' { toggleReaction?(document: TextDocument, comment: Comment, reaction: CommentReaction): Promise; } - export interface CommentThread { - /** - * The uri of the document the thread has been created on. - */ - readonly resource: Uri; - - /** - * Optional additonal commands. - * - * `additionalCommands` are the secondary actions rendered on Comment Widget. - */ - additionalCommands?: Command[]; - - /** - * The command to be executed when users try to delete the comment thread. Currently, this is only called - * when the user collapses a comment thread that has no comments in it. - */ - deleteCommand?: Command; - } - export interface CommentController { /** @@ -936,42 +738,6 @@ declare module 'vscode' { reactionProvider?: CommentReactionProvider; } - namespace workspace { - /** - * DEPRECATED - * Use vscode.comment.createCommentController instead. - */ - export function registerDocumentCommentProvider(provider: DocumentCommentProvider): Disposable; - /** - * DEPRECATED - * Use vscode.comment.createCommentController instead and we don't differentiate document comments and workspace comments anymore. - */ - export function registerWorkspaceCommentProvider(provider: WorkspaceCommentProvider): Disposable; - } - - /** - * A collection of [comments](#Comment) representing a conversation at a particular range in a document. - */ - export interface CommentThread { - /** - * A unique identifier of the comment thread. - */ - readonly id: string; - - /** - * The uri of the document the thread has been created on. - */ - readonly uri: Uri; - - /** - * Optional accept input command - * - * `acceptInputCommand` is the default action rendered on Comment Widget, which is always placed rightmost. - * This command will be invoked when users the user accepts the value in the comment editor. - * This command will disabled when the comment editor is empty. - */ - acceptInputCommand?: Command; - } /** * A comment is displayed within the editor or the Comments Panel, depending on how it is provided. @@ -980,45 +746,7 @@ declare module 'vscode' { /** * The id of the comment */ - id: string; - - /** - * The command to be executed if the comment is selected in the Comments Panel - */ - selectCommand?: Command; - - /** - * The command to be executed when users try to save the edits to the comment - */ - editCommand?: Command; - } - - /** - * The comment input box in Comment Widget. - */ - export interface CommentInputBox { - /** - * Setter and getter for the contents of the comment input box - */ - value: string; - } - - /** - * Commenting range provider for a [comment controller](#CommentController). - */ - export interface CommentingRangeProvider { - /** - * Provide a list of ranges which allow new comment threads creation or null for a given document - */ - provideCommentingRanges(document: TextDocument, token: CancellationToken): ProviderResult; - } - - export interface EmptyCommentThreadFactory { - /** - * The method `createEmptyCommentThread` is called when users attempt to create new comment thread from the gutter or command palette. - * Extensions still need to call `createCommentThread` inside this call when appropriate. - */ - createEmptyCommentThread(document: TextDocument, range: Range): ProviderResult; + commentId: string; } /** @@ -1026,52 +754,10 @@ declare module 'vscode' { * provide users various ways to interact with comments. */ export interface CommentController { - - /** - * The active [comment input box](#CommentInputBox) or `undefined`. The active `inputBox` is the input box of - * the comment thread widget that currently has focus. It's `undefined` when the focus is not in any CommentInputBox. - */ - readonly inputBox?: CommentInputBox; - - /** - * Create a [comment thread](#CommentThread). The comment thread will be displayed in visible text editors (if the resource matches) - * and Comments Panel once created. - * - * @param id An `id` for the comment thread. - * @param resource The uri of the document the thread has been created on. - * @param range The range the comment thread is located within the document. - * @param comments The ordered comments of the thread. - */ - createCommentThread(id: string, resource: Uri, range: Range, comments: Comment[]): CommentThread; - - /** - * Optional new comment thread factory. - */ - emptyCommentThreadFactory?: EmptyCommentThreadFactory; - /** * Optional reaction provider */ reactionProvider?: CommentReactionProvider; - - /** - * Dispose this comment controller. - * - * Once disposed, all [comment threads](#CommentThread) created by this comment controller will also be removed from the editor - * and Comments Panel. - */ - dispose(): void; - } - - namespace comment { - /** - * Creates a new [comment controller](#CommentController) instance. - * - * @param id An `id` for the comment controller. - * @param label A human-readable string for the comment controller. - * @return An instance of [comment controller](#CommentController). - */ - export function createCommentController(id: string, label: string): CommentController; } //#endregion diff --git a/src/vs/workbench/api/browser/mainThreadClipboard.ts b/src/vs/workbench/api/browser/mainThreadClipboard.ts index 78df4a034b0..85c041c5b6e 100644 --- a/src/vs/workbench/api/browser/mainThreadClipboard.ts +++ b/src/vs/workbench/api/browser/mainThreadClipboard.ts @@ -20,11 +20,10 @@ export class MainThreadClipboard implements MainThreadClipboardShape { } $readText(): Promise { - return Promise.resolve(this._clipboardService.readText()); + return this._clipboardService.readText(); } $writeText(value: string): Promise { - this._clipboardService.writeText(value); - return Promise.resolve(); + return this._clipboardService.writeText(value); } } diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index 444ace69064..7bce8f98dee 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -3,85 +3,25 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; -import { ICodeEditor, isCodeEditor, isDiffEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; -import * as modes from 'vs/editor/common/modes'; -import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { keys } from 'vs/base/common/map'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ExtHostCommentsShape, ExtHostContext, IExtHostContext, MainContext, MainThreadCommentsShape, CommentProviderFeatures } from '../common/extHost.protocol'; - -import { ICommentService, ICommentInfo } from 'vs/workbench/contrib/comments/browser/commentService'; -import { COMMENTS_PANEL_ID, CommentsPanel, COMMENTS_PANEL_TITLE } from 'vs/workbench/contrib/comments/browser/commentsPanel'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { generateUuid } from 'vs/base/common/uuid'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ICommentsConfiguration } from 'vs/workbench/contrib/comments/browser/comments.contribution'; +import { IRange } from 'vs/editor/common/core/range'; +import * as modes from 'vs/editor/common/modes'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; -import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor } from 'vs/workbench/browser/panel'; -import { IRange, Range } from 'vs/editor/common/core/range'; -import { Emitter, Event } from 'vs/base/common/event'; -import { CancellationToken } from 'vs/base/common/cancellation'; - -export class MainThreadDocumentCommentProvider implements modes.DocumentCommentProvider { - private readonly _proxy: ExtHostCommentsShape; - private readonly _handle: number; - private readonly _features: CommentProviderFeatures; - get startDraftLabel(): string | undefined { return this._features.startDraftLabel; } - get deleteDraftLabel(): string | undefined { return this._features.deleteDraftLabel; } - get finishDraftLabel(): string | undefined { return this._features.finishDraftLabel; } - get reactionGroup(): modes.CommentReaction[] | undefined { return this._features.reactionGroup; } - - constructor(proxy: ExtHostCommentsShape, handle: number, features: CommentProviderFeatures) { - this._proxy = proxy; - this._handle = handle; - this._features = features; - } - - async provideDocumentComments(uri: URI, token: CancellationToken) { - return this._proxy.$provideDocumentComments(this._handle, uri); - } - - async createNewCommentThread(uri: URI, range: Range, text: string, token: CancellationToken) { - return this._proxy.$createNewCommentThread(this._handle, uri, range, text); - } - - async replyToCommentThread(uri: URI, range: Range, thread: modes.CommentThread, text: string, token: CancellationToken) { - return this._proxy.$replyToCommentThread(this._handle, uri, range, thread, text); - } - - async editComment(uri: URI, comment: modes.Comment, text: string, token: CancellationToken) { - return this._proxy.$editComment(this._handle, uri, comment, text); - } - - async deleteComment(uri: URI, comment: modes.Comment, token: CancellationToken) { - return this._proxy.$deleteComment(this._handle, uri, comment); - } - - async startDraft(uri: URI, token: CancellationToken): Promise { - return this._proxy.$startDraft(this._handle, uri); - } - async deleteDraft(uri: URI, token: CancellationToken): Promise { - return this._proxy.$deleteDraft(this._handle, uri); - } - async finishDraft(uri: URI, token: CancellationToken): Promise { - return this._proxy.$finishDraft(this._handle, uri); - } - async addReaction(uri: URI, comment: modes.Comment, reaction: modes.CommentReaction, token: CancellationToken): Promise { - return this._proxy.$addReaction(this._handle, uri, comment, reaction); - } - async deleteReaction(uri: URI, comment: modes.Comment, reaction: modes.CommentReaction, token: CancellationToken): Promise { - return this._proxy.$deleteReaction(this._handle, uri, comment, reaction); - } +import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { Extensions as PanelExtensions, PanelDescriptor, PanelRegistry } from 'vs/workbench/browser/panel'; +import { ICommentInfo, ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; +import { CommentsPanel, COMMENTS_PANEL_ID, COMMENTS_PANEL_TITLE } from 'vs/workbench/contrib/comments/browser/commentsPanel'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { CommentProviderFeatures, ExtHostCommentsShape, ExtHostContext, IExtHostContext, MainContext, MainThreadCommentsShape } from '../common/extHost.protocol'; - // onDidChangeCommentThreads = null; -} - -export class MainThreadCommentThread implements modes.CommentThread2 { +export class MainThreadCommentThread implements modes.CommentThread { private _input?: modes.CommentInput; get input(): modes.CommentInput | undefined { return this._input; @@ -133,42 +73,6 @@ export class MainThreadCommentThread implements modes.CommentThread2 { private _onDidChangeComments = new Emitter(); get onDidChangeComments(): Event { return this._onDidChangeComments.event; } - private _acceptInputCommand: modes.Command | undefined; - set acceptInputCommand(newCommand: modes.Command | undefined) { - this._acceptInputCommand = newCommand; - this._onDidChangeAcceptInputCommand.fire(this._acceptInputCommand); - } - - get acceptInputCommand(): modes.Command | undefined { - return this._acceptInputCommand!; - } - - private _onDidChangeAcceptInputCommand = new Emitter(); - get onDidChangeAcceptInputCommand(): Event { return this._onDidChangeAcceptInputCommand.event; } - - private _additionalCommands: modes.Command[] | undefined; - set additionalCommands(newCommands: modes.Command[] | undefined) { - this._additionalCommands = newCommands; - this._onDidChangeAdditionalCommands.fire(this._additionalCommands); - } - - get additionalCommands(): modes.Command[] | undefined { - return this._additionalCommands; - } - - private _onDidChangeAdditionalCommands = new Emitter(); - get onDidChangeAdditionalCommands(): Event { return this._onDidChangeAdditionalCommands.event; } - - private _deleteCommand: modes.Command | undefined; - - set deleteCommand(newCommand: modes.Command | undefined) { - this._deleteCommand = newCommand; - } - - get deleteCommand(): modes.Command | undefined { - return this._deleteCommand; - } - set range(range: IRange) { this._range = range; this._onDidChangeRange.fire(this._range); @@ -216,24 +120,16 @@ export class MainThreadCommentThread implements modes.CommentThread2 { label: string, contextValue: string | undefined, comments: modes.Comment[], - acceptInputCommand: modes.Command | undefined, - additionalCommands: modes.Command[], - deleteCommand: modes.Command | undefined, collapsibleState: modes.CommentThreadCollapsibleState) { this._range = range; this._label = label; this._contextValue = contextValue; this._comments = comments; - this._acceptInputCommand = acceptInputCommand; - this._additionalCommands = additionalCommands; - this._deleteCommand = deleteCommand; this._collapsibleState = collapsibleState; } dispose() { this._isDisposed = true; - this._onDidChangeAcceptInputCommand.dispose(); - this._onDidChangeAdditionalCommands.dispose(); this._onDidChangeCollasibleState.dispose(); this._onDidChangeComments.dispose(); this._onDidChangeInput.dispose(); @@ -307,7 +203,7 @@ export class MainThreadCommentController { threadId: string, resource: UriComponents, range: IRange, - ): modes.CommentThread2 { + ): modes.CommentThread { let thread = new MainThreadCommentThread( commentThreadHandle, this.handle, @@ -319,17 +215,11 @@ export class MainThreadCommentController { this._threads.set(commentThreadHandle, thread); - // As we create comment thread from template and then restore from the newly created maint thread comment thread, - // we postpone the update event to avoid duplication. - // This can be actually removed once we are on the new API. - setTimeout(() => { - this._commentService.updateComments(this._uniqueId, { - added: [thread], - removed: [], - changed: [], - draftMode: modes.DraftMode.NotSupported - }); - }, 0); + this._commentService.updateComments(this._uniqueId, { + added: [thread], + removed: [], + changed: [] + }); return thread; } @@ -341,18 +231,14 @@ export class MainThreadCommentController { label: string, contextValue: string | undefined, comments: modes.Comment[], - acceptInputCommand: modes.Command | undefined, - additionalCommands: modes.Command[], - deleteCommand: modes.Command | undefined, collapsibleState: modes.CommentThreadCollapsibleState): void { let thread = this.getKnownThread(commentThreadHandle); - thread.batchUpdate(range, label, contextValue, comments, acceptInputCommand, additionalCommands, deleteCommand, collapsibleState); + thread.batchUpdate(range, label, contextValue, comments, collapsibleState); this._commentService.updateComments(this._uniqueId, { added: [], removed: [], - changed: [thread], - draftMode: modes.DraftMode.NotSupported + changed: [thread] }); } @@ -363,8 +249,7 @@ export class MainThreadCommentController { this._commentService.updateComments(this._uniqueId, { added: [], removed: [thread], - changed: [], - draftMode: modes.DraftMode.NotSupported + changed: [] }); thread.dispose(); @@ -397,7 +282,7 @@ export class MainThreadCommentController { } async getDocumentComments(resource: URI, token: CancellationToken) { - let ret: modes.CommentThread2[] = []; + let ret: modes.CommentThread[] = []; for (let thread of keys(this._threads)) { const commentThread = this._threads.get(thread)!; if (commentThread.resource === resource.toString()) { @@ -406,26 +291,15 @@ export class MainThreadCommentController { } let commentingRanges = await this._proxy.$provideCommentingRanges(this.handle, resource, token); - let staticContribution = await this._proxy.$checkStaticContribution(this.handle); return { owner: this._uniqueId, label: this.label, threads: ret, - commentingRanges: commentingRanges ? { + commentingRanges: { resource: resource, - ranges: commentingRanges, - newCommentThreadCallback: staticContribution ? undefined : async (uri: UriComponents, range: IRange) => { - let threadHandle = await this._proxy.$createNewCommentWidgetCallback(this.handle, uri, range, token); - - if (threadHandle !== undefined) { - return this.getKnownThread(threadHandle); - } - - return; - } - } : [], - draftMode: modes.DraftMode.NotSupported + ranges: commentingRanges || [] + } }; } @@ -438,7 +312,7 @@ export class MainThreadCommentController { return this._features.reactionGroup; } - async toggleReaction(uri: URI, thread: modes.CommentThread2, comment: modes.Comment, reaction: modes.CommentReaction, token: CancellationToken): Promise { + async toggleReaction(uri: URI, thread: modes.CommentThread, comment: modes.Comment, reaction: modes.CommentReaction, token: CancellationToken): Promise { return this._proxy.$toggleReaction(this._handle, thread.commentThreadHandle, uri, comment, reaction); } @@ -477,18 +351,14 @@ export class MainThreadComments extends Disposable implements MainThreadComments private _activeCommentThread?: MainThreadCommentThread; private readonly _activeCommentThreadDisposables = this._register(new DisposableStore()); - private _input?: modes.CommentInput; private _openPanelListener: IDisposable | null; constructor( extHostContext: IExtHostContext, - @IEditorService private readonly _editorService: IEditorService, @ICommentService private readonly _commentService: ICommentService, - @IPanelService private readonly _panelService: IPanelService, - @ITelemetryService private readonly _telemetryService: ITelemetryService, - @IConfigurationService private readonly _configurationService: IConfigurationService, + @IPanelService private readonly _panelService: IPanelService ) { super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostComments); @@ -504,13 +374,6 @@ export class MainThreadComments extends Disposable implements MainThreadComments this._activeCommentThreadDisposables.clear(); this._activeCommentThread = thread as MainThreadCommentThread; controller.activeCommentThread = this._activeCommentThread; - - this._activeCommentThreadDisposables.add(this._activeCommentThread.onDidChangeInput(input => { // todo, dispose - this._input = input; - this._proxy.$onCommentWidgetInputChange(handle, URI.parse(this._activeCommentThread!.resource), this._activeCommentThread!.range, this._input ? this._input.value : undefined); - })); - - await this._proxy.$onCommentWidgetInputChange(controller.handle, URI.parse(this._activeCommentThread!.resource), this._activeCommentThread.range, this._input ? this._input.value : undefined); })); } @@ -556,7 +419,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments resource: UriComponents, range: IRange, extensionId: ExtensionIdentifier - ): modes.CommentThread2 | undefined { + ): modes.CommentThread | undefined { let provider = this._commentControllers.get(handle); if (!provider) { @@ -574,9 +437,6 @@ export class MainThreadComments extends Disposable implements MainThreadComments label: string, contextValue: string | undefined, comments: modes.Comment[], - acceptInputCommand: modes.Command | undefined, - additionalCommands: modes.Command[], - deleteCommand: modes.Command, collapsibleState: modes.CommentThreadCollapsibleState): void { let provider = this._commentControllers.get(handle); @@ -584,7 +444,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments return undefined; } - return provider.updateCommentThread(commentThreadHandle, threadId, resource, range, label, contextValue, comments, acceptInputCommand, additionalCommands, deleteCommand, collapsibleState); + return provider.updateCommentThread(commentThreadHandle, threadId, resource, range, label, contextValue, comments, collapsibleState); } $deleteCommentThread(handle: number, commentThreadHandle: number) { @@ -597,26 +457,6 @@ export class MainThreadComments extends Disposable implements MainThreadComments return provider.deleteCommentThread(commentThreadHandle); } - $setInputValue(handle: number, input: string) { - let provider = this._commentControllers.get(handle); - - if (!provider) { - return; - } - - provider.updateInput(input); - } - - $registerDocumentCommentProvider(handle: number, features: CommentProviderFeatures): void { - this._documentProviders.set(handle, undefined); - const handler = new MainThreadDocumentCommentProvider(this._proxy, handle, features); - - const providerId = generateUuid(); - this._handlers.set(handle, providerId); - - this._commentService.registerDataProvider(providerId, handler); - } - private registerPanel(commentsPanelAlreadyConstructed: boolean) { if (!commentsPanelAlreadyConstructed) { Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescriptor( @@ -638,15 +478,6 @@ export class MainThreadComments extends Disposable implements MainThreadComments if (!commentsPanelAlreadyConstructed && !this._openPanelListener) { this._openPanelListener = this._panelService.onDidPanelOpen(e => { if (e.panel.getId() === COMMENTS_PANEL_ID) { - keys(this._workspaceProviders).forEach(handle => { - this._proxy.$provideWorkspaceComments(handle).then(commentThreads => { - if (commentThreads) { - const providerId = this.getHandler(handle); - this._commentService.setWorkspaceComments(providerId, commentThreads); - } - }); - }); - keys(this._commentControllers).forEach(handle => { let threads = this._commentControllers.get(handle)!.getAllComments(); @@ -672,116 +503,12 @@ export class MainThreadComments extends Disposable implements MainThreadComments return this._handlers.get(handle)!; } - $registerWorkspaceCommentProvider(handle: number, extensionId: ExtensionIdentifier): void { - this._workspaceProviders.set(handle, undefined); - - const providerId = generateUuid(); - this._handlers.set(handle, providerId); - - const commentsPanelAlreadyConstructed = this._panelService.getPanels().some(panel => panel.id === COMMENTS_PANEL_ID); - if (!commentsPanelAlreadyConstructed) { - this.registerPanel(commentsPanelAlreadyConstructed); - } - - const openPanel = this._configurationService.getValue('comments').openPanel; - - if (openPanel === 'neverOpen') { - this.registerOpenPanelListener(commentsPanelAlreadyConstructed); - } - - if (openPanel === 'openOnSessionStart') { - this._panelService.openPanel(COMMENTS_PANEL_ID); - } - - this._proxy.$provideWorkspaceComments(handle).then(commentThreads => { - if (commentThreads) { - if (openPanel === 'openOnSessionStartWithComments' && commentThreads.length) { - if (commentThreads.length) { - this._panelService.openPanel(COMMENTS_PANEL_ID); - } else { - this.registerOpenPanelListener(commentsPanelAlreadyConstructed); - } - } - - this._commentService.setWorkspaceComments(providerId, commentThreads); - } - }); - - /* __GDPR__ - "comments:registerWorkspaceCommentProvider" : { - "extensionId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this._telemetryService.publicLog('comments:registerWorkspaceCommentProvider', { - extensionId: extensionId.value - }); - } - - $unregisterDocumentCommentProvider(handle: number): void { - this._documentProviders.delete(handle); - const handlerId = this.getHandler(handle); - this._commentService.unregisterDataProvider(handlerId); - this._handlers.delete(handle); - } - - $unregisterWorkspaceCommentProvider(handle: number): void { - this._workspaceProviders.delete(handle); - if (this._workspaceProviders.size === 0) { - Registry.as(PanelExtensions.Panels).deregisterPanel(COMMENTS_PANEL_ID); - - if (this._openPanelListener) { - this._openPanelListener.dispose(); - this._openPanelListener = null; - } - } - - const handlerId = this.getHandler(handle); - this._commentService.removeWorkspaceComments(handlerId); - this._handlers.delete(handle); - } - $onDidCommentThreadsChange(handle: number, event: modes.CommentThreadChangedEvent) { // notify comment service const providerId = this.getHandler(handle); this._commentService.updateComments(providerId, event); } - getVisibleEditors(): ICodeEditor[] { - let ret: ICodeEditor[] = []; - - this._editorService.visibleControls.forEach(control => { - if (isCodeEditor(control.getControl())) { - ret.push(control.getControl() as ICodeEditor); - } - - if (isDiffEditor(control.getControl())) { - let diffEditor = control.getControl() as IDiffEditor; - ret.push(diffEditor.getOriginalEditor(), diffEditor.getModifiedEditor()); - } - }); - - return ret; - } - - async provideWorkspaceComments(): Promise { - const result: modes.CommentThread[] = []; - for (const handle of keys(this._workspaceProviders)) { - const result = await this._proxy.$provideWorkspaceComments(handle); - if (Array.isArray(result)) { - result.push(...result); - } - } - return result; - } - - async provideDocumentComments(resource: URI): Promise> { - const result: Array = []; - for (const handle of keys(this._documentProviders)) { - result.push(await this._proxy.$provideDocumentComments(handle, resource)); - } - return result; - } - dispose(): void { super.dispose(); this._workspaceProviders.forEach(value => dispose(value)); diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 48b250bff1d..e87a48fc9d9 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -10,7 +10,6 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import * as modes from 'vs/editor/common/modes'; import { localize } from 'vs/nls'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ExtHostContext, ExtHostWebviewsShape, IExtHostContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelShowOptions } from 'vs/workbench/api/common/extHost.protocol'; @@ -24,6 +23,11 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { extHostNamedCustomer } from '../common/extHostCustomers'; import { IProductService } from 'vs/platform/product/common/product'; +interface MainThreadWebviewState { + readonly viewType: string; + state: any; +} + @extHostNamedCustomer(MainContext.MainThreadWebviews) export class MainThreadWebviews extends Disposable implements MainThreadWebviewsShape { @@ -39,14 +43,13 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews private readonly _proxy: ExtHostWebviewsShape; - private readonly _webviews = new Map(); + private readonly _webviews = new Map>(); private readonly _revivers = new Map(); private _activeWebview: WebviewPanelHandle | undefined = undefined; constructor( context: IExtHostContext, - @ILifecycleService lifecycleService: ILifecycleService, @IExtensionService extensionService: IExtensionService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IEditorService private readonly _editorService: IEditorService, @@ -64,20 +67,15 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews // This reviver's only job is to activate webview extensions // This should trigger the real reviver to be registered from the extension host side. this._register(_webviewEditorService.registerReviver({ - canRevive: (webview) => { - const viewType = webview.state.viewType; - if (viewType) { + canRevive: (webview: WebviewEditorInput) => { + const viewType = webview.state && webview.state.viewType; + if (typeof viewType === 'string') { extensionService.activateByEvent(`onWebviewPanel:${viewType}`); } return false; }, reviveWebview: () => { throw new Error('not implemented'); } })); - - this._register(lifecycleService.onBeforeShutdown(e => { - this._onBeforeShutdown(); - e.veto(false); // Don't veto shutdown - }, this)); } public $createWebviewPanel( @@ -98,7 +96,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews const webview = this._webviewEditorService.createWebview(this.getInternalWebviewId(viewType), title, mainThreadShowOptions, reviveWebviewOptions(options), { location: URI.revive(extensionLocation), id: extensionId - }, this.createWebviewEventDelegate(handle)); + }, this.createWebviewEventDelegate(handle)) as WebviewEditorInput; webview.state = { viewType: viewType, state: undefined @@ -218,14 +216,6 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews return `mainThreadWebview-${viewType}`; } - private _onBeforeShutdown(): void { - this._webviews.forEach((webview) => { - if (!webview.isDisposed() && webview.state && this._revivers.has(webview.state.viewType)) { - webview.state.state = webview.webviewState; - } - }); - } - private createWebviewEventDelegate(handle: WebviewPanelHandle) { return { onDidClickLink: (uri: URI) => this.onDidClickLink(handle, uri), @@ -234,6 +224,13 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews this._proxy.$onDidDisposeWebviewPanel(handle).finally(() => { this._webviews.delete(handle); }); + }, + onDidUpdateWebviewState: (newState: any) => { + const webview = this.tryGetWebview(handle); + if (!webview || webview.isDisposed()) { + return; + } + (webview as WebviewEditorInput).state.state = newState; } }; } @@ -326,13 +323,17 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews } private getWebview(handle: WebviewPanelHandle): WebviewEditorInput { - const webview = this._webviews.get(handle); + const webview = this.tryGetWebview(handle); if (!webview) { throw new Error('Unknown webview handle:' + handle); } return webview; } + private tryGetWebview(handle: WebviewPanelHandle): WebviewEditorInput | undefined { + return this._webviews.get(handle); + } + private static getDeserializationFailedContents(viewType: string) { return ` diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 387f5443373..e28c6f484ba 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -3,50 +3,50 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IRemoteConsoleLog } from 'vs/base/common/console'; import { SerializedError } from 'vs/base/common/errors'; +import { IRelativePattern } from 'vs/base/common/glob'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; import Severity from 'vs/base/common/severity'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { TextEditorCursorStyle, RenderLineNumbersType } from 'vs/editor/common/config/editorOptions'; +import { RenderLineNumbersType, TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions'; import { IPosition } from 'vs/editor/common/core/position'; import { IRange } from 'vs/editor/common/core/range'; import { ISelection, Selection } from 'vs/editor/common/core/selection'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { ISingleEditOperation, EndOfLineSequence } from 'vs/editor/common/model'; +import { EndOfLineSequence, ISingleEditOperation } from 'vs/editor/common/model'; import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel'; import * as modes from 'vs/editor/common/modes'; import { CharacterPair, CommentRule, EnterAction } from 'vs/editor/common/modes/languageConfiguration'; import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; import { ConfigurationTarget, IConfigurationData, IConfigurationModel } from 'vs/platform/configuration/common/configuration'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import * as files from 'vs/platform/files/common/files'; import { ResourceLabelFormatter } from 'vs/platform/label/common/label'; import { LogLevel } from 'vs/platform/log/common/log'; import { IMarkerData } from 'vs/platform/markers/common/markers'; +import { IProgressOptions, IProgressStep } from 'vs/platform/progress/common/progress'; import * as quickInput from 'vs/platform/quickinput/common/quickInput'; -import * as search from 'vs/workbench/services/search/common/search'; +import { RemoteAuthorityResolverErrorCode, ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver'; import * as statusbar from 'vs/platform/statusbar/common/statusbar'; +import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings'; import { ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import * as tasks from 'vs/workbench/api/common/shared/tasks'; -import { ITreeItem, IRevealOptions } from 'vs/workbench/common/views'; +import { IRevealOptions, ITreeItem } from 'vs/workbench/common/views'; +import * as callHierarchy from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { IAdapterDescriptor, IConfig, ITerminalSettings } from 'vs/workbench/contrib/debug/common/debug'; import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder'; import { ITerminalDimensions } from 'vs/workbench/contrib/terminal/common/terminal'; import { ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions'; -import { IRPCProtocol, createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId } from 'vs/workbench/services/extensions/common/proxyIdentifier'; -import { IProgressOptions, IProgressStep } from 'vs/platform/progress/common/progress'; +import { createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId, IRPCProtocol } from 'vs/workbench/services/extensions/common/proxyIdentifier'; +import * as search from 'vs/workbench/services/search/common/search'; import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; -import { ResolvedAuthority, RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import * as callHierarchy from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; -import { IRelativePattern } from 'vs/base/common/glob'; -import { IRemoteConsoleLog } from 'vs/base/common/console'; -import { VSBuffer } from 'vs/base/common/buffer'; -import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; @@ -120,19 +120,8 @@ export interface MainThreadCommandsShape extends IDisposable { $getCommands(): Promise; } -export interface CommentThreadTemplate { - label: string; - acceptInputCommand?: modes.Command; - additionalCommands?: modes.Command[]; - deleteCommand?: modes.Command; -} - export interface CommentProviderFeatures { - startDraftLabel?: string; - deleteDraftLabel?: string; - finishDraftLabel?: string; reactionGroup?: modes.CommentReaction[]; - commentThreadTemplate?: CommentThreadTemplate; reactionHandler?: boolean; } @@ -140,14 +129,9 @@ export interface MainThreadCommentsShape extends IDisposable { $registerCommentController(handle: number, id: string, label: string): void; $unregisterCommentController(handle: number): void; $updateCommentControllerFeatures(handle: number, features: CommentProviderFeatures): void; - $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, extensionId: ExtensionIdentifier): modes.CommentThread2 | undefined; - $updateCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, label: string, contextValue: string | undefined, comments: modes.Comment[], acceptInputCommand: modes.Command | undefined, additionalCommands: modes.Command[], deleteCommand: modes.Command | undefined, collapseState: modes.CommentThreadCollapsibleState): void; + $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, extensionId: ExtensionIdentifier): modes.CommentThread | undefined; + $updateCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, label: string, contextValue: string | undefined, comments: modes.Comment[], collapseState: modes.CommentThreadCollapsibleState): void; $deleteCommentThread(handle: number, commentThreadHandle: number): void; - $setInputValue(handle: number, input: string): void; - $registerDocumentCommentProvider(handle: number, features: CommentProviderFeatures): void; - $unregisterDocumentCommentProvider(handle: number): void; - $registerWorkspaceCommentProvider(handle: number, extensionId: ExtensionIdentifier): void; - $unregisterWorkspaceCommentProvider(handle: number): void; $onDidCommentThreadsChange(handle: number, event: modes.CommentThreadChangedEvent): void; } @@ -1290,26 +1274,12 @@ export interface ExtHostProgressShape { } export interface ExtHostCommentsShape { - $provideDocumentComments(handle: number, document: UriComponents): Promise; - $createNewCommentThread(handle: number, document: UriComponents, range: IRange, text: string): Promise; $createCommentThreadTemplate(commentControllerHandle: number, uriComponents: UriComponents, range: IRange): void; $updateCommentThreadTemplate(commentControllerHandle: number, threadHandle: number, range: IRange): Promise; - $onCommentWidgetInputChange(commentControllerHandle: number, document: UriComponents, range: IRange, input: string | undefined): Promise; $deleteCommentThread(commentControllerHandle: number, commentThreadHandle: number): void; $provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise; - $checkStaticContribution(commentControllerHandle: number): Promise; $provideReactionGroup(commentControllerHandle: number): Promise; $toggleReaction(commentControllerHandle: number, threadHandle: number, uri: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise; - $createNewCommentWidgetCallback(commentControllerHandle: number, uriComponents: UriComponents, range: IRange, token: CancellationToken): Promise; - $replyToCommentThread(handle: number, document: UriComponents, range: IRange, commentThread: modes.CommentThread, text: string): Promise; - $editComment(handle: number, document: UriComponents, comment: modes.Comment, text: string): Promise; - $deleteComment(handle: number, document: UriComponents, comment: modes.Comment): Promise; - $startDraft(handle: number, document: UriComponents): Promise; - $deleteDraft(handle: number, document: UriComponents): Promise; - $finishDraft(handle: number, document: UriComponents): Promise; - $addReaction(handle: number, document: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise; - $deleteReaction(handle: number, document: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise; - $provideWorkspaceComments(handle: number): Promise; } export interface ExtHostStorageShape { diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 93b58afbd08..10166047fb2 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -4,26 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import { asPromise } from 'vs/base/common/async'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { debounce } from 'vs/base/common/decorators'; +import { Emitter } from 'vs/base/common/event'; +import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; +import { IRange } from 'vs/editor/common/core/range'; import * as modes from 'vs/editor/common/modes'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import * as extHostTypeConverter from 'vs/workbench/api/common/extHostTypeConverters'; import * as types from 'vs/workbench/api/common/extHostTypes'; import * as vscode from 'vscode'; -import { ExtHostCommentsShape, IMainContext, MainContext, MainThreadCommentsShape, CommandDto } from './extHost.protocol'; -import { CommandsConverter, ExtHostCommands } from './extHostCommands'; -import { IRange } from 'vs/editor/common/core/range'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { Event, Emitter } from 'vs/base/common/event'; -import { debounce } from 'vs/base/common/decorators'; -import { MutableDisposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; - -interface HandlerData { - - extensionId: ExtensionIdentifier; - provider: T; -} +import { ExtHostCommentsShape, IMainContext, MainContext, MainThreadCommentsShape } from './extHost.protocol'; +import { ExtHostCommands } from './extHostCommands'; type ProviderHandle = number; @@ -37,20 +31,15 @@ export class ExtHostComments implements ExtHostCommentsShape, IDisposable { private _commentControllersByExtension: Map = new Map(); - private _documentProviders = new Map>(); - private _workspaceProviders = new Map>(); - - private _commandDisposables = new MutableDisposable(); constructor( mainContext: IMainContext, - private _commands: ExtHostCommands, + commands: ExtHostCommands, private readonly _documents: ExtHostDocuments, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadComments); - this._commandDisposables.value = new DisposableStore(); - _commands.registerArgumentProcessor({ + commands.registerArgumentProcessor({ processArgument: arg => { if (arg && arg.$mid === 6) { const commentController = this._commentControllers.get(arg.handle); @@ -147,7 +136,7 @@ export class ExtHostComments implements ExtHostCommentsShape, IDisposable { createCommentController(extension: IExtensionDescription, id: string, label: string): vscode.CommentController { const handle = ExtHostComments.handlePool++; - const commentController = new ExtHostCommentController(extension, handle, this._commands.converter, this._proxy, id, label); + const commentController = new ExtHostCommentController(extension, handle, this._proxy, id, label); this._commentControllers.set(commentController.handle, commentController); const commentControllers = this._commentControllersByExtension.get(ExtensionIdentifier.toKey(extension.identifier)) || []; @@ -177,17 +166,6 @@ export class ExtHostComments implements ExtHostCommentsShape, IDisposable { commentController.$updateCommentThreadTemplate(threadHandle, range); } - $onCommentWidgetInputChange(commentControllerHandle: number, uriComponents: UriComponents, range: IRange, input: string): Promise { - const commentController = this._commentControllers.get(commentControllerHandle); - - if (!commentController) { - return Promise.resolve(undefined); - } - - commentController.$onCommentWidgetInputChange(uriComponents, range, input); - return Promise.resolve(commentControllerHandle); - } - $deleteCommentThread(commentControllerHandle: number, commentThreadHandle: number) { const commentController = this._commentControllers.get(commentControllerHandle); @@ -218,7 +196,7 @@ export class ExtHostComments implements ExtHostCommentsShape, IDisposable { return asPromise(() => { return commentController!.reactionProvider!.availableReactions; - }).then(reactions => reactions.map(reaction => convertToReaction2(commentController.reactionProvider, reaction))); + }).then(reactions => reactions.map(reaction => convertToReaction(commentController.reactionProvider, reaction))); } $toggleReaction(commentControllerHandle: number, threadHandle: number, uri: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise { @@ -250,233 +228,8 @@ export class ExtHostComments implements ExtHostCommentsShape, IDisposable { }); } - $createNewCommentWidgetCallback(commentControllerHandle: number, uriComponents: UriComponents, range: IRange, token: CancellationToken): Promise { - const commentController = this._commentControllers.get(commentControllerHandle); + dispose() { - if (!commentController) { - return Promise.resolve(); - } - - if (!(commentController as any).emptyCommentThreadFactory) { - return Promise.resolve(); - } - - const document = this._documents.getDocument(URI.revive(uriComponents)); - return asPromise(() => { - if ((commentController as any).emptyCommentThreadFactory) { - return (commentController as any).emptyCommentThreadFactory!.createEmptyCommentThread(document, extHostTypeConverter.Range.to(range)); - } - }).then(() => Promise.resolve()); - } - - $checkStaticContribution(commentControllerHandle: number): Promise { - const commentController = this._commentControllers.get(commentControllerHandle); - - if (!commentController) { - return Promise.resolve(false); - } - - if (!(commentController as any).emptyCommentThreadFactory) { - return Promise.resolve(true); - } - - return Promise.resolve(false); - } - - registerWorkspaceCommentProvider( - extensionId: ExtensionIdentifier, - provider: vscode.WorkspaceCommentProvider - ): vscode.Disposable { - const handle = ExtHostComments.handlePool++; - this._workspaceProviders.set(handle, { - extensionId, - provider - }); - this._proxy.$registerWorkspaceCommentProvider(handle, extensionId); - this.registerListeners(handle, extensionId, provider); - - return { - dispose: () => { - this._proxy.$unregisterWorkspaceCommentProvider(handle); - this._workspaceProviders.delete(handle); - } - }; - } - - registerDocumentCommentProvider( - extensionId: ExtensionIdentifier, - provider: vscode.DocumentCommentProvider - ): vscode.Disposable { - const handle = ExtHostComments.handlePool++; - this._documentProviders.set(handle, { - extensionId, - provider - }); - this._proxy.$registerDocumentCommentProvider(handle, { - startDraftLabel: provider.startDraftLabel, - deleteDraftLabel: provider.deleteDraftLabel, - finishDraftLabel: provider.finishDraftLabel, - reactionGroup: provider.reactionGroup ? provider.reactionGroup.map(reaction => convertToReaction(provider, reaction)) : undefined - }); - this.registerListeners(handle, extensionId, provider); - - return { - dispose: () => { - this._proxy.$unregisterDocumentCommentProvider(handle); - this._documentProviders.delete(handle); - } - }; - } - - $createNewCommentThread(handle: number, uri: UriComponents, range: IRange, text: string): Promise { - const data = this._documents.getDocumentData(URI.revive(uri)); - const ran = extHostTypeConverter.Range.to(range); - - if (!data || !data.document) { - return Promise.resolve(null); - } - - const handlerData = this.getDocumentProvider(handle); - return asPromise(() => { - return handlerData.provider.createNewCommentThread(data.document, ran, text, CancellationToken.None); - }).then(commentThread => commentThread ? convertToCommentThread(handlerData.extensionId, handlerData.provider, commentThread, this._commands.converter, this._commandDisposables.value!) : null); - } - - $replyToCommentThread(handle: number, uri: UriComponents, range: IRange, thread: modes.CommentThread, text: string): Promise { - const data = this._documents.getDocumentData(URI.revive(uri)); - const ran = extHostTypeConverter.Range.to(range); - - if (!data || !data.document) { - return Promise.resolve(null); - } - - const handlerData = this.getDocumentProvider(handle); - return asPromise(() => { - return handlerData.provider.replyToCommentThread(data.document, ran, convertFromCommentThread(thread), text, CancellationToken.None); - }).then(commentThread => commentThread ? convertToCommentThread(handlerData.extensionId, handlerData.provider, commentThread, this._commands.converter, this._commandDisposables.value!) : null); - } - - $editComment(handle: number, uri: UriComponents, comment: modes.Comment, text: string): Promise { - const document = this._documents.getDocument(URI.revive(uri)); - const handlerData = this.getDocumentProvider(handle); - if (!handlerData.provider.editComment) { - return Promise.reject(new Error('not implemented')); - } - return asPromise(() => { - return handlerData.provider.editComment!(document, convertFromComment(comment), text, CancellationToken.None); - }); - } - - $deleteComment(handle: number, uri: UriComponents, comment: modes.Comment): Promise { - const document = this._documents.getDocument(URI.revive(uri)); - const handlerData = this.getDocumentProvider(handle); - if (!handlerData.provider.deleteComment) { - return Promise.reject(new Error('not implemented')); - } - return asPromise(() => { - return handlerData.provider.deleteComment!(document, convertFromComment(comment), CancellationToken.None); - }); - } - - $startDraft(handle: number, uri: UriComponents): Promise { - const document = this._documents.getDocument(URI.revive(uri)); - - const handlerData = this.getDocumentProvider(handle); - if (!handlerData.provider.startDraft) { - return Promise.reject(new Error('not implemented')); - } - return asPromise(() => { - return handlerData.provider.startDraft!(document, CancellationToken.None); - }); - } - - $deleteDraft(handle: number, uri: UriComponents): Promise { - const document = this._documents.getDocument(URI.revive(uri)); - const handlerData = this.getDocumentProvider(handle); - if (!handlerData.provider.deleteDraft) { - return Promise.reject(new Error('not implemented')); - } - return asPromise(() => { - return handlerData.provider.deleteDraft!(document, CancellationToken.None); - }); - } - - $finishDraft(handle: number, uri: UriComponents): Promise { - const document = this._documents.getDocument(URI.revive(uri)); - const handlerData = this.getDocumentProvider(handle); - if (!handlerData.provider.finishDraft) { - return Promise.reject(new Error('not implemented')); - } - return asPromise(() => { - return handlerData.provider.finishDraft!(document, CancellationToken.None); - }); - } - - $addReaction(handle: number, uri: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise { - const document = this._documents.getDocument(URI.revive(uri)); - const handlerData = this.getDocumentProvider(handle); - if (!handlerData.provider.addReaction) { - return Promise.reject(new Error('not implemented')); - } - return asPromise(() => { - return handlerData.provider.addReaction!(document, convertFromComment(comment), convertFromReaction(reaction)); - }); - } - - $deleteReaction(handle: number, uri: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise { - const document = this._documents.getDocument(URI.revive(uri)); - const handlerData = this.getDocumentProvider(handle); - if (!handlerData.provider.deleteReaction) { - return Promise.reject(new Error('not implemented')); - } - return asPromise(() => { - return handlerData.provider.deleteReaction!(document, convertFromComment(comment), convertFromReaction(reaction)); - }); - } - - $provideDocumentComments(handle: number, uri: UriComponents): Promise { - const document = this._documents.getDocument(URI.revive(uri)); - const handlerData = this.getDocumentProvider(handle); - return asPromise(() => { - return handlerData.provider.provideDocumentComments(document, CancellationToken.None); - }).then(commentInfo => commentInfo ? convertCommentInfo(handle, handlerData.extensionId, handlerData.provider, commentInfo, this._commands.converter, this._commandDisposables.value!) : null); - } - - $provideWorkspaceComments(handle: number): Promise { - const handlerData = this._workspaceProviders.get(handle); - if (!handlerData) { - return Promise.resolve(null); - } - - return asPromise(() => { - return handlerData.provider.provideWorkspaceComments(CancellationToken.None); - }).then(comments => - comments.map(comment => convertToCommentThread(handlerData.extensionId, handlerData.provider, comment, this._commands.converter, this._commandDisposables.value!) - )); - } - - private registerListeners(handle: number, extensionId: ExtensionIdentifier, provider: vscode.DocumentCommentProvider | vscode.WorkspaceCommentProvider) { - provider.onDidChangeCommentThreads(event => { - - this._proxy.$onDidCommentThreadsChange(handle, { - changed: event.changed.map(thread => convertToCommentThread(extensionId, provider, thread, this._commands.converter, this._commandDisposables.value!)), - added: event.added.map(thread => convertToCommentThread(extensionId, provider, thread, this._commands.converter, this._commandDisposables.value!)), - removed: event.removed.map(thread => convertToCommentThread(extensionId, provider, thread, this._commands.converter, this._commandDisposables.value!)), - draftMode: !!(provider as vscode.DocumentCommentProvider).startDraft && !!(provider as vscode.DocumentCommentProvider).finishDraft ? (event.inDraftMode ? modes.DraftMode.InDraft : modes.DraftMode.NotInDraft) : modes.DraftMode.NotSupported - }); - }); - } - - private getDocumentProvider(handle: number): HandlerData { - const provider = this._documentProviders.get(handle); - if (!provider) { - throw new Error('unknown provider'); - } - return provider; - } - - dispose(): void { - this._commandDisposables.dispose(); } } @@ -550,36 +303,6 @@ export class ExtHostCommentThread implements vscode.CommentThread { this._onDidUpdateCommentThread.fire(); } - private _acceptInputCommand: vscode.Command; - get acceptInputCommand(): vscode.Command { - return this._acceptInputCommand; - } - - set acceptInputCommand(acceptInputCommand: vscode.Command) { - this._acceptInputCommand = acceptInputCommand; - this._onDidUpdateCommentThread.fire(); - } - - private _additionalCommands: vscode.Command[] = []; - get additionalCommands(): vscode.Command[] { - return this._additionalCommands; - } - - set additionalCommands(additionalCommands: vscode.Command[]) { - this._additionalCommands = additionalCommands; - this._onDidUpdateCommentThread.fire(); - } - - private _deleteCommand?: vscode.Command; - get deleteComand(): vscode.Command | undefined { - return this._deleteCommand; - } - - set deleteCommand(deleteCommand: vscode.Command) { - this._deleteCommand = deleteCommand; - this._onDidUpdateCommentThread.fire(); - } - private _collapseState?: vscode.CommentThreadCollapsibleState; get collapsibleState(): vscode.CommentThreadCollapsibleState { @@ -605,7 +328,6 @@ export class ExtHostCommentThread implements vscode.CommentThread { constructor( private _proxy: MainThreadCommentsShape, - private readonly _commandsConverter: CommandsConverter, private _commentController: ExtHostCommentController, private _id: string | undefined, private _uri: vscode.Uri, @@ -642,6 +364,10 @@ export class ExtHostCommentThread implements vscode.CommentThread { @debounce(100) eventuallyUpdateCommentThread(): void { + if (this._isDiposed) { + return; + } + if (!this._acceptInputDisposables.value) { this._acceptInputDisposables.value = new DisposableStore(); } @@ -649,10 +375,7 @@ export class ExtHostCommentThread implements vscode.CommentThread { const commentThreadRange = extHostTypeConverter.Range.from(this._range); const label = this.label; const contextValue = this.contextValue; - const comments = this._comments.map(cmt => { return convertToModeComment2(this, this._commentController, cmt, this._commandsConverter, this._commentsMap, this._acceptInputDisposables.value!); }); - const acceptInputCommand = this._acceptInputCommand ? this._commandsConverter.toInternal(this._acceptInputCommand, this._acceptInputDisposables.value) : undefined; - const additionalCommands = (this._additionalCommands ? this._additionalCommands.map(x => this._commandsConverter.toInternal(x, this._acceptInputDisposables.value!)) : []) as CommandDto[]; - const deleteCommand = this._deleteCommand ? this._commandsConverter.toInternal(this._deleteCommand, this._acceptInputDisposables.value) : undefined; + const comments = this._comments.map(cmt => { return convertToModeComment(this, this._commentController, cmt, this._commentsMap); }); const collapsibleState = convertToCollapsibleState(this._collapseState); this._proxy.$updateCommentThread( @@ -664,15 +387,12 @@ export class ExtHostCommentThread implements vscode.CommentThread { label, contextValue, comments, - acceptInputCommand, - additionalCommands, - deleteCommand, collapsibleState ); } getComment(commentId: string): vscode.Comment | undefined { - const comments = this._comments.filter(comment => (comment.commentId === commentId || comment.id === commentId)); + const comments = this._comments.filter(comment => comment.commentId === commentId); if (comments && comments.length) { return comments[0]; @@ -694,54 +414,13 @@ export class ExtHostCommentThread implements vscode.CommentThread { } dispose() { + this._isDiposed = true; this._acceptInputDisposables.dispose(); this._localDisposables.forEach(disposable => disposable.dispose()); this._proxy.$deleteCommentThread( this._commentController.handle, this.handle ); - this._isDiposed = true; - } -} - -export class ExtHostCommentInputBox implements vscode.CommentInputBox { - get resource(): vscode.Uri { - return this._resource; - } - - get range(): vscode.Range { - return this._range; - } - - get value(): string { - return this._value; - } - - set value(newInput: string) { - this._value = newInput; - this._onDidChangeValue.fire(this._value); - this._proxy.$setInputValue(this.commentControllerHandle, newInput); - } - - private _onDidChangeValue = new Emitter(); - - get onDidChangeValue(): Event { - return this._onDidChangeValue.event; - } - - constructor( - private _proxy: MainThreadCommentsShape, - public commentControllerHandle: number, - private _resource: vscode.Uri, - private _range: vscode.Range, - private _value: string - ) { - } - - setInput(resource: vscode.Uri, range: vscode.Range, input: string) { - this._resource = resource; - this._range = range; - this._value = input; } } @@ -756,16 +435,12 @@ class ExtHostCommentController implements vscode.CommentController { return this._label; } - public inputBox: ExtHostCommentInputBox | undefined; - - public activeCommentingRange?: vscode.Range; - public get handle(): number { return this._handle; } private _threads: Map = new Map(); - commentingRangeProvider?: vscode.CommentingRangeProvider & { createEmptyCommentThread: (document: vscode.TextDocument, range: types.Range) => Promise; }; + commentingRangeProvider?: vscode.CommentingRangeProvider; private _commentReactionProvider?: vscode.CommentReactionProvider; @@ -776,7 +451,7 @@ class ExtHostCommentController implements vscode.CommentController { set reactionProvider(provider: vscode.CommentReactionProvider | undefined) { this._commentReactionProvider = provider; if (provider) { - this._proxy.$updateCommentControllerFeatures(this.handle, { reactionGroup: provider.availableReactions.map(reaction => convertToReaction2(provider, reaction)) }); + this._proxy.$updateCommentControllerFeatures(this.handle, { reactionGroup: provider.availableReactions.map(reaction => convertToReaction(provider, reaction)) }); } } @@ -795,7 +470,6 @@ class ExtHostCommentController implements vscode.CommentController { constructor( private _extension: IExtensionDescription, private _handle: number, - private readonly _commandsConverter: CommandsConverter, private _proxy: MainThreadCommentsShape, private _id: string, private _label: string @@ -804,34 +478,33 @@ class ExtHostCommentController implements vscode.CommentController { } createCommentThread(resource: vscode.Uri, range: vscode.Range, comments: vscode.Comment[]): vscode.CommentThread; - createCommentThread(id: string, resource: vscode.Uri, range: vscode.Range, comments: vscode.Comment[]): vscode.CommentThread; createCommentThread(arg0: vscode.Uri | string, arg1: vscode.Uri | vscode.Range, arg2: vscode.Range | vscode.Comment[], arg3?: vscode.Comment[]): vscode.CommentThread { if (typeof arg0 === 'string') { - const commentThread = new ExtHostCommentThread(this._proxy, this._commandsConverter, this, arg0, arg1 as vscode.Uri, arg2 as vscode.Range, arg3 as vscode.Comment[], this._extension.identifier); + const commentThread = new ExtHostCommentThread(this._proxy, this, arg0, arg1 as vscode.Uri, arg2 as vscode.Range, arg3 as vscode.Comment[], this._extension.identifier); this._threads.set(commentThread.handle, commentThread); return commentThread; } else { - const commentThread = new ExtHostCommentThread(this._proxy, this._commandsConverter, this, undefined, arg0 as vscode.Uri, arg1 as vscode.Range, arg2 as vscode.Comment[], this._extension.identifier); + const commentThread = new ExtHostCommentThread(this._proxy, this, undefined, arg0 as vscode.Uri, arg1 as vscode.Range, arg2 as vscode.Comment[], this._extension.identifier); this._threads.set(commentThread.handle, commentThread); return commentThread; } } - $createCommentThreadTemplate(uriComponents: UriComponents, range: IRange) { - const commentThread = new ExtHostCommentThread(this._proxy, this._commandsConverter, this, undefined, URI.revive(uriComponents), extHostTypeConverter.Range.to(range), [], this._extension.identifier); + $createCommentThreadTemplate(uriComponents: UriComponents, range: IRange): ExtHostCommentThread { + const commentThread = new ExtHostCommentThread(this._proxy, this, undefined, URI.revive(uriComponents), extHostTypeConverter.Range.to(range), [], this._extension.identifier); commentThread.collapsibleState = modes.CommentThreadCollapsibleState.Expanded; this._threads.set(commentThread.handle, commentThread); return commentThread; } - $updateCommentThreadTemplate(threadHandle: number, range: IRange) { + $updateCommentThreadTemplate(threadHandle: number, range: IRange): void { let thread = this._threads.get(threadHandle); if (thread) { thread.range = extHostTypeConverter.Range.to(range); } } - $deleteCommentThread(threadHandle: number) { + $deleteCommentThread(threadHandle: number): void { let thread = this._threads.get(threadHandle); if (thread) { @@ -841,15 +514,7 @@ class ExtHostCommentController implements vscode.CommentController { this._threads.delete(threadHandle); } - $onCommentWidgetInputChange(uriComponents: UriComponents, range: IRange, input: string) { - if (!this.inputBox) { - this.inputBox = new ExtHostCommentInputBox(this._proxy, this.handle, URI.revive(uriComponents), extHostTypeConverter.Range.to(range), input); - } else { - this.inputBox.setInput(URI.revive(uriComponents), extHostTypeConverter.Range.to(range), input); - } - } - - getCommentThread(handle: number) { + getCommentThread(handle: number): ExtHostCommentThread | undefined { return this._threads.get(handle); } @@ -862,77 +527,7 @@ class ExtHostCommentController implements vscode.CommentController { } } -function convertCommentInfo(owner: number, extensionId: ExtensionIdentifier, provider: vscode.DocumentCommentProvider, vscodeCommentInfo: vscode.CommentInfo, commandsConverter: CommandsConverter, disposables: DisposableStore): modes.CommentInfo { - return { - extensionId: extensionId.value, - threads: vscodeCommentInfo.threads.map(x => convertToCommentThread(extensionId, provider, x, commandsConverter, disposables)), - commentingRanges: vscodeCommentInfo.commentingRanges ? vscodeCommentInfo.commentingRanges.map(range => extHostTypeConverter.Range.from(range)) : [], - draftMode: provider.startDraft && provider.finishDraft ? (vscodeCommentInfo.inDraftMode ? modes.DraftMode.InDraft : modes.DraftMode.NotInDraft) : modes.DraftMode.NotSupported - }; -} - -function convertToCommentThread(extensionId: ExtensionIdentifier, provider: vscode.DocumentCommentProvider | vscode.WorkspaceCommentProvider, vscodeCommentThread: vscode.CommentThread, commandsConverter: CommandsConverter, disposables: DisposableStore): modes.CommentThread { - return { - extensionId: extensionId.value, - threadId: vscodeCommentThread.id, - resource: vscodeCommentThread.resource.toString(), - range: extHostTypeConverter.Range.from(vscodeCommentThread.range), - comments: vscodeCommentThread.comments.map(comment => convertToComment(provider, comment as vscode.Comment, commandsConverter, disposables)), - collapsibleState: vscodeCommentThread.collapsibleState - }; -} - -function convertFromCommentThread(commentThread: modes.CommentThread): vscode.CommentThread { - return { - id: commentThread.threadId!, - threadId: commentThread.threadId!, - uri: URI.parse(commentThread.resource!), - resource: URI.parse(commentThread.resource!), - range: extHostTypeConverter.Range.to(commentThread.range), - comments: commentThread.comments ? commentThread.comments.map(convertFromComment) : [], - collapsibleState: commentThread.collapsibleState, - dispose: () => { } - } as vscode.CommentThread; -} - -function convertFromComment(comment: modes.Comment): vscode.Comment { - let userIconPath: URI | undefined; - if (comment.userIconPath) { - try { - userIconPath = URI.parse(comment.userIconPath); - } catch (e) { - // Ignore - } - } - - return { - id: comment.commentId, - commentId: comment.commentId, - body: extHostTypeConverter.MarkdownString.to(comment.body), - author: { - name: comment.userName, - iconPath: userIconPath - }, - userName: comment.userName, - userIconPath: userIconPath, - canEdit: comment.canEdit, - canDelete: comment.canDelete, - isDraft: comment.isDraft, - commentReactions: comment.commentReactions ? comment.commentReactions.map(reaction => { - return { - label: reaction.label || '', - count: reaction.count || 0, - iconPath: reaction.iconPath ? URI.revive(reaction.iconPath) : '', - hasReacted: reaction.hasReacted, - authorHasReacted: reaction.hasReacted || false - - }; - }) : undefined, - mode: comment.mode ? comment.mode : modes.CommentMode.Preview - }; -} - -function convertToModeComment2(thread: ExtHostCommentThread, commentController: ExtHostCommentController, vscodeComment: vscode.Comment, commandsConverter: CommandsConverter, commentsMap: Map, disposables: DisposableStore): modes.Comment { +function convertToModeComment(thread: ExtHostCommentThread, commentController: ExtHostCommentController, vscodeComment: vscode.Comment, commentsMap: Map): modes.Comment { let commentUniqueId = commentsMap.get(vscodeComment)!; if (!commentUniqueId) { commentUniqueId = ++thread.commentHandle; @@ -940,57 +535,21 @@ function convertToModeComment2(thread: ExtHostCommentThread, commentController: } const iconPath = vscodeComment.author && vscodeComment.author.iconPath ? vscodeComment.author.iconPath.toString() : undefined; - const reactions = vscodeComment.reactions || vscodeComment.commentReactions; return { - commentId: vscodeComment.id || vscodeComment.commentId, + commentId: vscodeComment.commentId, mode: vscodeComment.mode, contextValue: vscodeComment.contextValue, uniqueIdInThread: commentUniqueId, body: extHostTypeConverter.MarkdownString.from(vscodeComment.body), - userName: vscodeComment.author ? vscodeComment.author.name : vscodeComment.userName, + userName: vscodeComment.author.name, userIconPath: iconPath, - isDraft: vscodeComment.isDraft, - selectCommand: vscodeComment.selectCommand ? commandsConverter.toInternal(vscodeComment.selectCommand, disposables) : undefined, - editCommand: vscodeComment.editCommand ? commandsConverter.toInternal(vscodeComment.editCommand, disposables) : undefined, - deleteCommand: vscodeComment.deleteCommand ? commandsConverter.toInternal(vscodeComment.deleteCommand, disposables) : undefined, label: vscodeComment.label, - commentReactions: reactions ? reactions.map(reaction => convertToReaction2(commentController.reactionProvider, reaction)) : undefined + commentReactions: vscodeComment.reactions ? vscodeComment.reactions.map(reaction => convertToReaction(commentController.reactionProvider, reaction)) : undefined }; } -function convertToComment(provider: vscode.DocumentCommentProvider | vscode.WorkspaceCommentProvider, vscodeComment: vscode.Comment, commandsConverter: CommandsConverter, disposables: DisposableStore): modes.Comment { - const canEdit = !!(provider as vscode.DocumentCommentProvider).editComment && vscodeComment.canEdit; - const canDelete = !!(provider as vscode.DocumentCommentProvider).deleteComment && vscodeComment.canDelete; - const iconPath = vscodeComment.userIconPath ? vscodeComment.userIconPath.toString() : vscodeComment.gravatar; - - return { - commentId: vscodeComment.commentId, - body: extHostTypeConverter.MarkdownString.from(vscodeComment.body), - userName: vscodeComment.userName, - userIconPath: iconPath, - canEdit: canEdit, - canDelete: canDelete, - selectCommand: vscodeComment.command ? commandsConverter.toInternal(vscodeComment.command, disposables) : undefined, - isDraft: vscodeComment.isDraft, - commentReactions: vscodeComment.commentReactions ? vscodeComment.commentReactions.map(reaction => convertToReaction(provider, reaction)) : undefined - }; -} - -function convertToReaction(provider: vscode.DocumentCommentProvider | vscode.WorkspaceCommentProvider, reaction: vscode.CommentReaction): modes.CommentReaction { - const providerCanDeleteReaction = !!(provider as vscode.DocumentCommentProvider).deleteReaction; - const providerCanAddReaction = !!(provider as vscode.DocumentCommentProvider).addReaction; - - return { - label: reaction.label, - iconPath: reaction.iconPath ? extHostTypeConverter.pathOrURIToURI(reaction.iconPath) : undefined, - count: reaction.count, - hasReacted: reaction.hasReacted, - canEdit: (reaction.hasReacted && providerCanDeleteReaction) || (!reaction.hasReacted && providerCanAddReaction) - }; -} - -function convertToReaction2(provider: vscode.CommentReactionProvider | undefined, reaction: vscode.CommentReaction): modes.CommentReaction { +function convertToReaction(provider: vscode.CommentReactionProvider | undefined, reaction: vscode.CommentReaction): modes.CommentReaction { return { label: reaction.label, iconPath: reaction.iconPath ? extHostTypeConverter.pathOrURIToURI(reaction.iconPath) : undefined, diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index df40f2256ff..80fef1bdda0 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -237,8 +237,7 @@ export namespace MarkdownString { const resUris: { [href: string]: UriComponents } = Object.create(null); res.uris = resUris; - const renderer = new marked.Renderer(); - renderer.image = renderer.link = (href: string): string => { + const collectUri = (href: string): string => { try { let uri = URI.parse(href, true); uri = uri.with({ query: _uriMassage(uri.query, resUris) }); @@ -248,6 +247,10 @@ export namespace MarkdownString { } return ''; }; + const renderer = new marked.Renderer(); + renderer.link = collectUri; + renderer.image = href => collectUri(htmlContent.parseHrefAndDimensions(href).href); + marked(res.value, { renderer }); return res; diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index 2f4f10c13b8..cc83d789340 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -18,8 +18,6 @@ import { generateUuid } from 'vs/base/common/uuid'; type IconPath = URI | { light: URI, dark: URI }; export class ExtHostWebview implements vscode.Webview { - private readonly _uuid: string = generateUuid(); - private _html: string; private _isDisposed: boolean = false; @@ -38,7 +36,7 @@ export class ExtHostWebview implements vscode.Webview { } public toWebviewResource(resource: vscode.Uri): vscode.Uri { - return toWebviewResource(this._initData, this._uuid, resource); + return toWebviewResource(this._initData, this._handle, resource); } public get cspSource(): string { @@ -234,10 +232,9 @@ export class ExtHostWebviewPanel implements vscode.WebviewPanel { } export class ExtHostWebviews implements ExtHostWebviewsShape { - private static webviewHandlePool = 1; private static newHandle(): WebviewPanelHandle { - return ExtHostWebviews.webviewHandlePool++ + ''; + return generateUuid(); } private readonly _proxy: MainThreadWebviewsShape; diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 2f2235d1a71..6b05ef0759e 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -688,12 +688,6 @@ export function createApiFactory( registerTextSearchProvider: proposedApiFunction(extension, (scheme: string, provider: vscode.TextSearchProvider) => { return extHostSearch.registerTextSearchProvider(scheme, provider); }), - registerDocumentCommentProvider: proposedApiFunction(extension, (provider: vscode.DocumentCommentProvider) => { - return extHostComment.registerDocumentCommentProvider(extension.identifier, provider); - }), - registerWorkspaceCommentProvider: proposedApiFunction(extension, (provider: vscode.WorkspaceCommentProvider) => { - return extHostComment.registerWorkspaceCommentProvider(extension.identifier, provider); - }), registerRemoteAuthorityResolver: proposedApiFunction(extension, (authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver) => { return extensionService.registerRemoteAuthorityResolver(authorityPrefix, resolver); }), @@ -718,7 +712,7 @@ export function createApiFactory( } }; - const comment: typeof vscode.comment = { + const comment: typeof vscode.comments = { createCommentController(id: string, label: string) { return extHostComment.createCommentController(extension, id, label); } diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index 7d0376877c1..365faea190f 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -9,7 +9,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import * as glob from 'vs/base/common/glob'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { posix } from 'vs/base/common/path'; import { basename, dirname, isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; @@ -55,7 +55,7 @@ export interface SelectEvent { export abstract class BreadcrumbsPicker { - protected readonly _disposables = new Array(); + protected readonly _disposables = new DisposableStore(); protected readonly _domNode: HTMLDivElement; protected _arrow: HTMLDivElement; protected _treeContainer: HTMLDivElement; @@ -81,7 +81,7 @@ export abstract class BreadcrumbsPicker { } dispose(): void { - dispose(this._disposables); + this._disposables.dispose(); this._onDidPickElement.dispose(); this._tree.dispose(); } @@ -105,7 +105,7 @@ export abstract class BreadcrumbsPicker { this._layoutInfo = { maxHeight, width, arrowSize, arrowOffset, inputHeight: 0 }; this._tree = this._createTree(this._treeContainer); - this._disposables.push(this._tree.onDidChangeSelection(e => { + this._disposables.add(this._tree.onDidChangeSelection(e => { if (e.browserEvent !== this._fakeEvent) { const target = this._getTargetFromEvent(e.elements[0]); if (target) { @@ -115,13 +115,13 @@ export abstract class BreadcrumbsPicker { } } })); - this._disposables.push(this._tree.onDidChangeFocus(e => { + this._disposables.add(this._tree.onDidChangeFocus(e => { const target = this._getTargetFromEvent(e.elements[0]); if (target) { this._onDidFocusElement.fire({ target, browserEvent: e.browserEvent || new UIEvent('fake') }); } })); - this._disposables.push(this._tree.onDidChangeContentHeight(() => { + this._disposables.add(this._tree.onDidChangeContentHeight(() => { this._layout(); })); @@ -276,7 +276,7 @@ class FileNavigationLabelProvider implements IKeyboardNavigationLabelProvider { private readonly _cachedExpressions = new Map(); - private readonly _disposables: IDisposable[] = []; + private readonly _disposables = new DisposableStore(); constructor( @IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService, @@ -306,15 +306,13 @@ class FileFilter implements ITreeFilter { }); }; update(); - this._disposables.push( - config, - config.onDidChange(update), - _workspaceService.onDidChangeWorkspaceFolders(update) - ); + this._disposables.add(config); + this._disposables.add(config.onDidChange(update)); + this._disposables.add(_workspaceService.onDidChangeWorkspaceFolders(update)); } dispose(): void { - dispose(this._disposables); + this._disposables.dispose(); } filter(element: IWorkspaceFolder | IFileStat, _parentVisibility: TreeVisibility): boolean { @@ -371,11 +369,11 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { dom.toggleClass(this._treeContainer, 'align-icons-and-twisties', fileIconTheme.hasFileIcons && !fileIconTheme.hasFolderIcons); dom.toggleClass(this._treeContainer, 'hide-arrows', fileIconTheme.hidesExplorerArrows === true); }; - this._disposables.push(this._themeService.onDidFileIconThemeChange(onFileIconThemeChange)); + this._disposables.add(this._themeService.onDidFileIconThemeChange(onFileIconThemeChange)); onFileIconThemeChange(this._themeService.getFileIconTheme()); const labels = this._instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER /* TODO@Jo visibility propagation */); - this._disposables.push(labels); + this._disposables.add(labels); return this._instantiationService.createInstance(WorkbenchAsyncDataTree, container, new FileVirtualDelegate(), [this._instantiationService.createInstance(FileRenderer, labels)], this._instantiationService.createInstance(FileDataSource), { multipleSelectionSupport: false, diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index f57adac50c7..e1a7d9d0fec 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -1018,7 +1018,7 @@ export class ChangeModeAction extends Action { // If the association is already being made in the workspace, make sure to target workspace settings let target = ConfigurationTarget.USER; - if (fileAssociationsConfig.workspace && !!fileAssociationsConfig.workspace[associationKey]) { + if (fileAssociationsConfig.workspace && !!(fileAssociationsConfig.workspace as any)[associationKey]) { target = ConfigurationTarget.WORKSPACE; } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts index c4ef92629a4..16f7f408a3e 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts @@ -145,9 +145,7 @@ export class CopyNotificationMessageAction extends Action { } run(notification: INotificationViewItem): Promise { - this.clipboardService.writeText(notification.message.raw); - - return Promise.resolve(); + return this.clipboardService.writeText(notification.message.raw); } } diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index dc0f272fbcb..1c22b849fc3 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -8,7 +8,6 @@ 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 { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; // tslint:disable-next-line: import-patterns no-standalone-editor import { IDownloadService } from 'vs/platform/download/common/download'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -32,12 +31,6 @@ import { IRemoteConsoleLog } from 'vs/base/common/console'; // tslint:disable-next-line: import-patterns // tslint:disable-next-line: import-patterns import { IExtensionsWorkbenchService, IExtension as IExtension2 } from 'vs/workbench/contrib/extensions/common/extensions'; -// tslint:disable-next-line: import-patterns -import { ICommentService, IResourceCommentThreadEvent, IWorkspaceCommentThreadsEvent } from 'vs/workbench/contrib/comments/browser/commentService'; -// tslint:disable-next-line: import-patterns -import { ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel'; -import { CommentingRanges } from 'vs/editor/common/modes'; -import { Range } from 'vs/editor/common/core/range'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { addDisposableListener, EventType } from 'vs/base/browser/dom'; import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; @@ -49,45 +42,6 @@ import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/pla import { IProcessEnvironment } from 'vs/base/common/platform'; import { toStoreData, restoreRecentlyOpened } from 'vs/platform/history/common/historyStorage'; -//#region Clipboard - -export class SimpleClipboardService implements IClipboardService { - - _serviceBrand: any; - - writeText(text: string, type?: string): void { } - - readText(type?: string): string { - // @ts-ignore - return undefined; - } - - readTextSync(): string | undefined { - return undefined; - } - - readFindText(): string { - // @ts-ignore - return undefined; - } - - writeFindText(text: string): void { } - - writeResources(resources: URI[]): void { } - - readResources(): URI[] { - return []; - } - - hasResources(): boolean { - return false; - } -} - -registerSingleton(IClipboardService, SimpleClipboardService, true); - -//#endregion - //#region Download export class SimpleDownloadService implements IDownloadService { @@ -194,51 +148,6 @@ export class SimpleExtensionsWorkbenchService implements IExtensionsWorkbenchSer registerSingleton(IExtensionsWorkbenchService, SimpleExtensionsWorkbenchService, true); //#endregion -//#region ICommentService -export class SimpleCommentService implements ICommentService { - _serviceBrand: any; - onDidSetResourceCommentInfos: Event = Event.None; - onDidSetAllCommentThreads: Event = Event.None; - onDidUpdateCommentThreads: Event = Event.None; - onDidChangeActiveCommentingRange: Event<{ range: Range; commentingRangesInfo: CommentingRanges; }> = Event.None; - onDidChangeActiveCommentThread: Event = Event.None; - onDidSetDataProvider: Event = Event.None; - onDidDeleteDataProvider: Event = Event.None; - setDocumentComments: any; - setWorkspaceComments: any; - removeWorkspaceComments: any; - registerCommentController: any; - unregisterCommentController: any; - getCommentController: any; - createCommentThreadTemplate: any; - updateCommentThreadTemplate: any; - getCommentMenus: any; - registerDataProvider: any; - unregisterDataProvider: any; - updateComments: any; - disposeCommentThread: any; - createNewCommentThread: any; - replyToCommentThread: any; - editComment: any; - deleteComment: any; - getComments() { return Promise.resolve([]); } - getCommentingRanges: any; - startDraft: any; - deleteDraft: any; - finishDraft: any; - getStartDraftLabel: any; - getDeleteDraftLabel: any; - getFinishDraftLabel: any; - addReaction: any; - deleteReaction: any; - getReactionGroup: any; - hasReactionHandler: any; - toggleReaction: any; - setActiveCommentThread: any; -} -registerSingleton(ICommentService, SimpleCommentService, true); -//#endregion - //#region Extension Management //#region Extension Enablement diff --git a/src/vs/workbench/contrib/comments/browser/commentMenus.ts b/src/vs/workbench/contrib/comments/browser/commentMenus.ts index bdcf3118728..7494c2e6277 100644 --- a/src/vs/workbench/contrib/comments/browser/commentMenus.ts +++ b/src/vs/workbench/contrib/comments/browser/commentMenus.ts @@ -9,7 +9,7 @@ import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions' import { IAction } from 'vs/base/common/actions'; import { MainThreadCommentController } from 'vs/workbench/api/browser/mainThreadComments'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { Comment, CommentThread2 } from 'vs/editor/common/modes'; +import { Comment, CommentThread } from 'vs/editor/common/modes'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; export class CommentMenus implements IDisposable { @@ -24,11 +24,11 @@ export class CommentMenus implements IDisposable { commentControllerKey.set(controller.contextValue); } - getCommentThreadTitleActions(commentThread: CommentThread2, contextKeyService: IContextKeyService): IMenu { + getCommentThreadTitleActions(commentThread: CommentThread, contextKeyService: IContextKeyService): IMenu { return this.getMenu(MenuId.CommentThreadTitle, contextKeyService); } - getCommentThreadActions(commentThread: CommentThread2, contextKeyService: IContextKeyService): IMenu { + getCommentThreadActions(commentThread: CommentThread, contextKeyService: IContextKeyService): IMenu { return this.getMenu(MenuId.CommentThreadActions, contextKeyService); } diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 93a0acf25bf..0b6b347c978 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -7,7 +7,6 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import * as modes from 'vs/editor/common/modes'; import { ActionsOrientation, ActionViewItem, ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { Button } from 'vs/base/browser/ui/button/button'; import { Action, IActionRunner, IAction } from 'vs/base/common/actions'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; @@ -16,23 +15,17 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { inputValidationErrorBorder } from 'vs/platform/theme/common/colorRegistry'; -import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; import { SimpleCommentEditor } from 'vs/workbench/contrib/comments/browser/simpleCommentEditor'; import { Selection } from 'vs/editor/common/core/selection'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { Emitter, Event } from 'vs/base/common/event'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { assign } from 'vs/base/common/objects'; -import { MarkdownString } from 'vs/base/common/htmlContent'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { ToggleReactionsAction, ReactionAction, ReactionActionViewItem } from './reactionsAction'; -import { ICommandService } from 'vs/platform/commands/common/commands'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget'; import { MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; @@ -41,9 +34,6 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { CommentFormActions } from 'vs/workbench/contrib/comments/browser/commentFormActions'; -const UPDATE_COMMENT_LABEL = nls.localize('label.updateComment', "Update comment"); -const UPDATE_IN_PROGRESS_LABEL = nls.localize('label.updatingComment', "Updating comment..."); - export class CommentNode extends Disposable { private _domNode: HTMLElement; private _body: HTMLElement; @@ -59,13 +49,10 @@ export class CommentNode extends Disposable { private _commentEditor: SimpleCommentEditor | null; private _commentEditorDisposables: IDisposable[] = []; private _commentEditorModel: ITextModel; - private _updateCommentButton: Button; - private _errorEditingContainer: HTMLElement; private _isPendingLabel: HTMLElement; private _contextKeyService: IContextKeyService; private _commentContextValue: IContextKey; - private _deleteAction: Action; protected actionRunner?: IActionRunner; protected toolbar: ToolBar; private _commentFormActions: CommentFormActions; @@ -79,7 +66,7 @@ export class CommentNode extends Disposable { public isEditing: boolean; constructor( - private commentThread: modes.CommentThread | modes.CommentThread2, + private commentThread: modes.CommentThread, public comment: modes.Comment, private owner: string, private resource: URI, @@ -89,10 +76,8 @@ export class CommentNode extends Disposable { @IThemeService private themeService: IThemeService, @IInstantiationService private instantiationService: IInstantiationService, @ICommentService private commentService: ICommentService, - @ICommandService private commandService: ICommandService, @IModelService private modelService: IModelService, @IModeService private modeService: IModeService, - @IDialogService private dialogService: IDialogService, @IKeybindingService private keybindingService: IKeybindingService, @INotificationService private notificationService: INotificationService, @IContextMenuService private contextMenuService: IContextMenuService, @@ -141,8 +126,6 @@ export class CommentNode extends Disposable { if (this.comment.label) { this._isPendingLabel.innerText = this.comment.label; - } else if (this.comment.isDraft) { - this._isPendingLabel.innerText = 'Pending'; } else { this._isPendingLabel.innerText = ''; } @@ -160,29 +143,6 @@ export class CommentNode extends Disposable { if (hasReactionHandler) { let toggleReactionAction = this.createReactionPicker2(this.comment.commentReactions || []); actions.push(toggleReactionAction); - } else { - let reactionGroup = this.commentService.getReactionGroup(this.owner); - if (reactionGroup && reactionGroup.length) { - let commentThread = this.commentThread as modes.CommentThread2; - if (commentThread.commentThreadHandle !== undefined) { - let reactionGroup = this.commentService.getReactionGroup(this.owner); - let toggleReactionAction = this.createReactionPicker2(reactionGroup || []); - actions.push(toggleReactionAction); - } else { - let toggleReactionAction = this.createReactionPicker(); - actions.push(toggleReactionAction); - } - } - } - - if (this.comment.canEdit || this.comment.editCommand) { - this._editAction = this.createEditAction(this._commentDetailsContainer); - actions.push(this._editAction); - } - - if (this.comment.canDelete || this.comment.deleteCommand) { - this._deleteAction = this.createDeleteAction(); - actions.push(this._deleteAction); } let commentMenus = this.commentService.getCommentMenus(this.owner); @@ -273,7 +233,7 @@ export class CommentNode extends Disposable { reactionMenuActions = reactionGroup.map((reaction) => { return new Action(`reaction.command.${reaction.label}`, `${reaction.label}`, '', true, async () => { try { - await this.commentService.toggleReaction(this.owner, this.resource, this.commentThread as modes.CommentThread2, this.comment, reaction); + await this.commentService.toggleReaction(this.owner, this.resource, this.commentThread, this.comment, reaction); } catch (e) { const error = e.message ? nls.localize('commentToggleReactionError', "Toggling the comment reaction failed: {0}.", e.message) @@ -305,52 +265,6 @@ export class CommentNode extends Disposable { return toggleReactionAction; } - private createReactionPicker(): ToggleReactionsAction { - let toggleReactionActionViewItem: DropdownMenuActionViewItem; - let toggleReactionAction = this._register(new ToggleReactionsAction(() => { - if (toggleReactionActionViewItem) { - toggleReactionActionViewItem.show(); - } - }, nls.localize('commentAddReaction', "Add Reaction"))); - - let reactionMenuActions: Action[] = []; - let reactionGroup = this.commentService.getReactionGroup(this.owner); - if (reactionGroup && reactionGroup.length) { - reactionMenuActions = reactionGroup.map((reaction) => { - return new Action(`reaction.command.${reaction.label}`, `${reaction.label}`, '', true, async () => { - try { - await this.commentService.addReaction(this.owner, this.resource, this.comment, reaction); - } catch (e) { - const error = e.message - ? nls.localize('commentAddReactionError', "Deleting the comment reaction failed: {0}.", e.message) - : nls.localize('commentAddReactionDefaultError', "Deleting the comment reaction failed"); - this.notificationService.error(error); - } - }); - }); - } - - toggleReactionAction.menuActions = reactionMenuActions; - - toggleReactionActionViewItem = new DropdownMenuActionViewItem( - toggleReactionAction, - (toggleReactionAction).menuActions, - this.contextMenuService, - action => { - if (action.id === ToggleReactionsAction.ID) { - return toggleReactionActionViewItem; - } - return this.actionViewItemProvider(action as Action); - }, - this.actionRunner!, - undefined, - 'toolbar-toggle-pickReactions', - () => { return AnchorAlignment.RIGHT; } - ); - - return toggleReactionAction; - } - private createReactionsContainer(commentDetailsContainer: HTMLElement): void { this._reactionActionsContainer = dom.append(commentDetailsContainer, dom.$('div.comment-reactions')); this._reactionsActionBar = new ActionBar(this._reactionActionsContainer, { @@ -378,16 +292,7 @@ export class CommentNode extends Disposable { this.comment.commentReactions!.filter(reaction => !!reaction.count).map(reaction => { let action = new ReactionAction(`reaction.${reaction.label}`, `${reaction.label}`, reaction.hasReacted && (reaction.canEdit || hasReactionHandler) ? 'active' : '', (reaction.canEdit || hasReactionHandler), async () => { try { - let commentThread = this.commentThread as modes.CommentThread2; - if (commentThread.commentThreadHandle !== undefined) { - await this.commentService.toggleReaction(this.owner, this.resource, this.commentThread as modes.CommentThread2, this.comment, reaction); - } else { - if (reaction.hasReacted) { - await this.commentService.deleteReaction(this.owner, this.resource, this.comment, reaction); - } else { - await this.commentService.addReaction(this.owner, this.resource, this.comment, reaction); - } - } + await this.commentService.toggleReaction(this.owner, this.resource, this.commentThread, this.comment, reaction); } catch (e) { let error: string; @@ -415,14 +320,8 @@ export class CommentNode extends Disposable { } else { let reactionGroup = this.commentService.getReactionGroup(this.owner); if (reactionGroup && reactionGroup.length) { - let commentThread = this.commentThread as modes.CommentThread2; - if (commentThread.commentThreadHandle !== undefined) { - let toggleReactionAction = this.createReactionPicker2(reactionGroup || []); - this._reactionsActionBar.push(toggleReactionAction, { label: false, icon: true }); - } else { - let toggleReactionAction = this.createReactionPicker(); - this._reactionsActionBar.push(toggleReactionAction, { label: false, icon: true }); - } + let toggleReactionAction = this.createReactionPicker2(reactionGroup || []); + this._reactionsActionBar.push(toggleReactionAction, { label: false, icon: true }); } } } @@ -442,34 +341,32 @@ export class CommentNode extends Disposable { const lastColumn = this._commentEditorModel.getLineContent(lastLine).length + 1; this._commentEditor.setSelection(new Selection(lastLine, lastColumn, lastLine, lastColumn)); - let commentThread = this.commentThread as modes.CommentThread2; - if (commentThread.commentThreadHandle !== undefined) { + let commentThread = this.commentThread; + commentThread.input = { + uri: this._commentEditor.getModel()!.uri, + value: this.comment.body.value + }; + this.commentService.setActiveCommentThread(commentThread); + + this._commentEditorDisposables.push(this._commentEditor.onDidFocusEditorWidget(() => { commentThread.input = { - uri: this._commentEditor.getModel()!.uri, + uri: this._commentEditor!.getModel()!.uri, value: this.comment.body.value }; this.commentService.setActiveCommentThread(commentThread); + })); - this._commentEditorDisposables.push(this._commentEditor.onDidFocusEditorWidget(() => { - commentThread.input = { - uri: this._commentEditor!.getModel()!.uri, - value: this.comment.body.value - }; - this.commentService.setActiveCommentThread(commentThread); - })); - - this._commentEditorDisposables.push(this._commentEditor.onDidChangeModelContent(e => { - if (commentThread.input && this._commentEditor && this._commentEditor.getModel()!.uri === commentThread.input.uri) { - let newVal = this._commentEditor.getValue(); - if (newVal !== commentThread.input.value) { - let input = commentThread.input; - input.value = newVal; - commentThread.input = input; - this.commentService.setActiveCommentThread(commentThread); - } + this._commentEditorDisposables.push(this._commentEditor.onDidChangeModelContent(e => { + if (commentThread.input && this._commentEditor && this._commentEditor.getModel()!.uri === commentThread.input.uri) { + let newVal = this._commentEditor.getValue(); + if (newVal !== commentThread.input.value) { + let input = commentThread.input; + input.value = newVal; + commentThread.input = input; + this.commentService.setActiveCommentThread(commentThread); } - })); - } + } + })); this._register(this._commentEditor); this._register(this._commentEditorModel); @@ -496,85 +393,6 @@ export class CommentNode extends Disposable { this._commentEditContainer.remove(); } - async editComment(): Promise { - if (!this._commentEditor) { - throw new Error('No comment editor'); - } - - this._updateCommentButton.enabled = false; - this._updateCommentButton.label = UPDATE_IN_PROGRESS_LABEL; - - try { - const newBody = this._commentEditor.getValue(); - - if (this.comment.editCommand) { - let commentThread = this.commentThread as modes.CommentThread2; - commentThread.input = { - uri: this._commentEditor.getModel()!.uri, - value: newBody - }; - this.commentService.setActiveCommentThread(commentThread); - let commandId = this.comment.editCommand.id; - let args = this.comment.editCommand.arguments || []; - - await this.commandService.executeCommand(commandId, ...args); - } else { - await this.commentService.editComment(this.owner, this.resource, this.comment, newBody); - } - - this._updateCommentButton.enabled = true; - this._updateCommentButton.label = UPDATE_COMMENT_LABEL; - this._commentEditor.getDomNode()!.style.outline = ''; - this.removeCommentEditor(); - const editedComment = assign({}, this.comment, { body: new MarkdownString(newBody) }); - this.update(editedComment); - } catch (e) { - this._updateCommentButton.enabled = true; - this._updateCommentButton.label = UPDATE_COMMENT_LABEL; - - this._commentEditor.getDomNode()!.style.outline = `1px solid ${this.themeService.getTheme().getColor(inputValidationErrorBorder)}`; - this._errorEditingContainer.textContent = e.message - ? nls.localize('commentEditError', "Updating the comment failed: {0}.", e.message) - : nls.localize('commentEditDefaultError', "Updating the comment failed."); - this._errorEditingContainer.classList.remove('hidden'); - this._commentEditor.focus(); - } - } - - private createDeleteAction(): Action { - return new Action('comment.delete', nls.localize('label.delete', "Delete"), 'delete', true, () => { - return this.dialogService.confirm({ - message: nls.localize('confirmDelete', "Delete comment?"), - type: 'question', - primaryButton: nls.localize('label.delete', "Delete") - }).then(async result => { - if (result.confirmed) { - try { - if (this.comment.deleteCommand) { - this.commentService.setActiveCommentThread(this.commentThread); - let commandId = this.comment.deleteCommand.id; - let args = this.comment.deleteCommand.arguments || []; - - await this.commandService.executeCommand(commandId, ...args); - } else { - const didDelete = await this.commentService.deleteComment(this.owner, this.resource, this.comment); - if (didDelete) { - this._onDidDelete.fire(this); - } else { - throw Error(); - } - } - } catch (e) { - const error = e.message - ? nls.localize('commentDeletionError', "Deleting the comment failed: {0}.", e.message) - : nls.localize('commentDeletionDefaultError', "Deleting the comment failed"); - this.notificationService.error(error); - } - } - }); - }); - } - public switchToEditMode() { if (this.isEditing) { return; @@ -584,7 +402,6 @@ export class CommentNode extends Disposable { this._body.classList.add('hidden'); this._commentEditContainer = dom.append(this._commentDetailsContainer, dom.$('.edit-container')); this.createCommentEditor(); - this._errorEditingContainer = dom.append(this._commentEditContainer, dom.$('.validation-error.hidden')); const formActions = dom.append(this._commentEditContainer, dom.$('.form-actions')); const menus = this.commentService.getCommentMenus(this.owner); @@ -611,45 +428,6 @@ export class CommentNode extends Disposable { this._commentFormActions.setActions(menu); } - private createEditAction(commentDetailsContainer: HTMLElement): Action { - return new Action('comment.edit', nls.localize('label.edit', "Edit"), 'edit', true, () => { - return this.editCommentAction(commentDetailsContainer); - }); - } - - private editCommentAction(commentDetailsContainer: HTMLElement) { - this.isEditing = true; - this._body.classList.add('hidden'); - this._commentEditContainer = dom.append(commentDetailsContainer, dom.$('.edit-container')); - this.createCommentEditor(); - - this._errorEditingContainer = dom.append(this._commentEditContainer, dom.$('.validation-error.hidden')); - const formActions = dom.append(this._commentEditContainer, dom.$('.form-actions')); - - const cancelEditButton = new Button(formActions); - cancelEditButton.label = nls.localize('label.cancel', "Cancel"); - this._register(attachButtonStyler(cancelEditButton, this.themeService)); - - this._register(cancelEditButton.onDidClick(_ => { - this.removeCommentEditor(); - })); - - this._updateCommentButton = new Button(formActions); - this._updateCommentButton.label = UPDATE_COMMENT_LABEL; - this._register(attachButtonStyler(this._updateCommentButton, this.themeService)); - - this._register(this._updateCommentButton.onDidClick(_ => { - this.editComment(); - })); - - this._commentEditorDisposables.push(this._commentEditor!.onDidChangeModelContent(_ => { - this._updateCommentButton.enabled = !!this._commentEditor!.getValue(); - })); - - this._editAction.enabled = false; - return Promise.resolve(); - } - private registerActionBarListeners(actionsContainer: HTMLElement): void { this._register(dom.addDisposableListener(this._domNode, 'mouseenter', () => { actionsContainer.classList.remove('hidden'); @@ -664,16 +442,6 @@ export class CommentNode extends Disposable { actionsContainer.classList.add('hidden'); } })); - - this._register(dom.addDisposableListener(this._domNode, 'focusout', (e: FocusEvent) => { - if (!this._domNode.contains((e.relatedTarget))) { - actionsContainer.classList.add('hidden'); - - if (this._commentEditor && this._commentEditor.getValue() === this.comment.body.value) { - this.removeCommentEditor(); - } - } - })); } update(newComment: modes.Comment) { @@ -692,19 +460,10 @@ export class CommentNode extends Disposable { } } - const shouldUpdateActions = newComment.editCommand !== this.comment.editCommand || newComment.deleteCommand !== this.comment.deleteCommand; this.comment = newComment; - if (shouldUpdateActions) { - dom.clearNode(this._actionsToolbarContainer); - this.createActionsToolbar(); - } - - if (newComment.label) { this._isPendingLabel.innerText = newComment.label; - } else if (newComment.isDraft) { - this._isPendingLabel.innerText = 'Pending'; } else { this._isPendingLabel.innerText = ''; } diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index cc20345b198..d3db753315b 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -3,13 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CommentThread, DocumentCommentProvider, CommentThreadChangedEvent, CommentInfo, Comment, CommentReaction, CommentingRanges, CommentThread2 } from 'vs/editor/common/modes'; +import { CommentThreadChangedEvent, CommentInfo, Comment, CommentReaction, CommentingRanges, CommentThread } from 'vs/editor/common/modes'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { Range, IRange } from 'vs/editor/common/core/range'; -import { keys } from 'vs/base/common/map'; import { CancellationToken } from 'vs/base/common/cancellation'; import { assign } from 'vs/base/common/objects'; import { ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel'; @@ -43,7 +42,7 @@ export interface ICommentService { readonly onDidSetDataProvider: Event; readonly onDidDeleteDataProvider: Event; setDocumentComments(resource: URI, commentInfos: ICommentInfo[]): void; - setWorkspaceComments(owner: string, commentsByResource: CommentThread[] | CommentThread2[]): void; + setWorkspaceComments(owner: string, commentsByResource: CommentThread[]): void; removeWorkspaceComments(owner: string): void; registerCommentController(owner: string, commentControl: MainThreadCommentController): void; unregisterCommentController(owner: string): void; @@ -51,27 +50,13 @@ export interface ICommentService { createCommentThreadTemplate(owner: string, resource: URI, range: Range): void; updateCommentThreadTemplate(owner: string, threadHandle: number, range: Range): Promise; getCommentMenus(owner: string): CommentMenus; - registerDataProvider(owner: string, commentProvider: DocumentCommentProvider): void; - unregisterDataProvider(owner: string): void; updateComments(ownerId: string, event: CommentThreadChangedEvent): void; disposeCommentThread(ownerId: string, threadId: string): void; - createNewCommentThread(owner: string, resource: URI, range: Range, text: string): Promise; - replyToCommentThread(owner: string, resource: URI, range: Range, thread: CommentThread, text: string): Promise; - editComment(owner: string, resource: URI, comment: Comment, text: string): Promise; - deleteComment(owner: string, resource: URI, comment: Comment): Promise; getComments(resource: URI): Promise<(ICommentInfo | null)[]>; getCommentingRanges(resource: URI): Promise; - startDraft(owner: string, resource: URI): void; - deleteDraft(owner: string, resource: URI): void; - finishDraft(owner: string, resource: URI): void; - getStartDraftLabel(owner: string): string | undefined; - getDeleteDraftLabel(owner: string): string | undefined; - getFinishDraftLabel(owner: string): string | undefined; - addReaction(owner: string, resource: URI, comment: Comment, reaction: CommentReaction): Promise; - deleteReaction(owner: string, resource: URI, comment: Comment, reaction: CommentReaction): Promise; getReactionGroup(owner: string): CommentReaction[] | undefined; hasReactionHandler(owner: string): boolean; - toggleReaction(owner: string, resource: URI, thread: CommentThread2, comment: Comment, reaction: CommentReaction): Promise; + toggleReaction(owner: string, resource: URI, thread: CommentThread, comment: Comment, reaction: CommentReaction): Promise; setActiveCommentThread(commentThread: CommentThread | null): void; } @@ -105,8 +90,6 @@ export class CommentService extends Disposable implements ICommentService { }>()); readonly onDidChangeActiveCommentingRange: Event<{ range: Range, commentingRangesInfo: CommentingRanges }> = this._onDidChangeActiveCommentingRange.event; - private _commentProviders = new Map(); - private _commentControls = new Map(); private _commentMenus = new Map(); @@ -185,112 +168,12 @@ export class CommentService extends Disposable implements ICommentService { return menu; } - registerDataProvider(owner: string, commentProvider: DocumentCommentProvider): void { - this._commentProviders.set(owner, commentProvider); - this._onDidSetDataProvider.fire(); - } - - unregisterDataProvider(owner: string): void { - this._commentProviders.delete(owner); - this._onDidDeleteDataProvider.fire(owner); - } - updateComments(ownerId: string, event: CommentThreadChangedEvent): void { const evt: ICommentThreadChangedEvent = assign({}, event, { owner: ownerId }); this._onDidUpdateCommentThreads.fire(evt); } - async createNewCommentThread(owner: string, resource: URI, range: Range, text: string): Promise { - const commentProvider = this._commentProviders.get(owner); - - if (commentProvider) { - return await commentProvider.createNewCommentThread(resource, range, text, CancellationToken.None); - } - - return null; - } - - async replyToCommentThread(owner: string, resource: URI, range: Range, thread: CommentThread, text: string): Promise { - const commentProvider = this._commentProviders.get(owner); - - if (commentProvider) { - return await commentProvider.replyToCommentThread(resource, range, thread, text, CancellationToken.None); - } - - return null; - } - - editComment(owner: string, resource: URI, comment: Comment, text: string): Promise { - const commentProvider = this._commentProviders.get(owner); - - if (commentProvider) { - return commentProvider.editComment(resource, comment, text, CancellationToken.None); - } - - return Promise.resolve(undefined); - } - - deleteComment(owner: string, resource: URI, comment: Comment): Promise { - const commentProvider = this._commentProviders.get(owner); - - if (commentProvider) { - return commentProvider.deleteComment(resource, comment, CancellationToken.None).then(() => true); - } - - return Promise.resolve(false); - } - - async startDraft(owner: string, resource: URI): Promise { - const commentProvider = this._commentProviders.get(owner); - - if (commentProvider && commentProvider.startDraft) { - return commentProvider.startDraft(resource, CancellationToken.None); - } else { - throw new Error('Not supported'); - } - } - - async deleteDraft(owner: string, resource: URI): Promise { - const commentProvider = this._commentProviders.get(owner); - - if (commentProvider && commentProvider.deleteDraft) { - return commentProvider.deleteDraft(resource, CancellationToken.None); - } else { - throw new Error('Not supported'); - } - } - - async finishDraft(owner: string, resource: URI): Promise { - const commentProvider = this._commentProviders.get(owner); - - if (commentProvider && commentProvider.finishDraft) { - return commentProvider.finishDraft(resource, CancellationToken.None); - } else { - throw new Error('Not supported'); - } - } - - async addReaction(owner: string, resource: URI, comment: Comment, reaction: CommentReaction): Promise { - const commentProvider = this._commentProviders.get(owner); - - if (commentProvider && commentProvider.addReaction) { - return commentProvider.addReaction(resource, comment, reaction, CancellationToken.None); - } else { - throw new Error('Not supported'); - } - } - - async deleteReaction(owner: string, resource: URI, comment: Comment, reaction: CommentReaction): Promise { - const commentProvider = this._commentProviders.get(owner); - - if (commentProvider && commentProvider.deleteReaction) { - return commentProvider.deleteReaction(resource, comment, reaction, CancellationToken.None); - } else { - throw new Error('Not supported'); - } - } - - async toggleReaction(owner: string, resource: URI, thread: CommentThread2, comment: Comment, reaction: CommentReaction): Promise { + async toggleReaction(owner: string, resource: URI, thread: CommentThread, comment: Comment, reaction: CommentReaction): Promise { const commentController = this._commentControls.get(owner); if (commentController) { @@ -326,57 +209,7 @@ export class CommentService extends Disposable implements ICommentService { return false; } - getStartDraftLabel(owner: string): string | undefined { - const commentProvider = this._commentProviders.get(owner); - - if (commentProvider) { - return commentProvider.startDraftLabel; - } - - return undefined; - } - - getDeleteDraftLabel(owner: string): string | undefined { - const commentProvider = this._commentProviders.get(owner); - - if (commentProvider) { - return commentProvider.deleteDraftLabel; - } - - return undefined; - } - - getFinishDraftLabel(owner: string): string | undefined { - const commentProvider = this._commentProviders.get(owner); - - if (commentProvider) { - return commentProvider.finishDraftLabel; - } - - return undefined; - } - async getComments(resource: URI): Promise<(ICommentInfo | null)[]> { - const result: Promise[] = []; - for (const owner of keys(this._commentProviders)) { - const provider = this._commentProviders.get(owner); - if (provider && provider.provideDocumentComments) { - result.push(provider.provideDocumentComments(resource, CancellationToken.None).then(commentInfo => { - if (commentInfo) { - return { - owner: owner, - threads: commentInfo.threads, - commentingRanges: commentInfo.commentingRanges, - reply: commentInfo.reply, - draftMode: commentInfo.draftMode - }; - } else { - return null; - } - })); - } - } - let commentControlResult: Promise[] = []; this._commentControls.forEach(control => { @@ -387,7 +220,7 @@ export class CommentService extends Disposable implements ICommentService { })); }); - return Promise.all([...result, ...commentControlResult]); + return Promise.all(commentControlResult); } async getCommentingRanges(resource: URI): Promise { diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 963d71a6a3d..67f7d8f2042 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -3,50 +3,47 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { Button } from 'vs/base/browser/ui/button/button'; import { Action, IAction } from 'vs/base/common/actions'; import * as arrays from 'vs/base/common/arrays'; import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; +import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { IPosition } from 'vs/editor/common/core/position'; +import { IRange, Range } from 'vs/editor/common/core/range'; +import { ITextModel } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; import { peekViewBorder } from 'vs/editor/contrib/referenceSearch/referencesWidget'; import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; -import { attachButtonStyler } from 'vs/platform/theme/common/styler'; -import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; -import { CommentGlyphWidget } from 'vs/workbench/contrib/comments/browser/commentGlyphWidget'; +import * as nls from 'vs/nls'; +import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenu, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { SimpleCommentEditor } from './simpleCommentEditor'; -import { URI } from 'vs/base/common/uri'; -import { transparent, editorForeground, textLinkActiveForeground, textLinkForeground, focusBorder, textBlockQuoteBackground, textBlockQuoteBorder, contrastBorder, inputValidationErrorBorder, inputValidationErrorBackground, inputValidationErrorForeground } from 'vs/platform/theme/common/colorRegistry'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; -import { Range, IRange } from 'vs/editor/common/core/range'; -import { IPosition } from 'vs/editor/common/core/position'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; -import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; -import { CommentNode } from 'vs/workbench/contrib/comments/browser/commentNode'; -import { ITextModel } from 'vs/editor/common/model'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { generateUuid } from 'vs/base/common/uuid'; -import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget'; -import { withNullAsUndefined } from 'vs/base/common/types'; -import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus'; -import { MenuItemAction, IMenu } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { contrastBorder, editorForeground, focusBorder, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground, transparent } from 'vs/platform/theme/common/colorRegistry'; +import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { CommentFormActions } from 'vs/workbench/contrib/comments/browser/commentFormActions'; +import { CommentGlyphWidget } from 'vs/workbench/contrib/comments/browser/commentGlyphWidget'; +import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus'; +import { CommentNode } from 'vs/workbench/contrib/comments/browser/commentNode'; +import { ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; +import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; +import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget'; +import { SimpleCommentEditor } from './simpleCommentEditor'; export const COMMENTEDITOR_DECORATION_KEY = 'commenteditordecoration'; const COLLAPSE_ACTION_CLASS = 'expand-review-action'; @@ -73,7 +70,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget private _collapseAction: Action; private _commentGlyph?: CommentGlyphWidget; private _submitActionsDisposables: IDisposable[]; - private _globalToDispose: IDisposable[]; + private readonly _globalToDispose = new DisposableStore(); private _commentThreadDisposables: IDisposable[] = []; private _markdownRenderer: MarkdownRenderer; private _styleElement: HTMLStyleElement; @@ -89,28 +86,22 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget return this._owner; } public get commentThread(): modes.CommentThread { - return this._commentThread as modes.CommentThread; + return this._commentThread; } public get extensionId(): string | undefined { return this._commentThread.extensionId; } - public get draftMode(): modes.DraftMode | undefined { - return this._draftMode; - } - private _commentMenus: CommentMenus; constructor( editor: ICodeEditor, private _owner: string, - private _commentThread: modes.CommentThread | modes.CommentThread2, + private _commentThread: modes.CommentThread, private _pendingComment: string, - private _draftMode: modes.DraftMode | undefined, @IInstantiationService private instantiationService: IInstantiationService, @IModeService private modeService: IModeService, - @ICommandService private commandService: ICommandService, @IModelService private modelService: IModelService, @IThemeService private themeService: IThemeService, @ICommentService private commentService: ICommentService, @@ -128,7 +119,6 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._resizeObserver = null; this._isExpanded = _commentThread.collapsibleState === modes.CommentThreadCollapsibleState.Expanded; - this._globalToDispose = []; this._commentThreadDisposables = []; this._submitActionsDisposables = []; this._formActions = null; @@ -136,15 +126,15 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this.create(); this._styleElement = dom.createStyleSheet(this.domNode); - this._globalToDispose.push(this.themeService.onThemeChange(this._applyTheme, this)); - this._globalToDispose.push(this.editor.onDidChangeConfiguration(e => { + this._globalToDispose.add(this.themeService.onThemeChange(this._applyTheme, this)); + this._globalToDispose.add(this.editor.onDidChangeConfiguration(e => { if (e.fontInfo) { this._applyTheme(this.themeService.getTheme()); } })); this._applyTheme(this.themeService.getTheme()); - this._markdownRenderer = new MarkdownRenderer(editor, this.modeService, this.openerService); + this._markdownRenderer = this._globalToDispose.add(new MarkdownRenderer(editor, this.modeService, this.openerService)); this._parentEditor = editor; } @@ -240,17 +230,13 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._collapseAction = new Action('review.expand', nls.localize('label.collapse', "Collapse"), COLLAPSE_ACTION_CLASS, true, () => this.collapse()); - if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { - const menu = this._commentMenus.getCommentThreadTitleActions(this._commentThread as modes.CommentThread2, this._contextKeyService); - this.setActionBarActions(menu); + const menu = this._commentMenus.getCommentThreadTitleActions(this._commentThread, this._contextKeyService); + this.setActionBarActions(menu); - this._disposables.add(menu); - this._disposables.add(menu.onDidChange(e => { - this.setActionBarActions(menu); - })); - } else { - this._actionbarWidget.push([this._collapseAction], { label: false, icon: true }); - } + this._disposables.add(menu); + this._disposables.add(menu.onDidChange(e => { + this.setActionBarActions(menu); + })); this._actionbarWidget.context = this._commentThread; } @@ -263,20 +249,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget public collapse(): Promise { if (this._commentThread.comments && this._commentThread.comments.length === 0) { - if ((this._commentThread as modes.CommentThread2).commentThreadHandle === undefined) { - this.dispose(); - return Promise.resolve(); - } else { - const deleteCommand = (this._commentThread as modes.CommentThread2).deleteCommand; - if (deleteCommand) { - this.commentService.setActiveCommentThread(this._commentThread); - return this.commandService.executeCommand(deleteCommand.id, ...(deleteCommand.arguments || [])); - } else if (this._commentEditor.getValue() === '') { - this.commentService.disposeCommentThread(this._owner, this._commentThread.threadId!); - this.dispose(); - return Promise.resolve(); - } - } + this.dispose(); + return Promise.resolve(); } this.hide(); @@ -293,7 +267,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget toggleExpand(lineNumber: number) { if (this._isExpanded) { this.hide(); - if (this._commentThread === null || this._commentThread.threadId === null) { + if (!this._commentThread.comments || !this._commentThread.comments.length) { this.dispose(); } } else { @@ -301,7 +275,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } } - async update(commentThread: modes.CommentThread | modes.CommentThread2) { + async update(commentThread: modes.CommentThread) { const oldCommentsLen = this._commentElements.length; const newCommentsLen = commentThread.comments ? commentThread.comments.length : 0; this._threadIsEmpty.set(!newCommentsLen); @@ -356,7 +330,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget if (this._formActions && this._commentEditor.hasModel()) { dom.clearNode(this._formActions); const model = this._commentEditor.getModel(); - this.createCommentWidgetActions2(this._formActions, model); + this.createCommentWidgetActions(this._formActions, model); } // Move comment glyph widget and show position if the line has changed. @@ -394,18 +368,6 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } } - updateDraftMode(draftMode: modes.DraftMode | undefined) { - if (this._draftMode !== draftMode) { - this._draftMode = draftMode; - - if (this._formActions && this._commentEditor.hasModel()) { - const model = this._commentEditor.getModel(); - dom.clearNode(this._formActions); - this.createCommentWidgetActions(this._formActions, model); - } - } - } - protected _onWidth(widthInPixel: number): void { this._commentEditor.layout({ height: 5 * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ }); } @@ -464,9 +426,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentEditorIsEmpty.set(!this._commentEditor.getValue()); })); - if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { - this.createTextModelListener(); - } + this.createTextModelListener(); this.setCommentEditorDecorations(); @@ -482,12 +442,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._error = dom.append(this._commentForm, dom.$('.validation-error.hidden')); this._formActions = dom.append(this._commentForm, dom.$('.form-actions')); - if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { - this.createCommentWidgetActions2(this._formActions, model); - this.createCommentWidgetActionsListener(this._formActions, model); - } else { - this.createCommentWidgetActions(this._formActions, model); - } + this.createCommentWidgetActions(this._formActions, model); + this.createCommentWidgetActionsListener(); this._resizeObserver = new MutationObserver(this._refresh.bind(this)); @@ -504,7 +460,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget // If there are no existing comments, place focus on the text area. This must be done after show, which also moves focus. // if this._commentThread.comments is undefined, it doesn't finish initialization yet, so we don't focus the editor immediately. - if ((this._commentThread as modes.CommentThread).reply && this._commentThread.comments && !this._commentThread.comments.length) { + if (this._commentThread.comments && !this._commentThread.comments.length) { this._commentEditor.focus(); } else if (this._commentEditor.getModel()!.getValueLength() > 0) { this.expandReplyArea(); @@ -513,8 +469,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget private createTextModelListener() { this._commentThreadDisposables.push(this._commentEditor.onDidFocusEditorWidget(() => { - let commentThread = this._commentThread as modes.CommentThread2; - commentThread.input = { + this._commentThread.input = { uri: this._commentEditor.getModel()!.uri, value: this._commentEditor.getValue() }; @@ -523,17 +478,16 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentThreadDisposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => { let modelContent = this._commentEditor.getValue(); - let thread = (this._commentThread as modes.CommentThread2); - if (thread.input && thread.input.uri === this._commentEditor.getModel()!.uri && thread.input.value !== modelContent) { - let newInput: modes.CommentInput = thread.input; + if (this._commentThread.input && this._commentThread.input.uri === this._commentEditor.getModel()!.uri && this._commentThread.input.value !== modelContent) { + let newInput: modes.CommentInput = this._commentThread.input; newInput.value = modelContent; - thread.input = newInput; + this._commentThread.input = newInput; } this.commentService.setActiveCommentThread(this._commentThread); })); - this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeInput(input => { - let thread = (this._commentThread as modes.CommentThread2); + this._commentThreadDisposables.push(this._commentThread.onDidChangeInput(input => { + let thread = this._commentThread; if (thread.input && thread.input.uri !== this._commentEditor.getModel()!.uri) { return; @@ -557,31 +511,17 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } })); - this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeComments(async _ => { + this._commentThreadDisposables.push(this._commentThread.onDidChangeComments(async _ => { await this.update(this._commentThread); })); - this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeLabel(_ => { + this._commentThreadDisposables.push(this._commentThread.onDidChangeLabel(_ => { this.createThreadLabel(); })); } - private createCommentWidgetActionsListener(container: HTMLElement, model: ITextModel) { - this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeAcceptInputCommand(_ => { - if (container) { - dom.clearNode(container); - this.createCommentWidgetActions2(container, model); - } - })); - - this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeAdditionalCommands(_ => { - if (container) { - dom.clearNode(container); - this.createCommentWidgetActions2(container, model); - } - })); - - this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeRange(range => { + private createCommentWidgetActionsListener() { + this._commentThreadDisposables.push(this._commentThread.onDidChangeRange(range => { // Move comment glyph widget and show position if the line has changed. const lineNumber = this._commentThread.range.startLineNumber; let shouldMoveWidget = false; @@ -597,7 +537,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } })); - this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeCollasibleState(state => { + this._commentThreadDisposables.push(this._commentThread.onDidChangeCollasibleState(state => { if (state === modes.CommentThreadCollapsibleState.Expanded && !this._isExpanded) { const lineNumber = this._commentThread.range.startLineNumber; @@ -612,155 +552,15 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget })); } - private handleError(e: Error) { - this._error.textContent = e.message; - this._commentEditor.getDomNode()!.style.outline = `1px solid ${this.themeService.getTheme().getColor(inputValidationErrorBorder)}`; - dom.removeClass(this._error, 'hidden'); - } - private getActiveComment(): CommentNode | ReviewZoneWidget { return this._commentElements.filter(node => node.isEditing)[0] || this; } - private createCommentWidgetActions(container: HTMLElement, model: ITextModel) { - dispose(this._submitActionsDisposables); - - const button = new Button(container); - this._submitActionsDisposables.push(attachButtonStyler(button, this.themeService)); - button.label = 'Add comment'; - - button.enabled = model.getValueLength() > 0; - this._submitActionsDisposables.push(this._commentEditor.onDidChangeModelContent(_ => { - if (this._commentEditor.getValue()) { - button.enabled = true; - } else { - button.enabled = false; - } - })); - - button.onDidClick(async () => { - this.createComment(); - }); - - if (this._draftMode === modes.DraftMode.NotSupported) { - return; - } - - switch (this._draftMode) { - case modes.DraftMode.InDraft: - const deleteDraftLabel = this.commentService.getDeleteDraftLabel(this._owner); - if (deleteDraftLabel) { - const deletedraftButton = new Button(container); - this._submitActionsDisposables.push(attachButtonStyler(deletedraftButton, this.themeService)); - deletedraftButton.label = deleteDraftLabel; - deletedraftButton.enabled = true; - - this._disposables.add(deletedraftButton.onDidClick(async () => { - try { - await this.commentService.deleteDraft(this._owner, this.editor.getModel()!.uri); - } catch (e) { - this.handleError(e); - } - })); - } - - const submitDraftLabel = this.commentService.getFinishDraftLabel(this._owner); - if (submitDraftLabel) { - const submitdraftButton = new Button(container); - this._submitActionsDisposables.push(attachButtonStyler(submitdraftButton, this.themeService)); - submitdraftButton.label = this.commentService.getFinishDraftLabel(this._owner)!; - submitdraftButton.enabled = true; - - submitdraftButton.onDidClick(async () => { - try { - if (this._commentEditor.getValue()) { - await this.createComment(); - } - await this.commentService.finishDraft(this._owner, this.editor.getModel()!.uri); - } catch (e) { - this.handleError(e); - } - }); - } - - break; - case modes.DraftMode.NotInDraft: - const startDraftLabel = this.commentService.getStartDraftLabel(this._owner); - if (startDraftLabel) { - const draftButton = new Button(container); - this._disposables.add(attachButtonStyler(draftButton, this.themeService)); - draftButton.label = this.commentService.getStartDraftLabel(this._owner)!; - - draftButton.enabled = model.getValueLength() > 0; - this._submitActionsDisposables.push(this._commentEditor.onDidChangeModelContent(_ => { - if (this._commentEditor.getValue()) { - draftButton.enabled = true; - } else { - draftButton.enabled = false; - } - })); - - this._disposables.add(draftButton.onDidClick(async () => { - try { - await this.commentService.startDraft(this._owner, this.editor.getModel()!.uri); - await this.createComment(); - } catch (e) { - this.handleError(e); - } - })); - } - - break; - } - } - /** * Command based actions. */ - private createCommentWidgetActions2(container: HTMLElement, model: ITextModel) { - let commentThread = this._commentThread as modes.CommentThread2; - const { acceptInputCommand, additionalCommands } = commentThread; - - if (acceptInputCommand) { - const button = new Button(container); - this._disposables.add(attachButtonStyler(button, this.themeService)); - - button.label = acceptInputCommand.title; - this._disposables.add(button.onDidClick(async () => { - commentThread.input = { - uri: this._commentEditor.getModel()!.uri, - value: this._commentEditor.getValue() - }; - this.commentService.setActiveCommentThread(this._commentThread); - await this.commandService.executeCommand(acceptInputCommand.id, ...(acceptInputCommand.arguments || [])); - })); - - button.enabled = model.getValueLength() > 0; - this._disposables.add(this._commentEditor.onDidChangeModelContent(_ => { - if (this._commentEditor.getValue()) { - button.enabled = true; - } else { - button.enabled = false; - } - })); - } - - if (additionalCommands) { - additionalCommands.reverse().forEach(command => { - const button = new Button(container); - this._disposables.add(attachButtonStyler(button, this.themeService)); - - button.label = command.title; - this._disposables.add(button.onDidClick(async () => { - commentThread.input = { - uri: this._commentEditor.getModel()!.uri, - value: this._commentEditor.getValue() - }; - this.commentService.setActiveCommentThread(this._commentThread); - await this.commandService.executeCommand(command.id, ...(command.arguments || [])); - })); - }); - } + private createCommentWidgetActions(container: HTMLElement, model: ITextModel) { + const commentThread = this._commentThread; const menu = this._commentMenus.getCommentThreadActions(commentThread, this._contextKeyService); @@ -826,97 +626,15 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget async submitComment(): Promise { const activeComment = this.getActiveComment(); if (activeComment instanceof ReviewZoneWidget) { - if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { - let commentThread = this._commentThread as modes.CommentThread2; - - if (commentThread.acceptInputCommand) { - commentThread.input = { - uri: this._commentEditor.getModel()!.uri, - value: this._commentEditor.getValue() - }; - this.commentService.setActiveCommentThread(this._commentThread); - let commandId = commentThread.acceptInputCommand.id; - let args = commentThread.acceptInputCommand.arguments || []; - - await this.commandService.executeCommand(commandId, ...args); - return; - } else if (this._commentFormActions) { - this._commentFormActions.triggerDefaultAction(); - } - } else { - this.createComment(); + if (this._commentFormActions) { + this._commentFormActions.triggerDefaultAction(); } } - - if (activeComment instanceof CommentNode) { - activeComment.editComment(); - } - } - - async createComment(): Promise { - try { - if (this._commentEditor.getModel()!.getValueLength() === 0) { - return; - } - if (!this._commentGlyph) { - return; - } - - let newCommentThread; - const lineNumber = this._commentGlyph.getPosition().position!.lineNumber; - const isReply = this._commentThread.threadId !== null; - - - if (isReply) { - newCommentThread = await this.commentService.replyToCommentThread( - this._owner, - this.editor.getModel()!.uri, - new Range(lineNumber, 1, lineNumber, 1), - this._commentThread, - this._commentEditor.getValue() - ); - } else { - newCommentThread = await this.commentService.createNewCommentThread( - this._owner, - this.editor.getModel()!.uri, - new Range(lineNumber, 1, lineNumber, 1), - this._commentEditor.getValue() - ); - - if (newCommentThread) { - this.createReplyButton(); - } - } - - if (newCommentThread) { - this._commentEditor.setValue(''); - this._pendingComment = ''; - if (dom.hasClass(this._commentForm, 'expand')) { - dom.removeClass(this._commentForm, 'expand'); - } - this._commentEditor.getDomNode()!.style.outline = ''; - this._error.textContent = ''; - dom.addClass(this._error, 'hidden'); - this.update(newCommentThread); - - if (!isReply) { - this._onDidCreateThread.fire(this); - } - } - } catch (e) { - this._error.textContent = e.message - ? nls.localize('commentCreationError', "Adding a comment failed: {0}.", e.message) - : nls.localize('commentCreationDefaultError', "Adding a comment failed. Please try again or report an issue with the extension if the problem persists."); - this._commentEditor.getDomNode()!.style.outline = `1px solid ${this.themeService.getTheme().getColor(inputValidationErrorBorder)}`; - dom.removeClass(this._error, 'hidden'); - } } private createThreadLabel() { let label: string | undefined; - if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { - label = (this._commentThread as modes.CommentThread2).label; - } + label = this._commentThread.label; if (label === undefined) { if (this._commentThread.comments && this._commentThread.comments.length) { @@ -953,11 +671,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget private createReplyButton() { this._reviewThreadReplyButton = dom.append(this._commentForm, dom.$('button.review-thread-reply-button')); - if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { - // this._reviewThreadReplyButton.title = (this._commentThread as modes.CommentThread2).acceptInputCommands.title; - } else { - this._reviewThreadReplyButton.title = nls.localize('reply', "Reply..."); - } + this._reviewThreadReplyButton.title = nls.localize('reply', "Reply..."); + this._reviewThreadReplyButton.textContent = nls.localize('reply', "Reply..."); // bind click/escape actions for reviewThreadReplyButton and textArea this._disposables.add(dom.addDisposableListener(this._reviewThreadReplyButton, 'click', _ => this.expandReplyArea())); @@ -1173,7 +888,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentGlyph = undefined; } - this._globalToDispose.forEach(global => global.dispose()); + this._globalToDispose.dispose(); this._commentThreadDisposables.forEach(global => global.dispose()); this._submitActionsDisposables.forEach(local => local.dispose()); this._onDidClose.fire(undefined); diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index e1888faedda..35d61c27f4b 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -3,40 +3,39 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/review'; -import * as nls from 'vs/nls'; +import { ContextSubMenu } from 'vs/base/browser/contextmenu'; import { $ } from 'vs/base/browser/dom'; -import { findFirstInSorted, coalesce } from 'vs/base/common/arrays'; +import { Action, IAction } from 'vs/base/common/actions'; +import { coalesce, findFirstInSorted } from 'vs/base/common/arrays'; +import { CancelablePromise, createCancelablePromise, Delayer } from 'vs/base/common/async'; +import { onUnexpectedError } from 'vs/base/common/errors'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { ICodeEditor, IEditorMouseEvent, IViewZone, MouseTargetType, isDiffEditor, isCodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { registerEditorContribution, EditorAction, registerEditorAction } from 'vs/editor/browser/editorExtensions'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import 'vs/css!./media/review'; +import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; +import { IActiveCodeEditor, ICodeEditor, IEditorMouseEvent, isCodeEditor, isDiffEditor, IViewZone, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { EditorAction, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { IEditorContribution, IModelChangedEvent } from 'vs/editor/common/editorCommon'; import { IRange, Range } from 'vs/editor/common/core/range'; +import { IEditorContribution, IModelChangedEvent } from 'vs/editor/common/editorCommon'; +import { IModelDecorationOptions } from 'vs/editor/common/model'; +import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import * as modes from 'vs/editor/common/modes'; import { peekViewResultsBackground, peekViewResultsSelectionBackground, peekViewTitleBackground } from 'vs/editor/contrib/referenceSearch/referencesWidget'; -import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import * as nls from 'vs/nls'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { editorForeground } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { ReviewZoneWidget, COMMENTEDITOR_DECORATION_KEY } from 'vs/workbench/contrib/comments/browser/commentThreadWidget'; -import { ICommentService, ICommentInfo } from 'vs/workbench/contrib/comments/browser/commentService'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { IModelDecorationOptions } from 'vs/editor/common/model'; -import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { CancelablePromise, createCancelablePromise, Delayer } from 'vs/base/common/async'; +import { STATUS_BAR_ITEM_ACTIVE_BACKGROUND, STATUS_BAR_ITEM_HOVER_BACKGROUND } from 'vs/workbench/common/theme'; import { overviewRulerCommentingRangeForeground } from 'vs/workbench/contrib/comments/browser/commentGlyphWidget'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { STATUS_BAR_ITEM_HOVER_BACKGROUND, STATUS_BAR_ITEM_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ICommentInfo, ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; +import { COMMENTEDITOR_DECORATION_KEY, ReviewZoneWidget } from 'vs/workbench/contrib/comments/browser/commentThreadWidget'; import { ctxCommentEditorFocused, SimpleCommentEditor } from 'vs/workbench/contrib/comments/browser/simpleCommentEditor'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { IAction, Action } from 'vs/base/common/actions'; -import { ContextSubMenu } from 'vs/base/browser/contextmenu'; -import { IQuickInputService, QuickPickInput, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export const ID = 'editor.contrib.review'; @@ -64,7 +63,7 @@ class CommentingRangeDecoration { return this._decorationId; } - constructor(private _editor: ICodeEditor, private _ownerId: string, private _extensionId: string | undefined, private _label: string | undefined, private _range: IRange, private _reply: modes.Command | undefined, commentingOptions: ModelDecorationOptions, private commentingRangesInfo?: modes.CommentingRanges) { + constructor(private _editor: ICodeEditor, private _ownerId: string, private _extensionId: string | undefined, private _label: string | undefined, private _range: IRange, commentingOptions: ModelDecorationOptions, private commentingRangesInfo: modes.CommentingRanges) { const startLineNumber = _range.startLineNumber; const endLineNumber = _range.endLineNumber; let commentingRangeDecorations = [{ @@ -81,11 +80,10 @@ class CommentingRangeDecoration { } } - public getCommentAction(): { replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined } { + public getCommentAction(): { ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges } { return { extensionId: this._extensionId, label: this._label, - replyCommand: this._reply, ownerId: this._ownerId, commentingRangesInfo: this.commentingRangesInfo }; @@ -122,15 +120,9 @@ class CommentingRangeDecorator { let commentingRangeDecorations: CommentingRangeDecoration[] = []; for (const info of commentInfos) { - if (Array.isArray(info.commentingRanges)) { - info.commentingRanges.forEach(range => { - commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, range, info.reply, this.decorationOptions)); - }); - } else { - (info.commentingRanges ? info.commentingRanges.ranges : []).forEach(range => { - commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, range, undefined, this.decorationOptions, info.commentingRanges as modes.CommentingRanges)); - }); - } + info.commentingRanges.ranges.forEach(range => { + commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, range, this.decorationOptions, info.commentingRanges)); + }); } let oldDecorations = this.commentingRangeDecorations.map(decoration => decoration.id); @@ -161,7 +153,6 @@ export class ReviewController implements IEditorContribution { private globalToDispose: IDisposable[]; private localToDispose: IDisposable[]; private editor: ICodeEditor; - private _newCommentWidget?: ReviewZoneWidget; private _commentWidgets: ReviewZoneWidget[]; private _commentInfos: ICommentInfo[]; private _commentingRangeDecorator: CommentingRangeDecorator; @@ -172,13 +163,11 @@ export class ReviewController implements IEditorContribution { private _emptyThreadsToAddQueue: [number, IEditorMouseEvent | undefined][] = []; private _computeCommentingRangePromise: CancelablePromise | null; private _computeCommentingRangeScheduler: Delayer> | null; - private _pendingCommentCache: { [key: number]: { [key: string]: string } }; - private _pendingNewCommentCache: { [key: string]: { lineNumber: number, replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, pendingComment: string, draftMode: modes.DraftMode | undefined } }; + private _pendingCommentCache: { [key: string]: { [key: string]: string } }; constructor( editor: ICodeEditor, @ICommentService private readonly commentService: ICommentService, - @INotificationService private readonly notificationService: INotificationService, @IInstantiationService private readonly instantiationService: IInstantiationService, @ICodeEditorService private readonly codeEditorService: ICodeEditorService, @IContextMenuService readonly contextMenuService: IContextMenuService, @@ -190,18 +179,11 @@ export class ReviewController implements IEditorContribution { this._commentInfos = []; this._commentWidgets = []; this._pendingCommentCache = {}; - this._pendingNewCommentCache = {}; this._computePromise = null; this._commentingRangeDecorator = new CommentingRangeDecorator(); this.globalToDispose.push(this.commentService.onDidDeleteDataProvider(ownerId => { - // Remove new comment widget and glyph, refresh comments - if (this._newCommentWidget && this._newCommentWidget.owner === ownerId) { - this._newCommentWidget.dispose(); - this._newCommentWidget = undefined; - } - delete this._pendingCommentCache[ownerId]; this.beginCompute(); })); @@ -342,49 +324,14 @@ export class ReviewController implements IEditorContribution { this._commentWidgets.forEach(widget => widget.dispose()); - if (this._newCommentWidget) { - this._newCommentWidget.dispose(); - this._newCommentWidget = undefined; - } this.editor = null!; // Strict null override — nulling out in dispose } public onModelChanged(e: IModelChangedEvent): void { this.localToDispose = dispose(this.localToDispose); - if (this._newCommentWidget) { - let pendingNewComment = this._newCommentWidget.getPendingComment(); - - if (e.oldModelUrl) { - if (pendingNewComment) { - // we can't fetch zone widget's position as the model is already gone - const position = this._newCommentWidget.getPosition(); - if (position) { - this._pendingNewCommentCache[e.oldModelUrl.toString()] = { - lineNumber: position.lineNumber, - ownerId: this._newCommentWidget.owner, - extensionId: this._newCommentWidget.extensionId, - replyCommand: this._newCommentWidget.commentThread.reply, - pendingComment: pendingNewComment, - draftMode: this._newCommentWidget.draftMode - }; - } - } else { - // clear cache if it is empty - delete this._pendingNewCommentCache[e.oldModelUrl.toString()]; - } - } - - this._newCommentWidget.dispose(); - this._newCommentWidget = undefined; - } this.removeCommentWidgetsAndStoreCache(); - if (e.newModelUrl && this._pendingNewCommentCache[e.newModelUrl.toString()]) { - let newCommentCache = this._pendingNewCommentCache[e.newModelUrl.toString()]; - this.addComment(newCommentCache.lineNumber, newCommentCache.replyCommand, newCommentCache.ownerId, newCommentCache.extensionId, newCommentCache.draftMode, newCommentCache.pendingComment); - } - this.localToDispose.push(this.editor.onMouseDown(e => this.onEditorMouseDown(e))); this.localToDispose.push(this.editor.onMouseUp(e => this.onEditorMouseUp(e))); @@ -400,12 +347,16 @@ export class ReviewController implements IEditorContribution { this.localToDispose.push(this.editor.onDidChangeModelContent(async () => { this.beginComputeCommentingRanges(); })); - this.localToDispose.push(this.commentService.onDidUpdateCommentThreads(e => { + this.localToDispose.push(this.commentService.onDidUpdateCommentThreads(async e => { const editorURI = this.editor && this.editor.hasModel() && this.editor.getModel().uri; if (!editorURI) { return; } + if (this._computePromise) { + await this._computePromise; + } + let commentInfo = this._commentInfos.filter(info => info.owner === e.owner); if (!commentInfo || !commentInfo.length) { return; @@ -414,13 +365,6 @@ export class ReviewController implements IEditorContribution { let added = e.added.filter(thread => thread.resource && thread.resource.toString() === editorURI.toString()); let removed = e.removed.filter(thread => thread.resource && thread.resource.toString() === editorURI.toString()); let changed = e.changed.filter(thread => thread.resource && thread.resource.toString() === editorURI.toString()); - let draftMode = e.draftMode; - - commentInfo.forEach(info => info.draftMode = draftMode); - this._commentWidgets.filter(ZoneWidget => ZoneWidget.owner === e.owner).forEach(widget => widget.updateDraftMode(draftMode)); - if (this._newCommentWidget && this._newCommentWidget.owner === e.owner) { - this._newCommentWidget.updateDraftMode(draftMode); - } removed.forEach(thread => { let matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.threadId === thread.threadId && zoneWidget.commentThread.threadId !== ''); @@ -452,8 +396,8 @@ export class ReviewController implements IEditorContribution { return; } - const pendingCommentText = this._pendingCommentCache[e.owner] && this._pendingCommentCache[e.owner][thread.threadId]; - this.displayCommentThread(e.owner, thread, pendingCommentText, draftMode); + const pendingCommentText = this._pendingCommentCache[e.owner] && this._pendingCommentCache[e.owner][thread.threadId!]; + this.displayCommentThread(e.owner, thread, pendingCommentText); this._commentInfos.filter(info => info.owner === e.owner)[0].threads.push(thread); }); @@ -462,56 +406,12 @@ export class ReviewController implements IEditorContribution { this.beginCompute(); } - private displayCommentThread(owner: string, thread: modes.CommentThread | modes.CommentThread2, pendingComment: string | null, draftMode: modes.DraftMode | undefined): void { - const zoneWidget = this.instantiationService.createInstance(ReviewZoneWidget, this.editor, owner, thread, pendingComment, draftMode); + private displayCommentThread(owner: string, thread: modes.CommentThread, pendingComment: string | null): void { + const zoneWidget = this.instantiationService.createInstance(ReviewZoneWidget, this.editor, owner, thread, pendingComment); zoneWidget.display(thread.range.startLineNumber); this._commentWidgets.push(zoneWidget); } - private addComment(lineNumber: number, replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, draftMode: modes.DraftMode | undefined, pendingComment: string | null) { - if (this._newCommentWidget) { - this.notificationService.warn(`Please submit the comment at line ${this._newCommentWidget.position ? this._newCommentWidget.position.lineNumber : -1} before creating a new one.`); - return; - } - - // add new comment - this._newCommentWidget = this.instantiationService.createInstance(ReviewZoneWidget, this.editor, ownerId, { - extensionId: extensionId, - threadId: null, - resource: null, - comments: [], - range: { - startLineNumber: lineNumber, - startColumn: 0, - endLineNumber: lineNumber, - endColumn: 0 - }, - reply: replyCommand, - collapsibleState: modes.CommentThreadCollapsibleState.Expanded, - }, pendingComment, draftMode); - - this.localToDispose.push(this._newCommentWidget!.onDidClose(e => { - this.clearNewCommentWidget(); - })); - - this.localToDispose.push(this._newCommentWidget!.onDidCreateThread(commentWidget => { - const thread = commentWidget.commentThread; - this._commentWidgets.push(commentWidget); - this._commentInfos.filter(info => info.owner === commentWidget.owner)[0].threads.push(thread); - this.clearNewCommentWidget(); - })); - - this._newCommentWidget!.display(lineNumber); - } - - private clearNewCommentWidget() { - this._newCommentWidget = undefined; - - if (this.editor && this.editor.hasModel()) { - delete this._pendingNewCommentCache[this.editor.getModel().uri.toString()]; - } - } - private onEditorMouseDown(e: IEditorMouseEvent): void { this.mouseDownInfo = null; @@ -623,22 +523,22 @@ export class ReviewController implements IEditorContribution { const commentInfos = newCommentInfos.filter(info => info.ownerId === pick.id); if (commentInfos.length) { - const { replyCommand, ownerId, extensionId, commentingRangesInfo } = commentInfos[0]; - this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo); + const { ownerId } = commentInfos[0]; + this.addCommentAtLine2(lineNumber, ownerId); } }).then(() => { this._addInProgress = false; }); } } else { - const { replyCommand, ownerId, extensionId, commentingRangesInfo } = newCommentInfos[0]!; - this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo); + const { ownerId } = newCommentInfos[0]!; + this.addCommentAtLine2(lineNumber, ownerId); } return Promise.resolve(); } - private getCommentProvidersQuickPicks(commentInfos: { replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined }[]) { + private getCommentProvidersQuickPicks(commentInfos: { ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined }[]) { const picks: QuickPickInput[] = commentInfos.map((commentInfo) => { const { ownerId, extensionId, label } = commentInfo; @@ -651,11 +551,11 @@ export class ReviewController implements IEditorContribution { return picks; } - private getContextMenuActions(commentInfos: { replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined }[], lineNumber: number): (IAction | ContextSubMenu)[] { + private getContextMenuActions(commentInfos: { ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges }[], lineNumber: number): (IAction | ContextSubMenu)[] { const actions: (IAction | ContextSubMenu)[] = []; commentInfos.forEach(commentInfo => { - const { replyCommand, ownerId, extensionId, label, commentingRangesInfo } = commentInfo; + const { ownerId, extensionId, label } = commentInfo; actions.push(new Action( 'addCommentThread', @@ -663,7 +563,7 @@ export class ReviewController implements IEditorContribution { undefined, true, () => { - this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo); + this.addCommentAtLine2(lineNumber, ownerId); return Promise.resolve(); } )); @@ -671,37 +571,11 @@ export class ReviewController implements IEditorContribution { return actions; } - public addCommentAtLine2(lineNumber: number, replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined) { - if (commentingRangesInfo) { - let range = new Range(lineNumber, 1, lineNumber, 1); - if (commentingRangesInfo.newCommentThreadCallback) { - return commentingRangesInfo.newCommentThreadCallback(this.editor.getModel()!.uri, range) - .then(_ => { - this.processNextThreadToAdd(); - }) - .catch(e => { - this.notificationService.error(nls.localize('commentThreadAddFailure', "Adding a new comment thread failed: {0}.", e.message)); - this.processNextThreadToAdd(); - }); - } else { - // latest api, no comments creation callback - this.commentService.createCommentThreadTemplate(ownerId, this.editor.getModel()!.uri, range); - this.processNextThreadToAdd(); - return; - } - } else { - const commentInfo = this._commentInfos.filter(info => info.owner === ownerId); - if (!commentInfo || !commentInfo.length) { - this._addInProgress = false; - return Promise.resolve(); - } - - const draftMode = commentInfo[0].draftMode; - this.addComment(lineNumber, replyCommand, ownerId, extensionId, draftMode, null); - this._addInProgress = false; - } - - return Promise.resolve(); + public addCommentAtLine2(lineNumber: number, ownerId: string) { + const range = new Range(lineNumber, 1, lineNumber, 1); + this.commentService.createCommentThreadTemplate(ownerId, this.editor.getModel()!.uri, range); + this.processNextThreadToAdd(); + return; } private setComments(commentInfos: ICommentInfo[]): void { @@ -750,30 +624,21 @@ export class ReviewController implements IEditorContribution { info.threads.forEach(thread => { let pendingComment: string | null = null; if (providerCacheStore) { - pendingComment = providerCacheStore[thread.threadId]; + pendingComment = providerCacheStore[thread.threadId!]; } if (pendingComment) { thread.collapsibleState = modes.CommentThreadCollapsibleState.Expanded; } - this.displayCommentThread(info.owner, thread, pendingComment, info.draftMode); + this.displayCommentThread(info.owner, thread, pendingComment); }); }); - const commentingRanges: IRange[] = []; - this._commentInfos.forEach(info => { - commentingRanges.push(...(Array.isArray(info.commentingRanges) ? info.commentingRanges : info.commentingRanges ? info.commentingRanges.ranges : [])); - }); this._commentingRangeDecorator.update(this.editor, this._commentInfos); } public closeWidget(): void { - if (this._newCommentWidget) { - this._newCommentWidget.dispose(); - this._newCommentWidget = undefined; - } - if (this._commentWidgets) { this._commentWidgets.forEach(widget => widget.hide()); } @@ -793,10 +658,10 @@ export class ReviewController implements IEditorContribution { this._pendingCommentCache[zone.owner] = {}; } - this._pendingCommentCache[zone.owner][zone.commentThread.threadId] = pendingComment; + this._pendingCommentCache[zone.owner][zone.commentThread.threadId!] = pendingComment; } else { if (providerCacheStore) { - delete providerCacheStore[zone.commentThread.threadId]; + delete providerCacheStore[zone.commentThread.threadId!]; } } diff --git a/src/vs/workbench/contrib/comments/browser/commentsPanel.ts b/src/vs/workbench/contrib/comments/browser/commentsPanel.ts index 5411480fc28..dd9a18a0ff7 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsPanel.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsPanel.ts @@ -8,7 +8,7 @@ import * as dom from 'vs/base/browser/dom'; import { IAction } from 'vs/base/common/actions'; import { Event } from 'vs/base/common/event'; import { CollapseAllAction, DefaultAccessibilityProvider, DefaultController, DefaultDragAndDrop } from 'vs/base/parts/tree/browser/treeDefaults'; -import { isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TreeResourceNavigator, WorkbenchTree } from 'vs/platform/list/browser/listService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -19,7 +19,7 @@ import { ReviewController } from 'vs/workbench/contrib/comments/browser/comments import { CommentsDataFilter, CommentsDataSource, CommentsModelRenderer } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; import { ICommentService, IWorkspaceCommentThreadsEvent } from 'vs/workbench/contrib/comments/browser/commentService'; import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { textLinkForeground, textLinkActiveForeground, focusBorder, textPreformatForeground } from 'vs/platform/theme/common/colorRegistry'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -42,7 +42,6 @@ export class CommentsPanel extends Panel { @IInstantiationService private readonly instantiationService: IInstantiationService, @ICommentService private readonly commentService: ICommentService, @IEditorService private readonly editorService: IEditorService, - @ICommandService private readonly commandService: ICommandService, @IOpenerService private readonly openerService: IOpenerService, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @@ -189,55 +188,22 @@ export class CommentsPanel extends Panel { const threadToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].threadId : element.threadId; const commentToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].comment : element.comment; - if (commentToReveal.selectCommand) { - this.commandService.executeCommand(commentToReveal.selectCommand.id, ...(commentToReveal.selectCommand.arguments || [])).then(_ => { - let activeWidget = this.editorService.activeTextEditorWidget; - if (isDiffEditor(activeWidget)) { - const originalEditorWidget = activeWidget.getOriginalEditor(); - const modifiedEditorWidget = activeWidget.getModifiedEditor(); - - let controller; - if (originalEditorWidget.getModel()!.uri.toString() === element.resource.toString()) { - controller = ReviewController.get(originalEditorWidget); - } else if (modifiedEditorWidget.getModel()!.uri.toString() === element.resource.toString()) { - controller = ReviewController.get(modifiedEditorWidget); - } - - if (controller) { - controller.revealCommentThread(threadToReveal, commentToReveal.commentId, true); - } - } else { - let activeEditor = this.editorService.activeEditor; - let currentActiveResource = activeEditor ? activeEditor.getResource() : undefined; - if (currentActiveResource && currentActiveResource.toString() === element.resource.toString()) { - const control = this.editorService.activeTextEditorWidget; - if (threadToReveal && isCodeEditor(control)) { - const controller = ReviewController.get(control); - controller.revealCommentThread(threadToReveal, commentToReveal.commentId, true); - } - } + this.editorService.openEditor({ + resource: element.resource, + options: { + pinned: pinned, + preserveFocus: preserveFocus, + selection: range + } + }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => { + if (editor) { + const control = editor.getControl(); + if (threadToReveal && isCodeEditor(control)) { + const controller = ReviewController.get(control); + controller.revealCommentThread(threadToReveal, commentToReveal.commentId, true); } - - return true; - }); - } else { - this.editorService.openEditor({ - resource: element.resource, - options: { - pinned: pinned, - preserveFocus: preserveFocus, - selection: range - } - }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => { - if (editor) { - const control = editor.getControl(); - if (threadToReveal && isCodeEditor(control)) { - const controller = ReviewController.get(control); - controller.revealCommentThread(threadToReveal, commentToReveal.commentId, true); - } - } - }); - } + } + }); return true; } diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index 0f16da0f89f..f2ed8a479ac 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -398,11 +398,11 @@ export class CopyValueAction extends Action { if (this.value instanceof Variable && stackFrame && session && this.value.evaluateName) { return session.evaluate(this.value.evaluateName, stackFrame.frameId, this.context).then(result => { - this.clipboardService.writeText(result.body.result); + return this.clipboardService.writeText(result.body.result); }, err => this.clipboardService.writeText(this.value.value)); } - this.clipboardService.writeText(this.value); - return Promise.resolve(undefined); + + return this.clipboardService.writeText(this.value); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 95010a4ea63..95cd771b7f3 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -71,11 +71,11 @@ export function registerCommands(): void { CommandsRegistry.registerCommand({ id: COPY_STACK_TRACE_ID, - handler: (accessor: ServicesAccessor, _: string, frame: IStackFrame) => { + handler: async (accessor: ServicesAccessor, _: string, frame: IStackFrame) => { const textResourcePropertiesService = accessor.get(ITextResourcePropertiesService); const clipboardService = accessor.get(IClipboardService); const eol = textResourcePropertiesService.getEOL(frame.source.uri); - clipboardService.writeText(frame.thread.getCallStack().map(sf => sf.toString()).join(eol)); + await clipboardService.writeText(frame.thread.getCallStack().map(sf => sf.toString()).join(eol)); } }); diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 9e89ac09d99..c099ec691a9 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -472,16 +472,16 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati private onContextMenu(e: ITreeContextMenuEvent): void { const actions: IAction[] = []; - actions.push(new Action('debug.replCopy', nls.localize('copy', "Copy"), undefined, true, () => { + actions.push(new Action('debug.replCopy', nls.localize('copy', "Copy"), undefined, true, async () => { const nativeSelection = window.getSelection(); if (nativeSelection) { - this.clipboardService.writeText(nativeSelection.toString()); + await this.clipboardService.writeText(nativeSelection.toString()); } return Promise.resolve(); })); - actions.push(new Action('workbench.debug.action.copyAll', nls.localize('copyAll', "Copy All"), undefined, true, () => { - this.clipboardService.writeText(this.getVisibleContent()); - return Promise.resolve(undefined); + actions.push(new Action('workbench.debug.action.copyAll', nls.localize('copyAll', "Copy All"), undefined, true, async () => { + await this.clipboardService.writeText(this.getVisibleContent()); + return Promise.resolve(); })); actions.push(new Action('debug.collapseRepl', nls.localize('collapse', "Collapse All"), undefined, true, () => { this.tree.collapseAll(); @@ -902,7 +902,7 @@ class ReplCopyAllAction extends EditorAction { run(accessor: ServicesAccessor, editor: ICodeEditor): void | Promise { const clipboardService = accessor.get(IClipboardService); - clipboardService.writeText(accessor.get(IPrivateReplService).getVisibleContent()); + return clipboardService.writeText(accessor.get(IPrivateReplService).getVisibleContent()); } } diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index b12ac70119b..6b51044d076 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -164,8 +164,7 @@ export class VariablesView extends ViewletPanel { actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable, 'variables')); if (variable.evaluateName) { actions.push(new Action('debug.copyEvaluatePath', nls.localize('copyAsExpression', "Copy as Expression"), undefined, true, () => { - this.clipboardService.writeText(variable.evaluateName!); - return Promise.resolve(); + return this.clipboardService.writeText(variable.evaluateName!); })); actions.push(new Separator()); actions.push(new Action('debug.addToWatchExpressions', nls.localize('addToWatchExpressions', "Add to Watch"), undefined, true, () => { diff --git a/src/vs/workbench/contrib/experiments/electron-browser/experimentService.ts b/src/vs/workbench/contrib/experiments/electron-browser/experimentService.ts index 9a0aad7548b..22f39699416 100644 --- a/src/vs/workbench/contrib/experiments/electron-browser/experimentService.ts +++ b/src/vs/workbench/contrib/experiments/electron-browser/experimentService.ts @@ -178,7 +178,7 @@ export class ExperimentService extends Disposable implements IExperimentService if (context.res.statusCode !== 200) { return Promise.resolve(null); } - return asJson(context).then(result => { + return asJson(context).then((result: any) => { return result && Array.isArray(result['experiments']) ? result['experiments'] : []; }); }, () => Promise.resolve(null)); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts index fe4f91ede5c..7f600063a1b 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts @@ -857,8 +857,7 @@ export class ExtensionInfoAction extends ExtensionAction { const clipboardStr = `${name}\n${id}\n${description}\n${verision}\n${publisher}${link ? '\n' + link : ''}`; - this.clipboardService.writeText(clipboardStr); - return Promise.resolve(); + return this.clipboardService.writeText(clipboardStr); } } diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsList.ts index 3f6427704d9..78fca332fc1 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsList.ts @@ -116,7 +116,7 @@ export class Renderer implements IPagedRenderer { const extensionContainers: ExtensionContainers = this.instantiationService.createInstance(ExtensionContainers, [...actions, ...widgets, disabledLabelAction]); actionbar.push(actions, actionOptions); - const disposables = combinedDisposable(...actions, ...widgets, actionbar, extensionContainers); + const disposables = combinedDisposable(...actions, ...widgets, actionbar, extensionContainers, disabledLabelAction); return { root, element, icon, name, installCount, ratings, author, description, disposables: [disposables], actionbar, diff --git a/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts b/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts index 308f43bf60f..c250dcea937 100644 --- a/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts +++ b/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import * as paths from 'vs/base/common/path'; -import { URI as uri } from 'vs/base/common/uri'; +import { URI } from 'vs/base/common/uri'; import { IExternalTerminalConfiguration, IExternalTerminalService } from 'vs/workbench/contrib/externalTerminal/common/externalTerminal'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; @@ -21,32 +21,39 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { Schemas } from 'vs/base/common/network'; import { distinct } from 'vs/base/common/arrays'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { optional } from 'vs/platform/instantiation/common/instantiation'; const OPEN_IN_TERMINAL_COMMAND_ID = 'openInTerminal'; CommandsRegistry.registerCommand({ id: OPEN_IN_TERMINAL_COMMAND_ID, - handler: (accessor, resource: uri) => { + handler: (accessor, resource: URI) => { const configurationService = accessor.get(IConfigurationService); const editorService = accessor.get(IEditorService); const fileService = accessor.get(IFileService); + const terminalService: IExternalTerminalService | undefined = accessor.get(IExternalTerminalService, optional); const integratedTerminalService = accessor.get(IIntegratedTerminalService); - const terminalService = accessor.get(IExternalTerminalService); - const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService); + const remoteAgentService = accessor.get(IRemoteAgentService); - return fileService.resolveAll(resources.map(r => ({ resource: r }))).then(stats => { - const directoriesToOpen = distinct(stats.filter(data => data.success).map(({ stat }) => stat!.isDirectory ? stat!.resource.fsPath : paths.dirname(stat!.resource.fsPath))); - return directoriesToOpen.map(dir => { - if (configurationService.getValue().terminal.explorerKind === 'integrated') { - const instance = integratedTerminalService.createTerminal({ cwd: dir }); - if (instance && (resources.length === 1 || !resource || dir === resource.fsPath || dir === paths.dirname(resource.fsPath))) { + const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService); + return fileService.resolveAll(resources.map(r => ({ resource: r }))).then(async stats => { + const targets = distinct(stats.filter(data => data.success)); + // Always use integrated terminal when using a remote + const useIntegratedTerminal = remoteAgentService.getConnection() || configurationService.getValue().terminal.explorerKind === 'integrated'; + if (useIntegratedTerminal) { + distinct(targets.map(({ stat }) => stat!.isDirectory ? stat!.resource.path : paths.dirname(stat!.resource.path))).map(cwd => { + const instance = integratedTerminalService.createTerminal({ cwd }); + if (instance && (resources.length === 1 || !resource || cwd === resource.path || cwd === paths.dirname(resource.path))) { integratedTerminalService.setActiveInstance(instance); integratedTerminalService.showPanel(true); } - } else { - terminalService.openTerminal(dir); - } - }); + }); + } else { + distinct(targets.map(({ stat }) => stat!.isDirectory ? stat!.resource.fsPath : paths.dirname(stat!.resource.fsPath))).map(cwd => { + terminalService.openTerminal(cwd); + }); + } }); } }); @@ -58,7 +65,31 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: KEYBINDING_CONTEXT_TERMINAL_NOT_FOCUSED, weight: KeybindingWeight.WorkbenchContrib, handler: (accessor) => { + const remoteAgentService = accessor.get(IRemoteAgentService); const historyService = accessor.get(IHistoryService); + + // Open integrated terminal in remote workspaces + if (remoteAgentService.getConnection()) { + const integratedTerminalService = accessor.get(IIntegratedTerminalService); + const root = historyService.getLastActiveWorkspaceRoot(Schemas.vscodeRemote); + let cwd: string | undefined; + if (root) { + cwd = root.path; + } else { + const activeFile = historyService.getLastActiveFile(Schemas.vscodeRemote); + if (activeFile) { + cwd = paths.dirname(activeFile.path); + } + } + if (cwd) { + const instance = integratedTerminalService.createTerminal({ cwd }); + integratedTerminalService.setActiveInstance(instance); + integratedTerminalService.showPanel(true); + } + return; + } + + // Open external terminal in local workspaces const terminalService = accessor.get(IExternalTerminalService); const root = historyService.getLastActiveWorkspaceRoot(Schemas.file); if (root) { @@ -90,10 +121,22 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { command: openConsoleCommand, when: ResourceContextKey.Scheme.isEqualTo(Schemas.file) }); - +MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { + group: 'navigation', + order: 30, + command: openConsoleCommand, + when: ResourceContextKey.Scheme.isEqualTo(Schemas.vscodeRemote) +}); MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { group: 'navigation', order: 30, command: openConsoleCommand, when: ResourceContextKey.Scheme.isEqualTo(Schemas.file) }); +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: 'navigation', + order: 30, + command: openConsoleCommand, + when: ResourceContextKey.Scheme.isEqualTo(Schemas.vscodeRemote) +}); + diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index bfe380ec78b..6e4cb97c935 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -796,10 +796,10 @@ class ClipboardContentProvider implements ITextModelContentProvider { @IModelService private readonly modelService: IModelService ) { } - provideTextContent(resource: URI): Promise { - const model = this.modelService.createModel(this.clipboardService.readText(), this.modeService.createByFilepathOrFirstLine(resource), resource); + async provideTextContent(resource: URI): Promise { + const model = this.modelService.createModel(await this.clipboardService.readText(), this.modeService.createByFilepathOrFirstLine(resource), resource); - return Promise.resolve(model); + return model; } } diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 7155fc4dbbe..e94990d2390 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -436,13 +436,13 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -function resourcesToClipboard(resources: URI[], relative: boolean, clipboardService: IClipboardService, notificationService: INotificationService, labelService: ILabelService): void { +async function resourcesToClipboard(resources: URI[], relative: boolean, clipboardService: IClipboardService, notificationService: INotificationService, labelService: ILabelService): Promise { if (resources.length) { const lineDelimiter = isWindows ? '\r\n' : '\n'; const text = resources.map(resource => labelService.getUriLabel(resource, { relative, noPrefix: true })) .join(lineDelimiter); - clipboardService.writeText(text); + await clipboardService.writeText(text); } else { notificationService.info(nls.localize('openFileToCopy', "Open a file first to copy its path")); } @@ -456,9 +456,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_C }, id: COPY_PATH_COMMAND_ID, - handler: (accessor, resource: URI | object) => { + handler: async (accessor, resource: URI | object) => { const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService)); - resourcesToClipboard(resources, false, accessor.get(IClipboardService), accessor.get(INotificationService), accessor.get(ILabelService)); + await resourcesToClipboard(resources, false, accessor.get(IClipboardService), accessor.get(INotificationService), accessor.get(ILabelService)); } }); @@ -470,9 +470,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C) }, id: COPY_RELATIVE_PATH_COMMAND_ID, - handler: (accessor, resource: URI | object) => { + handler: async (accessor, resource: URI | object) => { const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService)); - resourcesToClipboard(resources, true, accessor.get(IClipboardService), accessor.get(INotificationService), accessor.get(ILabelService)); + await resourcesToClipboard(resources, true, accessor.get(IClipboardService), accessor.get(INotificationService), accessor.get(ILabelService)); } }); @@ -481,12 +481,12 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: undefined, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_P), id: 'workbench.action.files.copyPathOfActiveFile', - handler: (accessor) => { + handler: async (accessor) => { const editorService = accessor.get(IEditorService); const activeInput = editorService.activeEditor; const resource = activeInput ? activeInput.getResource() : null; const resources = resource ? [resource] : []; - resourcesToClipboard(resources, false, accessor.get(IClipboardService), accessor.get(INotificationService), accessor.get(ILabelService)); + await resourcesToClipboard(resources, false, accessor.get(IClipboardService), accessor.get(INotificationService), accessor.get(ILabelService)); } }); diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index f9c5325648a..08d95709864 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -109,8 +109,8 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(ShowProblemsPanelActio registerAction({ id: Constants.MARKER_COPY_ACTION_ID, title: { value: localize('copyMarker', "Copy"), original: 'Copy' }, - handler(accessor) { - copyMarker(accessor.get(IPanelService), accessor.get(IClipboardService)); + async handler(accessor) { + await copyMarker(accessor.get(IPanelService), accessor.get(IClipboardService)); }, menu: { menuId: MenuId.ProblemsPanelContext, @@ -127,8 +127,8 @@ registerAction({ registerAction({ id: Constants.MARKER_COPY_MESSAGE_ACTION_ID, title: { value: localize('copyMessage', "Copy Message"), original: 'Copy Message' }, - handler(accessor) { - copyMessage(accessor.get(IPanelService), accessor.get(IClipboardService)); + async handler(accessor) { + await copyMessage(accessor.get(IPanelService), accessor.get(IClipboardService)); }, menu: { menuId: MenuId.ProblemsPanelContext, @@ -139,8 +139,8 @@ registerAction({ registerAction({ id: Constants.RELATED_INFORMATION_COPY_MESSAGE_ACTION_ID, title: { value: localize('copyMessage', "Copy Message"), original: 'Copy Message' }, - handler(accessor) { - copyRelatedInformationMessage(accessor.get(IPanelService), accessor.get(IClipboardService)); + async handler(accessor) { + await copyRelatedInformationMessage(accessor.get(IPanelService), accessor.get(IClipboardService)); }, menu: { menuId: MenuId.ProblemsPanelContext, @@ -205,32 +205,32 @@ registerAction({ } }); -function copyMarker(panelService: IPanelService, clipboardService: IClipboardService) { +async function copyMarker(panelService: IPanelService, clipboardService: IClipboardService) { const activePanel = panelService.getActivePanel(); if (activePanel instanceof MarkersPanel) { const element = (activePanel).getFocusElement(); if (element instanceof Marker) { - clipboardService.writeText(`${element}`); + await clipboardService.writeText(`${element}`); } } } -function copyMessage(panelService: IPanelService, clipboardService: IClipboardService) { +async function copyMessage(panelService: IPanelService, clipboardService: IClipboardService) { const activePanel = panelService.getActivePanel(); if (activePanel instanceof MarkersPanel) { const element = (activePanel).getFocusElement(); if (element instanceof Marker) { - clipboardService.writeText(element.marker.message); + await clipboardService.writeText(element.marker.message); } } } -function copyRelatedInformationMessage(panelService: IPanelService, clipboardService: IClipboardService) { +async function copyRelatedInformationMessage(panelService: IPanelService, clipboardService: IClipboardService) { const activePanel = panelService.getActivePanel(); if (activePanel instanceof MarkersPanel) { const element = (activePanel).getFocusElement(); if (element instanceof RelatedInformation) { - clipboardService.writeText(element.raw.message); + await clipboardService.writeText(element.raw.message); } } } diff --git a/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts index 3509f0f2c51..ecfdd8e2ea0 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts @@ -103,21 +103,19 @@ export class StartupProfiler implements IWorkbenchContribution { }); } - private _createPerfIssue(files: string[]): Promise { - return this._textModelResolverService.createModelReference(PerfviewInput.Uri).then(ref => { + private async _createPerfIssue(files: string[]): Promise { + const ref = await this._textModelResolverService.createModelReference(PerfviewInput.Uri); + await this._clipboardService.writeText(ref.object.textEditorModel.getValue()); + ref.dispose(); - this._clipboardService.writeText(ref.object.textEditorModel.getValue()); - ref.dispose(); - - const body = ` + const body = ` 1. :warning: We have copied additional data to your clipboard. Make sure to **paste** here. :warning: 1. :warning: Make sure to **attach** these files from your *home*-directory: :warning:\n${files.map(file => `-\`${file}\``).join('\n')} `; - const baseUrl = product.reportIssueUrl; - const queryStringPrefix = baseUrl.indexOf('?') === -1 ? '?' : '&'; + const baseUrl = product.reportIssueUrl; + const queryStringPrefix = baseUrl.indexOf('?') === -1 ? '?' : '&'; - window.open(`${baseUrl}${queryStringPrefix}body=${encodeURIComponent(body)}`); - }); + window.open(`${baseUrl}${queryStringPrefix}body=${encodeURIComponent(body)}`); } } diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index d060784e1fa..b108f6696ba 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -248,7 +248,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor }); } - copyKeybinding(keybinding: IKeybindingItemEntry): void { + async copyKeybinding(keybinding: IKeybindingItemEntry): Promise { this.selectEntry(keybinding); this.reportKeybindingAction(KEYBINDINGS_EDITOR_COMMAND_COPY, keybinding.keybindingItem.command, keybinding.keybindingItem.keybinding); const userFriendlyKeybinding: IUserFriendlyKeybinding = { @@ -258,13 +258,13 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor if (keybinding.keybindingItem.when) { userFriendlyKeybinding.when = keybinding.keybindingItem.when; } - this.clipboardService.writeText(JSON.stringify(userFriendlyKeybinding, null, ' ')); + await this.clipboardService.writeText(JSON.stringify(userFriendlyKeybinding, null, ' ')); } - copyKeybindingCommand(keybinding: IKeybindingItemEntry): void { + async copyKeybindingCommand(keybinding: IKeybindingItemEntry): Promise { this.selectEntry(keybinding); this.reportKeybindingAction(KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, keybinding.keybindingItem.command, keybinding.keybindingItem.keybinding); - this.clipboardService.writeText(keybinding.keybindingItem.command); + await this.clipboardService.writeText(keybinding.keybindingItem.command); } focusSearch(): void { diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index 89039aff9a2..fd207e9002b 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -332,10 +332,10 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib, when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), primary: KeyMod.CtrlCmd | KeyCode.KEY_C, - handler: (accessor, args: any) => { + handler: async (accessor, args: any) => { const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; if (control) { - control.copyKeybinding(control.activeKeybindingEntry!); + await control.copyKeybinding(control.activeKeybindingEntry!); } } }); @@ -345,10 +345,10 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib, when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), primary: 0, - handler: (accessor, args: any) => { + handler: async (accessor, args: any) => { const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; if (control) { - control.copyKeybindingCommand(control.activeKeybindingEntry!); + await control.copyKeybindingCommand(control.activeKeybindingEntry!); } } }); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 5b0626f933f..44acebf1ed4 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -1117,11 +1117,12 @@ export class SettingsEditor2 extends BaseEditor { const nlpResult = results[SearchResultIdx.Remote]; const nlpMetadata = nlpResult && nlpResult.metadata; - const durations = {}; - durations['nlpResult'] = nlpMetadata && nlpMetadata.duration; + const durations = { + nlpResult: nlpMetadata && nlpMetadata.duration + }; // Count unique results - const counts = {}; + const counts: { nlpResult?: number, filterResult?: number } = {}; const filterResult = results[SearchResultIdx.Local]; if (filterResult) { counts['filterResult'] = filterResult.filterMatches.length; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index a83f4cdbb08..68a62920e9a 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -702,17 +702,16 @@ export class SettingExcludeRenderer extends AbstractSettingRenderer implements I } } - const sortKeys = (obj: Object) => { - const keyArray = Object.keys(obj) - .map(key => ({ key, val: obj[key] })) - .sort((a, b) => a.key.localeCompare(b.key)); + function sortKeys(obj: T) { + const sortedKeys = Object.keys(obj) + .sort((a, b) => a.localeCompare(b)) as Array; - const retVal = {}; - keyArray.forEach(pair => { - retVal[pair.key] = pair.val; - }); + const retVal: Partial = {}; + for (const key of sortedKeys) { + retVal[key] = obj[key]; + } return retVal; - }; + } this._onDidChangeSetting.fire({ key: template.context.setting.key, @@ -1416,9 +1415,9 @@ class CopySettingIdAction extends Action { super(CopySettingIdAction.ID, CopySettingIdAction.LABEL); } - run(context: SettingsTreeSettingElement): Promise { + async run(context: SettingsTreeSettingElement): Promise { if (context) { - this.clipboardService.writeText(context.setting.key); + await this.clipboardService.writeText(context.setting.key); } return Promise.resolve(undefined); @@ -1435,10 +1434,10 @@ class CopySettingAsJSONAction extends Action { super(CopySettingAsJSONAction.ID, CopySettingAsJSONAction.LABEL); } - run(context: SettingsTreeSettingElement): Promise { + async run(context: SettingsTreeSettingElement): Promise { if (context) { const jsonResult = `"${context.setting.key}": ${JSON.stringify(context.value, undefined, ' ')}`; - this.clipboardService.writeText(jsonResult); + await this.clipboardService.writeText(jsonResult); } return Promise.resolve(undefined); diff --git a/src/vs/workbench/contrib/preferences/common/preferences.ts b/src/vs/workbench/contrib/preferences/common/preferences.ts index b0b5540271f..eb8c57ee939 100644 --- a/src/vs/workbench/contrib/preferences/common/preferences.ts +++ b/src/vs/workbench/contrib/preferences/common/preferences.ts @@ -62,8 +62,8 @@ export interface IKeybindingsEditor extends IEditor { updateKeybinding(keybindingEntry: IKeybindingItemEntry, key: string, when: string | undefined): Promise; removeKeybinding(keybindingEntry: IKeybindingItemEntry): Promise; resetKeybinding(keybindingEntry: IKeybindingItemEntry): Promise; - copyKeybinding(keybindingEntry: IKeybindingItemEntry): void; - copyKeybindingCommand(keybindingEntry: IKeybindingItemEntry): void; + copyKeybinding(keybindingEntry: IKeybindingItemEntry): Promise; + copyKeybindingCommand(keybindingEntry: IKeybindingItemEntry): Promise; showSimilarKeybindings(keybindingEntry: IKeybindingItemEntry): void; } diff --git a/src/vs/workbench/contrib/preferences/electron-browser/preferencesSearch.ts b/src/vs/workbench/contrib/preferences/electron-browser/preferencesSearch.ts index 829d560a6ad..63b5f0c3909 100644 --- a/src/vs/workbench/contrib/preferences/electron-browser/preferencesSearch.ts +++ b/src/vs/workbench/contrib/preferences/electron-browser/preferencesSearch.ts @@ -18,6 +18,7 @@ import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { nullRange } from 'vs/workbench/services/preferences/common/preferencesModels'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { PreferencesSearchService as LocalPreferencesSearchService, SettingMatches } from 'vs/workbench/contrib/preferences/browser/preferencesSearch'; +import { IStringDictionary } from 'vs/base/common/collections'; export interface IEndpointDetails { urlBase?: string; @@ -251,7 +252,7 @@ class RemoteSearchProvider implements ISearchProvider { } const requestType = details.body ? 'post' : 'get'; - const headers = { + const headers: IStringDictionary = { 'User-Agent': 'request', 'Content-Type': 'application/json; charset=utf-8', }; diff --git a/src/vs/workbench/contrib/resources/browser/resourceServiceWorker.ts b/src/vs/workbench/contrib/resources/browser/resourceServiceWorker.ts index c3fb3ae07d3..85c162c2327 100644 --- a/src/vs/workbench/contrib/resources/browser/resourceServiceWorker.ts +++ b/src/vs/workbench/contrib/resources/browser/resourceServiceWorker.ts @@ -60,6 +60,12 @@ self.addEventListener('fetch', async (event: FetchEvent) => { }); async function respondWithDefault(event: FetchEvent): Promise { + if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin') { + // https://bugs.chromium.org/p/chromium/issues/detail?id=823392 + // https://stackoverflow.com/questions/48463483/what-causes-a-failed-to-execute-fetch-on-serviceworkerglobalscope-only-if#49719964 + // https://developer.mozilla.org/en-US/docs/Web/API/Request/cache + return new Response(undefined, { status: 504, statusText: 'Gateway Timeout (dev tools: https://bugs.chromium.org/p/chromium/issues/detail?id=823392)' }); + } return await event.preloadResponse || await fetch(event.request); } diff --git a/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerClient.ts b/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerClient.ts index 1a8fa94bb4e..d5a8df90d04 100644 --- a/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerClient.ts +++ b/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerClient.ts @@ -11,6 +11,7 @@ import { IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { isEqualOrParent } from 'vs/base/common/resources'; +import { ILogService } from 'vs/platform/log/common/log'; class ResourceServiceWorker { @@ -19,6 +20,7 @@ class ResourceServiceWorker { constructor( @IFileService private readonly _fileService: IFileService, @IExtensionService private readonly _extensionService: IExtensionService, + @ILogService private readonly _logService: ILogService, ) { this._initServiceWorker(); this._initFetchHandler(); @@ -31,20 +33,20 @@ class ResourceServiceWorker { private _initServiceWorker(): void { const url = require.toUrl('./resourceServiceWorkerMain.js'); navigator.serviceWorker.register(url, { scope: '/' }).then(reg => { - // console.log('registered', reg); return navigator.serviceWorker.ready; }).then(() => { - // console.log('ready'); + this._logService.trace('SW#init', navigator.serviceWorker.controller); }).catch(err => { - console.error(err); + this._logService.error('SW#init', err); }); } private _initFetchHandler(): void { const fetchListener: (this: ServiceWorkerContainer, ev: ExtendableMessageEvent) => void = event => { - const uri = URI.revive(event.data.uri); + this._logService.trace('SW#fetch', event.data.uri); + const uri = URI.revive(event.data.uri); Promise.all([ this._fileService.readFile(uri), this._isExtensionResource(uri) diff --git a/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerMain.ts b/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerMain.ts index a20e16e17d6..960e1772e8c 100644 --- a/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerMain.ts +++ b/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerMain.ts @@ -10,7 +10,7 @@ // statement. // trigger service worker updates -const _tag = '91e182d6-d06b-40ff-a517-32df368117f8'; +const _tag = 'c6f6120b-1407-4dd2-84cf-0fe0ef955140'; // loader world const baseUrl = '../../../../../'; diff --git a/src/vs/workbench/contrib/scm/browser/scmActivity.ts b/src/vs/workbench/contrib/scm/browser/scmActivity.ts index de8d68429cf..aaa9676f892 100644 --- a/src/vs/workbench/contrib/scm/browser/scmActivity.ts +++ b/src/vs/workbench/contrib/scm/browser/scmActivity.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { basename } from 'vs/base/common/resources'; -import { IDisposable, dispose, Disposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable, DisposableStore, combinedDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { VIEWLET_ID, ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; @@ -18,7 +18,7 @@ import { ILogService } from 'vs/platform/log/common/log'; export class StatusUpdater implements IWorkbenchContribution { - private badgeDisposable: IDisposable = Disposable.None; + private readonly badgeDisposable = new MutableDisposable(); private disposables: IDisposable[] = []; constructor( @@ -51,7 +51,7 @@ export class StatusUpdater implements IWorkbenchContribution { } private render(): void { - this.badgeDisposable.dispose(); + this.badgeDisposable.clear(); const count = this.scmService.repositories.reduce((r, repository) => { if (typeof repository.provider.count === 'number') { @@ -66,9 +66,9 @@ export class StatusUpdater implements IWorkbenchContribution { if (count > 0) { const badge = new NumberBadge(count, num => localize('scmPendingChangesBadge', '{0} pending changes', num)); - this.badgeDisposable = this.activityService.showActivity(VIEWLET_ID, badge, 'scm-viewlet-label'); + this.badgeDisposable.value = this.activityService.showActivity(VIEWLET_ID, badge, 'scm-viewlet-label'); } else { - this.badgeDisposable = Disposable.None; + this.badgeDisposable.clear(); } } diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index 68dc2624f5a..0d2d44cb5a1 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -671,11 +671,11 @@ function uriToClipboardString(resource: URI): string { return resource.scheme === Schemas.file ? normalize(normalizeDriveLetter(resource.fsPath)) : resource.toString(); } -export const copyPathCommand: ICommandHandler = (accessor, fileMatch: FileMatch | FolderMatch) => { +export const copyPathCommand: ICommandHandler = async (accessor, fileMatch: FileMatch | FolderMatch) => { const clipboardService = accessor.get(IClipboardService); const text = uriToClipboardString(fileMatch.resource()); - clipboardService.writeText(text); + await clipboardService.writeText(text); }; function matchToString(match: Match, indent = 0): string { @@ -736,7 +736,7 @@ function folderMatchToString(folderMatch: FolderMatch | BaseFolderMatch, maxMatc } const maxClipboardMatches = 1e4; -export const copyMatchCommand: ICommandHandler = (accessor, match: RenderableMatch) => { +export const copyMatchCommand: ICommandHandler = async (accessor, match: RenderableMatch) => { const clipboardService = accessor.get(IClipboardService); let text: string | undefined; @@ -749,7 +749,7 @@ export const copyMatchCommand: ICommandHandler = (accessor, match: RenderableMat } if (text) { - clipboardService.writeText(text); + await clipboardService.writeText(text); } }; @@ -768,7 +768,7 @@ function allFolderMatchesToString(folderMatches: Array { +export const copyAllCommand: ICommandHandler = async (accessor) => { const viewletService = accessor.get(IViewletService); const panelService = accessor.get(IPanelService); const clipboardService = accessor.get(IClipboardService); @@ -778,7 +778,7 @@ export const copyAllCommand: ICommandHandler = accessor => { const root = searchView.searchResult; const text = allFolderMatchesToString(root.folderMatches(), maxClipboardMatches); - clipboardService.writeText(text); + await clipboardService.writeText(text); } }; diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 310040325d7..676f63a171d 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -472,7 +472,17 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return this._taskSystem.customExecutionComplete(task, result); } + private isBadTsConfig(taskId: string | TaskIdentifier | undefined): taskId is string { + const badTsconfig = '\\tsconfig.json'; + const tsc = 'tsc'; + return typeof taskId === 'string' && (taskId.length > badTsconfig.length) && strings.equalsIgnoreCase(taskId.substring(taskId.length - badTsconfig.length, taskId.length), badTsconfig) && (taskId.substring(0, tsc.length) === tsc); + } + public getTask(folder: IWorkspaceFolder | string, identifier: string | TaskIdentifier, compareId: boolean = false): Promise { + if (this.isBadTsConfig(identifier)) { + return Promise.reject(new Error(nls.localize('badTsConfig', "Task '{0}' contains \"\\\\\". Typescript tasks must use \"/\"", identifier))); + } + const name = Types.isString(folder) ? folder : folder.name; if (this.ignoredWorkspaceFolders.some(ignored => ignored.name === name)) { return Promise.reject(new Error(nls.localize('TaskServer.folderIgnored', 'The folder {0} is ignored since it uses task version 0.1.0', name))); diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 3dc54054450..22d43005858 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -473,7 +473,7 @@ export class TerminalTaskSystem implements ITaskSystem { return resolvedVariables.then((resolvedVariables) => { const isCustomExecution = (task.command.runtime === RuntimeType.CustomExecution) || (task.command.runtime === RuntimeType.CustomExecution2); - if (resolvedVariables && task.command && task.command.runtime && (isCustomExecution || task.command.name)) { + if (resolvedVariables && (task.command !== undefined) && task.command.runtime && (isCustomExecution || (task.command.name !== undefined))) { this.currentTask.resolvedVariables = resolvedVariables; return this.executeInTerminal(task, trigger, new VariableResolver(workspaceFolder, systemInfo, resolvedVariables.variables, this.configurationResolverService), workspaceFolder); } else { diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index 528b95b0134..4e856c3b65c 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -289,7 +289,7 @@ export interface ConfigurationProperties { label?: string; /** - * An optional indentifier which can be used to reference a task + * An optional identifier which can be used to reference a task * in a dependsOn or other attributes. */ identifier?: string; @@ -432,7 +432,7 @@ export interface BaseTaskRunnerConfiguration { taskSelector?: string; /** - * The problem matcher(s) to used if a global command is exucuted (e.g. no tasks + * The problem matcher(s) to used if a global command is executed (e.g. no tasks * are defined). A tasks.json file can either contain a global problemMatcher * property or a tasks property but not both. */ @@ -503,7 +503,7 @@ export interface ExternalTaskRunnerConfiguration extends BaseTaskRunnerConfigura osx?: BaseTaskRunnerConfiguration; /** - * Linux speciif task configuration + * Linux specific task configuration */ linux?: BaseTaskRunnerConfiguration; } @@ -913,13 +913,13 @@ namespace CommandConfiguration { } } - interface BaseCommandConfiguationShape extends BaseCommandProperties, LegacyCommandProperties { + interface BaseCommandConfigurationShape extends BaseCommandProperties, LegacyCommandProperties { } - interface CommandConfiguationShape extends BaseCommandConfiguationShape { - windows?: BaseCommandConfiguationShape; - osx?: BaseCommandConfiguationShape; - linux?: BaseCommandConfiguationShape; + interface CommandConfigurationShape extends BaseCommandConfigurationShape { + windows?: BaseCommandConfigurationShape; + osx?: BaseCommandConfigurationShape; + linux?: BaseCommandConfigurationShape; } const properties: MetaData[] = [ @@ -928,7 +928,7 @@ namespace CommandConfiguration { { property: 'presentation', type: PresentationOptions } ]; - export function from(this: void, config: CommandConfiguationShape, context: ParseContext): Tasks.CommandConfiguration | undefined { + export function from(this: void, config: CommandConfigurationShape, context: ParseContext): Tasks.CommandConfiguration | undefined { let result: Tasks.CommandConfiguration = fromBase(config, context)!; let osConfig: Tasks.CommandConfiguration | undefined = undefined; @@ -945,7 +945,7 @@ namespace CommandConfiguration { return isEmpty(result) ? undefined : result; } - function fromBase(this: void, config: BaseCommandConfiguationShape, context: ParseContext): Tasks.CommandConfiguration | undefined { + function fromBase(this: void, config: BaseCommandConfigurationShape, context: ParseContext): Tasks.CommandConfiguration | undefined { let name: Tasks.CommandString | undefined = ShellString.from(config.command); let runtime: Tasks.RuntimeType; if (Types.isString(config.type)) { @@ -1172,7 +1172,7 @@ namespace ProblemMatcherConverter { return localProblemMatcher; } } - context.taskLoadIssues.push(nls.localize('ConfigurationParser.invalidVaraibleReference', 'Error: Invalid problemMatcher reference: {0}\n', value)); + context.taskLoadIssues.push(nls.localize('ConfigurationParser.invalidVariableReference', 'Error: Invalid problemMatcher reference: {0}\n', value)); return undefined; } else { let json = value; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 0e01b387a47..d17e12cae98 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -159,10 +159,10 @@ export class CopyTerminalSelectionAction extends Action { super(id, label); } - public run(event?: any): Promise { + public async run(event?: any): Promise { const terminalInstance = this.terminalService.getActiveInstance(); if (terminalInstance) { - terminalInstance.copySelection(); + await terminalInstance.copySelection(); } return Promise.resolve(undefined); } @@ -754,7 +754,7 @@ export class SwitchTerminalActionViewItem extends SelectActionViewItem { private _updateItems(): void { const items = this.terminalService.getTabLabels().map(label => { text: label }); - items.push({ text: SwitchTerminalActionViewItem.SEPARATOR }); + items.push({ text: SwitchTerminalActionViewItem.SEPARATOR, isDisabled: true }); items.push({ text: SelectDefaultShellWindowsTerminalAction.LABEL }); this.setOptions(items, this.terminalService.activeTabIndex); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 76c6127aa99..1f8229e39bc 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -713,9 +713,9 @@ export class TerminalInstance implements ITerminalInstance { return this._xterm && this._xterm.hasSelection(); } - public copySelection(): void { + public async copySelection(): Promise { if (this.hasSelection()) { - this._clipboardService.writeText(this._xterm.getSelection()); + await this._clipboardService.writeText(this._xterm.getSelection()); } else { this._notificationService.warn(nls.localize('terminal.integrated.copySelection.noSelection', 'The terminal has no selection to copy')); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts index 01d79e0c592..c79e4eedcb0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts @@ -203,7 +203,7 @@ export class TerminalPanel extends Panel { } private _attachEventListeners(): void { - this._register(dom.addDisposableListener(this._parentDomElement, 'mousedown', (event: MouseEvent) => { + this._register(dom.addDisposableListener(this._parentDomElement, 'mousedown', async (event: MouseEvent) => { if (this._terminalService.terminalInstances.length === 0) { return; } @@ -222,7 +222,7 @@ export class TerminalPanel extends Panel { return; } if (terminal.hasSelection()) { - terminal.copySelection(); + await terminal.copySelection(); terminal.clearSelection(); } else { terminal.paste(); @@ -240,7 +240,7 @@ export class TerminalPanel extends Panel { } } })); - this._register(dom.addDisposableListener(this._parentDomElement, 'mouseup', (event: MouseEvent) => { + this._register(dom.addDisposableListener(this._parentDomElement, 'mouseup', async (event: MouseEvent) => { if (this._configurationService.getValue('terminal.integrated.copyOnSelection')) { if (this._terminalService.terminalInstances.length === 0) { return; @@ -249,7 +249,7 @@ export class TerminalPanel extends Panel { if (event.which === 1) { const terminal = this._terminalService.getActiveInstance(); if (terminal && terminal.hasSelection()) { - terminal.copySelection(); + await terminal.copySelection(); } } } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 643d0aaeda2..53144ef0683 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -533,7 +533,7 @@ export interface ITerminalInstance { /** * Copies the terminal selection to the clipboard. */ - copySelection(): void; + copySelection(): Promise; /** * Current selection in the terminal. diff --git a/src/vs/workbench/contrib/update/electron-browser/update.ts b/src/vs/workbench/contrib/update/electron-browser/update.ts index 7078a1bb2fd..9ab8d2d859a 100644 --- a/src/vs/workbench/contrib/update/electron-browser/update.ts +++ b/src/vs/workbench/contrib/update/electron-browser/update.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import severity from 'vs/base/common/severity'; import { Action } from 'vs/base/common/actions'; -import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import pkg from 'vs/platform/product/node/package'; import product from 'vs/platform/product/node/product'; import { URI } from 'vs/base/common/uri'; @@ -219,7 +219,7 @@ export class Win3264BitContribution implements IWorkbenchContribution { export class UpdateContribution extends Disposable implements IWorkbenchContribution { private state: UpdateState; - private badgeDisposable: IDisposable = Disposable.None; + private readonly badgeDisposable = this._register(new MutableDisposable()); private updateStateContextKey: IContextKey; constructor( @@ -298,10 +298,10 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu clazz = 'progress-badge'; } - this.badgeDisposable.dispose(); + this.badgeDisposable.clear(); if (badge) { - this.badgeDisposable = this.activityService.showActivity(GLOBAL_ACTIVITY_ID, badge, clazz); + this.badgeDisposable.value = this.activityService.showActivity(GLOBAL_ACTIVITY_ID, badge, clazz); } this.state = state; diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts index 9c4bb48b1df..77a8253ff66 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts @@ -256,7 +256,7 @@ export class WebviewEditor extends BaseEditor { this._webview.initialScrollProgress = input.scrollYPercentage; } - this._webview.state = input.webviewState; + this._webview.state = input.state ? input.state.state : undefined; this._content!.setAttribute('aria-flowto', this._webviewContent.id); diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts index d0619038471..2b114064420 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts @@ -13,7 +13,7 @@ import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/bro import { WebviewEvents, WebviewInputOptions } from './webviewEditorService'; import { Webview, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview'; -export class WebviewEditorInput extends EditorInput { +export class WebviewEditorInput extends EditorInput { private static handlePool = 0; private static _styleElement?: HTMLStyleElement; @@ -62,7 +62,7 @@ export class WebviewEditorInput extends EditorInput { private readonly _webviewDisposables = this._register(new DisposableStore()); private _group?: GroupIdentifier; private _scrollYPercentage: number = 0; - private _state: any; + private _state: State; public readonly extension?: { readonly location: URI; @@ -74,7 +74,7 @@ export class WebviewEditorInput extends EditorInput { public readonly viewType: string, name: string, options: WebviewInputOptions, - state: any, + state: State, events: WebviewEvents, extension: undefined | { readonly location: URI; @@ -175,18 +175,14 @@ export class WebviewEditorInput extends EditorInput { } } - public get state(): any { + public get state(): State { return this._state; } - public set state(value: any) { + public set state(value: State) { this._state = value; } - public get webviewState() { - return this._state.state; - } - public get options(): WebviewInputOptions { return this._options; } @@ -253,7 +249,9 @@ export class WebviewEditorInput extends EditorInput { }, null, this._webviewDisposables); this._webview.onDidUpdateState(newState => { - this._state.state = newState; + if (this._events && this._events.onDidUpdateWebviewState) { + this._events.onDidUpdateWebviewState(newState); + } }, null, this._webviewDisposables); } @@ -262,7 +260,6 @@ export class WebviewEditorInput extends EditorInput { } public claimWebview(owner: any) { - this._webviewOwner = owner; } diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts index 187214dbe60..6064f07dd95 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts @@ -79,6 +79,7 @@ export interface WebviewEvents { onMessage?(message: any): void; onDispose?(): void; onDidClickLink?(link: URI, options: IWebviewOptions): void; + onDidUpdateWebviewState?(newState: any): void; } export interface WebviewInputOptions extends IWebviewOptions, IWebviewPanelOptions { diff --git a/src/vs/workbench/services/decorations/browser/decorationsService.ts b/src/vs/workbench/services/decorations/browser/decorationsService.ts index b183a2629d8..1a4e587636a 100644 --- a/src/vs/workbench/services/decorations/browser/decorationsService.ts +++ b/src/vs/workbench/services/decorations/browser/decorationsService.ts @@ -7,7 +7,7 @@ import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { IDecorationsService, IDecoration, IResourceDecorationChangeEvent, IDecorationsProvider, IDecorationData } from './decorations'; import { TernarySearchTree } from 'vs/base/common/map'; -import { Disposable, IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { isThenable } from 'vs/base/common/async'; import { LinkedList } from 'vs/base/common/linkedList'; import { createStyleSheet, createCSSRule, removeCSSRulesContainingSelector } from 'vs/base/browser/dom'; @@ -334,15 +334,14 @@ class DecorationProviderWrapper { } } -export class FileDecorationsService implements IDecorationsService { +export class FileDecorationsService extends Disposable implements IDecorationsService { _serviceBrand: any; private readonly _data = new LinkedList(); - private readonly _onDidChangeDecorationsDelayed = new Emitter(); - private readonly _onDidChangeDecorations = new Emitter(); + private readonly _onDidChangeDecorationsDelayed = this._register(new Emitter()); + private readonly _onDidChangeDecorations = this._register(new Emitter()); private readonly _decorationStyles: DecorationStyles; - private readonly _disposables: IDisposable[]; readonly onDidChangeDecorations: Event = Event.any( this._onDidChangeDecorations.event, @@ -356,27 +355,17 @@ export class FileDecorationsService implements IDecorationsService { constructor( @IThemeService themeService: IThemeService ) { - this._decorationStyles = new DecorationStyles(themeService); + super(); + this._decorationStyles = this._register(new DecorationStyles(themeService)); // every so many events we check if there are // css styles that we don't need anymore let count = 0; - let reg = this.onDidChangeDecorations(() => { + this._register(this.onDidChangeDecorations(() => { if (++count % 17 === 0) { this._decorationStyles.cleanUp(this._data.iterator()); } - }); - - this._disposables = [ - reg, - this._decorationStyles - ]; - } - - dispose(): void { - dispose(this._disposables); - dispose(this._onDidChangeDecorations); - dispose(this._onDidChangeDecorationsDelayed); + })); } registerDecorationsProvider(provider: IDecorationsProvider): IDisposable { diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index 6f16a842068..d57fa8b1801 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -231,14 +231,12 @@ export abstract class AbstractExtensionService extends Disposable implements IEx public readExtensionPointContributions(extPoint: IExtensionPoint): Promise[]> { return this._installedExtensionsReady.wait().then(() => { - let availableExtensions = this._registry.getAllExtensionDescriptions(); - - let result: ExtensionPointContribution[] = [], resultLen = 0; - for (let i = 0, len = availableExtensions.length; i < len; i++) { - let desc = availableExtensions[i]; + const availableExtensions = this._registry.getAllExtensionDescriptions(); + const result: ExtensionPointContribution[] = []; + for (const desc of availableExtensions) { if (desc.contributes && hasOwnProperty.call(desc.contributes, extPoint.name)) { - result[resultLen++] = new ExtensionPointContribution(desc, desc.contributes[extPoint.name]); + result.push(new ExtensionPointContribution(desc, desc.contributes[extPoint.name as keyof typeof desc.contributes])); } } @@ -320,9 +318,9 @@ export abstract class AbstractExtensionService extends Disposable implements IEx const messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg); const availableExtensions = this._registry.getAllExtensionDescriptions(); const extensionPoints = ExtensionsRegistry.getExtensionPoints(); - for (let i = 0, len = extensionPoints.length; i < len; i++) { - if (affectedExtensionPoints[extensionPoints[i].name]) { - AbstractExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler); + for (const extensionPoint of extensionPoints) { + if (affectedExtensionPoints[extensionPoint.name]) { + AbstractExtensionService._handleExtensionPoint(extensionPoint, availableExtensions, messageHandler); } } } @@ -365,16 +363,14 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } private static _handleExtensionPoint(extensionPoint: ExtensionPoint, availableExtensions: IExtensionDescription[], messageHandler: (msg: IMessage) => void): void { - let users: IExtensionPointUser[] = [], usersLen = 0; - for (let i = 0, len = availableExtensions.length; i < len; i++) { - let desc = availableExtensions[i]; - + const users: IExtensionPointUser[] = []; + for (const desc of availableExtensions) { if (desc.contributes && hasOwnProperty.call(desc.contributes, extensionPoint.name)) { - users[usersLen++] = { + users.push({ description: desc, - value: desc.contributes[extensionPoint.name], + value: desc.contributes[extensionPoint.name as keyof typeof desc.contributes], collector: new ExtensionMessageCollector(messageHandler, desc, extensionPoint.name) - }; + }); } } perf.mark(`willHandleExtensionPoint/${extensionPoint.name}`); diff --git a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts index 577cc633360..9776a42da6a 100644 --- a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts @@ -206,7 +206,7 @@ export const schema = { type: 'object', properties: { // extensions will fill in - }, + } as { [key: string]: any }, default: {} }, preview: { diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 8ce24a1b1b4..60173e2f739 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -53,8 +53,8 @@ import { IMarkerService } from 'vs/platform/markers/common/markers'; import { MarkerService } from 'vs/platform/markers/common/markerService'; // import { IDownloadService } from 'vs/platform/download/common/download'; // import { DownloadService } from 'vs/platform/download/node/downloadService'; -// import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -// import { ClipboardService } from 'vs/platform/clipboard/electron-browser/clipboardService'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { BrowserClipboardService } from 'vs/platform/clipboard/browser/clipboardService'; import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -149,7 +149,7 @@ registerSingleton(IEditorWorkerService, EditorWorkerServiceImpl); registerSingleton(IMarkerDecorationsService, MarkerDecorationsService); registerSingleton(IMarkerService, MarkerService, true); // registerSingleton(IDownloadService, DownloadService, true); -// registerSingleton(IClipboardService, ClipboardService, true); +registerSingleton(IClipboardService, BrowserClipboardService, true); registerSingleton(IContextKeyService, ContextKeyService); registerSingleton(IModelService, ModelServiceImpl, true); registerSingleton(ITextResourceConfigurationService, TextResourceConfigurationService); @@ -250,7 +250,7 @@ import 'vs/workbench/contrib/debug/browser/debugHelperService'; import 'vs/workbench/contrib/markers/browser/markers.contribution'; // Comments -// import 'vs/workbench/contrib/comments/browser/comments.contribution'; +import 'vs/workbench/contrib/comments/browser/comments.contribution'; // URL Support import 'vs/workbench/contrib/url/common/url.contribution'; @@ -306,8 +306,8 @@ import 'vs/workbench/contrib/emmet/browser/emmet.contribution'; import 'vs/workbench/contrib/codeEditor/browser/codeEditor.contribution'; // import 'vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution'; -// Execution -// import 'vs/workbench/contrib/externalTerminal/electron-browser/externalTerminal.contribution'; +// External terminal +import 'vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution'; // Snippets import 'vs/workbench/contrib/snippets/browser/snippets.contribution';