diff --git a/extensions/configuration-editing/src/extension.ts b/extensions/configuration-editing/src/extension.ts index 17dbaa543a2..4b7b3e678aa 100644 --- a/extensions/configuration-editing/src/extension.ts +++ b/extensions/configuration-editing/src/extension.ts @@ -69,7 +69,6 @@ function autoFixSettingsJSON(willSaveEvent: vscode.TextDocumentWillSaveEvent): v onError(error: ParseErrorCode, offset: number, length: number): void { if (error === ParseErrorCode.CommaExpected && lastEndOfSomething > -1) { - const fixPosition = document.positionAt(lastEndOfSomething); edit.insert(document.uri, fixPosition, ','); } @@ -104,31 +103,6 @@ function registerSettingsCompletions(): vscode.Disposable { }); } -// tslint:disable-next-line:no-unused-variable -function provideContributedLocalesProposals(range: vscode.Range): vscode.ProviderResult { - const contributedLocales: string[] = []; - for (const extension of vscode.extensions.all) { - if (extension.packageJSON && extension.packageJSON['contributes'] && extension.packageJSON['contributes']['localizations'] && extension.packageJSON['contributes']['localizations'].length) { - const localizations: { languageId: string }[] = extension.packageJSON['contributes']['localizations']; - for (const localization of localizations) { - if (contributedLocales.indexOf(localization.languageId) === -1) { - contributedLocales.push(localization.languageId); - } - } - } - } - return contributedLocales.map(locale => { - const text = `"${locale}"`; - const item = new vscode.CompletionItem(text); - item.kind = vscode.CompletionItemKind.Value; - item.insertText = text; - item.range = range; - item.filterText = text; - return item; - }); -} - - interface IExtensionsContent { recommendations: string[]; } diff --git a/extensions/configuration-editing/tsconfig.json b/extensions/configuration-editing/tsconfig.json index 23392904dee..84b75434d20 100644 --- a/extensions/configuration-editing/tsconfig.json +++ b/extensions/configuration-editing/tsconfig.json @@ -6,9 +6,10 @@ "lib": [ "es2015" ], - "strict": true + "strict": true, + "noUnusedLocals": true }, "include": [ "src/**/*" ] -} +} \ No newline at end of file diff --git a/extensions/css/package.json b/extensions/css/package.json index c5022b59db6..3374eea3078 100644 --- a/extensions/css/package.json +++ b/extensions/css/package.json @@ -715,7 +715,7 @@ ] }, "dependencies": { - "vscode-languageclient": "^4.0.0-next.9", + "vscode-languageclient": "4.0.0-next.9", "vscode-nls": "^3.2.1" }, "devDependencies": { diff --git a/extensions/css/server/package.json b/extensions/css/server/package.json index eaf6a20397b..3d2778ecfae 100644 --- a/extensions/css/server/package.json +++ b/extensions/css/server/package.json @@ -10,7 +10,7 @@ "dependencies": { "vscode-css-languageservice": "^3.0.6", "vscode-emmet-helper": "^1.1.34", - "vscode-languageserver": "^4.0.0-next.4" + "vscode-languageserver": "4.0.0-next.4" }, "devDependencies": { "@types/mocha": "2.2.33", diff --git a/extensions/css/server/yarn.lock b/extensions/css/server/yarn.lock index 3deaf65197a..c886d30c17f 100644 --- a/extensions/css/server/yarn.lock +++ b/extensions/css/server/yarn.lock @@ -48,7 +48,7 @@ vscode-languageserver-types@^3.6.0-next.1: version "3.6.0-next.1" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.6.0-next.1.tgz#98e488d3f87b666b4ee1a3d89f0023e246d358f3" -vscode-languageserver@^4.0.0-next.4: +vscode-languageserver@4.0.0-next.4: version "4.0.0-next.4" resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-4.0.0-next.4.tgz#162440b15bedaab07e1676f046e4d9b8578b3d92" dependencies: diff --git a/extensions/css/yarn.lock b/extensions/css/yarn.lock index 7ab2a57b25f..5f63bd90373 100644 --- a/extensions/css/yarn.lock +++ b/extensions/css/yarn.lock @@ -10,7 +10,7 @@ vscode-jsonrpc@^3.6.0-next.1: version "3.6.0-next.1" resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.6.0-next.1.tgz#3cb463dffe5842d6aec16718ca9252708cd6aabe" -vscode-languageclient@^4.0.0-next.9: +vscode-languageclient@4.0.0-next.9: version "4.0.0-next.9" resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-4.0.0-next.9.tgz#2a06568f46ee9de3490f85e227d3740a21a03d3a" dependencies: diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 755c4a2a448..8ce710d56b4 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1051,14 +1051,6 @@ export class CommandCenter { value = (await repository.getCommit(repository.HEAD.commit)).message; } - // tslint:disable-next-line:no-unused-variable - const getPreviousCommitMessage = async () => { - //Only return the previous commit message if it's an amend commit and the repo already has a commit - if (opts && opts.amend && repository.HEAD && repository.HEAD.commit) { - return (await repository.getCommit('HEAD')).message; - } - }; - return await window.showInputBox({ value, placeHolder: localize('commit message', "Commit message"), diff --git a/extensions/git/src/decorators.ts b/extensions/git/src/decorators.ts index ce705e1da3f..abd0265ef9b 100644 --- a/extensions/git/src/decorators.ts +++ b/extensions/git/src/decorators.ts @@ -78,8 +78,7 @@ function _throttle(fn: Function, key: string): Function { export const throttle = decorate(_throttle); -// tslint:disable-next-line:no-unused-variable -function _sequentialize(fn: Function, key: string): Function { +function _sequentialize(fn: Function, key: string): Function { const currentKey = `__$sequence$${key}`; return function (this: any, ...args: any[]) { diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 9ae633d6ade..d2f0ee6f914 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -362,8 +362,6 @@ function getGitErrorCode(stderr: string): string | undefined { export class Git { private gitPath: string; - // tslint:disable-next-line:no-unused-variable - private version: string; private env: any; private _onOutput = new EventEmitter(); @@ -371,7 +369,6 @@ export class Git { constructor(options: IGitOptions) { this.gitPath = options.gitPath; - this.version = options.version; this.env = options.env || {}; } diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index c20311c8031..38c57678e43 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -398,8 +398,7 @@ class ProgressManager { private disposable: IDisposable = EmptyDisposable; - // tslint:disable-next-line:no-unused-variable - constructor(private repository: Repository) { + constructor(repository: Repository) { const start = onceEvent(filterEvent(repository.onDidChangeOperations, () => repository.operations.shouldShowProgress())); const end = onceEvent(filterEvent(debounceEvent(repository.onDidChangeOperations, 300), () => !repository.operations.shouldShowProgress())); @@ -791,9 +790,8 @@ export class Repository implements Disposable { async buffer(ref: string, filePath: string): Promise { return await this.run(Operation.Show, async () => { const relativePath = path.relative(this.repository.root, filePath).replace(/\\/g, '/'); - const configFiles = workspace.getConfiguration('files', Uri.file(filePath)); - // tslint:disable-next-line:no-unused-variable - const encoding = configFiles.get('encoding'); + // const configFiles = workspace.getConfiguration('files', Uri.file(filePath)); + // const encoding = configFiles.get('encoding'); // TODO@joao: REsource config api return await this.repository.buffer(`${ref}:${relativePath}`); @@ -1011,11 +1009,8 @@ export class Repository implements Disposable { case 'UU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_MODIFIED, useIcons)); } - // tslint:disable-next-line:no-unused-variable - let isModifiedInIndex = false; - switch (raw.x) { - case 'M': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_MODIFIED, useIcons)); isModifiedInIndex = true; break; + case 'M': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_MODIFIED, useIcons)); break; case 'A': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_ADDED, useIcons)); break; case 'D': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_DELETED, useIcons)); break; case 'R': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_RENAMED, useIcons, renameUri)); break; diff --git a/extensions/git/tsconfig.json b/extensions/git/tsconfig.json index ea7679c9ac0..a7eca805f97 100644 --- a/extensions/git/tsconfig.json +++ b/extensions/git/tsconfig.json @@ -10,7 +10,8 @@ "./node_modules/@types" ], "strict": true, - "experimentalDecorators": true + "experimentalDecorators": true, + "noUnusedLocals": true }, "include": [ "src/**/*" diff --git a/extensions/html/package.json b/extensions/html/package.json index 7c97ed5ac7c..d7a3611d25e 100644 --- a/extensions/html/package.json +++ b/extensions/html/package.json @@ -229,7 +229,7 @@ }, "dependencies": { "vscode-extension-telemetry": "0.0.13", - "vscode-languageclient": "^4.0.0-next.9", + "vscode-languageclient": "4.0.0-next.9", "vscode-nls": "^3.2.1" }, "devDependencies": { diff --git a/extensions/html/server/package.json b/extensions/html/server/package.json index 9237e4b8716..4253cdfb9e7 100644 --- a/extensions/html/server/package.json +++ b/extensions/html/server/package.json @@ -11,7 +11,7 @@ "vscode-css-languageservice": "^3.0.6", "vscode-emmet-helper": "^1.1.34", "vscode-html-languageservice": "^2.0.16", - "vscode-languageserver": "^4.0.0-next.4", + "vscode-languageserver": "4.0.0-next.4", "vscode-languageserver-types": "^3.6.0-next.1", "vscode-nls": "^3.2.1", "vscode-uri": "^1.0.1" diff --git a/extensions/html/server/yarn.lock b/extensions/html/server/yarn.lock index 13bfdd813f7..f594dc4cf3c 100644 --- a/extensions/html/server/yarn.lock +++ b/extensions/html/server/yarn.lock @@ -56,7 +56,7 @@ vscode-languageserver-types@^3.6.0-next.1: version "3.6.0-next.1" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.6.0-next.1.tgz#98e488d3f87b666b4ee1a3d89f0023e246d358f3" -vscode-languageserver@^4.0.0-next.4: +vscode-languageserver@4.0.0-next.4: version "4.0.0-next.4" resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-4.0.0-next.4.tgz#162440b15bedaab07e1676f046e4d9b8578b3d92" dependencies: diff --git a/extensions/html/yarn.lock b/extensions/html/yarn.lock index 293e9314996..9c554a123f2 100644 --- a/extensions/html/yarn.lock +++ b/extensions/html/yarn.lock @@ -38,7 +38,7 @@ vscode-jsonrpc@^3.6.0-next.1: version "3.6.0-next.1" resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.6.0-next.1.tgz#3cb463dffe5842d6aec16718ca9252708cd6aabe" -vscode-languageclient@^4.0.0-next.9: +vscode-languageclient@4.0.0-next.9: version "4.0.0-next.9" resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-4.0.0-next.9.tgz#2a06568f46ee9de3490f85e227d3740a21a03d3a" dependencies: diff --git a/extensions/json/client/src/jsonMain.ts b/extensions/json/client/src/jsonMain.ts index 9491dc03e3e..949a1207c0a 100644 --- a/extensions/json/client/src/jsonMain.ts +++ b/extensions/json/client/src/jsonMain.ts @@ -8,8 +8,8 @@ import * as path from 'path'; import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); -import { workspace, languages, ExtensionContext, extensions, Uri, LanguageConfiguration } from 'vscode'; -import { LanguageClient, LanguageClientOptions, RequestType, ServerOptions, TransportKind, NotificationType, DidChangeConfigurationNotification } from 'vscode-languageclient'; +import { workspace, languages, ExtensionContext, extensions, Uri, LanguageConfiguration, TextDocument, FoldingRangeList as VSFoldingRangeList, FoldingRange as VSFoldingRange } from 'vscode'; +import { LanguageClient, LanguageClientOptions, RequestType, ServerOptions, TransportKind, NotificationType, DidChangeConfigurationNotification, TextDocumentIdentifier } from 'vscode-languageclient'; import TelemetryReporter from 'vscode-extension-telemetry'; import { hash } from './utils/hash'; @@ -30,6 +30,57 @@ namespace SchemaAssociationNotification { export const type: NotificationType = new NotificationType('json/schemaAssociations'); } +interface FoldingRangeList { + /** + * The folding ranges. + */ + ranges: FoldingRange[]; +} + +export enum FoldingRangeType { + /** + * Folding range for a comment + */ + Comment = 'comment', + /** + * Folding range for a imports or includes + */ + Imports = 'imports', + /** + * Folding range for a region (e.g. `#region`) + */ + Region = 'region' +} + +interface FoldingRange { + + /** + * The start line number + */ + startLine: number; + + /** + * The end line number + */ + endLine: number; + + /** + * The actual color value for this color range. + */ + type?: FoldingRangeType; +} + +interface FoldingRangeRequest { + /** + * The text document. + */ + textDocument: TextDocumentIdentifier; +} + +namespace FoldingRangesRequest { + export const type: RequestType = new RequestType('textDocument/foldingRanges'); +} + interface IPackageInfo { name: string; version: string; @@ -124,6 +175,17 @@ export function activate(context: ExtensionContext) { toDispose.push(workspace.onDidCloseTextDocument(d => handleContentChange(d.uri))); client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociation(context)); + + languages.registerFoldingProvider(documentSelector, { + provideFoldingRanges(document: TextDocument) { + return client.sendRequest(FoldingRangesRequest.type, { textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document) }).then(res => { + if (res && Array.isArray(res.ranges)) { + return new VSFoldingRangeList(res.ranges.map(r => new VSFoldingRange(r.startLine, r.endLine, r.type))); + } + return null; + }); + } + }); }); let languageConfiguration: LanguageConfiguration = { diff --git a/extensions/json/package.json b/extensions/json/package.json index 0f976e9b2f6..e5bbf304d4c 100644 --- a/extensions/json/package.json +++ b/extensions/json/package.json @@ -165,7 +165,7 @@ }, "dependencies": { "vscode-extension-telemetry": "0.0.13", - "vscode-languageclient": "^4.0.0-next.9", + "vscode-languageclient": "4.0.0-next.9", "vscode-nls": "^3.2.1" }, "devDependencies": { diff --git a/extensions/json/server/package.json b/extensions/json/server/package.json index 34d3b370410..c597f26e5cc 100644 --- a/extensions/json/server/package.json +++ b/extensions/json/server/package.json @@ -11,7 +11,7 @@ "jsonc-parser": "^1.0.1", "request-light": "^0.2.2", "vscode-json-languageservice": "^3.0.7", - "vscode-languageserver": "^4.0.0-next.3", + "vscode-languageserver": "4.0.0-next.3", "vscode-nls": "^3.2.1", "vscode-uri": "^1.0.1" }, diff --git a/extensions/json/server/src/jsonServerMain.ts b/extensions/json/server/src/jsonServerMain.ts index 1adbcfbf546..bd3a7fb03f0 100644 --- a/extensions/json/server/src/jsonServerMain.ts +++ b/extensions/json/server/src/jsonServerMain.ts @@ -7,7 +7,7 @@ import { createConnection, IConnection, TextDocuments, TextDocument, InitializeParams, InitializeResult, NotificationType, RequestType, - DocumentRangeFormattingRequest, Disposable, ServerCapabilities + DocumentRangeFormattingRequest, Disposable, ServerCapabilities, TextDocumentIdentifier } from 'vscode-languageserver'; import { DocumentColorRequest, ServerCapabilities as CPServerCapabilities, ColorPresentationRequest } from 'vscode-languageserver-protocol/lib/protocol.colorProvider.proposed'; @@ -20,6 +20,7 @@ import Strings = require('./utils/strings'); import { formatError, runSafe, runSafeAsync } from './utils/errors'; import { JSONDocument, JSONSchema, getLanguageService, DocumentLanguageSettings, SchemaConfiguration } from 'vscode-json-languageservice'; import { getLanguageModelCache } from './languageModelCache'; +import { createScanner, SyntaxKind } from 'jsonc-parser'; interface ISchemaAssociations { [pattern: string]: string[]; @@ -37,6 +38,57 @@ namespace SchemaContentChangeNotification { export const type: NotificationType = new NotificationType('json/schemaContent'); } +interface FoldingRangeList { + /** + * The folding ranges. + */ + ranges: FoldingRange[]; +} + +export enum FoldingRangeType { + /** + * Folding range for a comment + */ + Comment = 'comment', + /** + * Folding range for a imports or includes + */ + Imports = 'imports', + /** + * Folding range for a region (e.g. `#region`) + */ + Region = 'region' +} + +interface FoldingRange { + + /** + * The start line number + */ + startLine: number; + + /** + * The end line number + */ + endLine: number; + + /** + * The actual color value for this color range. + */ + type?: FoldingRangeType | string; +} + +interface FoldingRangeRequest { + /** + * The text document. + */ + textDocument: TextDocumentIdentifier; +} + +namespace FoldingRangesRequest { + export const type: RequestType = new RequestType('textDocument/foldingRanges'); +} + // Create a connection for the server let connection: IConnection = createConnection(); @@ -347,7 +399,83 @@ connection.onRequest(ColorPresentationRequest.type, params => { return languageService.getColorPresentations(document, jsonDocument, params.color, params.range); } return []; - }, [], `Error while computing color presentationsd for ${params.textDocument.uri}`); + }, [], `Error while computing color presentations for ${params.textDocument.uri}`); +}); + +connection.onRequest(FoldingRangesRequest.type, params => { + return runSafe(() => { + let document = documents.get(params.textDocument.uri); + if (document) { + let ranges: FoldingRange[] = []; + let stack: FoldingRange[] = []; + let prevStart = -1; + let scanner = createScanner(document.getText(), false); + let token = scanner.scan(); + while (token !== SyntaxKind.EOF) { + switch (token) { + case SyntaxKind.OpenBraceToken: + case SyntaxKind.OpenBracketToken: { + let startLine = document.positionAt(scanner.getTokenOffset()).line; + let range = { startLine, endLine: startLine, type: token === SyntaxKind.OpenBraceToken ? 'object' : 'array' }; + stack.push(range); + } + break; + case SyntaxKind.CloseBraceToken: + case SyntaxKind.CloseBracketToken: { + let type = token === SyntaxKind.CloseBraceToken ? 'object' : 'array'; + if (stack.length > 0 && stack[stack.length - 1].type === type) { + let range = stack.pop(); + let line = document.positionAt(scanner.getTokenOffset()).line; + if (range && line > range.startLine + 1 && prevStart !== range.startLine) { + range.endLine = line - 1; + ranges.push(range); + prevStart = range.startLine; + } + } + } + break; + case SyntaxKind.BlockCommentTrivia: { + let startLine = document.positionAt(scanner.getTokenOffset()).line; + let endLine = document.positionAt(scanner.getTokenOffset() + scanner.getTokenLength()).line; + if (startLine < endLine) { + ranges.push({ startLine, endLine, type: FoldingRangeType.Comment }); + prevStart = startLine; + } + } + break; + case SyntaxKind.LineCommentTrivia: { + let text = document.getText().substr(scanner.getTokenOffset(), scanner.getTokenLength()); + let m = text.match(/^\/\/\s*#(region\b)|(endregion\b)/); + if (m) { + let line = document.positionAt(scanner.getTokenOffset()).line; + if (m[1]) { // start pattern match + let range = { startLine: line, endLine: line, type: FoldingRangeType.Region }; + stack.push(range); + } else { + let i = stack.length - 1; + while (i >= 0 && stack[i].type !== FoldingRangeType.Region) { + i--; + } + if (i >= 0) { + let range = stack[i]; + stack.length = i; + if (line > range.startLine && prevStart !== range.startLine) { + range.endLine = line; + ranges.push(range); + prevStart = range.startLine; + } + } + } + } + } + break; + } + token = scanner.scan(); + } + return { ranges }; + } + return null; + }, null, `Error while computing folding ranges for ${params.textDocument.uri}`); }); // Listen on the connection diff --git a/extensions/json/server/yarn.lock b/extensions/json/server/yarn.lock index 169311dfdf0..ac7f8a50213 100644 --- a/extensions/json/server/yarn.lock +++ b/extensions/json/server/yarn.lock @@ -88,7 +88,7 @@ vscode-languageserver-types@^3.6.0-next.1: version "3.6.0-next.1" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.6.0-next.1.tgz#98e488d3f87b666b4ee1a3d89f0023e246d358f3" -vscode-languageserver@^4.0.0-next.3: +vscode-languageserver@4.0.0-next.3: version "4.0.0-next.3" resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-4.0.0-next.3.tgz#89a9ce5078e3a86a78e3551c3766194ce4295611" dependencies: diff --git a/extensions/json/yarn.lock b/extensions/json/yarn.lock index 293e9314996..9c554a123f2 100644 --- a/extensions/json/yarn.lock +++ b/extensions/json/yarn.lock @@ -38,7 +38,7 @@ vscode-jsonrpc@^3.6.0-next.1: version "3.6.0-next.1" resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.6.0-next.1.tgz#3cb463dffe5842d6aec16718ca9252708cd6aabe" -vscode-languageclient@^4.0.0-next.9: +vscode-languageclient@4.0.0-next.9: version "4.0.0-next.9" resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-4.0.0-next.9.tgz#2a06568f46ee9de3490f85e227d3740a21a03d3a" dependencies: diff --git a/extensions/log/package.json b/extensions/log/package.json index ab8b0da1c9d..b0c0122fd39 100644 --- a/extensions/log/package.json +++ b/extensions/log/package.json @@ -24,11 +24,6 @@ } ], "grammars": [ - { - "language": "Log", - "scopeName": "text.log", - "path": "./syntaxes/log.tmLanguage.json" - }, { "language": "log", "scopeName": "text.log", diff --git a/extensions/markdown/media/loading.js b/extensions/markdown/media/loading.js index 720ff7fb204..cfa4c0adbde 100644 --- a/extensions/markdown/media/loading.js +++ b/extensions/markdown/media/loading.js @@ -27,10 +27,9 @@ if (!unloadedStyles.length) { return; } - const args = [unloadedStyles]; window.parent.postMessage({ - command: 'did-click-link', - data: `command:_markdown.onPreviewStyleLoadError?${encodeURIComponent(JSON.stringify(args))}` + command: '_markdown.onPreviewStyleLoadError', + args: [unloadedStyles] }, '*'); }); }()); \ No newline at end of file diff --git a/extensions/markdown/src/features/previewContentProvider.ts b/extensions/markdown/src/features/previewContentProvider.ts index a0356d51ce4..a942b4e16e6 100644 --- a/extensions/markdown/src/features/previewContentProvider.ts +++ b/extensions/markdown/src/features/previewContentProvider.ts @@ -354,7 +354,10 @@ export class MarkdownPreviewWebviewManager { const view = vscode.window.createWebview( localize('previewTitle', 'Preview {0}', path.basename(resource.fsPath)), viewColumn, - { enableScripts: true }); + { + enableScripts: true, + localResourceRoots: this.getLocalResourceRoots(resource) + }); this.contentProvider.provideTextDocumentContent(resource, this.previewConfigurations).then(x => view.html = x); @@ -373,4 +376,19 @@ export class MarkdownPreviewWebviewManager { this.webviews.set(resource.fsPath, view); return view; } + + private getLocalResourceRoots( + resource: vscode.Uri + ): vscode.Uri[] { + const folder = vscode.workspace.getWorkspaceFolder(resource); + if (folder) { + return [folder.uri]; + } + + if (!resource.scheme || resource.scheme === 'file') { + return [vscode.Uri.parse(path.dirname(resource.fsPath))]; + } + + return []; + } } \ No newline at end of file diff --git a/extensions/npm/src/main.ts b/extensions/npm/src/main.ts index 17d2c47b1c9..dd154d49e0d 100644 --- a/extensions/npm/src/main.ts +++ b/extensions/npm/src/main.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -//tslint:disable 'use strict'; import * as path from 'path'; diff --git a/package.json b/package.json index 6a49f962342..8e5041622cf 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "vscode-debugprotocol": "1.25.0", "vscode-ripgrep": "^0.7.1-patch.1.5", "vscode-textmate": "^3.2.0", - "vscode-xterm": "3.2.0-beta3", + "vscode-xterm": "3.2.0-beta4", "yauzl": "2.8.0" }, "devDependencies": { diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index c0aadf8e400..f0f96b1a236 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -120,6 +120,11 @@ export class ListView implements ISpliceable, IDisposable { this.scrollableElement.onScroll(this.onScroll, this, this.disposables); domEvent(this.rowsContainer, TouchEventType.Change)(this.onTouchChange, this, this.disposables); + // Prevent the monaco-scrollable-element from scrolling + // https://github.com/Microsoft/vscode/issues/44181 + domEvent(this.scrollableElement.getDomNode(), 'scroll') + (e => (e.target as HTMLElement).scrollTop = 0, null, this.disposables); + const onDragOver = mapEvent(domEvent(this.rowsContainer, 'dragover'), e => new DragMouseEvent(e)); onDragOver(this.onDragOver, this, this.disposables); @@ -150,7 +155,7 @@ export class ListView implements ISpliceable, IDisposable { const removeRange = intersect(previousRenderRange, deleteRange); for (let i = removeRange.start; i < removeRange.end; i++) { - this.removeItemFromDOM(this.items[i]); + this.removeItemFromDOM(i); } const previousRestRange: IRange = { start: start + deleteCount, end: this.items.length }; @@ -183,19 +188,20 @@ export class ListView implements ISpliceable, IDisposable { const removeRange = removeRanges[r]; for (let i = removeRange.start; i < removeRange.end; i++) { - this.removeItemFromDOM(this.items[i]); + this.removeItemFromDOM(i); } } const unrenderedRestRanges = previousUnrenderedRestRanges.map(r => shift(r, delta)); const elementsRange = { start, end: start + elements.length }; const insertRanges = [elementsRange, ...unrenderedRestRanges].map(r => intersect(renderRange, r)); + const beforeElement = this.getNextToLastElement(insertRanges); for (let r = 0; r < insertRanges.length; r++) { const insertRange = insertRanges[r]; for (let i = insertRange.start; i < insertRange.end; i++) { - this.insertItemInDOM(this.items[i], i); + this.insertItemInDOM(i, beforeElement); } } @@ -254,16 +260,17 @@ export class ListView implements ISpliceable, IDisposable { const rangesToInsert = relativeComplement(renderRange, previousRenderRange); const rangesToRemove = relativeComplement(previousRenderRange, renderRange); + const beforeElement = this.getNextToLastElement(rangesToInsert); for (const range of rangesToInsert) { for (let i = range.start; i < range.end; i++) { - this.insertItemInDOM(this.items[i], i); + this.insertItemInDOM(i, beforeElement); } } for (const range of rangesToRemove) { for (let i = range.start; i < range.end; i++) { - this.removeItemFromDOM(this.items[i], ); + this.removeItemFromDOM(i); } } @@ -281,28 +288,38 @@ export class ListView implements ISpliceable, IDisposable { // DOM operations - private insertItemInDOM(item: IItem, index: number): void { + private insertItemInDOM(index: number, beforeElement: HTMLElement | null): void { + const item = this.items[index]; + if (!item.row) { item.row = this.cache.alloc(item.templateId); } if (!item.row.domNode.parentElement) { - this.rowsContainer.appendChild(item.row.domNode); + if (beforeElement) { + this.rowsContainer.insertBefore(item.row.domNode, beforeElement); + } else { + this.rowsContainer.appendChild(item.row.domNode); + } } - const renderer = this.renderers.get(item.templateId); - item.row.domNode.style.top = `${this.elementTop(index)}px`; item.row.domNode.style.height = `${item.size}px`; - item.row.domNode.setAttribute('data-index', `${index}`); + this.updateItemInDOM(item, index); + + const renderer = this.renderers.get(item.templateId); renderer.renderElement(item.element, index, item.row.templateData); } private updateItemInDOM(item: IItem, index: number): void { item.row.domNode.style.top = `${this.elementTop(index)}px`; item.row.domNode.setAttribute('data-index', `${index}`); + item.row.domNode.setAttribute('data-last-element', index === this.length - 1 ? 'true' : 'false'); + item.row.domNode.setAttribute('aria-setsize', `${this.length}`); + item.row.domNode.setAttribute('aria-posinset', `${index + 1}`); } - private removeItemFromDOM(item: IItem): void { + private removeItemFromDOM(index: number): void { + const item = this.items[index]; this.cache.release(item.row); item.row = null; } @@ -450,6 +467,26 @@ export class ListView implements ISpliceable, IDisposable { }; } + private getNextToLastElement(ranges: IRange[]): HTMLElement | null { + const lastRange = ranges[ranges.length - 1]; + + if (!lastRange) { + return null; + } + + const nextToLastItem = this.items[lastRange.end]; + + if (!nextToLastItem) { + return null; + } + + if (!nextToLastItem.row) { + return null; + } + + return nextToLastItem.row.domNode; + } + // Dispose dispose() { diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 97e46087653..fe92a7203f0 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -21,6 +21,7 @@ import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { ISpliceable } from 'vs/base/common/sequence'; +import { clamp } from 'vs/base/common/numbers'; export interface IIdentityProvider { (element: T): string; @@ -203,32 +204,6 @@ class FocusTrait extends Trait { } } -class Aria implements IRenderer, ISpliceable { - - private length = 0; - - get templateId(): string { - return 'aria'; - } - - splice(start: number, deleteCount: number, elements: T[]): void { - this.length += elements.length - deleteCount; - } - - renderTemplate(container: HTMLElement): HTMLElement { - return container; - } - - renderElement(element: T, index: number, container: HTMLElement): void { - container.setAttribute('aria-setsize', `${this.length}`); - container.setAttribute('aria-posinset', `${index + 1}`); - } - - disposeTemplate(container: HTMLElement): void { - // noop - } -} - /** * The TraitSpliceable is used as a util class to be able * to preserve traits across splice calls, given an identity @@ -781,13 +756,12 @@ export class List implements ISpliceable, IDisposable { renderers: IRenderer[], options: IListOptions = DefaultOptions ) { - const aria = new Aria(); this.focus = new FocusTrait(i => this.getElementDomId(i)); this.selection = new Trait('selected'); mixin(options, defaultStyles, false); - renderers = renderers.map(r => new PipelineRenderer(r.templateId, [aria, this.focus.renderer, this.selection.renderer, r])); + renderers = renderers.map(r => new PipelineRenderer(r.templateId, [this.focus.renderer, this.selection.renderer, r])); this.view = new ListView(container, delegate, renderers, options); this.view.domNode.setAttribute('role', 'tree'); @@ -797,7 +771,6 @@ export class List implements ISpliceable, IDisposable { this.styleElement = DOM.createStyleSheet(this.view.domNode); this.spliceable = new CombinedSpliceable([ - aria, new TraitSpliceable(this.focus, this.view, options.identityProvider), new TraitSpliceable(this.selection, this.view, options.identityProvider), this.view @@ -980,12 +953,9 @@ export class List implements ISpliceable, IDisposable { const elementHeight = this.view.elementHeight(index); if (isNumber(relativeTop)) { - relativeTop = relativeTop < 0 ? 0 : relativeTop; - relativeTop = relativeTop > 1 ? 1 : relativeTop; - // y = mx + b const m = elementHeight - this.view.renderHeight; - this.view.setScrollTop(m * relativeTop + elementTop); + this.view.setScrollTop(m * clamp(relativeTop, 0, 1) + elementTop); } else { const viewItemBottom = elementTop + elementHeight; const wrapperBottom = scrollTop + this.view.renderHeight; @@ -998,6 +968,24 @@ export class List implements ISpliceable, IDisposable { } } + /** + * Returns the relative position of an element rendered in the list. + * Returns `null` if the element isn't *entirely* in the visible viewport. + */ + getRelativeTop(index: number): number | null { + const scrollTop = this.view.getScrollTop(); + const elementTop = this.view.elementTop(index); + const elementHeight = this.view.elementHeight(index); + + if (elementTop < scrollTop || elementTop + elementHeight > scrollTop + this.view.renderHeight) { + return null; + } + + // y = mx + b + const m = elementHeight - this.view.renderHeight; + return Math.abs((scrollTop - elementTop) / m); + } + private getElementDomId(index: number): string { return `${this.idPrefix}_${index}`; } @@ -1097,6 +1085,7 @@ export class List implements ISpliceable, IDisposable { if (focus.length > 0) { this.view.domNode.setAttribute('aria-activedescendant', this.getElementDomId(focus[0])); + console.log(this.getRelativeTop(focus[0])); } else { this.view.domNode.removeAttribute('aria-activedescendant'); } diff --git a/src/vs/base/browser/ui/splitview/panelview.ts b/src/vs/base/browser/ui/splitview/panelview.ts index eafc85d142a..74bce829de6 100644 --- a/src/vs/base/browser/ui/splitview/panelview.ts +++ b/src/vs/base/browser/ui/splitview/panelview.ts @@ -49,6 +49,10 @@ export abstract class Panel implements IView { private _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; + get element(): HTMLElement { + return this.el; + } + get draggableElement(): HTMLElement { return this.header; } diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 34247aa7796..64e2f36c829 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -180,7 +180,7 @@ export class Delayer { private timeout: number; private completionPromise: Promise; private onSuccess: ValueCallback; - private task: ITask; + private task: ITask>; constructor(public defaultDelay: number) { this.timeout = null; @@ -189,7 +189,7 @@ export class Delayer { this.task = null; } - trigger(task: ITask, delay: number = this.defaultDelay): TPromise { + trigger(task: ITask>, delay: number = this.defaultDelay): TPromise { this.task = task; this.cancelTimeout(); diff --git a/src/vs/base/test/node/extfs/extfs.test.ts b/src/vs/base/test/node/extfs/extfs.test.ts index 17d020743e5..bb01309a025 100644 --- a/src/vs/base/test/node/extfs/extfs.test.ts +++ b/src/vs/base/test/node/extfs/extfs.test.ts @@ -16,7 +16,7 @@ import strings = require('vs/base/common/strings'); import extfs = require('vs/base/node/extfs'); import { onError } from 'vs/base/test/common/utils'; import { Readable } from 'stream'; -import { isLinux } from 'vs/base/common/platform'; +import { isLinux, isWindows } from 'vs/base/common/platform'; const ignore = () => { }; @@ -75,6 +75,11 @@ suite('Extfs', () => { }); test('stat link', function (done: () => void) { + if (isWindows) { + // Symlinks are not the same on win, and we can not create them programitically without admin privileges + return done(); + } + const id1 = uuid.generateUuid(); const parentDir = path.join(os.tmpdir(), 'vsctests', id1); const directory = path.join(parentDir, 'extfs', id1); diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index 2f35e2b14a2..dfeb7d10fbf 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -356,8 +356,7 @@ export class IssueReporter extends Disposable { const disableExtensions = document.getElementById('disableExtensions'); disableExtensions.addEventListener('click', () => { - ipcRenderer.send('workbenchCommand', 'workbench.extensions.action.disableAll'); - ipcRenderer.send('workbenchCommand', 'workbench.action.reloadWindow'); + ipcRenderer.send('workbenchCommand', 'workbench.action.reloadWindowWithExtensionsDisabled'); }); disableExtensions.addEventListener('keydown', (e) => { diff --git a/src/vs/code/electron-browser/issue/media/issueReporter.css b/src/vs/code/electron-browser/issue/media/issueReporter.css index 04187fc4c2d..f9bb710bc5d 100644 --- a/src/vs/code/electron-browser/issue/media/issueReporter.css +++ b/src/vs/code/electron-browser/issue/media/issueReporter.css @@ -262,6 +262,7 @@ input:disabled { .workbenchCommand { cursor: pointer; } + .workbenchCommand:disabled { color: #868e96; cursor: default diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 24b15696e0d..6e1dbe926b8 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -555,6 +555,10 @@ export class CodeWindow implements ICodeWindow { configuration['extensions-dir'] = cli['extensions-dir']; } + if (cli) { + configuration['disable-extensions'] = cli['disable-extensions']; + } + configuration.isInitialStartup = false; // since this is a reload // Load config diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index c7fd1b7b243..33c9cbcce55 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -826,6 +826,67 @@ export interface DocumentColorProvider { provideColorPresentations(model: model.ITextModel, colorInfo: IColorInformation, token: CancellationToken): IColorPresentation[] | Thenable; } +/** + * A provider of colors for editor models. + */ +/** + * @internal + */ +export interface FoldingProvider { + /** + * Provides the color ranges for a specific model. + */ + provideFoldingRanges(model: model.ITextModel, token: CancellationToken): IFoldingRangeList | Thenable; +} +/** + * @internal + */ +export interface IFoldingRangeList { + + ranges: IFoldingRange[]; +} +/** + * @internal + */ +export interface IFoldingRange { + + /** + * The start line number + */ + startLineNumber: number; + + /** + * The end line number + */ + endLineNumber: number; + + /** + * The optional type of the folding range + */ + type?: FoldingRangeType | string; + + // auto-collapse + // header span + +} +/** + * @internal + */ +export enum FoldingRangeType { + /** + * Folding range for a comment + */ + Comment = 'comment', + /** + * Folding range for a imports or includes + */ + Imports = 'imports', + /** + * Folding range for a region (e.g. `#region`) + */ + Region = 'region' +} + /** * @internal */ @@ -971,6 +1032,11 @@ export const LinkProviderRegistry = new LanguageFeatureRegistry(); */ export const ColorProviderRegistry = new LanguageFeatureRegistry(); +/** + * @internal + */ +export const FoldingProviderRegistry = new LanguageFeatureRegistry(); + /** * @internal */ diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index 9fd6f41c706..2eaa4756dba 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -19,17 +19,24 @@ import { registerEditorAction, registerEditorContribution, ServicesAccessor, Edi import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { FoldingModel, setCollapseStateAtLevel, CollapseMemento, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateForMatchingLines } from 'vs/editor/contrib/folding/foldingModel'; import { FoldingDecorationProvider } from './foldingDecorations'; +import { FoldingRegions } from './foldingRanges'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions'; import { IMarginData, IEmptyContentData } from 'vs/editor/browser/controller/mouseTarget'; import { HiddenRangeModel } from 'vs/editor/contrib/folding/hiddenRangeModel'; import { IRange } from 'vs/editor/common/core/range'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; -import { computeRanges as computeIndentRanges } from 'vs/editor/contrib/folding/indentRangeProvider'; +import { IndentRangeProvider } from 'vs/editor/contrib/folding/indentRangeProvider'; import { IPosition } from 'vs/editor/common/core/position'; +import { FoldingProviderRegistry } from 'vs/editor/common/modes'; +import { SyntaxRangeProvider } from './syntaxRangeProvider'; export const ID = 'editor.contrib.folding'; +export interface RangeProvider { + compute(editorModel: ITextModel): TPromise; +} + export class FoldingController implements IEditorContribution { static MAX_FOLDING_REGIONS = 5000; @@ -48,6 +55,8 @@ export class FoldingController implements IEditorContribution { private foldingModel: FoldingModel; private hiddenRangeModel: HiddenRangeModel; + private rangeProvider: RangeProvider; + private foldingModelPromise: TPromise; private updateScheduler: Delayer; @@ -69,6 +78,7 @@ export class FoldingController implements IEditorContribution { this.foldingDecorationProvider.autoHideFoldingControls = this._autoHideFoldingControls; this.globalToDispose.push(this.editor.onDidChangeModel(() => this.onModelChanged())); + this.globalToDispose.push(FoldingProviderRegistry.onDidChange(() => this.onModelChanged())); this.globalToDispose.push(this.editor.onDidChangeConfiguration((e: IConfigurationChangedEvent) => { if (e.contribInfo) { @@ -167,17 +177,22 @@ export class FoldingController implements IEditorContribution { this.foldingModelPromise = null; this.hiddenRangeModel = null; this.cursorChangedScheduler = null; + this.rangeProvider = null; } }); this.onModelContentChanged(); } - private computeRanges(editorModel: ITextModel) { - let foldingRules = LanguageConfigurationRegistry.getFoldingRules(editorModel.getLanguageIdentifier().id); - let offSide = foldingRules && foldingRules.offSide; - let markers = foldingRules && foldingRules.markers; - let ranges = computeIndentRanges(editorModel, offSide, markers); - return ranges; + private getRangeProvider(): RangeProvider { + if (!this.rangeProvider) { + let foldingProviders = FoldingProviderRegistry.ordered(this.foldingModel.textModel); + if (foldingProviders.length) { + this.rangeProvider = new SyntaxRangeProvider(foldingProviders); + } else { + this.rangeProvider = new IndentRangeProvider(); + } + } + return this.rangeProvider; } public getFoldingModel() { @@ -191,9 +206,12 @@ export class FoldingController implements IEditorContribution { // some cursors might have moved into hidden regions, make sure they are in expanded regions let selections = this.editor.getSelections(); let selectionLineNumbers = selections ? selections.map(s => s.startLineNumber) : []; - this.foldingModel.update(this.computeRanges(this.foldingModel.textModel), selectionLineNumbers); + return this.getRangeProvider().compute(this.foldingModel.textModel).then(foldingRanges => { + this.foldingModel.update(foldingRanges, selectionLineNumbers); + return this.foldingModel; + }); } - return this.foldingModel; + return null; }); } } diff --git a/src/vs/editor/contrib/folding/foldingModel.ts b/src/vs/editor/contrib/folding/foldingModel.ts index 33e1826061c..5fbb9f2289a 100644 --- a/src/vs/editor/contrib/folding/foldingModel.ts +++ b/src/vs/editor/contrib/folding/foldingModel.ts @@ -5,7 +5,7 @@ import { ITextModel, IModelDecorationOptions, IModelDeltaDecoration, IModelDecorationsChangeAccessor } from 'vs/editor/common/model'; import Event, { Emitter } from 'vs/base/common/event'; -import { FoldingRanges, ILineRange, FoldingRegion } from './foldingRanges'; +import { FoldingRegions, ILineRange, FoldingRegion } from './foldingRanges'; export interface IDecorationProvider { getDecorationOption(isCollapsed: boolean): IModelDecorationOptions; @@ -24,13 +24,13 @@ export class FoldingModel { private _textModel: ITextModel; private _decorationProvider: IDecorationProvider; - private _ranges: FoldingRanges; + private _regions: FoldingRegions; private _editorDecorationIds: string[]; private _isInitialized: boolean; private _updateEventEmitter = new Emitter(); - public get ranges(): FoldingRanges { return this._ranges; } + public get regions(): FoldingRegions { return this._regions; } public get onDidChange(): Event { return this._updateEventEmitter.event; } public get textModel() { return this._textModel; } public get isInitialized() { return this._isInitialized; } @@ -38,7 +38,7 @@ export class FoldingModel { constructor(textModel: ITextModel, decorationProvider: IDecorationProvider) { this._textModel = textModel; this._decorationProvider = decorationProvider; - this._ranges = new FoldingRanges(new Uint32Array(0), new Uint32Array(0)); + this._regions = new FoldingRegions(new Uint32Array(0), new Uint32Array(0)); this._editorDecorationIds = []; this._isInitialized = false; } @@ -54,8 +54,8 @@ export class FoldingModel { let editorDecorationId = this._editorDecorationIds[index]; if (editorDecorationId && !processed[editorDecorationId]) { processed[editorDecorationId] = true; - let newCollapseState = !this._ranges.isCollapsed(index); - this._ranges.setCollapsed(index, newCollapseState); + let newCollapseState = !this._regions.isCollapsed(index); + this._regions.setCollapsed(index, newCollapseState); accessor.changeDecorationOptions(editorDecorationId, this._decorationProvider.getDecorationOption(newCollapseState)); } } @@ -63,7 +63,7 @@ export class FoldingModel { this._updateEventEmitter.fire({ model: this, collapseStateChanged: regions }); } - public update(newRanges: FoldingRanges, blockedLineNumers: number[] = []): void { + public update(newRegions: FoldingRegions, blockedLineNumers: number[] = []): void { let newEditorDecorations = []; let isBlocked = (startLineNumber, endLineNumber) => { @@ -76,11 +76,11 @@ export class FoldingModel { }; let initRange = (index: number, isCollapsed: boolean) => { - let startLineNumber = newRanges.getStartLineNumber(index); - if (isCollapsed && isBlocked(startLineNumber, newRanges.getEndLineNumber(index))) { + let startLineNumber = newRegions.getStartLineNumber(index); + if (isCollapsed && isBlocked(startLineNumber, newRegions.getEndLineNumber(index))) { isCollapsed = false; } - newRanges.setCollapsed(index, isCollapsed); + newRegions.setCollapsed(index, isCollapsed); let maxColumn = this._textModel.getLineMaxColumn(startLineNumber); let decorationRange = { startLineNumber: startLineNumber, @@ -93,8 +93,8 @@ export class FoldingModel { let i = 0; let nextCollapsed = () => { - while (i < this._ranges.length) { - let isCollapsed = this._ranges.isCollapsed(i); + while (i < this._regions.length) { + let isCollapsed = this._regions.isCollapsed(i); i++; if (isCollapsed) { return i - 1; @@ -105,14 +105,14 @@ export class FoldingModel { let k = 0; let collapsedIndex = nextCollapsed(); - while (collapsedIndex !== -1 && k < newRanges.length) { + while (collapsedIndex !== -1 && k < newRegions.length) { // get the latest range let decRange = this._textModel.getDecorationRange(this._editorDecorationIds[collapsedIndex]); if (decRange) { let collapsedStartLineNumber = decRange.startLineNumber; if (this._textModel.getLineMaxColumn(collapsedStartLineNumber) === decRange.startColumn) { // test that the decoration is still at the end otherwise it got deleted - while (k < newRanges.length) { - let startLineNumber = newRanges.getStartLineNumber(k); + while (k < newRegions.length) { + let startLineNumber = newRegions.getStartLineNumber(k); if (collapsedStartLineNumber >= startLineNumber) { initRange(k, collapsedStartLineNumber === startLineNumber); k++; @@ -124,13 +124,13 @@ export class FoldingModel { } collapsedIndex = nextCollapsed(); } - while (k < newRanges.length) { + while (k < newRegions.length) { initRange(k, false); k++; } this._editorDecorationIds = this._decorationProvider.deltaDecorations(this._editorDecorationIds, newEditorDecorations); - this._ranges = newRanges; + this._regions = newRegions; this._isInitialized = true; this._updateEventEmitter.fire({ model: this }); } @@ -140,12 +140,12 @@ export class FoldingModel { */ public getMemento(): CollapseMemento { let collapsedRanges: ILineRange[] = []; - for (let i = 0; i < this._ranges.length; i++) { - if (this._ranges.isCollapsed(i)) { + for (let i = 0; i < this._regions.length; i++) { + if (this._regions.isCollapsed(i)) { let range = this._textModel.getDecorationRange(this._editorDecorationIds[i]); if (range) { let startLineNumber = range.startLineNumber; - let endLineNumber = range.endLineNumber + this._ranges.getEndLineNumber(i) - this._ranges.getStartLineNumber(i); + let endLineNumber = range.endLineNumber + this._regions.getEndLineNumber(i) - this._regions.getStartLineNumber(i); collapsedRanges.push({ startLineNumber, endLineNumber }); } } @@ -179,11 +179,11 @@ export class FoldingModel { getAllRegionsAtLine(lineNumber: number, filter?: (r: FoldingRegion, level: number) => boolean): FoldingRegion[] { let result: FoldingRegion[] = []; - if (this._ranges) { - let index = this._ranges.findRange(lineNumber); + if (this._regions) { + let index = this._regions.findRange(lineNumber); let level = 1; while (index >= 0) { - let current = this._ranges.toRegion(index); + let current = this._regions.toRegion(index); if (!filter || filter(current, level)) { result.push(current); } @@ -195,10 +195,10 @@ export class FoldingModel { } getRegionAtLine(lineNumber: number): FoldingRegion { - if (this._ranges) { - let index = this._ranges.findRange(lineNumber); + if (this._regions) { + let index = this._regions.findRange(lineNumber); if (index >= 0) { - return this._ranges.toRegion(index); + return this._regions.toRegion(index); } } return null; @@ -210,9 +210,9 @@ export class FoldingModel { let levelStack: FoldingRegion[] = trackLevel ? [] : null; let index = region ? region.regionIndex + 1 : 0; let endLineNumber = region ? region.endLineNumber : Number.MAX_VALUE; - for (let i = index, len = this._ranges.length; i < len; i++) { - let current = this._ranges.toRegion(i); - if (this._ranges.getStartLineNumber(i) < endLineNumber) { + for (let i = index, len = this._regions.length; i < len; i++) { + let current = this._regions.toRegion(i); + if (this._regions.getStartLineNumber(i) < endLineNumber) { if (trackLevel) { while (levelStack.length > 0 && !current.containedBy(levelStack[levelStack.length - 1])) { levelStack.pop(); @@ -296,13 +296,13 @@ export function setCollapseStateAtLevel(foldingModel: FoldingModel, foldLevel: n */ export function setCollapseStateForMatchingLines(foldingModel: FoldingModel, regExp: RegExp, doCollapse: boolean): void { let editorModel = foldingModel.textModel; - let ranges = foldingModel.ranges; + let regions = foldingModel.regions; let toToggle = []; - for (let i = ranges.length - 1; i >= 0; i--) { - if (doCollapse !== ranges.isCollapsed(i)) { - let startLineNumber = ranges.getStartLineNumber(i); + for (let i = regions.length - 1; i >= 0; i--) { + if (doCollapse !== regions.isCollapsed(i)) { + let startLineNumber = regions.getStartLineNumber(i); if (regExp.test(editorModel.getLineContent(startLineNumber))) { - toToggle.push(ranges.toRegion(i)); + toToggle.push(regions.toRegion(i)); } } } diff --git a/src/vs/editor/contrib/folding/foldingRanges.ts b/src/vs/editor/contrib/folding/foldingRanges.ts index 666e174f1b2..613fe4c169b 100644 --- a/src/vs/editor/contrib/folding/foldingRanges.ts +++ b/src/vs/editor/contrib/folding/foldingRanges.ts @@ -15,7 +15,7 @@ export const MAX_LINE_NUMBER = 0xFFFFFF; const MASK_INDENT = 0xFF000000; -export class FoldingRanges { +export class FoldingRegions { private _startIndexes: Uint32Array; private _endIndexes: Uint32Array; private _collapseStates: Uint32Array; @@ -138,7 +138,7 @@ export class FoldingRanges { export class FoldingRegion { - constructor(private ranges: FoldingRanges, private index: number) { + constructor(private ranges: FoldingRegions, private index: number) { } public get startLineNumber() { diff --git a/src/vs/editor/contrib/folding/hiddenRangeModel.ts b/src/vs/editor/contrib/folding/hiddenRangeModel.ts index fee0f5108c2..0acb3f59648 100644 --- a/src/vs/editor/contrib/folding/hiddenRangeModel.ts +++ b/src/vs/editor/contrib/folding/hiddenRangeModel.ts @@ -23,7 +23,7 @@ export class HiddenRangeModel { this._foldingModel = model; this._foldingModelListener = model.onDidChange(_ => this.updateHiddenRanges()); this._hiddenRanges = []; - if (model.ranges.length) { + if (model.regions.length) { this.updateHiddenRanges(); } } @@ -37,7 +37,7 @@ export class HiddenRangeModel { let lastCollapsedStart = Number.MAX_VALUE; let lastCollapsedEnd = -1; - let ranges = this._foldingModel.ranges; + let ranges = this._foldingModel.regions; for (; i < ranges.length; i++) { if (!ranges.isCollapsed(i)) { continue; diff --git a/src/vs/editor/contrib/folding/indentRangeProvider.ts b/src/vs/editor/contrib/folding/indentRangeProvider.ts index 04259b2c8b8..9ec730af0e2 100644 --- a/src/vs/editor/contrib/folding/indentRangeProvider.ts +++ b/src/vs/editor/contrib/folding/indentRangeProvider.ts @@ -7,25 +7,37 @@ import { ITextModel } from 'vs/editor/common/model'; import { FoldingMarkers } from 'vs/editor/common/modes/languageConfiguration'; -import { FoldingRanges, MAX_LINE_NUMBER } from 'vs/editor/contrib/folding/foldingRanges'; +import { FoldingRegions, MAX_LINE_NUMBER } from 'vs/editor/contrib/folding/foldingRanges'; import { TextModel } from 'vs/editor/common/model/textModel'; +import { RangeProvider } from './folding'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; const MAX_FOLDING_REGIONS_FOR_INDENT_LIMIT = 5000; +export class IndentRangeProvider implements RangeProvider { + compute(editorModel: ITextModel): TPromise { + let foldingRules = LanguageConfigurationRegistry.getFoldingRules(editorModel.getLanguageIdentifier().id); + let offSide = foldingRules && foldingRules.offSide; + let markers = foldingRules && foldingRules.markers; + return TPromise.as(computeRanges(editorModel, offSide, markers)); + } +} + // public only for testing export class RangesCollector { private _startIndexes: number[]; private _endIndexes: number[]; private _indentOccurrences: number[]; private _length: number; - private _FoldingRangesLimit: number; + private _foldingRangesLimit: number; - constructor(FoldingRangesLimit: number) { + constructor(foldingRangesLimit: number) { this._startIndexes = []; this._endIndexes = []; this._indentOccurrences = []; this._length = 0; - this._FoldingRangesLimit = FoldingRangesLimit; + this._foldingRangesLimit = foldingRangesLimit; } public insertFirst(startLineNumber: number, endLineNumber: number, indent: number) { @@ -42,7 +54,7 @@ export class RangesCollector { } public toIndentRanges(model: ITextModel) { - if (this._length <= this._FoldingRangesLimit) { + if (this._length <= this._foldingRangesLimit) { // reverse and create arrays of the exact length let startIndexes = new Uint32Array(this._length); let endIndexes = new Uint32Array(this._length); @@ -50,14 +62,14 @@ export class RangesCollector { startIndexes[k] = this._startIndexes[i]; endIndexes[k] = this._endIndexes[i]; } - return new FoldingRanges(startIndexes, endIndexes); + return new FoldingRegions(startIndexes, endIndexes); } else { let entries = 0; let maxIndent = this._indentOccurrences.length; for (let i = 0; i < this._indentOccurrences.length; i++) { let n = this._indentOccurrences[i]; if (n) { - if (n + entries > this._FoldingRangesLimit) { + if (n + entries > this._foldingRangesLimit) { maxIndent = i; break; } @@ -78,7 +90,7 @@ export class RangesCollector { k++; } } - return new FoldingRanges(startIndexes, endIndexes); + return new FoldingRegions(startIndexes, endIndexes); } } @@ -87,9 +99,9 @@ export class RangesCollector { interface PreviousRegion { indent: number; line: number; marker: boolean; } -export function computeRanges(model: ITextModel, offSide: boolean, markers?: FoldingMarkers, FoldingRangesLimit = MAX_FOLDING_REGIONS_FOR_INDENT_LIMIT): FoldingRanges { +export function computeRanges(model: ITextModel, offSide: boolean, markers?: FoldingMarkers, foldingRangesLimit = MAX_FOLDING_REGIONS_FOR_INDENT_LIMIT): FoldingRegions { const tabSize = model.getOptions().tabSize; - let result = new RangesCollector(FoldingRangesLimit); + let result = new RangesCollector(foldingRangesLimit); let pattern = void 0; if (markers) { diff --git a/src/vs/editor/contrib/folding/syntaxRangeProvider.ts b/src/vs/editor/contrib/folding/syntaxRangeProvider.ts new file mode 100644 index 00000000000..345f08a12f3 --- /dev/null +++ b/src/vs/editor/contrib/folding/syntaxRangeProvider.ts @@ -0,0 +1,154 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { FoldingProvider, IFoldingRange } from 'vs/editor/common/modes'; +import { onUnexpectedExternalError } from 'vs/base/common/errors'; +import { asWinJsPromise } from 'vs/base/common/async'; +import { ITextModel } from 'vs/editor/common/model'; +import { RangeProvider } from './folding'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { MAX_LINE_NUMBER, FoldingRegions } from './foldingRanges'; + +const MAX_FOLDING_REGIONS_FOR_INDENT_LIMIT = 5000; + +export interface IFoldingRangeData extends IFoldingRange { + rank: number; +} + +export class SyntaxRangeProvider implements RangeProvider { + + constructor(private providers: FoldingProvider[]) { + } + + compute(model: ITextModel): TPromise { + return collectSyntaxRanges(this.providers, model).then(ranges => { + return sanitizeRanges(ranges); + }); + } + +} + +function collectSyntaxRanges(providers: FoldingProvider[], model: ITextModel): TPromise { + const rangeData: IFoldingRangeData[] = []; + let promises = providers.map((provider, rank) => asWinJsPromise(token => provider.provideFoldingRanges(model, token)).then(list => { + if (list && Array.isArray(list.ranges)) { + for (let r of list.ranges) { + rangeData.push({ startLineNumber: r.startLineNumber, endLineNumber: r.endLineNumber, rank, type: r.type }); + } + } + }, onUnexpectedExternalError)); + + return TPromise.join(promises).then(() => { + return rangeData; + }); +} + +export class RangesCollector { + private _startIndexes: number[]; + private _endIndexes: number[]; + private _nestingLevels: number[]; + private _nestingLevelCounts: number[]; + private _length: number; + private _foldingRangesLimit: number; + + constructor(foldingRangesLimit: number) { + this._startIndexes = []; + this._endIndexes = []; + this._nestingLevels = []; + this._nestingLevelCounts = []; + this._length = 0; + this._foldingRangesLimit = foldingRangesLimit; + } + + public add(startLineNumber: number, endLineNumber: number, nestingLevel: number) { + if (startLineNumber > MAX_LINE_NUMBER || endLineNumber > MAX_LINE_NUMBER) { + return; + } + let index = this._length; + this._startIndexes[index] = startLineNumber; + this._endIndexes[index] = endLineNumber; + this._nestingLevels[index] = nestingLevel; + this._length++; + if (nestingLevel < 30) { + this._nestingLevelCounts[nestingLevel] = (this._nestingLevelCounts[nestingLevel] || 0) + 1; + } + } + + public toIndentRanges() { + if (this._length <= this._foldingRangesLimit) { + let startIndexes = new Uint32Array(this._length); + let endIndexes = new Uint32Array(this._length); + for (let i = 0; i < this._length; i++) { + startIndexes[i] = this._startIndexes[i]; + endIndexes[i] = this._endIndexes[i]; + } + return new FoldingRegions(startIndexes, endIndexes); + } else { + let entries = 0; + let maxLevel = this._nestingLevelCounts.length; + for (let i = 0; i < this._nestingLevelCounts.length; i++) { + let n = this._nestingLevelCounts[i]; + if (n) { + if (n + entries > this._foldingRangesLimit) { + maxLevel = i; + break; + } + entries += n; + } + } + let startIndexes = new Uint32Array(entries); + let endIndexes = new Uint32Array(entries); + for (let i = 0, k = 0; i < this._length; i++) { + let level = this._nestingLevels[i]; + if (level < maxLevel) { + startIndexes[k] = this._startIndexes[i]; + endIndexes[k] = this._endIndexes[i]; + k++; + } + } + return new FoldingRegions(startIndexes, endIndexes); + } + + } +} + +export function sanitizeRanges(rangeData: IFoldingRangeData[]): FoldingRegions { + + let sorted = rangeData.sort((d1, d2) => { + let diff = d1.startLineNumber - d2.startLineNumber; + if (diff === 0) { + diff = d1.rank - d2.rank; + } + return diff; + }); + let collector = new RangesCollector(MAX_FOLDING_REGIONS_FOR_INDENT_LIMIT); + + let top: IFoldingRangeData = null; + let previous = []; + for (let entry of sorted) { + if (!top) { + top = entry; + collector.add(entry.startLineNumber, entry.endLineNumber, previous.length); + } else { + if (entry.startLineNumber > top.startLineNumber) { + if (entry.endLineNumber <= top.endLineNumber) { + previous.push(top); + top = entry; + collector.add(entry.startLineNumber, entry.endLineNumber, previous.length); + } else if (entry.startLineNumber > top.endLineNumber) { + do { + top = previous.pop(); + } while (top && entry.startLineNumber > top.endLineNumber); + previous.push(top); + top = entry; + collector.add(entry.startLineNumber, entry.endLineNumber, previous.length); + } + } + } + } + return collector.toIndentRanges(); +} \ No newline at end of file diff --git a/src/vs/editor/contrib/folding/test/foldingModel.test.ts b/src/vs/editor/contrib/folding/test/foldingModel.test.ts index 0b415eae211..a77fbf071c1 100644 --- a/src/vs/editor/contrib/folding/test/foldingModel.test.ts +++ b/src/vs/editor/contrib/folding/test/foldingModel.test.ts @@ -61,7 +61,7 @@ suite('Folding Model', () => { function assertFoldedRanges(foldingModel: FoldingModel, expectedRegions: ExpectedRegion[], message?: string) { let actualRanges = []; - let actual = foldingModel.ranges; + let actual = foldingModel.regions; for (let i = 0; i < actual.length; i++) { if (actual.isCollapsed(i)) { actualRanges.push(r(actual.getStartLineNumber(i), actual.getEndLineNumber(i))); @@ -72,7 +72,7 @@ suite('Folding Model', () => { function assertRanges(foldingModel: FoldingModel, expectedRegions: ExpectedRegion[], message?: string) { let actualRanges = []; - let actual = foldingModel.ranges; + let actual = foldingModel.regions; for (let i = 0; i < actual.length; i++) { actualRanges.push(r(actual.getStartLineNumber(i), actual.getEndLineNumber(i), actual.isCollapsed(i))); } diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index eded61cea6f..55ce345642e 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -389,6 +389,14 @@ export function registerColorProvider(languageId: string, provider: modes.Docume return modes.ColorProviderRegistry.register(languageId, provider); } +/** + * Register a folding provider + */ +/*export function registerFoldingProvider(languageId: string, provider: modes.FoldingProvider): IDisposable { + return modes.FoldingProviderRegistry.register(languageId, provider); +}*/ + + /** * Contains additional diagnostic information about the context in which * a [code action](#CodeActionProvider.provideCodeActions) is run. diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 0a1cc793efd..9165cba0dc6 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4047,6 +4047,9 @@ declare module monaco.languages { */ export function registerColorProvider(languageId: string, provider: DocumentColorProvider): IDisposable; + /** + * Register a folding provider + */ /** * Contains additional diagnostic information about the context in which * a [code action](#CodeActionProvider.provideCodeActions) is run. diff --git a/src/vs/platform/extensionManagement/node/extensionLifecycle.ts b/src/vs/platform/extensionManagement/node/extensionLifecycle.ts new file mode 100644 index 00000000000..cbeef87b2bc --- /dev/null +++ b/src/vs/platform/extensionManagement/node/extensionLifecycle.ts @@ -0,0 +1,123 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { Disposable } from 'vs/base/common/lifecycle'; +import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { ILogService } from 'vs/platform/log/common/log'; +import { fork, ChildProcess } from 'child_process'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { join } from 'vs/base/common/paths'; +import { Limiter } from 'vs/base/common/async'; +import { fromNodeEventEmitter, anyEvent, mapEvent, debounceEvent } from 'vs/base/common/event'; + +export class ExtensionsLifecycle extends Disposable { + + private processesLimiter: Limiter = new Limiter(5); // Run max 5 processes in parallel + + constructor( + @ILogService private logService: ILogService + ) { + super(); + } + + uninstall(extension: ILocalExtension): TPromise { + const uninstallScript = this.parseUninstallScript(extension); + if (uninstallScript) { + this.logService.info(extension.identifier.id, 'Running Uninstall hook'); + return this.processesLimiter.queue(() => + this.runUninstallHook(uninstallScript.uninstallHook, uninstallScript.args, extension) + .then(() => this.logService.info(extension.identifier.id, 'Finished running uninstall hook'), err => this.logService.error(extension.identifier.id, `Failed to run uninstall hook: ${err}`))); + } + return TPromise.as(null); + } + + private parseUninstallScript(extension: ILocalExtension): { uninstallHook: string, args: string[] } { + if (extension.manifest && extension.manifest['scripts'] && typeof extension.manifest['scripts']['vscode:uninstall'] === 'string') { + const uninstallScript = (extension.manifest['scripts']['vscode:uninstall']).split(' '); + if (uninstallScript.length < 2 || uninstallScript[0] !== 'node' || !uninstallScript[1]) { + this.logService.warn(extension.identifier.id, 'Uninstall script should be a node script'); + return null; + } + return { uninstallHook: join(extension.path, uninstallScript[1]), args: uninstallScript.slice(2) || [] }; + } + return null; + } + + private runUninstallHook(lifecycleHook: string, args: string[], extension: ILocalExtension): TPromise { + return new TPromise((c, e) => { + + const extensionLifecycleProcess = this.start(lifecycleHook, args, extension); + let timeoutHandler; + + const onexit = (error?: string) => { + clearTimeout(timeoutHandler); + timeoutHandler = null; + if (error) { + e(error); + } else { + c(null); + } + }; + + // on error + extensionLifecycleProcess.on('error', (err) => { + if (timeoutHandler) { + onexit(toErrorMessage(err) || 'Unknown'); + } + }); + + // on exit + extensionLifecycleProcess.on('exit', (code: number, signal: string) => { + if (timeoutHandler) { + onexit(code ? `Process exited with code ${code}` : void 0); + } + }); + + // timeout: kill process after waiting for 5s + timeoutHandler = setTimeout(() => { + timeoutHandler = null; + extensionLifecycleProcess.kill(); + e('timed out'); + }, 5000); + }); + } + + private start(uninstallHook: string, args: string[], extension: ILocalExtension): ChildProcess { + const opts = { + silent: true, + execArgv: undefined + }; + const extensionUninstallProcess = fork(uninstallHook, ['--type=extensionUninstall', ...args], opts); + + // Catch all output coming from the process + type Output = { data: string, format: string[] }; + extensionUninstallProcess.stdout.setEncoding('utf8'); + extensionUninstallProcess.stderr.setEncoding('utf8'); + const onStdout = fromNodeEventEmitter(extensionUninstallProcess.stdout, 'data'); + const onStderr = fromNodeEventEmitter(extensionUninstallProcess.stderr, 'data'); + const onOutput = anyEvent( + mapEvent(onStdout, o => ({ data: `%c${o}`, format: [''] })), + mapEvent(onStderr, o => ({ data: `%c${o}`, format: ['color: red'] })) + ); + + // Debounce all output, so we can render it in the Chrome console as a group + const onDebouncedOutput = debounceEvent(onOutput, (r, o) => { + return r + ? { data: r.data + o.data, format: [...r.format, ...o.format] } + : { data: o.data, format: o.format }; + }, 100); + + // Print out extension host output + onDebouncedOutput(data => { + console.group(extension.identifier.id); + console.log(data.data, ...data.format); + console.groupEnd(); + }); + + return extensionUninstallProcess; + } +} diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 718e8fd1b3d..7f82e757218 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -35,6 +35,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { ExtensionsManifestCache } from 'vs/platform/extensionManagement/node/extensionsManifestCache'; import { IChoiceService } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; +import { ExtensionsLifecycle } from 'vs/platform/extensionManagement/node/extensionLifecycle'; const SystemExtensionsRoot = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', 'extensions')); const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem'; @@ -110,6 +111,7 @@ export class ExtensionManagementService extends Disposable implements IExtension private lastReportTimestamp = 0; private readonly installingExtensions: Map> = new Map>(); private readonly manifestCache: ExtensionsManifestCache; + private readonly extensionLifecycle: ExtensionsLifecycle; private readonly _onInstallExtension = new Emitter(); readonly onInstallExtension: Event = this._onInstallExtension.event; @@ -135,6 +137,7 @@ export class ExtensionManagementService extends Disposable implements IExtension this.uninstalledFileLimiter = new Limiter(1); this._register(toDisposable(() => this.installingExtensions.clear())); this.manifestCache = this._register(new ExtensionsManifestCache(environmentService, this)); + this.extensionLifecycle = this._register(new ExtensionsLifecycle(this.logService)); } install(zipPath: string): TPromise { @@ -708,24 +711,43 @@ export class ExtensionManagementService extends Disposable implements IExtension } removeDeprecatedExtensions(): TPromise { + return this.removeUninstalledExtensions() + .then(() => this.removeOutdatedExtensions()); + } + + private removeUninstalledExtensions(): TPromise { return this.getUninstalledExtensions() .then(uninstalled => this.scanExtensions(this.extensionsPath, LocalExtensionType.User) // All user extensions .then(extensions => { - const toRemove: { path: string, id: string }[] = []; - // Uninstalled extensions - toRemove.push(...extensions.filter(e => uninstalled[e.identifier.id]).map(e => ({ path: e.path, id: e.identifier.id }))); - - // Outdated extensions - const byExtension: ILocalExtension[][] = groupByExtension(extensions, e => ({ id: getGalleryExtensionIdFromLocal(e), uuid: e.identifier.uuid })); - toRemove.push(...flatten(byExtension.map(p => p.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version)).slice(1))) - .map(e => ({ path: e.path, id: e.identifier.id }))); - - return TPromise.join(distinct(toRemove, e => e.path).map(({ path, id }) => { - return pfs.rimraf(path) - .then(() => this.withUninstalledExtensions(uninstalled => delete uninstalled[id])); - })); + const toRemove: ILocalExtension[] = extensions.filter(e => uninstalled[e.identifier.id]); + return TPromise.join(toRemove.map(e => this.removeUninstalledExtension(e))); }) - ); + ).then(() => null); + } + + private removeOutdatedExtensions(): TPromise { + return this.scanExtensions(this.extensionsPath, LocalExtensionType.User) // All user extensions + .then(extensions => { + const toRemove: ILocalExtension[] = []; + + // Outdated extensions + const byExtension: ILocalExtension[][] = groupByExtension(extensions, e => ({ id: getGalleryExtensionIdFromLocal(e), uuid: e.identifier.uuid })); + toRemove.push(...flatten(byExtension.map(p => p.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version)).slice(1)))); + + return TPromise.join(toRemove.map(extension => this.removeExtension(extension, 'outdated'))); + }).then(() => null); + } + + private removeUninstalledExtension(extension: ILocalExtension): TPromise { + return this.extensionLifecycle.uninstall(extension) + .then(() => this.removeExtension(extension, 'uninstalled')) + .then(() => this.withUninstalledExtensions(uninstalled => delete uninstalled[extension.identifier.id])) + .then(() => null); + } + + private removeExtension(extension: ILocalExtension, type: string): TPromise { + this.logService.trace(extension.identifier.id, 'Deleting from disk', type); + return pfs.rimraf(extension.path).then(() => this.logService.info(extension.identifier.id, 'Deleted from disk')); } private isUninstalled(id: string): TPromise { diff --git a/src/vs/platform/extensions/common/extensionsRegistry.ts b/src/vs/platform/extensions/common/extensionsRegistry.ts index d5cf06b32c3..c230839ad34 100644 --- a/src/vs/platform/extensions/common/extensionsRegistry.ts +++ b/src/vs/platform/extensions/common/extensionsRegistry.ts @@ -264,6 +264,10 @@ const schema: IJSONSchema = { 'vscode:prepublish': { description: nls.localize('vscode.extension.scripts.prepublish', 'Script executed before the package is published as a VS Code extension.'), type: 'string' + }, + 'vscode:uninstall': { + description: nls.localize('vscode.extension.scripts.uninstall', 'Script executed after the extension is uninstalled from VS Code. Only Node script is supported.'), + type: 'string' } } }, diff --git a/src/vs/platform/statusbar/common/statusbar.ts b/src/vs/platform/statusbar/common/statusbar.ts index a7287a62e9f..fffa1184e33 100644 --- a/src/vs/platform/statusbar/common/statusbar.ts +++ b/src/vs/platform/statusbar/common/statusbar.ts @@ -51,6 +51,11 @@ export interface IStatusbarEntry { * An optional extension ID if this entry is provided from an extension. */ extensionId?: string; + + /** + * Wether to show a beak above the status bar entry. + */ + showBeak?: boolean; } export interface IStatusbarService { diff --git a/src/vs/platform/telemetry/node/commonProperties.ts b/src/vs/platform/telemetry/node/commonProperties.ts index ba097b617ff..75450151be7 100644 --- a/src/vs/platform/telemetry/node/commonProperties.ts +++ b/src/vs/platform/telemetry/node/commonProperties.ts @@ -11,7 +11,7 @@ import { readFile } from 'vs/base/node/pfs'; export function resolveCommonProperties(commit: string, version: string, machineId: string, installSourcePath: string): TPromise<{ [name: string]: string; }> { const result: { [name: string]: string; } = Object.create(null); - // __GDPR__COMMON__ "common.machineId" : { "classification": "EndUserPseudonymizedInformation", "purpose": "FeatureInsight" } + // __GDPR__COMMON__ "common.machineId" : { "endPoint": "MacAddressHash", "classification": "EndUserPseudonymizedInformation", "purpose": "FeatureInsight" } result['common.machineId'] = machineId; // __GDPR__COMMON__ "sessionID" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } result['sessionID'] = uuid.generateUuid() + Date.now(); diff --git a/src/vs/platform/update/electron-main/updateService.linux.ts b/src/vs/platform/update/electron-main/updateService.linux.ts index cb36c658585..7b0ece17fe3 100644 --- a/src/vs/platform/update/electron-main/updateService.linux.ts +++ b/src/vs/platform/update/electron-main/updateService.linux.ts @@ -5,6 +5,7 @@ 'use strict'; +import product from 'vs/platform/node/product'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; import { IRequestService } from 'vs/platform/request/node/request'; @@ -76,7 +77,13 @@ export class LinuxUpdateService extends AbstractUpdateService { } protected doDownloadUpdate(state: AvailableForDownload): TPromise { - shell.openExternal(state.update.url); + // Use the download URL if available as we don't currently detect the package type that was + // installed and the website download page is more useful than the tarball generally. + if (product.downloadUrl && product.downloadUrl.length > 0) { + shell.openExternal(product.downloadUrl); + } else { + shell.openExternal(state.update.url); + } this.setState(State.Idle); return TPromise.as(null); diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 227a9a81106..03c83a946b4 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -108,7 +108,7 @@ export interface IWindowsService { showSaveDialog(windowId: number, options: SaveDialogOptions): TPromise; showOpenDialog(windowId: number, options: OpenDialogOptions): TPromise; - reloadWindow(windowId: number): TPromise; + reloadWindow(windowId: number, args?: ParsedArgs): TPromise; openDevTools(windowId: number): TPromise; toggleDevTools(windowId: number): TPromise; closeWorkspace(windowId: number): TPromise; @@ -183,7 +183,7 @@ export interface IWindowService { pickFileAndOpen(options: INativeOpenDialogOptions): TPromise; pickFolderAndOpen(options: INativeOpenDialogOptions): TPromise; pickWorkspaceAndOpen(options: INativeOpenDialogOptions): TPromise; - reloadWindow(): TPromise; + reloadWindow(args?: ParsedArgs): TPromise; openDevTools(): TPromise; toggleDevTools(): TPromise; closeWorkspace(): TPromise; diff --git a/src/vs/platform/windows/common/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts index 6a899f79556..aae46542f41 100644 --- a/src/vs/platform/windows/common/windowsIpc.ts +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -13,6 +13,7 @@ import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceFolde import { IRecentlyOpened } from 'vs/platform/history/common/history'; import { ICommandAction } from 'vs/platform/actions/common/actions'; import URI from 'vs/base/common/uri'; +import { ParsedArgs } from 'vs/platform/environment/common/environment'; export interface IWindowsChannel extends IChannel { call(command: 'event:onWindowOpen'): TPromise; @@ -25,7 +26,7 @@ export interface IWindowsChannel extends IChannel { call(command: 'showMessageBox', arg: [number, MessageBoxOptions]): TPromise; call(command: 'showSaveDialog', arg: [number, SaveDialogOptions]): TPromise; call(command: 'showOpenDialog', arg: [number, OpenDialogOptions]): TPromise; - call(command: 'reloadWindow', arg: number): TPromise; + call(command: 'reloadWindow', arg: [number, ParsedArgs]): TPromise; call(command: 'toggleDevTools', arg: number): TPromise; call(command: 'closeWorkspace', arg: number): TPromise; call(command: 'createAndEnterWorkspace', arg: [number, IWorkspaceFolderCreationData[], string]): TPromise; @@ -91,7 +92,7 @@ export class WindowsChannel implements IWindowsChannel { case 'showMessageBox': return this.service.showMessageBox(arg[0], arg[1]); case 'showSaveDialog': return this.service.showSaveDialog(arg[0], arg[1]); case 'showOpenDialog': return this.service.showOpenDialog(arg[0], arg[1]); - case 'reloadWindow': return this.service.reloadWindow(arg); + case 'reloadWindow': return this.service.reloadWindow(arg[0], arg[1]); case 'openDevTools': return this.service.openDevTools(arg); case 'toggleDevTools': return this.service.toggleDevTools(arg); case 'closeWorkspace': return this.service.closeWorkspace(arg); @@ -192,8 +193,8 @@ export class WindowsChannelClient implements IWindowsService { return this.channel.call('showOpenDialog', [windowId, options]); } - reloadWindow(windowId: number): TPromise { - return this.channel.call('reloadWindow', windowId); + reloadWindow(windowId: number, args?: ParsedArgs): TPromise { + return this.channel.call('reloadWindow', [windowId, args]); } openDevTools(windowId: number): TPromise { diff --git a/src/vs/platform/windows/electron-browser/windowService.ts b/src/vs/platform/windows/electron-browser/windowService.ts index 8548cf17b9f..7e442728b50 100644 --- a/src/vs/platform/windows/electron-browser/windowService.ts +++ b/src/vs/platform/windows/electron-browser/windowService.ts @@ -11,6 +11,7 @@ import { IWindowService, IWindowsService, INativeOpenDialogOptions, IEnterWorksp import { IRecentlyOpened } from 'vs/platform/history/common/history'; import { ICommandAction } from 'vs/platform/actions/common/actions'; import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; +import { ParsedArgs } from 'vs/platform/environment/common/environment'; export class WindowService implements IWindowService { @@ -60,8 +61,8 @@ export class WindowService implements IWindowService { return this.windowsService.pickWorkspaceAndOpen(options); } - reloadWindow(): TPromise { - return this.windowsService.reloadWindow(this.windowId); + reloadWindow(args?: ParsedArgs): TPromise { + return this.windowsService.reloadWindow(this.windowId, args); } openDevTools(): TPromise { diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index 51f8d64a09e..4afa98698bf 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -12,7 +12,7 @@ import { assign } from 'vs/base/common/objects'; import URI from 'vs/base/common/uri'; import product from 'vs/platform/node/product'; import { IWindowsService, OpenContext, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult } from 'vs/platform/windows/common/windows'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { shell, crashReporter, app, Menu, clipboard } from 'electron'; import Event, { chain, fromNodeEventEmitter } from 'vs/base/common/event'; import { IURLService } from 'vs/platform/url/common/url'; @@ -107,12 +107,12 @@ export class WindowsService implements IWindowsService, IDisposable { return this.windowsMainService.showOpenDialog(options, codeWindow); } - reloadWindow(windowId: number): TPromise { + reloadWindow(windowId: number, args: ParsedArgs): TPromise { this.logService.trace('windowsService#reloadWindow', windowId); const codeWindow = this.windowsMainService.getWindowById(windowId); if (codeWindow) { - this.windowsMainService.reload(codeWindow); + this.windowsMainService.reload(codeWindow, args); } return TPromise.as(null); diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index dc2a142150c..5061c95a0c9 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -7,6 +7,63 @@ declare module 'vscode' { + export class FoldingRangeList { + + /** + * The folding ranges. + */ + ranges: FoldingRange[]; + + /** + * Creates mew folding range list. + * + * @param ranges The folding ranges + */ + constructor(ranges: FoldingRange[]); + } + + + export class FoldingRange { + + /** + * The start line number (0-based) + */ + startLine: number; + + /** + * The end line number (0-based) + */ + endLine: number; + + /** + * The actual color value for this color range. + */ + type?: FoldingRangeType | string; + + /** + * Creates a new folding range. + * + * @param startLineNumber The first line of the fold + * @param type The last line of the fold + */ + constructor(startLineNumber: number, endLineNumber: number, type?: FoldingRangeType); + } + + export enum FoldingRangeType { + /** + * Folding range for a comment + */ + Comment = 'comment', + /** + * Folding range for a imports or includes + */ + Imports = 'imports', + /** + * Folding range for a region (e.g. `#region`) + */ + Region = 'region' + } + // export enum FileErrorCodes { // /** // * Not owner. @@ -340,10 +397,27 @@ declare module 'vscode' { } export namespace languages { + + /** + * Register a folding provider. + * + * Multiple folding can be registered for a language. In that case providers are sorted + * by their [score](#languages.match) and the best-matching provider is used. Failure + * of the selected provider will cause a failure of the whole operation. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider A folding provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerFoldingProvider(selector: DocumentSelector, provider: FoldingProvider): Disposable; + export interface RenameProvider2 extends RenameProvider { resolveInitialRenameValue?(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; } } + export interface FoldingProvider { + provideFoldingRanges(document: TextDocument, token: CancellationToken): ProviderResult; + } /** * Represents the validation type of the Source Control input. @@ -396,7 +470,7 @@ declare module 'vscode' { */ export interface WebviewOptions { /** - * Should scripts be enabled in the webview contetn? + * Should scripts be enabled in the webview content? * * Defaults to false (scripts-disabled). */ @@ -423,6 +497,15 @@ declare module 'vscode' { * webview content cannot be quickly saved and restored. */ readonly keepAlive?: boolean; + + /** + * Root paths from which the webview can load local (filesystem) resources using the `vscode-workspace-resource:` scheme. + * + * Default to the root folders of the current workspace. + * + * Pass in an empty array to disallow access to any local resources. + */ + readonly localResourceRoots?: Uri[]; } /** diff --git a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts index 8d5bf82d505..3ca33d51e0a 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts @@ -346,6 +346,17 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha }); } + // --- folding + + $registerFoldingProvider(handle: number, selector: vscode.DocumentSelector): void { + const proxy = this._proxy; + this._registrations[handle] = modes.FoldingProviderRegistry.register(toLanguageSelector(selector), { + provideFoldingRanges: (model, token) => { + return wireCancellationToken(token, proxy.$provideFoldingRanges(handle, model.uri)); + } + }); + } + // --- configuration private static _reviveRegExp(regExp: ISerializedRegExp): RegExp { diff --git a/src/vs/workbench/api/electron-browser/mainThreadWebview.ts b/src/vs/workbench/api/electron-browser/mainThreadWebview.ts index cf42eee83d2..6cbfd960fae 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadWebview.ts @@ -151,14 +151,8 @@ class WebviewEditor extends BaseWebviewEditor { public layout(dimension: Dimension): void { if (this._webview) { this.doUpdateContainer(); - this._webview.layout(); - } - } - - public focus() { - if (this._webview) { - this._webview.focus(); } + super.layout(dimension); } public dispose(): void { @@ -207,10 +201,11 @@ class WebviewEditor extends BaseWebviewEditor { this.webview.options = { allowScripts: input.options.enableScripts, - enableWrappedPostMessage: true + enableWrappedPostMessage: true, + useSameOriginForRoot: false, + localResourceRoots: (input && input.options.localResourceRoots) || this._contextService.getWorkspace().folders.map(x => x.uri) }; this.webview.contents = input.html; - this.webview.style(this.themeService.getTheme()); } private get webview(): Webview { @@ -220,26 +215,18 @@ class WebviewEditor extends BaseWebviewEditor { this._webview = new Webview( this.webviewContent, this._partService.getContainer(Parts.EDITOR_PART), + this.themeService, this._environmentService, - this._contextService, this._contextViewService, this.contextKey, this.findInputFocusContextKey, { - enableWrappedPostMessage: true - }, - false); - this.webview.style(this.themeService.getTheme()); + enableWrappedPostMessage: true, + useSameOriginForRoot: false + }); this._webview.onDidClickLink(this.onDidClickLink, this, this._contentDisposables); - - this.themeService.onThemeChange(theme => { - if (this._webview) { - this._webview.style(theme); - } - }, null, this._contentDisposables); - this._webview.onMessage(message => { if (this.input) { (this.input as WebviewInput).events.onMessage(message); diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 466456f5bc3..863b1bfc5ab 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -292,6 +292,9 @@ export function createApiFactory( registerColorProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentColorProvider): vscode.Disposable { return extHostLanguageFeatures.registerColorProvider(selector, provider); }, + registerFoldingProvider: proposedApiFunction(extension, (selector: vscode.DocumentSelector, provider: vscode.FoldingProvider): vscode.Disposable => { + return extHostLanguageFeatures.registerFoldingProvider(selector, provider); + }), setLanguageConfiguration: (language: string, configuration: vscode.LanguageConfiguration): vscode.Disposable => { return extHostLanguageFeatures.setLanguageConfiguration(language, configuration); } @@ -634,7 +637,10 @@ export function createApiFactory( RelativePattern: extHostTypes.RelativePattern, FileChangeType: extHostTypes.FileChangeType, - FileType: extHostTypes.FileType + FileType: extHostTypes.FileType, + FoldingRangeList: extHostTypes.FoldingRangeList, + FoldingRange: extHostTypes.FoldingRange, + FoldingRangeType: extHostTypes.FoldingRangeType }; }; } diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index b4715e17ff2..2c694546d36 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -282,6 +282,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerSignatureHelpProvider(handle: number, selector: vscode.DocumentSelector, triggerCharacter: string[]): void; $registerDocumentLinkProvider(handle: number, selector: vscode.DocumentSelector): void; $registerDocumentColorProvider(handle: number, selector: vscode.DocumentSelector): void; + $registerFoldingProvider(handle: number, selector: vscode.DocumentSelector): void; $setLanguageConfiguration(handle: number, languageId: string, configuration: ISerializedLanguageConfiguration): void; } @@ -692,6 +693,7 @@ export interface ExtHostLanguageFeaturesShape { $resolveDocumentLink(handle: number, link: modes.ILink): TPromise; $provideDocumentColors(handle: number, resource: UriComponents): TPromise; $provideColorPresentations(handle: number, resource: UriComponents, colorInfo: IRawColorInfo): TPromise; + $provideFoldingRanges(handle: number, resource: UriComponents): TPromise; } export interface ExtHostQuickOpenShape { diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index 56d191ee98b..8a270631e0b 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -804,10 +804,29 @@ class ColorProviderAdapter { } } +class FoldingProviderAdapter { + + constructor( + private _documents: ExtHostDocuments, + private _provider: vscode.FoldingProvider + ) { } + + provideFoldingRanges(resource: URI): TPromise { + const doc = this._documents.getDocumentData(resource).document; + return asWinJsPromise(token => this._provider.provideFoldingRanges(doc, token)).then(list => { + if (!Array.isArray(list.ranges)) { + return void 0; + } + return TypeConverters.FoldingRangeList.from(list); + }); + } +} + type Adapter = OutlineAdapter | CodeLensAdapter | DefinitionAdapter | HoverAdapter | DocumentHighlightAdapter | ReferenceAdapter | CodeActionAdapter | DocumentFormattingAdapter | RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter - | SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter | TypeDefinitionAdapter | ColorProviderAdapter; + | SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter | TypeDefinitionAdapter + | ColorProviderAdapter | FoldingProviderAdapter; export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { @@ -1108,6 +1127,16 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._withAdapter(handle, ColorProviderAdapter, adapter => adapter.provideColorPresentations(URI.revive(resource), colorInfo)); } + registerFoldingProvider(selector: vscode.DocumentSelector, provider: vscode.FoldingProvider): vscode.Disposable { + const handle = this._addNewAdapter(new FoldingProviderAdapter(this._documents, provider)); + this._proxy.$registerFoldingProvider(handle, selector); + return this._createDisposable(handle); + } + + $provideFoldingRanges(handle: number, resource: UriComponents): TPromise { + return this._withAdapter(handle, FoldingProviderAdapter, adapter => adapter.provideFoldingRanges(URI.revive(resource))); + } + // --- configuration private static _serializeRegExp(regExp: RegExp): ISerializedRegExp { diff --git a/src/vs/workbench/api/node/extHostTypeConverters.ts b/src/vs/workbench/api/node/extHostTypeConverters.ts index 428d05ab635..c15538d7339 100644 --- a/src/vs/workbench/api/node/extHostTypeConverters.ts +++ b/src/vs/workbench/api/node/extHostTypeConverters.ts @@ -586,6 +586,14 @@ export namespace ProgressLocation { } } +export namespace FoldingRangeList { + export function from(rangeList: vscode.FoldingRangeList): modes.IFoldingRangeList { + return { + ranges: rangeList.ranges.map(r => ({ startLineNumber: r.startLine + 1, endLineNumber: r.endLine + 1 })) + }; + } +} + export function toTextEditorOptions(options?: vscode.TextDocumentShowOptions): ITextEditorOptions { if (options) { return { diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index 2cac1f11910..41bf5746751 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -1659,3 +1659,46 @@ export enum FileType { } //#endregion + +//#region folding api + +export class FoldingRangeList { + + ranges: FoldingRange[]; + + constructor(ranges: FoldingRange[]) { + this.ranges = ranges; + } +} + +export class FoldingRange { + + startLine: number; + + endLine: number; + + type?: FoldingRangeType; + + constructor(startLine: number, endLine: number, type?: FoldingRangeType) { + this.startLine = startLine; + this.endLine = endLine; + this.type = type; + } +} + +export enum FoldingRangeType { + /** + * Folding range for a comment + */ + Comment = 'comment', + /** + * Folding range for a imports or includes + */ + Imports = 'imports', + /** + * Folding range for a region (e.g. `#region`) + */ + Region = 'region' +} + +//#endregion \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/editor/media/titlecontrol.css b/src/vs/workbench/browser/parts/editor/media/titlecontrol.css index 7f7fe4a23c7..3a1d53f23c8 100644 --- a/src/vs/workbench/browser/parts/editor/media/titlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/titlecontrol.css @@ -11,6 +11,11 @@ flex: 1; } +.monaco-workbench > .part.editor > .content > .one-editor-silo.centered > .container > .title .title-label { + flex-direction: row; + justify-content: center; +} + .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label a, .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label a { text-decoration: none; @@ -104,4 +109,4 @@ .vs-dark .monaco-workbench .show-group-editors-action, .hc-black .monaco-workbench .show-group-editors-action { background: url('stackview-inverse.svg') center center no-repeat; -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsCenter.css b/src/vs/workbench/browser/parts/notifications/media/notificationsCenter.css index 066804bea77..c8356cea896 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsCenter.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsCenter.css @@ -7,14 +7,13 @@ position: absolute; z-index: 1000; right: 8px; - bottom: 30px; /* above status bar */ + bottom: 31px; display: none; overflow: hidden; } .monaco-workbench.nostatusbar > .notifications-center { - right: 12px; - bottom: 12px; + bottom: 8px; } .monaco-workbench > .notifications-center.visible { diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsList.css b/src/vs/workbench/browser/parts/notifications/media/notificationsList.css index fd017ca60e6..451a93476c1 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsList.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsList.css @@ -14,6 +14,16 @@ box-sizing: border-box; } +.monaco-workbench .notifications-list-container .notification-offset-helper { + opacity: 0; + margin-top: 10px; + margin-bottom: 10px; + margin-left: 35px; /* 5px padding and 30px severity icon */ + margin-right: 77px; /* 5px padding and (3 * 24px) toolbar */ + position: absolute; + line-height: 22px; +} + /** Notification: Main Row */ .monaco-workbench .notifications-list-container .notification-list-item > .notification-list-item-main-row { diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css b/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css index 80cb61d0eb4..bf75bd6e55b 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css @@ -7,14 +7,13 @@ position: absolute; z-index: 1000; right: 3px; - bottom: 28px; /* above status bar */ + bottom: 26px; display: none; overflow: hidden; } .monaco-workbench.nostatusbar > .notifications-toasts { - right: 12px; - bottom: 12px; + bottom: 3px; } .monaco-workbench > .notifications-toasts.visible { diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index 428f1158a71..7a5d2319390 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -23,7 +23,7 @@ import { localize } from 'vs/nls'; export class NotificationsCenter extends Themable { - private static MAX_DIMENSIONS = new Dimension(600, 400); + private static MAX_DIMENSIONS = new Dimension(450, 400); private notificationsCenterContainer: HTMLElement; private notificationsList: NotificationsList; @@ -75,7 +75,10 @@ export class NotificationsCenter extends Themable { this.notificationsCenterContainer = document.createElement('div'); addClass(this.notificationsCenterContainer, 'notifications-center'); - this.notificationsList = this.instantiationService.createInstance(NotificationsList, this.notificationsCenterContainer, { ariaLabel: localize('notificationsList', "Notifications List") }); + this.notificationsList = this.instantiationService.createInstance(NotificationsList, this.notificationsCenterContainer, { + ariaLabel: localize('notificationsList', "Notifications List"), + useShadows: false + }); this.container.appendChild(this.notificationsCenterContainer); } @@ -166,7 +169,7 @@ export class NotificationsCenter extends Themable { protected updateStyles(): void { if (this.notificationsCenterContainer) { const widgetShadowColor = this.getColor(widgetShadow); - this.notificationsCenterContainer.style.boxShadow = widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : null; + this.notificationsCenterContainer.style.boxShadow = widgetShadowColor ? `0 0px 8px ${widgetShadowColor}` : null; } } @@ -219,6 +222,6 @@ export class NotificationsCenter extends Themable { registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const notificationBorderColor = theme.getColor(NOTIFICATIONS_BORDER); if (notificationBorderColor) { - collector.addRule(`.monaco-workbench > .notifications-center .notifications-list-container .notification-list-item { border-bottom: 1px solid ${notificationBorderColor}; }`); + collector.addRule(`.monaco-workbench > .notifications-center .notifications-list-container .monaco-list-row[data-last-element="false"] > .notification-list-item { border-bottom: 1px solid ${notificationBorderColor}; }`); } }); \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/notifications/notificationsList.ts b/src/vs/workbench/browser/parts/notifications/notificationsList.ts index fe162dd8c57..d835d7ef0b3 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsList.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsList.ts @@ -18,10 +18,6 @@ import { NotificationsListDelegate, NotificationRenderer } from 'vs/workbench/br import { NotificationActionRunner } from 'vs/workbench/browser/parts/notifications/notificationsActions'; import { NotificationFocusedContext } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; -export interface INotificationsListOptions { - ariaLabel: string; -} - export class NotificationsList extends Themable { private listContainer: HTMLElement; private list: WorkbenchList; @@ -30,7 +26,7 @@ export class NotificationsList extends Themable { constructor( private container: HTMLElement, - private options: INotificationsListOptions, + private options: IListOptions, @IInstantiationService private instantiationService: IInstantiationService, @IThemeService themeService: IThemeService ) { @@ -77,9 +73,7 @@ export class NotificationsList extends Themable { this.listContainer, new NotificationsListDelegate(this.listContainer), [renderer], - { - ariaLabel: this.options.ariaLabel - } as IListOptions + this.options ); this.toUnbind.push(this.list); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts index c9215fa253c..258ccc96e82 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts @@ -72,7 +72,8 @@ export class NotificationsStatus { this.statusItem = this.statusbarService.addEntry({ text: this.counter === 0 ? '$(megaphone)' : `$(megaphone) ${this.counter}`, command: this.isNotificationsCenterVisible ? HIDE_NOTIFICATIONS_CENTER_COMMAND_ID : this.model.notifications.length > 0 ? SHOW_NOTIFICATIONS_CENTER_COMMAND_ID : void 0, - tooltip: this.getTooltip() + tooltip: this.getTooltip(), + showBeak: this.isNotificationsCenterVisible }, StatusbarAlignment.RIGHT, -1000 /* towards the far end of the right hand side */); } @@ -81,14 +82,14 @@ export class NotificationsStatus { return localize('hideNotifications', "Hide Notifications"); } - if (this.counter === 0) { - return localize('noNotifications', "No New Notifications"); - } - if (this.model.notifications.length === 0) { return localize('zeroNotifications', "No Notifications"); } + if (this.counter === 0) { + return localize('noNotifications', "No New Notifications"); + } + if (this.counter === 1) { return localize('oneNotification', "1 new notification"); } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index dfa261b4128..62d94a47f55 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -22,6 +22,7 @@ import { NotificationsToastsVisibleContext } from 'vs/workbench/browser/parts/no import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { localize } from 'vs/nls'; import { Severity } from 'vs/platform/notification/common/notification'; +import { ScrollbarVisibility } from 'vs/base/common/scrollable'; interface INotificationToast { list: NotificationsList; @@ -106,7 +107,10 @@ export class NotificationsToasts extends Themable { itemDisposeables.push(toDisposable(() => this.notificationsToastsContainer.removeChild(notificationToastContainer))); // Create toast with item and show - const notificationList = this.instantiationService.createInstance(NotificationsList, notificationToastContainer, { ariaLabel: localize('notificationsToast', "Notification Toast") }); + const notificationList = this.instantiationService.createInstance(NotificationsList, notificationToastContainer, { + ariaLabel: localize('notificationsToast', "Notification Toast"), + verticalScrollMode: ScrollbarVisibility.Hidden + }); itemDisposeables.push(notificationList); this.mapNotificationToToast.set(item, { list: notificationList, container: notificationToastContainer, disposeables: itemDisposeables }); @@ -299,7 +303,7 @@ export class NotificationsToasts extends Themable { protected updateStyles(): void { this.mapNotificationToToast.forEach(toast => { const widgetShadowColor = this.getColor(widgetShadow); - toast.container.style.boxShadow = widgetShadowColor ? `0 2px 8px ${widgetShadowColor}` : null; + toast.container.style.boxShadow = widgetShadowColor ? `0 0px 8px ${widgetShadowColor}` : null; }); } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index 4bdc59a5789..3fae826811e 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -42,11 +42,7 @@ export class NotificationsListDelegate implements IDelegate { return super.layout(dimension); } - - protected getTitleAreaContextMenuActions(): IAction[] { - const contextMenuActions = super.getTitleAreaContextMenuActions(); - if (contextMenuActions.length) { - contextMenuActions.push(new Separator()); - } - contextMenuActions.push(this.createHideSideBarAction()); - return contextMenuActions; - } - - private createHideSideBarAction(): IAction { - return { - id: ToggleSidebarVisibilityAction.ID, - label: nls.localize('compositePart.hideSideBarLabel', "Hide Side Bar"), - enabled: true, - run: () => this.partService.setSideBarHidden(true) - }; - } - } class FocusSideBarAction extends Action { diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index 77ace7d3011..886fc554729 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -9,7 +9,6 @@ height: 22px; font-size: 12px; padding: 0 10px; - overflow: hidden; } .monaco-workbench > .part.statusbar > .statusbar-item { @@ -19,6 +18,21 @@ vertical-align: top; } +.monaco-workbench > .part.statusbar > .statusbar-item.has-beak { + position: relative; +} + +.monaco-workbench > .part.statusbar > .statusbar-item.has-beak:before { + content: ''; + position: absolute; + left: 11px; + top: -5px; + border-bottom-width: 5px; + border-bottom-style: solid; + border-left: 5px solid transparent; + border-right: 5px solid transparent; +} + .monaco-workbench > .part.statusbar > .statusbar-item.left > :first-child { margin-right: 5px; } diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 60f6efe34df..8c875405a93 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -29,7 +29,7 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/ import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { isThemeColor } from 'vs/editor/common/editorCommon'; import { Color } from 'vs/base/common/color'; -import { addClass, EventHelper } from 'vs/base/browser/dom'; +import { addClass, EventHelper, createStyleSheet } from 'vs/base/browser/dom'; import { INotificationService } from 'vs/platform/notification/common/notification'; export class StatusbarPart extends Part implements IStatusbarService { @@ -42,6 +42,8 @@ export class StatusbarPart extends Part implements IStatusbarService { private statusItemsContainer: Builder; private statusMsgDispose: IDisposable; + private styleElement: HTMLStyleElement; + constructor( id: string, @IInstantiationService private instantiationService: IInstantiationService, @@ -60,7 +62,7 @@ export class StatusbarPart extends Part implements IStatusbarService { public addEntry(entry: IStatusbarEntry, alignment: StatusbarAlignment, priority: number = 0): IDisposable { // Render entry in status bar - const el = this.doCreateStatusItem(alignment, priority); + const el = this.doCreateStatusItem(alignment, priority, entry.showBeak ? 'has-beak' : void 0); const item = this.instantiationService.createInstance(StatusBarEntryItem, entry); const toDispose = item.render(el); @@ -140,18 +142,31 @@ export class StatusbarPart extends Part implements IStatusbarService { const container = this.getContainer(); + // Background colors + const backgroundColor = this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_BACKGROUND : STATUS_BAR_NO_FOLDER_BACKGROUND); + container.style('background-color', backgroundColor); container.style('color', this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_FOREGROUND : STATUS_BAR_NO_FOLDER_FOREGROUND)); - container.style('background-color', this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_BACKGROUND : STATUS_BAR_NO_FOLDER_BACKGROUND)); + // Border color const borderColor = this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_BORDER : STATUS_BAR_NO_FOLDER_BORDER) || this.getColor(contrastBorder); container.style('border-top-width', borderColor ? '1px' : null); container.style('border-top-style', borderColor ? 'solid' : null); container.style('border-top-color', borderColor); + + // Notification Beak + if (!this.styleElement) { + this.styleElement = createStyleSheet(container.getHTMLElement()); + } + + this.styleElement.innerHTML = `.monaco-workbench > .part.statusbar > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor}; }`; } - private doCreateStatusItem(alignment: StatusbarAlignment, priority: number = 0): HTMLElement { + private doCreateStatusItem(alignment: StatusbarAlignment, priority: number = 0, extraClass?: string): HTMLElement { const el = document.createElement('div'); addClass(el, 'statusbar-item'); + if (extraClass) { + addClass(el, extraClass); + } if (alignment === StatusbarAlignment.RIGHT) { addClass(el, 'right'); diff --git a/src/vs/workbench/browser/parts/views/panelViewlet.ts b/src/vs/workbench/browser/parts/views/panelViewlet.ts index 22c581580ec..2d24634db83 100644 --- a/src/vs/workbench/browser/parts/views/panelViewlet.ts +++ b/src/vs/workbench/browser/parts/views/panelViewlet.ts @@ -11,7 +11,7 @@ import { ColorIdentifier, contrastBorder } from 'vs/platform/theme/common/colorR import { attachStyler, IColorMapping, IThemable } from 'vs/platform/theme/common/styler'; import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND } from 'vs/workbench/common/theme'; import { Dimension, Builder } from 'vs/base/browser/builder'; -import { append, $, trackFocus, toggleClass } from 'vs/base/browser/dom'; +import { append, $, trackFocus, toggleClass, EventType, isAncestor } from 'vs/base/browser/dom'; import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { firstIndex } from 'vs/base/common/arrays'; import { IAction, IActionRunner } from 'vs/base/common/actions'; @@ -26,6 +26,8 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { PanelView, IPanelViewOptions, IPanelOptions, Panel } from 'vs/base/browser/ui/splitview/panelview'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; export interface IPanelColors extends IColorMapping { dropBackground?: ColorIdentifier; @@ -165,10 +167,12 @@ export class PanelViewlet extends Viewlet { constructor( id: string, private options: IViewsViewletOptions, + @IPartService partService: IPartService, + @IContextMenuService protected contextMenuService: IContextMenuService, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService ) { - super(id, telemetryService, themeService); + super(id, partService, telemetryService, themeService); } async create(parent: Builder): TPromise { @@ -176,7 +180,26 @@ export class PanelViewlet extends Viewlet { const container = parent.getHTMLElement(); this.panelview = this._register(new PanelView(container, this.options)); - this.panelview.onDidDrop(({ from, to }) => this.movePanel(from as ViewletPanel, to as ViewletPanel)); + this._register(this.panelview.onDidDrop(({ from, to }) => this.movePanel(from as ViewletPanel, to as ViewletPanel))); + this._register(parent.on(EventType.CONTEXT_MENU, (e: MouseEvent) => this.showContextMenu(new StandardMouseEvent(e)))); + } + + private showContextMenu(event: StandardMouseEvent): void { + event.stopPropagation(); + event.preventDefault(); + + for (const panelItem of this.panelItems) { + // Do not show context menu if requested from inside panel views + if (isAncestor(event.target, panelItem.panel.element)) { + return; + } + } + + let anchor: { x: number, y: number } = { x: event.posx, y: event.posy }; + this.contextMenuService.showContextMenu({ + getAnchor: () => anchor, + getActions: () => TPromise.as(this.getContextMenuActions()) + }); } getTitle(): string { diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 4ecac43df70..d96df6d36cc 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; import * as errors from 'vs/base/common/errors'; import * as DOM from 'vs/base/browser/dom'; @@ -11,7 +10,7 @@ import { $, Dimension, Builder } from 'vs/base/browser/builder'; import { Scope } from 'vs/workbench/common/memento'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { IAction, IActionRunner } from 'vs/base/common/actions'; -import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { firstIndex } from 'vs/base/common/arrays'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -31,6 +30,8 @@ import { IWorkbenchThemeService, IFileIconTheme } from 'vs/workbench/services/th import { ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree'; import Event, { Emitter } from 'vs/base/common/event'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { localize } from 'vs/nls'; export interface IViewOptions extends IPanelOptions { id: string; @@ -200,6 +201,7 @@ export class ViewsViewlet extends PanelViewlet implements IViewsViewlet { id: string, private location: ViewLocation, private showHeaderInTitleWhenSingleView: boolean, + @IPartService partService: IPartService, @ITelemetryService telemetryService: ITelemetryService, @IStorageService protected storageService: IStorageService, @IInstantiationService protected instantiationService: IInstantiationService, @@ -208,7 +210,7 @@ export class ViewsViewlet extends PanelViewlet implements IViewsViewlet { @IContextMenuService protected contextMenuService: IContextMenuService, @IExtensionService protected extensionService: IExtensionService ) { - super(id, { showHeaderInTitleWhenSingleView, dnd: true }, telemetryService, themeService); + super(id, { showHeaderInTitleWhenSingleView, dnd: true }, partService, contextMenuService, telemetryService, themeService); this.viewletSettings = this.getMemento(storageService, Scope.WORKSPACE); } @@ -232,7 +234,8 @@ export class ViewsViewlet extends PanelViewlet implements IViewsViewlet { } getContextMenuActions(): IAction[] { - return this.getViewDescriptorsFromRegistry(true) + const result: IAction[] = []; + const viewToggleActions = this.getViewDescriptorsFromRegistry(true) .filter(viewDescriptor => viewDescriptor.canToggleVisibility && this.contextKeyService.contextMatchesRules(viewDescriptor.when)) .map(viewDescriptor => ({ id: `${viewDescriptor.id}.toggleVisibility`, @@ -241,6 +244,13 @@ export class ViewsViewlet extends PanelViewlet implements IViewsViewlet { enabled: true, run: () => this.toggleViewVisibility(viewDescriptor.id) })); + result.push(...viewToggleActions); + const parentActions = super.getContextMenuActions(); + if (viewToggleActions.length && parentActions.length) { + result.push(new Separator()); + } + result.push(...parentActions); + return result; } setVisible(visible: boolean): TPromise { @@ -481,9 +491,7 @@ export class ViewsViewlet extends PanelViewlet implements IViewsViewlet { this.viewHeaderContextMenuListeners.push(DOM.addDisposableListener(view.draggableElement, DOM.EventType.CONTEXT_MENU, e => { e.stopPropagation(); e.preventDefault(); - if (viewDescriptor.canToggleVisibility) { - this.onContextMenu(new StandardMouseEvent(e), view); - } + this.onContextMenu(new StandardMouseEvent(e), viewDescriptor); })); } } @@ -498,19 +506,26 @@ export class ViewsViewlet extends PanelViewlet implements IViewsViewlet { } } - private onContextMenu(event: StandardMouseEvent, view: ViewsViewletPanel): void { + private onContextMenu(event: StandardMouseEvent, viewDescriptor: IViewDescriptor): void { event.stopPropagation(); event.preventDefault(); + const actions: IAction[] = []; + actions.push({ + id: `${viewDescriptor.id}.removeView`, + label: localize('hideView', "Hide"), + enabled: viewDescriptor.canToggleVisibility, + run: () => this.toggleViewVisibility(viewDescriptor.id) + }); + const otherActions = this.getContextMenuActions(); + if (otherActions.length) { + actions.push(...[new Separator(), ...otherActions]); + } + let anchor: { x: number, y: number } = { x: event.posx, y: event.posy }; this.contextMenuService.showContextMenu({ getAnchor: () => anchor, - getActions: () => TPromise.as([{ - id: `${view.id}.removeView`, - label: nls.localize('hideView', "Hide"), - enabled: true, - run: () => this.toggleViewVisibility(view.id) - }]), + getActions: () => TPromise.as(actions) }); } @@ -610,6 +625,7 @@ export class PersistentViewsViewlet extends ViewsViewlet { location: ViewLocation, private readonly viewletStateStorageId: string, showHeaderInTitleWhenSingleView: boolean, + @IPartService partService: IPartService, @ITelemetryService telemetryService: ITelemetryService, @IStorageService storageService: IStorageService, @IInstantiationService instantiationService: IInstantiationService, @@ -619,7 +635,7 @@ export class PersistentViewsViewlet extends ViewsViewlet { @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService ) { - super(id, location, showHeaderInTitleWhenSingleView, telemetryService, storageService, instantiationService, themeService, contextKeyService, contextMenuService, extensionService); + super(id, location, showHeaderInTitleWhenSingleView, partService, telemetryService, storageService, instantiationService, themeService, contextKeyService, contextMenuService, extensionService); this.hiddenViewsStorageId = `${this.viewletStateStorageId}.hidden`; this._register(this.onDidChangeViewVisibilityState(id => this.onViewVisibilityChanged(id))); } diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index bbf35cdb272..ae4dc027589 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -7,19 +7,40 @@ import nls = require('vs/nls'); import { TPromise } from 'vs/base/common/winjs.base'; import DOM = require('vs/base/browser/dom'); import { Registry } from 'vs/platform/registry/common/platform'; -import { Action } from 'vs/base/common/actions'; +import { Action, IAction } from 'vs/base/common/actions'; import { ITree } from 'vs/base/parts/tree/browser/tree'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { Composite, CompositeDescriptor, CompositeRegistry } from 'vs/workbench/browser/composite'; import { IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation'; +import { ToggleSidebarVisibilityAction } from 'vs/workbench/browser/actions/toggleSidebarVisibility'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; export abstract class Viewlet extends Composite implements IViewlet { + constructor(id: string, + private partService: IPartService, + telemetryService: ITelemetryService, + themeService: IThemeService + ) { + super(id, telemetryService, themeService); + } + public getOptimalWidth(): number { return null; } + + public getContextMenuActions(): IAction[] { + return [{ + id: ToggleSidebarVisibilityAction.ID, + label: nls.localize('compositePart.hideSideBarLabel', "Hide Side Bar"), + enabled: true, + run: () => this.partService.setSideBarHidden(true) + }]; + } } /** diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index dbc2e4b31df..ea4e3732fe7 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -197,7 +197,7 @@ export interface INotificationViewItem { readonly onDidLabelChange: Event; expand(): void; - collapse(): void; + collapse(skipEvents?: boolean): void; toggle(): void; hasProgress(): boolean; @@ -494,13 +494,16 @@ export class NotificationViewItem implements INotificationViewItem { this._onDidExpansionChange.fire(); } - public collapse(): void { + public collapse(skipEvents?: boolean): void { if (!this._expanded || !this.canCollapse) { return; } this._expanded = false; - this._onDidExpansionChange.fire(); + + if (!skipEvents) { + this._onDidExpansionChange.fire(); + } } public toggle(): void { diff --git a/src/vs/workbench/electron-browser/actions.ts b/src/vs/workbench/electron-browser/actions.ts index a3546b2344d..38ba0708cfc 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -555,6 +555,24 @@ export class ReloadWindowAction extends Action { } } +export class ReloadWindowWithExtensionsDisabledAction extends Action { + + static readonly ID = 'workbench.action.reloadWindowWithExtensionsDisabled'; + static LABEL = nls.localize('reloadWindowWithExntesionsDisabled', "Reload Window With Extensions Disabled"); + + constructor( + id: string, + label: string, + @IWindowService private windowService: IWindowService + ) { + super(id, label); + } + + run(): TPromise { + return this.windowService.reloadWindow({ _: [], 'disable-extensions': true }).then(() => true); + } +} + export abstract class BaseSwitchWindow extends Action { private closeWindowAction: CloseWindowAction; diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index 873e612d355..67bcc00e7b6 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -84,7 +84,7 @@ import { MenuService } from 'vs/platform/actions/common/menuService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; -import { OpenRecentAction, ToggleDevToolsAction, ReloadWindowAction, ShowPreviousWindowTab, MoveWindowTabToNewWindow, MergeAllWindowTabs, ShowNextWindowTab, ToggleWindowTabsBar } from 'vs/workbench/electron-browser/actions'; +import { OpenRecentAction, ToggleDevToolsAction, ReloadWindowAction, ShowPreviousWindowTab, MoveWindowTabToNewWindow, MergeAllWindowTabs, ShowNextWindowTab, ToggleWindowTabsBar, ReloadWindowWithExtensionsDisabledAction } from 'vs/workbench/electron-browser/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { WorkspaceEditingService } from 'vs/workbench/services/workspace/node/workspaceEditingService'; @@ -207,7 +207,7 @@ export class Workbench implements IPartService { private sideBarPosition: Position; private panelPosition: Position; private panelHidden: boolean; - private editorBackgroundDelayer: Delayer; + private editorBackgroundDelayer: Delayer; private closeEmptyWindowScheduler: RunOnceScheduler; private editorsVisibleContext: IContextKey; private inZenMode: IContextKey; @@ -418,7 +418,7 @@ export class Workbench implements IPartService { workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ReloadWindowAction, ReloadWindowAction.ID, ReloadWindowAction.LABEL, isDeveloping ? { primary: KeyMod.CtrlCmd | KeyCode.KEY_R } : void 0), 'Reload Window'); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleDevToolsAction, ToggleDevToolsAction.ID, ToggleDevToolsAction.LABEL, isDeveloping ? { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_I, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_I } } : void 0), 'Developer: Toggle Developer Tools', localize('developer', "Developer")); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenRecentAction, OpenRecentAction.ID, OpenRecentAction.LABEL, { primary: isDeveloping ? null : KeyMod.CtrlCmd | KeyCode.KEY_R, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_R } }), 'File: Open Recent...', localize('file', "File")); - + workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ReloadWindowWithExtensionsDisabledAction, ReloadWindowWithExtensionsDisabledAction.ID, ReloadWindowWithExtensionsDisabledAction.LABEL), 'Reload Window Without Extensions'); // Actions for macOS native tabs management (only when enabled) const windowConfig = this.configurationService.getValue(); if (windowConfig && windowConfig.window && windowConfig.window.nativeTabs) { diff --git a/src/vs/workbench/parts/debug/browser/debugViewlet.ts b/src/vs/workbench/parts/debug/browser/debugViewlet.ts index eddef39bcdd..3f1885ea3e3 100644 --- a/src/vs/workbench/parts/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/parts/debug/browser/debugViewlet.ts @@ -26,6 +26,7 @@ import { ViewLocation } from 'vs/workbench/common/views'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IPartService } from 'vs/workbench/services/part/common/partService'; export class DebugViewlet extends PersistentViewsViewlet { @@ -35,6 +36,7 @@ export class DebugViewlet extends PersistentViewsViewlet { private panelListeners = new Map(); constructor( + @IPartService partService: IPartService, @ITelemetryService telemetryService: ITelemetryService, @IProgressService private progressService: IProgressService, @IDebugService private debugService: IDebugService, @@ -46,7 +48,7 @@ export class DebugViewlet extends PersistentViewsViewlet { @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService ) { - super(VIEWLET_ID, ViewLocation.Debug, `${VIEWLET_ID}.state`, false, telemetryService, storageService, instantiationService, themeService, contextService, contextKeyService, contextMenuService, extensionService); + super(VIEWLET_ID, ViewLocation.Debug, `${VIEWLET_ID}.state`, false, partService, telemetryService, storageService, instantiationService, themeService, contextService, contextKeyService, contextMenuService, extensionService); this.progressRunner = null; diff --git a/src/vs/workbench/parts/debug/browser/statusbarColorProvider.ts b/src/vs/workbench/parts/debug/browser/statusbarColorProvider.ts index 579b84ebb18..4afe110097b 100644 --- a/src/vs/workbench/parts/debug/browser/statusbarColorProvider.ts +++ b/src/vs/workbench/parts/debug/browser/statusbarColorProvider.ts @@ -11,7 +11,7 @@ import { IPartService, Parts } from 'vs/workbench/services/part/common/partServi import { IDebugService, State } from 'vs/workbench/parts/debug/common/debug'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_BACKGROUND, Themable, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER, STATUS_BAR_BORDER } from 'vs/workbench/common/theme'; -import { addClass, removeClass } from 'vs/base/browser/dom'; +import { addClass, removeClass, createStyleSheet } from 'vs/base/browser/dom'; // colors for theming @@ -34,6 +34,7 @@ export const STATUS_BAR_DEBUGGING_BORDER = registerColor('statusBar.debuggingBor }, localize('statusBarDebuggingBorder', "Status bar border color separating to the sidebar and editor when a program is being debugged. The status bar is shown in the bottom of the window")); export class StatusBarColorProvider extends Themable implements IWorkbenchContribution { + private styleElement: HTMLStyleElement; constructor( @IThemeService themeService: IThemeService, @@ -61,13 +62,23 @@ export class StatusBarColorProvider extends Themable implements IWorkbenchContri removeClass(container, 'debugging'); } - container.style.backgroundColor = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_DEBUGGING_BACKGROUND, STATUS_BAR_BACKGROUND)); + // Container Colors + const backgroundColor = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_DEBUGGING_BACKGROUND, STATUS_BAR_BACKGROUND)); + container.style.backgroundColor = backgroundColor; container.style.color = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_DEBUGGING_FOREGROUND, STATUS_BAR_FOREGROUND)); + // Border Color const borderColor = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_BORDER, STATUS_BAR_DEBUGGING_BORDER, STATUS_BAR_BORDER)) || this.getColor(contrastBorder); container.style.borderTopWidth = borderColor ? '1px' : null; container.style.borderTopStyle = borderColor ? 'solid' : null; container.style.borderTopColor = borderColor; + + // Notification Beak + if (!this.styleElement) { + this.styleElement = createStyleSheet(container); + } + + this.styleElement.innerHTML = `.monaco-workbench > .part.statusbar > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor} !important; }`; } private getColorKey(noFolderColor: string, debuggingColor: string, normalColor: string): string { diff --git a/src/vs/workbench/parts/debug/electron-browser/replViewer.ts b/src/vs/workbench/parts/debug/electron-browser/replViewer.ts index a46709ce358..f3562497d05 100644 --- a/src/vs/workbench/parts/debug/electron-browser/replViewer.ts +++ b/src/vs/workbench/parts/debug/electron-browser/replViewer.ts @@ -9,7 +9,7 @@ import { IAction } from 'vs/base/common/actions'; import * as lifecycle from 'vs/base/common/lifecycle'; import * as errors from 'vs/base/common/errors'; import { isFullWidthCharacter, removeAnsiEscapeCodes, endsWith } from 'vs/base/common/strings'; -import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import * as dom from 'vs/base/browser/dom'; import severity from 'vs/base/common/severity'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -23,6 +23,7 @@ import { CopyAction, CopyAllAction } from 'vs/workbench/parts/debug/electron-bro import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LinkDetector } from 'vs/workbench/parts/debug/browser/linkDetector'; +import { CollapseAction } from 'vs/workbench/browser/viewlet'; const $ = dom.$; @@ -437,6 +438,8 @@ export class ReplExpressionsActionProvider implements IActionProvider { const actions: IAction[] = []; actions.push(new CopyAction(CopyAction.ID, CopyAction.LABEL)); actions.push(new CopyAllAction(CopyAllAction.ID, CopyAllAction.LABEL, tree)); + actions.push(new CollapseAction(tree, true, 'explore-action collapse-all')); + actions.push(new Separator()); actions.push(this.instantiationService.createInstance(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL)); return TPromise.as(actions); diff --git a/src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts b/src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts index 061852aad56..914a20f5984 100644 --- a/src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts +++ b/src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts @@ -56,12 +56,17 @@ export class TerminalSupport { const result = cp.spawnSync('wmic', ['process', 'get', 'ParentProcessId']); if (result.stdout) { const pids = result.stdout.toString().split('\r\n'); - return pids.some(p => parseInt(p) === t.processId); + if (!pids.some(p => parseInt(p) === t.processId)) { + return false; + } } } else { - const result = cp.spawnSync('/usr/bin/pgrep', ['-P', String(t.processId)]); + const result = cp.spawnSync('/usr/bin/pgrep', ['-lP', String(t.processId)]); if (result.stdout) { - return result.stdout.toString().trim().length > 0; + const r = result.stdout.toString().trim(); + if (r.length === 0 || r.indexOf(' tmux') >= 0) { // ignore 'tmux'; see #43683 + return false; + } } } } diff --git a/src/vs/workbench/parts/extensions/browser/extensionEditor.ts b/src/vs/workbench/parts/extensions/browser/extensionEditor.ts index ced754665f7..12432f28ceb 100644 --- a/src/vs/workbench/parts/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/parts/extensions/browser/extensionEditor.ts @@ -52,7 +52,6 @@ import { Color } from 'vs/base/common/color'; import { WorkbenchTree } from 'vs/platform/list/browser/listService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { assign } from 'vs/base/common/objects'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { INotificationService } from 'vs/platform/notification/common/notification'; /** A context key that is set when an extension editor webview has focus. */ @@ -183,19 +182,18 @@ export class ExtensionEditor extends BaseEditor { constructor( @ITelemetryService telemetryService: ITelemetryService, - @IInstantiationService private instantiationService: IInstantiationService, - @IViewletService private viewletService: IViewletService, - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IViewletService private readonly viewletService: IViewletService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IThemeService protected themeService: IThemeService, - @IKeybindingService private keybindingService: IKeybindingService, - @INotificationService private notificationService: INotificationService, - @IOpenerService private openerService: IOpenerService, - @IPartService private partService: IPartService, - @IContextViewService private contextViewService: IContextViewService, - @IContextKeyService private contextKeyService: IContextKeyService, - @IExtensionTipsService private extensionTipsService: IExtensionTipsService, - @IEnvironmentService private environmentService: IEnvironmentService, - @IWorkspaceContextService private contextService: IWorkspaceContextService + @IKeybindingService private readonly keybindingService: IKeybindingService, + @INotificationService private readonly notificationService: INotificationService, + @IOpenerService private readonly openerService: IOpenerService, + @IPartService private readonly partService: IPartService, + @IContextViewService private readonly contextViewService: IContextViewService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService, + @IEnvironmentService private readonly environmentService: IEnvironmentService ) { super(ExtensionEditor.ID, telemetryService, themeService); @@ -421,11 +419,9 @@ export class ExtensionEditor extends BaseEditor { .then(body => { const allowedBadgeProviders = this.extensionsWorkbenchService.allowedBadgeProviders; const webViewOptions = allowedBadgeProviders.length > 0 ? { allowScripts: false, allowSvgs: false, svgWhiteList: allowedBadgeProviders } : {}; - this.activeWebview = new Webview(this.content, this.partService.getContainer(Parts.EDITOR_PART), this.environmentService, this.contextService, this.contextViewService, this.contextKey, this.findInputFocusContextKey, webViewOptions, false); + this.activeWebview = new Webview(this.content, this.partService.getContainer(Parts.EDITOR_PART), this.themeService, this.environmentService, this.contextViewService, this.contextKey, this.findInputFocusContextKey, webViewOptions); const removeLayoutParticipant = arrays.insert(this.layoutParticipants, this.activeWebview); this.contentDisposables.push(toDisposable(removeLayoutParticipant)); - - this.activeWebview.style(this.themeService.getTheme()); this.activeWebview.contents = body; this.activeWebview.onDidClickLink(link => { @@ -434,7 +430,6 @@ export class ExtensionEditor extends BaseEditor { this.openerService.open(link); } }, null, this.contentDisposables); - this.themeService.onThemeChange(theme => this.activeWebview.style(theme), null, this.contentDisposables); this.contentDisposables.push(this.activeWebview); }) .then(null, () => { diff --git a/src/vs/workbench/parts/extensions/browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/browser/extensionsActions.ts index 9f84e91c9af..ccfdb915b63 100644 --- a/src/vs/workbench/parts/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/browser/extensionsActions.ts @@ -1601,7 +1601,7 @@ export class DisableAllAction extends Action { } run(): TPromise { - return TPromise.join(this.extensionsWorkbenchService.local.map(e => this.extensionsWorkbenchService.setEnablement(e, EnablementState.Disabled))); + return TPromise.join(this.extensionsWorkbenchService.local.filter(e => e.type === LocalExtensionType.User).map(e => this.extensionsWorkbenchService.setEnablement(e, EnablementState.Disabled))); } dispose(): void { @@ -1633,7 +1633,7 @@ export class DisableAllWorkpsaceAction extends Action { } run(): TPromise { - return TPromise.join(this.extensionsWorkbenchService.local.map(e => this.extensionsWorkbenchService.setEnablement(e, EnablementState.WorkspaceDisabled))); + return TPromise.join(this.extensionsWorkbenchService.local.filter(e => e.type === LocalExtensionType.User).map(e => this.extensionsWorkbenchService.setEnablement(e, EnablementState.WorkspaceDisabled))); } dispose(): void { @@ -1645,7 +1645,7 @@ export class DisableAllWorkpsaceAction extends Action { export class EnableAllAction extends Action { static readonly ID = 'workbench.extensions.action.enableAll'; - static LABEL = localize('enableAll', "Enable All Installed Extensions"); + static LABEL = localize('enableAll', "Enable All Extensions"); private disposables: IDisposable[] = []; @@ -1676,7 +1676,7 @@ export class EnableAllAction extends Action { export class EnableAllWorkpsaceAction extends Action { static readonly ID = 'workbench.extensions.action.enableAllWorkspace'; - static LABEL = localize('enableAllWorkspace', "Enable All Installed Extensions for this Workspace"); + static LABEL = localize('enableAllWorkspace', "Enable All Extensions for this Workspace"); private disposables: IDisposable[] = []; diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts index 594a6114a88..9abba273904 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts @@ -55,6 +55,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IChoiceService } from 'vs/platform/dialogs/common/dialogs'; import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IPartService } from 'vs/workbench/services/part/common/partService'; interface SearchInputEvent extends Event { target: HTMLInputElement; @@ -88,6 +89,7 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens private disposables: IDisposable[] = []; constructor( + @IPartService partService: IPartService, @ITelemetryService telemetryService: ITelemetryService, @IProgressService private progressService: IProgressService, @IInstantiationService instantiationService: IInstantiationService, @@ -104,7 +106,7 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService ) { - super(VIEWLET_ID, ViewLocation.Extensions, `${VIEWLET_ID}.state`, true, telemetryService, storageService, instantiationService, themeService, contextService, contextKeyService, contextMenuService, extensionService); + super(VIEWLET_ID, ViewLocation.Extensions, `${VIEWLET_ID}.state`, true, partService, telemetryService, storageService, instantiationService, themeService, contextService, contextKeyService, contextMenuService, extensionService); this.registerViews(); this.searchDelayer = new ThrottledDelayer(500); diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css b/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css index 7927dffe0ab..e34f01a8025 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css +++ b/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css @@ -27,10 +27,6 @@ height: calc(100% - 38px); } -.extensions-viewlet > .extensions .extensions-list > .monaco-list { - height: auto; -} - .extensions-viewlet > .extensions .list-actionbar-container .monaco-action-bar .action-item > .octicon { font-size: 12px; line-height: 1; diff --git a/src/vs/workbench/parts/files/electron-browser/explorerViewlet.ts b/src/vs/workbench/parts/files/electron-browser/explorerViewlet.ts index 7dd040a28c2..ef92afe2742 100644 --- a/src/vs/workbench/parts/files/electron-browser/explorerViewlet.ts +++ b/src/vs/workbench/parts/files/electron-browser/explorerViewlet.ts @@ -34,6 +34,7 @@ import { ViewsRegistry, ViewLocation, IViewDescriptor } from 'vs/workbench/commo import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IPartService } from 'vs/workbench/services/part/common/partService'; export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution { @@ -150,6 +151,7 @@ export class ExplorerViewlet extends PersistentViewsViewlet implements IExplorer private viewletVisibleContextKey: IContextKey; constructor( + @IPartService partService: IPartService, @ITelemetryService telemetryService: ITelemetryService, @IWorkspaceContextService protected contextService: IWorkspaceContextService, @IStorageService protected storageService: IStorageService, @@ -162,7 +164,7 @@ export class ExplorerViewlet extends PersistentViewsViewlet implements IExplorer @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService ) { - super(VIEWLET_ID, ViewLocation.Explorer, ExplorerViewlet.EXPLORER_VIEWS_STATE, true, telemetryService, storageService, instantiationService, themeService, contextService, contextKeyService, contextMenuService, extensionService); + super(VIEWLET_ID, ViewLocation.Explorer, ExplorerViewlet.EXPLORER_VIEWS_STATE, true, partService, telemetryService, storageService, instantiationService, themeService, contextService, contextKeyService, contextMenuService, extensionService); this.viewletState = new FileViewletState(); this.viewletVisibleContextKey = ExplorerViewletVisibleContext.bindTo(contextKeyService); diff --git a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts b/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts index e4d632d5b27..4ea35fb2089 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts @@ -799,11 +799,15 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop { // In-Explorer DND else { + const sources: FileStat[] = data.getData(); if (target instanceof Model) { + if (sources.length === 1 && sources[0].isRoot) { + return DRAG_OVER_ACCEPT_BUBBLE_DOWN(false); + } + return DRAG_OVER_REJECT; } - const sources: FileStat[] = data.getData(); if (!Array.isArray(sources)) { return DRAG_OVER_REJECT; } @@ -864,9 +868,7 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop { // In-Explorer DND (Move/Copy file) else { - if (target instanceof FileStat) { - promise = this.handleExplorerDrop(tree, data, target, originalEvent); - } + promise = this.handleExplorerDrop(tree, data, target, originalEvent); } promise.done(null, errors.onUnexpectedError); @@ -915,7 +917,7 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop { }); } - private handleExplorerDrop(tree: ITree, data: IDragAndDropData, target: FileStat, originalEvent: DragMouseEvent): TPromise { + private handleExplorerDrop(tree: ITree, data: IDragAndDropData, target: FileStat | Model, originalEvent: DragMouseEvent): TPromise { const sources: FileStat[] = distinctParents(data.getData(), s => s.resource); const isCopy = (originalEvent.ctrlKey && !isMacintosh) || (originalEvent.altKey && isMacintosh); @@ -955,19 +957,20 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop { }); } - private doHandleExplorerDrop(tree: ITree, source: FileStat, target: FileStat, isCopy: boolean): TPromise { + private doHandleExplorerDrop(tree: ITree, source: FileStat, target: FileStat | Model, isCopy: boolean): TPromise { return tree.expand(target).then(() => { if (source.isRoot) { const folders = this.contextService.getWorkspace().folders; let sourceIndex: number; let targetIndex: number; const workspaceCreationData: IWorkspaceFolderCreationData[] = []; + const targetUri = target instanceof FileStat ? target.resource : folders[folders.length - 1].uri; for (let index = 0; index < folders.length; index++) { if (folders[index].uri.toString() === source.resource.toString()) { sourceIndex = index; } - if (folders[index].uri.toString() === target.resource.toString()) { + if (folders[index].uri.toString() === targetUri.toString()) { targetIndex = index; } workspaceCreationData.push({ @@ -1004,6 +1007,9 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop { return TPromise.join(dirtyMoved.map(d => this.backupFileService.discardResourceBackup(d))); }; + if (!(target instanceof FileStat)) { + return TPromise.as(void 0); + } // 1. check for dirty files that are being moved and backup to new target const dirty = this.textFileService.getDirty().filter(d => resources.isEqualOrParent(d, source.resource, !isLinux /* ignorecase */)); diff --git a/src/vs/workbench/parts/html/browser/html.contribution.ts b/src/vs/workbench/parts/html/browser/html.contribution.ts index 6316766624e..4aa5a6481f5 100644 --- a/src/vs/workbench/parts/html/browser/html.contribution.ts +++ b/src/vs/workbench/parts/html/browser/html.contribution.ts @@ -16,7 +16,6 @@ import { HtmlPreviewPart } from 'vs/workbench/parts/html/browser/htmlPreviewPart import { Registry } from 'vs/platform/registry/common/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; -import { MenuRegistry } from 'vs/platform/actions/common/actions'; import { IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; @@ -31,6 +30,7 @@ function getActivePreviewsForResource(accessor: ServicesAccessor, resource: URI } // --- Register Editor + (Registry.as(EditorExtensions.Editors)).registerEditor(new EditorDescriptor( HtmlPreviewPart, HtmlPreviewPart.ID, @@ -39,17 +39,11 @@ function getActivePreviewsForResource(accessor: ServicesAccessor, resource: URI // --- Register Commands -const defaultPreviewHtmlOptions: HtmlInputOptions = { - allowScripts: true, - allowSvgs: true -}; - CommandsRegistry.registerCommand('_workbench.previewHtml', function ( accessor: ServicesAccessor, resource: URI | string, position?: EditorPosition, - label?: string, - options?: HtmlInputOptions + label?: string ) { const uri = resource instanceof URI ? resource : URI.parse(resource); label = label || uri.fsPath; @@ -66,9 +60,13 @@ CommandsRegistry.registerCommand('_workbench.previewHtml', function ( } } - const inputOptions = (Object as any).assign({}, options || defaultPreviewHtmlOptions); const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); - inputOptions.svgWhiteList = extensionsWorkbenchService.allowedBadgeProviders; + + const inputOptions: HtmlInputOptions = { + allowScripts: true, + allowSvgs: true, + svgWhiteList: extensionsWorkbenchService.allowedBadgeProviders + }; // Otherwise, create new input and open it if (!input) { @@ -93,19 +91,3 @@ CommandsRegistry.registerCommand('_workbench.htmlPreview.postMessage', function } return activePreviews.length > 0; }); - -CommandsRegistry.registerCommand('_webview.openDevTools', function () { - const elements = document.querySelectorAll('webview.ready'); - for (let i = 0; i < elements.length; i++) { - try { - (elements.item(i) as Electron.WebviewTag).openDevTools(); - } catch (e) { - console.error(e); - } - } -}); - -MenuRegistry.addCommand({ - id: '_webview.openDevTools', - title: localize('devtools.webview', "Developer: Webview Tools") -}); diff --git a/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts b/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts index 60894b14c39..5e6aa8ac6a4 100644 --- a/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts +++ b/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts @@ -26,7 +26,6 @@ import { Webview, WebviewOptions } from './webview'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { WebviewEditor } from './webviewEditor'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; /** @@ -55,8 +54,7 @@ export class HtmlPreviewPart extends WebviewEditor { @IOpenerService private readonly openerService: IOpenerService, @IPartService private readonly partService: IPartService, @IContextViewService private readonly _contextViewService: IContextViewService, - @IEnvironmentService private readonly _environmentService: IEnvironmentService, - @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService + @IEnvironmentService private readonly _environmentService: IEnvironmentService ) { super(HtmlPreviewPart.ID, telemetryService, themeService, storageService, contextKeyService); } @@ -91,13 +89,15 @@ export class HtmlPreviewPart extends WebviewEditor { this._webview = new Webview( this.content, this.partService.getContainer(Parts.EDITOR_PART), + this.themeService, this._environmentService, - this._contextService, this._contextViewService, this.contextKey, this.findInputFocusContextKey, - webviewOptions, - true); + { + ...webviewOptions, + useSameOriginForRoot: true + }); if (this.input && this.input instanceof HtmlInput) { const state = this.loadViewState(this.input.getResource()); @@ -107,7 +107,6 @@ export class HtmlPreviewPart extends WebviewEditor { const resourceUri = this.input.getResource(); this.webview.baseUrl = resourceUri.toString(true); } - this.onThemeChange(this.themeService.getTheme()); this._webviewDisposables = [ this._webview, this._webview.onDidClickLink(uri => this.openerService.open(uri)), @@ -157,13 +156,8 @@ export class HtmlPreviewPart extends WebviewEditor { const { width, height } = dimension; this.content.style.width = `${width}px`; this.content.style.height = `${height}px`; - if (this._webview) { - this._webview.layout(); - } - } - public focus(): void { - this.webview.focus(); + super.layout(dimension); } public clearInput(): void { diff --git a/src/vs/workbench/parts/html/browser/webview.contribution.ts b/src/vs/workbench/parts/html/browser/webview.contribution.ts index a1a41b33605..79ce5b3c4bf 100644 --- a/src/vs/workbench/parts/html/browser/webview.contribution.ts +++ b/src/vs/workbench/parts/html/browser/webview.contribution.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { ContextKeyExpr, } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -11,10 +12,12 @@ import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/wor import { KEYBINDING_CONTEXT_WEBVIEWEDITOR_FIND_WIDGET_INPUT_FOCUSED, KEYBINDING_CONTEXT_WEBVIEWEDITOR_FOCUS, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE } from './webviewEditor'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ShowWebViewEditorFindWidgetAction, ShowWebViewEditorFindTermCommand, HideWebViewEditorFindCommand } from './webviewCommands'; +import { ShowWebViewEditorFindWidgetAction, ShowWebViewEditorFindTermCommand, HideWebViewEditorFindCommand, OpenWebviewDeveloperToolsAction, ReloadWebviewAction } from './webviewCommands'; const category = 'Webview'; +const webviewDeveloperCategory = nls.localize('developer', "Developer"); + const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowWebViewEditorFindWidgetAction, ShowWebViewEditorFindWidgetAction.ID, ShowWebViewEditorFindWidgetAction.LABEL, { @@ -52,3 +55,14 @@ const hideCommand = new HideWebViewEditorFindCommand({ } }); KeybindingsRegistry.registerCommandAndKeybindingRule(hideCommand.toCommandAndKeybindingRule(KeybindingsRegistry.WEIGHT.editorContrib())); + + +actionRegistry.registerWorkbenchAction( + new SyncActionDescriptor(OpenWebviewDeveloperToolsAction, OpenWebviewDeveloperToolsAction.ID, OpenWebviewDeveloperToolsAction.LABEL), + 'Webview Tools', + webviewDeveloperCategory); + +actionRegistry.registerWorkbenchAction( + new SyncActionDescriptor(ReloadWebviewAction, ReloadWebviewAction.ID, ReloadWebviewAction.LABEL), + 'Reload Webview', + webviewDeveloperCategory); \ No newline at end of file diff --git a/src/vs/workbench/parts/html/browser/webview.ts b/src/vs/workbench/parts/html/browser/webview.ts index 65c4376fc92..00669ce6dad 100644 --- a/src/vs/workbench/parts/html/browser/webview.ts +++ b/src/vs/workbench/parts/html/browser/webview.ts @@ -10,20 +10,21 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import Event, { Emitter } from 'vs/base/common/event'; import { addDisposableListener, addClass } from 'vs/base/browser/dom'; import { editorBackground, editorForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; -import { ITheme, LIGHT, DARK } from 'vs/platform/theme/common/themeService'; +import { ITheme, LIGHT, DARK, IThemeService } from 'vs/platform/theme/common/themeService'; import { WebviewFindWidget } from './webviewFindWidget'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { normalize, nativeSep } from 'vs/base/common/paths'; import { startsWith } from 'vs/base/common/strings'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; export interface WebviewOptions { readonly allowScripts?: boolean; readonly allowSvgs?: boolean; readonly svgWhiteList?: string[]; readonly enableWrappedPostMessage?: boolean; + readonly useSameOriginForRoot?: boolean; + readonly localResourceRoots?: URI[]; } export class Webview { @@ -33,17 +34,17 @@ export class Webview { private _webviewFindWidget: WebviewFindWidget; private _findStarted: boolean = false; + private _contents: string = ''; constructor( private readonly parent: HTMLElement, private readonly _styleElement: Element, + private readonly _themeService: IThemeService, private readonly _environmentService: IEnvironmentService, - private readonly _contextService: IWorkspaceContextService, private readonly _contextViewService: IContextViewService, private readonly _contextKey: IContextKey, private readonly _findInputContextKey: IContextKey, - private _options: WebviewOptions, - useSameOriginForRoot: boolean + private _options: WebviewOptions ) { this._webview = document.createElement('webview'); this._webview.setAttribute('partition', this._options.allowSvgs ? 'webview' : `webview${Date.now()}`); @@ -60,7 +61,7 @@ export class Webview { this._webview.style.outline = '0'; this._webview.preload = require.toUrl('./webview-pre.js'); - this._webview.src = useSameOriginForRoot ? require.toUrl('./webview.html') : 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%09%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E'; + this._webview.src = this._options.useSameOriginForRoot ? require.toUrl('./webview.html') : 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%09%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E'; this._ready = new Promise(resolve => { const subscription = addDisposableListener(this._webview, 'ipc-message', (event) => { @@ -74,7 +75,7 @@ export class Webview { }); }); - if (!useSameOriginForRoot) { + if (!this._options.useSameOriginForRoot) { let loaded = false; this._disposables.push(addDisposableListener(this._webview, 'did-start-loading', () => { if (loaded) { @@ -84,7 +85,6 @@ export class Webview { const contents = this._webview.getWebContents(); this.registerFileProtocols(contents); - })); } @@ -167,22 +167,20 @@ export class Webview { if (this._contextKey) { this._contextKey.set(true); } - this._onFocus.fire(); }), addDisposableListener(this._webview, 'blur', () => { if (this._contextKey) { this._contextKey.reset(); } - this._onBlur.fire(); - }), - addDisposableListener(this._webview, 'found-in-page', (event) => { - this._onFoundInPageResults.fire(event.result); }) ); this._webviewFindWidget = new WebviewFindWidget(this._contextViewService, this); this._disposables.push(this._webviewFindWidget); + this.style(this._themeService.getTheme()); + this._themeService.onThemeChange(this.style, this, this._disposables); + if (parent) { parent.appendChild(this._webviewFindWidget.getDomNode()); parent.appendChild(this._webview); @@ -218,44 +216,36 @@ export class Webview { private readonly _onDidScroll = new Emitter<{ scrollYPercentage: number }>(); public readonly onDidScroll: Event<{ scrollYPercentage: number }> = this._onDidScroll.event; - private readonly _onFoundInPageResults = new Emitter(); - public readonly onFindResults: Event = this._onFoundInPageResults.event; - private readonly _onMessage = new Emitter(); public readonly onMessage: Event = this._onMessage.event; - private readonly _onFocus = new Emitter(); - public readonly onFocus: Event = this._onFocus.event; - - private readonly _onBlur = new Emitter(); - public readonly onBlur: Event = this._onBlur.event; - private _send(channel: string, ...args: any[]): void { this._ready .then(() => this._webview.send(channel, ...args)) .catch(err => console.error(err)); } - set initialScrollProgress(value: number) { + public set initialScrollProgress(value: number) { this._send('initial-scroll-position', value); } - set options(value: WebviewOptions) { + public set options(value: WebviewOptions) { this._options = value; } - set contents(value: string) { + public set contents(value: string) { + this._contents = value; this._send('content', { contents: value, options: this._options }); } - set baseUrl(value: string) { + public set baseUrl(value: string) { this._send('baseUrl', value); } - focus(): void { + public focus(): void { this._webview.focus(); this._send('focus'); } @@ -270,7 +260,7 @@ export class Webview { }); } - style(theme: ITheme): void { + private style(theme: ITheme): void { const { fontFamily, fontWeight, fontSize } = window.getComputedStyle(this._styleElement); // TODO@theme avoid styleElement const styles = { @@ -330,16 +320,16 @@ export class Webview { return; } - registerFileProtocol(contents, 'vscode-core-resource', [ + registerFileProtocol(contents, 'vscode-core-resource', () => [ this._environmentService.appRoot ]); - registerFileProtocol(contents, 'vscode-extension-resource', [ + registerFileProtocol(contents, 'vscode-extension-resource', () => [ this._environmentService.extensionsPath, this._environmentService.appRoot, this._environmentService.extensionDevelopmentPath ]); - registerFileProtocol(contents, 'vscode-workspace-resource', - this._contextService.getWorkspace().folders.map(folder => folder.uri.fsPath) + registerFileProtocol(contents, 'vscode-workspace-resource', () => + this._options.localResourceRoots.map(uri => uri.fsPath) ); } @@ -361,7 +351,6 @@ export class Webview { this._findStarted = true; this._webview.findInPage(value, findOptions); - return; } /** @@ -406,6 +395,10 @@ export class Webview { public showPreviousFindTerm() { this._webviewFindWidget.showPreviousFindTerm(); } + + public reload() { + this.contents = this._contents; + } } @@ -430,11 +423,11 @@ namespace ApiThemeClassName { function registerFileProtocol( contents: Electron.WebContents, protocol: string, - roots: string[] + getRoots: () => string[] ) { contents.session.protocol.registerFileProtocol(protocol, (request, callback: any) => { const requestPath = URI.parse(request.url).fsPath; - for (const root of roots) { + for (const root of getRoots()) { const normalizedPath = normalize(requestPath, true); if (startsWith(normalizedPath, root + nativeSep)) { callback({ path: normalizedPath }); diff --git a/src/vs/workbench/parts/html/browser/webviewCommands.ts b/src/vs/workbench/parts/html/browser/webviewCommands.ts index 01c0ce9a775..6bd277359e6 100644 --- a/src/vs/workbench/parts/html/browser/webviewCommands.ts +++ b/src/vs/workbench/parts/html/browser/webviewCommands.ts @@ -82,3 +82,55 @@ export class ShowWebViewEditorFindTermCommand extends Command { return null; } } + + +export class OpenWebviewDeveloperToolsAction extends Action { + static readonly ID = 'workbench.action.webview.openDeveloperTools'; + static LABEL = nls.localize('openToolsLabel', "Webview Tools"); + + public constructor( + id: string, + label: string + ) { + super(id, label); + } + + public run(): TPromise { + const elements = document.querySelectorAll('webview.ready'); + for (let i = 0; i < elements.length; i++) { + try { + (elements.item(i) as Electron.WebviewTag).openDevTools(); + } catch (e) { + console.error(e); + } + } + return null; + } +} + + +export class ReloadWebviewAction extends Action { + static readonly ID = 'workbench.action.webview.reloadWebviewAction'; + static LABEL = nls.localize('refreshWebviewLabel', "Reload Webviews"); + + public constructor( + id: string, + label: string, + @IWorkbenchEditorService private readonly workbenchEditorService: IWorkbenchEditorService + ) { + super(id, label); + } + + public run(): TPromise { + for (const webview of this.getVisibleWebviews()) { + webview.reload(); + } + return null; + } + + private getVisibleWebviews() { + return this.workbenchEditorService.getVisibleEditors() + .filter(c => c && (c as any).isWebviewEditor) + .map(e => e as WebviewEditor); + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/html/browser/webviewEditor.ts b/src/vs/workbench/parts/html/browser/webviewEditor.ts index 9021b5f7725..c3f870f510c 100644 --- a/src/vs/workbench/parts/html/browser/webviewEditor.ts +++ b/src/vs/workbench/parts/html/browser/webviewEditor.ts @@ -12,6 +12,7 @@ import { IContextKey, RawContextKey, IContextKeyService } from 'vs/platform/cont import { Webview } from './webview'; import { Builder } from 'vs/base/browser/builder'; +import { Dimension } from 'vs/workbench/services/part/common/partService'; /** A context key that is set when a webview editor has focus. */ export const KEYBINDING_CONTEXT_WEBVIEWEDITOR_FOCUS = new RawContextKey('webviewEditorFocus', false); @@ -73,15 +74,26 @@ export abstract class WebviewEditor extends BaseWebviewEditor { } } - public updateStyles() { - super.updateStyles(); + public get isWebviewEditor() { + return true; + } + + public reload() { if (this._webview) { - this._webview.style(this.themeService.getTheme()); + this._webview.reload(); } } - public get isWebviewEditor() { - return true; + public layout(dimension: Dimension): void { + if (this._webview) { + this._webview.layout(); + } + } + + public focus(): void { + if (this._webview) { + this._webview.focus(); + } } protected abstract createEditor(parent: Builder): void; diff --git a/src/vs/workbench/parts/output/common/output.ts b/src/vs/workbench/parts/output/common/output.ts index 16786934caa..a81b9c68c42 100644 --- a/src/vs/workbench/parts/output/common/output.ts +++ b/src/vs/workbench/parts/output/common/output.ts @@ -22,14 +22,24 @@ export const OUTPUT_MIME = 'text/x-code-output'; export const OUTPUT_SCHEME = 'output'; /** - * Output resource scheme. + * Id used by the output editor. + */ +export const OUTPUT_MODE_ID = 'Log'; + +/** + * Mime type used by the log output editor. + */ +export const LOG_MIME = 'text/x-code-log-output'; + +/** + * Log resource scheme. */ export const LOG_SCHEME = 'log'; /** - * Id used by the output editor. + * Id used by the log output editor. */ -export const OUTPUT_MODE_ID = 'Log'; +export const LOG_MODE_ID = 'log'; /** * Output panel id diff --git a/src/vs/workbench/parts/output/common/outputLinkProvider.ts b/src/vs/workbench/parts/output/common/outputLinkProvider.ts index 8f2b9dba601..8014d8bd950 100644 --- a/src/vs/workbench/parts/output/common/outputLinkProvider.ts +++ b/src/vs/workbench/parts/output/common/outputLinkProvider.ts @@ -11,7 +11,7 @@ import { RunOnceScheduler, wireCancellationToken } from 'vs/base/common/async'; import { IModelService } from 'vs/editor/common/services/modelService'; import { LinkProviderRegistry, ILink } from 'vs/editor/common/modes'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { OUTPUT_MODE_ID } from 'vs/workbench/parts/output/common/output'; +import { OUTPUT_MODE_ID, LOG_MODE_ID } from 'vs/workbench/parts/output/common/output'; import { MonacoWebWorker, createWebWorker } from 'vs/editor/common/services/webWorker'; import { ICreateData, OutputLinkComputer } from 'vs/workbench/parts/output/common/outputLinkComputer'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -44,7 +44,7 @@ export class OutputLinkProvider { const folders = this.contextService.getWorkspace().folders; if (folders.length > 0) { if (!this.linkProviderRegistration) { - this.linkProviderRegistration = LinkProviderRegistry.register({ language: OUTPUT_MODE_ID, scheme: '*' }, { + this.linkProviderRegistration = LinkProviderRegistry.register([{ language: OUTPUT_MODE_ID, scheme: '*' }, { language: LOG_MODE_ID, scheme: '*' }], { provideLinks: (model, token): Thenable => { return wireCancellationToken(token, this.provideLinks(model.uri)); } diff --git a/src/vs/workbench/parts/output/electron-browser/output.contribution.ts b/src/vs/workbench/parts/output/electron-browser/output.contribution.ts index e989b92d769..3f4518913ff 100644 --- a/src/vs/workbench/parts/output/electron-browser/output.contribution.ts +++ b/src/vs/workbench/parts/output/electron-browser/output.contribution.ts @@ -13,7 +13,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { OutputService, LogContentProvider } from 'vs/workbench/parts/output/electron-browser/outputServices'; import { ToggleOutputAction, ClearOutputAction } from 'vs/workbench/parts/output/browser/outputActions'; -import { OUTPUT_MODE_ID, OUTPUT_MIME, OUTPUT_PANEL_ID, IOutputService, CONTEXT_IN_OUTPUT, LOG_SCHEME, COMMAND_OPEN_LOG_VIEWER } from 'vs/workbench/parts/output/common/output'; +import { OUTPUT_MODE_ID, OUTPUT_MIME, OUTPUT_PANEL_ID, IOutputService, CONTEXT_IN_OUTPUT, LOG_SCHEME, COMMAND_OPEN_LOG_VIEWER, LOG_MODE_ID, LOG_MIME } from 'vs/workbench/parts/output/common/output'; import { PanelRegistry, Extensions, PanelDescriptor } from 'vs/workbench/browser/panel'; import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -39,6 +39,14 @@ ModesRegistry.registerLanguage({ mimetypes: [OUTPUT_MIME] }); +// Register Log Output Mode +ModesRegistry.registerLanguage({ + id: LOG_MODE_ID, + extensions: [], + aliases: [null], + mimetypes: [LOG_MIME] +}); + // Register Output Panel Registry.as(Extensions.Panels).registerPanel(new PanelDescriptor( OutputPanel, diff --git a/src/vs/workbench/parts/output/electron-browser/outputServices.ts b/src/vs/workbench/parts/output/electron-browser/outputServices.ts index 2b642f95a58..546e3855152 100644 --- a/src/vs/workbench/parts/output/electron-browser/outputServices.ts +++ b/src/vs/workbench/parts/output/electron-browser/outputServices.ts @@ -16,7 +16,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorOptions } from 'vs/workbench/common/editor'; -import { IOutputChannelIdentifier, IOutputChannel, IOutputService, Extensions, OUTPUT_PANEL_ID, IOutputChannelRegistry, OUTPUT_SCHEME, OUTPUT_MIME, MAX_OUTPUT_LENGTH, LOG_SCHEME } from 'vs/workbench/parts/output/common/output'; +import { IOutputChannelIdentifier, IOutputChannel, IOutputService, Extensions, OUTPUT_PANEL_ID, IOutputChannelRegistry, OUTPUT_SCHEME, OUTPUT_MIME, MAX_OUTPUT_LENGTH, LOG_SCHEME, LOG_MIME } from 'vs/workbench/parts/output/common/output'; import { OutputPanel } from 'vs/workbench/parts/output/browser/outputPanel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -134,6 +134,7 @@ abstract class AbstractFileOutputChannel extends Disposable { constructor( protected readonly outputChannelIdentifier: IOutputChannelIdentifier, private readonly modelUri: URI, + private mimeType: string, protected fileService: IFileService, protected modelService: IModelService, protected modeService: IModeService, @@ -166,7 +167,7 @@ abstract class AbstractFileOutputChannel extends Disposable { if (this.model) { this.model.setValue(content); } else { - this.model = this.modelService.createModel(content, this.modeService.getOrCreateMode(OUTPUT_MIME), this.modelUri); + this.model = this.modelService.createModel(content, this.modeService.getOrCreateMode(this.mimeType), this.modelUri); this.onModelCreated(this.model); const disposables: IDisposable[] = []; disposables.push(this.model.onWillDispose(() => { @@ -217,7 +218,7 @@ class OutputChannelBackedByFile extends AbstractFileOutputChannel implements Out @IModeService modeService: IModeService, @ILogService logService: ILogService ) { - super({ ...outputChannelIdentifier, file: URI.file(paths.join(outputDir, `${outputChannelIdentifier.id}.log`)) }, modelUri, fileService, modelService, modeService); + super({ ...outputChannelIdentifier, file: URI.file(paths.join(outputDir, `${outputChannelIdentifier.id}.log`)) }, modelUri, OUTPUT_MIME, fileService, modelService, modeService); // Use one rotating file to check for main file reset this.outputWriter = new RotatingLogger(this.id, this.file.fsPath, 1024 * 1024 * 30, 1); @@ -363,7 +364,7 @@ class FileOutputChannel extends AbstractFileOutputChannel implements OutputChann @IModeService modeService: IModeService, @ILogService logService: ILogService, ) { - super(outputChannelIdentifier, modelUri, fileService, modelService, modeService); + super(outputChannelIdentifier, modelUri, LOG_MIME, fileService, modelService, modeService); this.fileHandler = this._register(new OutputFileListener(this.file)); this._register(this.fileHandler.onDidContentChange(() => this.onDidContentChange())); diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 7f179fcb8df..7c5758e181c 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -57,6 +57,7 @@ import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ThrottledDelayer } from 'vs/base/common/async'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IPartService } from 'vs/workbench/services/part/common/partService'; export interface ISpliceEvent { index: number; @@ -1047,6 +1048,7 @@ export class SCMViewlet extends PanelViewlet implements IViewModel { get selectedRepositories(): ISCMRepository[] { return this.repositoryPanels.map(p => p.repository); } constructor( + @IPartService partService: IPartService, @ITelemetryService telemetryService: ITelemetryService, @ISCMService protected scmService: ISCMService, @IInstantiationService protected instantiationService: IInstantiationService, @@ -1054,7 +1056,7 @@ export class SCMViewlet extends PanelViewlet implements IViewModel { @IContextKeyService contextKeyService: IContextKeyService, @IKeybindingService protected keybindingService: IKeybindingService, @INotificationService protected notificationService: INotificationService, - @IContextMenuService private contextMenuService: IContextMenuService, + @IContextMenuService protected contextMenuService: IContextMenuService, @IThemeService protected themeService: IThemeService, @ICommandService protected commandService: ICommandService, @IEditorGroupService protected editorGroupService: IEditorGroupService, @@ -1064,7 +1066,7 @@ export class SCMViewlet extends PanelViewlet implements IViewModel { @IExtensionService extensionService: IExtensionService, @IConfigurationService private configurationService: IConfigurationService, ) { - super(VIEWLET_ID, { showHeaderInTitleWhenSingleView: true }, telemetryService, themeService); + super(VIEWLET_ID, { showHeaderInTitleWhenSingleView: true }, partService, contextMenuService, telemetryService, themeService); this.menus = instantiationService.createInstance(SCMMenus, undefined); this.menus.onDidChangeTitle(this.updateTitleArea, this, this.disposables); diff --git a/src/vs/workbench/parts/search/browser/searchViewlet.ts b/src/vs/workbench/parts/search/browser/searchViewlet.ts index 173957be352..24e553b9c14 100644 --- a/src/vs/workbench/parts/search/browser/searchViewlet.ts +++ b/src/vs/workbench/parts/search/browser/searchViewlet.ts @@ -59,6 +59,7 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { SimpleFileResourceDragAndDrop } from 'vs/workbench/browser/dnd'; import { IConfirmation, IConfirmationService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IPartService } from 'vs/workbench/services/part/common/partService'; export class SearchViewlet extends Viewlet { @@ -103,6 +104,7 @@ export class SearchViewlet extends Viewlet { private searchWithoutFolderMessageBuilder: Builder; constructor( + @IPartService partService: IPartService, @ITelemetryService telemetryService: ITelemetryService, @IFileService private fileService: IFileService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @@ -122,7 +124,7 @@ export class SearchViewlet extends Viewlet { @IPreferencesService private preferencesService: IPreferencesService, @IThemeService protected themeService: IThemeService ) { - super(Constants.VIEWLET_ID, telemetryService, themeService); + super(Constants.VIEWLET_ID, partService, telemetryService, themeService); this.viewletVisible = Constants.SearchViewletVisibleKey.bindTo(contextKeyService); this.inputBoxFocused = Constants.InputBoxFocusedKey.bindTo(this.contextKeyService); diff --git a/src/vs/workbench/parts/terminal/common/terminalService.ts b/src/vs/workbench/parts/terminal/common/terminalService.ts index 6f97e2f97fa..6212923c472 100644 --- a/src/vs/workbench/parts/terminal/common/terminalService.ts +++ b/src/vs/workbench/parts/terminal/common/terminalService.ts @@ -267,7 +267,12 @@ export abstract class TerminalService implements ITerminalService { if (focus) { // Do the focus call asynchronously as going through the // command palette will force editor focus - setTimeout(() => this.getActiveInstance().focus(true), 0); + setTimeout(() => { + const instance = this.getActiveInstance(); + if (instance) { + instance.focus(true); + } + }, 0); } complete(void 0); }); @@ -275,7 +280,12 @@ export abstract class TerminalService implements ITerminalService { if (focus) { // Do the focus call asynchronously as going through the // command palette will force editor focus - setTimeout(() => this.getActiveInstance().focus(true), 0); + setTimeout(() => { + const instance = this.getActiveInstance(); + if (instance) { + instance.focus(true); + } + }, 0); } complete(void 0); } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts index 87351d5db48..a0d6296a026 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts @@ -14,7 +14,7 @@ import { TerminalWidgetManager } from 'vs/workbench/parts/terminal/browser/termi import { TPromise } from 'vs/base/common/winjs.base'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITerminalService } from 'vs/workbench/parts/terminal/common/terminal'; -import { IEditorService } from 'vs/platform/editor/common/editor'; +import { IEditorService, ITextEditorSelection } from 'vs/platform/editor/common/editor'; const pathPrefix = '(\\.\\.?|\\~)'; const pathSeparatorClause = '\\/'; @@ -144,17 +144,16 @@ export class TerminalLinkHandler { return void 0; } - let normalizedPath = path.normalize(path.resolve(resolvedLink)); + const normalizedPath = path.normalize(path.resolve(resolvedLink)); const normalizedUrl = this.extractLinkUrl(normalizedPath); + const resource = Uri.file(normalizedUrl); + const lineColumnInfo: LineColumnInfo = this.extractLineColumnInfo(link); + const selection: ITextEditorSelection = { + startLineNumber: lineColumnInfo.lineNumber, + startColumn: lineColumnInfo.columnNumber + }; - normalizedPath = this._formatLocalLinkPath(normalizedPath); - - let resource = Uri.file(normalizedUrl); - resource = resource.with({ - fragment: Uri.parse(normalizedPath).fragment - }); - - return this._editorService.openEditor({ resource, options: { pinned: true } }); + return this._editorService.openEditor({ resource, options: { pinned: true, selection } }); }); } @@ -240,23 +239,6 @@ export class TerminalLinkHandler { }); } - /** - * Appends line number and column number to link if they exists. - * @param link link to format, will become link#line_num,col_num. - */ - private _formatLocalLinkPath(link: string): string { - const lineColumnInfo: LineColumnInfo = this.extractLineColumnInfo(link); - if (lineColumnInfo.lineNumber) { - link += `#${lineColumnInfo.lineNumber}`; - - if (lineColumnInfo.columnNumber) { - link += `,${lineColumnInfo.columnNumber}`; - } - } - - return link; - } - /** * Returns line and column number of URl if that is present. * @@ -264,18 +246,21 @@ export class TerminalLinkHandler { */ public extractLineColumnInfo(link: string): LineColumnInfo { const matches: string[] = this._localLinkRegex.exec(link); - const lineColumnInfo: LineColumnInfo = {}; + const lineColumnInfo: LineColumnInfo = { + lineNumber: 1, + columnNumber: 1 + }; const lineAndColumnMatchIndex = this._platform === platform.Platform.Windows ? winLineAndColumnMatchIndex : unixLineAndColumnMatchIndex; for (let i = 0; i < lineAndColumnClause.length; i++) { const lineMatchIndex = lineAndColumnMatchIndex + (lineAndColumnClauseGroupCount * i); const rowNumber = matches[lineMatchIndex]; if (rowNumber) { - lineColumnInfo['lineNumber'] = rowNumber; + lineColumnInfo['lineNumber'] = parseInt(rowNumber, 10); // Check if column number exists const columnNumber = matches[lineMatchIndex + 2]; if (columnNumber) { - lineColumnInfo['columnNumber'] = columnNumber; + lineColumnInfo['columnNumber'] = parseInt(columnNumber, 10); } break; } @@ -299,6 +284,6 @@ export class TerminalLinkHandler { } export interface LineColumnInfo { - lineNumber?: string; - columnNumber?: string; + lineNumber: number; + columnNumber: number; } \ No newline at end of file diff --git a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts b/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts index 52abc7a5262..04a9a71e292 100644 --- a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts +++ b/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts @@ -28,7 +28,6 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { onUnexpectedError } from 'vs/base/common/errors'; import { addGAParameters } from 'vs/platform/telemetry/node/telemetryNodeUtils'; import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; function renderBody( body: string, @@ -57,15 +56,14 @@ export class ReleaseNotesEditor extends WebviewEditor { constructor( @ITelemetryService telemetryService: ITelemetryService, - @IThemeService protected themeService: IThemeService, @IStorageService storageService: IStorageService, @IContextKeyService contextKeyService: IContextKeyService, - @IEnvironmentService private environmentService: IEnvironmentService, - @IOpenerService private openerService: IOpenerService, - @IModeService private modeService: IModeService, - @IPartService private partService: IPartService, - @IContextViewService private readonly _contextViewService: IContextViewService, - @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService + @IThemeService protected readonly themeService: IThemeService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IOpenerService private readonly openerService: IOpenerService, + @IModeService private readonly modeService: IModeService, + @IPartService private readonly partService: IPartService, + @IContextViewService private readonly _contextViewService: IContextViewService ) { super(ReleaseNotesEditor.ID, telemetryService, themeService, storageService, contextKeyService); } @@ -87,35 +85,16 @@ export class ReleaseNotesEditor extends WebviewEditor { await super.setInput(input, options); - const result: TPromise[] = []; - const renderer = new marked.Renderer(); - renderer.code = (code, lang) => { - const modeId = this.modeService.getModeIdForLanguageName(lang); - result.push(this.modeService.getOrCreateMode(modeId)); - return ''; - }; - - marked(text, { renderer }); - await TPromise.join(result); - - renderer.code = (code, lang) => { - const modeId = this.modeService.getModeIdForLanguageName(lang); - return `${tokenizeToString(code, modeId)}`; - }; - - const colorMap = TokenizationRegistry.getColorMap(); - const css = generateTokensCSSForColorMap(colorMap); - const body = renderBody(marked(text, { renderer }), css); + const body = await this.renderBody(text); this._webview = new Webview( this.content, this.partService.getContainer(Parts.EDITOR_PART), + this.themeService, this.environmentService, - this._contextService, this._contextViewService, this.contextKey, this.findInputFocusContextKey, - {}, - false); + {}); if (this.input && this.input instanceof ReleaseNotesInput) { const state = this.loadViewState(this.input.version); @@ -123,7 +102,7 @@ export class ReleaseNotesEditor extends WebviewEditor { this._webview.initialScrollProgress = state.scrollYPercentage; } } - this.onThemeChange(this.themeService.getTheme()); + this._webview.contents = body; this._webview.onDidClickLink(link => { @@ -134,25 +113,10 @@ export class ReleaseNotesEditor extends WebviewEditor { this._webview.onDidScroll(event => { this.scrollYPercentage = event.scrollYPercentage; }, null, this.contentDisposables); - this.themeService.onThemeChange(this.onThemeChange, this, this.contentDisposables); this.contentDisposables.push(this._webview); this.contentDisposables.push(toDisposable(() => this._webview = null)); } - layout(): void { - if (this._webview) { - this._webview.layout(); - } - } - - focus(): void { - if (!this._webview) { - return; - } - - this._webview.focus(); - } - dispose(): void { this.contentDisposables = dispose(this.contentDisposables); super.dispose(); @@ -181,4 +145,35 @@ export class ReleaseNotesEditor extends WebviewEditor { } super.shutdown(); } + + private async renderBody(text: string) { + const colorMap = TokenizationRegistry.getColorMap(); + const css = generateTokensCSSForColorMap(colorMap); + const body = renderBody(await this.renderContent(text), css); + return body; + } + + private async renderContent(text: string): TPromise { + const renderer = await this.getRenderer(text); + return marked(text, { renderer }); + } + + private async getRenderer(text: string) { + const result: TPromise[] = []; + const renderer = new marked.Renderer(); + renderer.code = (code, lang) => { + const modeId = this.modeService.getModeIdForLanguageName(lang); + result.push(this.modeService.getOrCreateMode(modeId)); + return ''; + }; + + marked(text, { renderer }); + await TPromise.join(result); + + renderer.code = (code, lang) => { + const modeId = this.modeService.getModeIdForLanguageName(lang); + return `${tokenizeToString(code, modeId)}`; + }; + return renderer; + } } diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index f8f97e35a74..c812adc567b 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -17,7 +17,10 @@ export const VS_LIGHT_THEME = 'vs'; export const VS_DARK_THEME = 'vs-dark'; export const VS_HC_THEME = 'hc-black'; +export const HC_THEME_ID = 'Default High Contrast'; + export const COLOR_THEME_SETTING = 'workbench.colorTheme'; +export const DETECT_HC_SETTING = 'window.autoDetectHighContrast'; export const ICON_THEME_SETTING = 'workbench.iconTheme'; export const CUSTOM_WORKBENCH_COLORS_SETTING = 'workbench.colorCustomizations'; export const CUSTOM_EDITOR_COLORS_SETTING = 'editor.tokenColorCustomizations'; diff --git a/src/vs/workbench/services/themes/electron-browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/electron-browser/workbenchThemeService.ts index 6aff720a1c3..a001da55c57 100644 --- a/src/vs/workbench/services/themes/electron-browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/electron-browser/workbenchThemeService.ts @@ -8,7 +8,7 @@ import { TPromise, Promise } from 'vs/base/common/winjs.base'; import nls = require('vs/nls'); import * as types from 'vs/base/common/types'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; -import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, CUSTOM_EDITOR_SCOPE_COLORS_SETTING } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, CUSTOM_EDITOR_SCOPE_COLORS_SETTING, DETECT_HC_SETTING, HC_THEME_ID } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -31,6 +31,7 @@ import { IBroadcastService } from 'vs/platform/broadcast/electron-browser/broadc import { ColorThemeStore } from 'vs/workbench/services/themes/electron-browser/colorThemeStore'; import { FileIconThemeStore } from 'vs/workbench/services/themes/electron-browser/fileIconThemeStore'; import { FileIconThemeData } from 'vs/workbench/services/themes/electron-browser/fileIconThemeData'; +import { IWindowService } from 'vs/platform/windows/common/windows'; // implementation @@ -97,6 +98,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { @IBroadcastService private broadcastService: IBroadcastService, @IConfigurationService private configurationService: IConfigurationService, @ITelemetryService private telemetryService: ITelemetryService, + @IWindowService private windowService: IWindowService, @IInstantiationService private instantiationService: IInstantiationService) { this.container = container; @@ -192,8 +194,15 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } private initialize(): TPromise<[IColorTheme, IFileIconTheme]> { + let detectHCThemeSetting = this.configurationService.getValue(DETECT_HC_SETTING); + + let colorThemeSetting: string; + if (this.windowService.getConfiguration().highContrast && detectHCThemeSetting) { + colorThemeSetting = HC_THEME_ID; + } else { + colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); + } - let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); let iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING) || ''; return Promise.join([ diff --git a/src/vs/workbench/test/browser/viewlet.test.ts b/src/vs/workbench/test/browser/viewlet.test.ts index f8b54dfda12..e649533f6cc 100644 --- a/src/vs/workbench/test/browser/viewlet.test.ts +++ b/src/vs/workbench/test/browser/viewlet.test.ts @@ -15,7 +15,7 @@ suite('Workbench Viewlet', () => { class TestViewlet extends Viewlet { constructor() { - super('id', null, null); + super('id', null, null, null); } public layout(dimension: any): void { diff --git a/yarn.lock b/yarn.lock index d8c1242ae5b..54f62a0a388 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6012,9 +6012,9 @@ vscode-textmate@^3.2.0: fast-plist "^0.1.2" oniguruma "^6.0.1" -vscode-xterm@3.2.0-beta3: - version "3.2.0-beta3" - resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.2.0-beta3.tgz#f82a54dd56f33b8875b68651cdcdd950317c8fb4" +vscode-xterm@3.2.0-beta4: + version "3.2.0-beta4" + resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.2.0-beta4.tgz#fdcabafe1b6f89e5490cc816dcf29f0383147d48" vso-node-api@^6.1.2-preview: version "6.1.2-preview"