diff --git a/build/azure-pipelines/exploration-build.yml b/build/azure-pipelines/exploration-build.yml new file mode 100644 index 00000000000..1fbc9eaa9cc --- /dev/null +++ b/build/azure-pipelines/exploration-build.yml @@ -0,0 +1,37 @@ +trigger: + branches: + include: ['master'] +pr: + branches: + include: ['master'] + +steps: +- task: NodeTool@0 + inputs: + versionSpec: "10.15.1" + +- task: AzureKeyVault@1 + displayName: 'Azure Key Vault: Get Secrets' + inputs: + azureSubscription: 'vscode-builds-subscription' + KeyVaultName: vscode + +- script: | + set -e + + cat << EOF > ~/.netrc + machine github.com + login vscode + password $(github-distro-mixin-password) + EOF + + git config user.email "vscode@microsoft.com" + git config user.name "VSCode" + + git checkout origin/ben/electron-test + git merge origin/master + + # Push master branch into exploration branch + git push origin HEAD:ben/electron-test + + displayName: Sync & Merge Exploration diff --git a/build/lib/tslint/abstractGlobalsRule.js b/build/lib/tslint/abstractGlobalsRule.js index 2aadf2c3bd9..1566c0aa576 100644 --- a/build/lib/tslint/abstractGlobalsRule.js +++ b/build/lib/tslint/abstractGlobalsRule.js @@ -19,18 +19,23 @@ class AbstractGlobalsRuleWalker extends Lint.RuleWalker { const checker = this.program.getTypeChecker(); const symbol = checker.getSymbolAtLocation(node); if (symbol) { - const valueDeclaration = symbol.valueDeclaration; - if (valueDeclaration) { - const parent = valueDeclaration.parent; - if (parent) { - const sourceFile = parent.getSourceFile(); - if (sourceFile) { - const fileName = sourceFile.fileName; - if (fileName && fileName.indexOf(this.getDefinitionPattern()) >= 0) { - this.addFailureAtNode(node, `Cannot use global '${node.text}' in '${this._config.target}'`); + const declarations = symbol.declarations; + if (Array.isArray(declarations) && symbol.declarations.some(declaration => { + if (declaration) { + const parent = declaration.parent; + if (parent) { + const sourceFile = parent.getSourceFile(); + if (sourceFile) { + const fileName = sourceFile.fileName; + if (fileName && fileName.indexOf(this.getDefinitionPattern()) >= 0) { + return true; + } } } } + return false; + })) { + this.addFailureAtNode(node, `Cannot use global '${node.text}' in '${this._config.target}'`); } } } diff --git a/build/lib/tslint/abstractGlobalsRule.ts b/build/lib/tslint/abstractGlobalsRule.ts index 8c80b3e0cfd..543720455c3 100644 --- a/build/lib/tslint/abstractGlobalsRule.ts +++ b/build/lib/tslint/abstractGlobalsRule.ts @@ -30,18 +30,24 @@ export abstract class AbstractGlobalsRuleWalker extends Lint.RuleWalker { const checker = this.program.getTypeChecker(); const symbol = checker.getSymbolAtLocation(node); if (symbol) { - const valueDeclaration = symbol.valueDeclaration; - if (valueDeclaration) { - const parent = valueDeclaration.parent; - if (parent) { - const sourceFile = parent.getSourceFile(); - if (sourceFile) { - const fileName = sourceFile.fileName; - if (fileName && fileName.indexOf(this.getDefinitionPattern()) >= 0) { - this.addFailureAtNode(node, `Cannot use global '${node.text}' in '${this._config.target}'`); + const declarations = symbol.declarations; + if (Array.isArray(declarations) && symbol.declarations.some(declaration => { + if (declaration) { + const parent = declaration.parent; + if (parent) { + const sourceFile = parent.getSourceFile(); + if (sourceFile) { + const fileName = sourceFile.fileName; + if (fileName && fileName.indexOf(this.getDefinitionPattern()) >= 0) { + return true; + } } } } + + return false; + })) { + this.addFailureAtNode(node, `Cannot use global '${node.text}' in '${this._config.target}'`); } } } diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index e555d61457e..3d1049af6b4 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -18,7 +18,7 @@ "watch": "gulp watch-extension:configuration-editing" }, "dependencies": { - "jsonc-parser": "2.0.2", + "jsonc-parser": "^2.1.1", "vscode-nls": "^4.0.0" }, "contributes": { diff --git a/extensions/configuration-editing/yarn.lock b/extensions/configuration-editing/yarn.lock index ce653f097a9..39ad2337314 100644 --- a/extensions/configuration-editing/yarn.lock +++ b/extensions/configuration-editing/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== -jsonc-parser@2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.0.2.tgz#42fcf56d70852a043fadafde51ddb4a85649978d" - integrity sha512-TSU435K5tEKh3g7bam1AFf+uZrISheoDsLlpmAo6wWZYqjsnd09lHYK1Qo+moK4Ikifev1Gdpa69g4NELKnCrQ== +jsonc-parser@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.1.1.tgz#83dc3d7a6e7186346b889b1280eefa04446c6d3e" + integrity sha512-VC0CjnWJylKB1iov4u76/W/5Ef0ydDkjtYWxoZ9t3HdWlSnZQwZL5MgFikaB/EtQ4RmMEw3tmQzuYnZA2/Ja1g== vscode-nls@^4.0.0: version "4.0.0" diff --git a/extensions/extension-editing/package.json b/extensions/extension-editing/package.json index 9c07a2568aa..50c97fb26f4 100644 --- a/extensions/extension-editing/package.json +++ b/extensions/extension-editing/package.json @@ -19,7 +19,7 @@ "watch": "gulp watch-extension:extension-editing" }, "dependencies": { - "jsonc-parser": "^2.0.2", + "jsonc-parser": "^2.1.1", "markdown-it": "^8.3.1", "parse5": "^3.0.2", "vscode-nls": "^4.0.0" diff --git a/extensions/extension-editing/yarn.lock b/extensions/extension-editing/yarn.lock index 971bf580ba7..fb98728f237 100644 --- a/extensions/extension-editing/yarn.lock +++ b/extensions/extension-editing/yarn.lock @@ -29,10 +29,10 @@ entities@~1.1.1: resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" integrity sha1-blwtClYhtdra7O+AuQ7ftc13cvA= -jsonc-parser@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.0.2.tgz#42fcf56d70852a043fadafde51ddb4a85649978d" - integrity sha512-TSU435K5tEKh3g7bam1AFf+uZrISheoDsLlpmAo6wWZYqjsnd09lHYK1Qo+moK4Ikifev1Gdpa69g4NELKnCrQ== +jsonc-parser@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.1.1.tgz#83dc3d7a6e7186346b889b1280eefa04446c6d3e" + integrity sha512-VC0CjnWJylKB1iov4u76/W/5Ef0ydDkjtYWxoZ9t3HdWlSnZQwZL5MgFikaB/EtQ4RmMEw3tmQzuYnZA2/Ja1g== linkify-it@^2.0.0: version "2.0.3" diff --git a/extensions/json-language-features/client/src/jsonMain.ts b/extensions/json-language-features/client/src/jsonMain.ts index 6f1ca02d580..52441738fef 100644 --- a/extensions/json-language-features/client/src/jsonMain.ts +++ b/extensions/json-language-features/client/src/jsonMain.ts @@ -145,11 +145,14 @@ export function activate(context: ExtensionContext) { } }); + const schemaDocuments: { [uri: string]: boolean } = {}; + // handle content request client.onRequest(VSCodeContentRequest.type, (uriPath: string) => { let uri = Uri.parse(uriPath); if (uri.scheme !== 'http' && uri.scheme !== 'https') { return workspace.openTextDocument(uri).then(doc => { + schemaDocuments[uri.toString()] = true; return doc.getText(); }, error => { return Promise.reject(error); @@ -164,10 +167,12 @@ export function activate(context: ExtensionContext) { } }); - let handleContentChange = (uri: Uri) => { - if (uri.scheme === 'vscode' && uri.authority === 'schemas') { - client.sendNotification(SchemaContentChangeNotification.type, uri.toString()); + let handleContentChange = (uriString: string) => { + if (schemaDocuments[uriString]) { + client.sendNotification(SchemaContentChangeNotification.type, uriString); + return true; } + return false; }; let handleActiveEditorChange = (activeEditor?: TextEditor) => { @@ -184,10 +189,13 @@ export function activate(context: ExtensionContext) { } }; - toDispose.push(workspace.onDidChangeTextDocument(e => handleContentChange(e.document.uri))); + toDispose.push(workspace.onDidChangeTextDocument(e => handleContentChange(e.document.uri.toString()))); toDispose.push(workspace.onDidCloseTextDocument(d => { - handleContentChange(d.uri); - fileSchemaErrors.delete(d.uri.toString()); + const uriString = d.uri.toString(); + if (handleContentChange(uriString)) { + delete schemaDocuments[uriString]; + } + fileSchemaErrors.delete(uriString); })); toDispose.push(window.onDidChangeActiveTextEditor(handleActiveEditorChange)); diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index bf5e3c8b09a..bf76da32dba 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -12,7 +12,7 @@ }, "main": "./out/jsonServerMain", "dependencies": { - "jsonc-parser": "^2.1.0", + "jsonc-parser": "^2.1.1", "request-light": "^0.2.4", "vscode-json-languageservice": "^3.3.1", "vscode-languageserver": "^5.3.0-next.8", diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index 3bfcb75ac7d..3c176e12603 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -59,6 +59,11 @@ jsonc-parser@^2.1.0: resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.1.0.tgz#eb0d0c7a3c33048524ce3574c57c7278fb2f1bf3" integrity sha512-n9GrT8rrr2fhvBbANa1g+xFmgGK5X91KFeDwlKQ3+SJfmH5+tKv/M/kahx/TXOMflfWHKGKqKyfHQaLKTNzJ6w== +jsonc-parser@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.1.1.tgz#83dc3d7a6e7186346b889b1280eefa04446c6d3e" + integrity sha512-VC0CjnWJylKB1iov4u76/W/5Ef0ydDkjtYWxoZ9t3HdWlSnZQwZL5MgFikaB/EtQ4RmMEw3tmQzuYnZA2/Ja1g== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" diff --git a/extensions/npm/package.json b/extensions/npm/package.json index 843255c2141..a987d6082b4 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -18,7 +18,7 @@ "watch": "gulp watch-extension:npm" }, "dependencies": { - "jsonc-parser": "^2.0.2", + "jsonc-parser": "^2.1.1", "minimatch": "^3.0.4", "request-light": "^0.2.4", "vscode-nls": "^4.0.0" diff --git a/extensions/npm/yarn.lock b/extensions/npm/yarn.lock index e80a4ca5a2d..0f34421c9aa 100644 --- a/extensions/npm/yarn.lock +++ b/extensions/npm/yarn.lock @@ -72,10 +72,10 @@ https-proxy-agent@^2.2.1: agent-base "^4.1.0" debug "^3.1.0" -jsonc-parser@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.0.2.tgz#42fcf56d70852a043fadafde51ddb4a85649978d" - integrity sha512-TSU435K5tEKh3g7bam1AFf+uZrISheoDsLlpmAo6wWZYqjsnd09lHYK1Qo+moK4Ikifev1Gdpa69g4NELKnCrQ== +jsonc-parser@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.1.1.tgz#83dc3d7a6e7186346b889b1280eefa04446c6d3e" + integrity sha512-VC0CjnWJylKB1iov4u76/W/5Ef0ydDkjtYWxoZ9t3HdWlSnZQwZL5MgFikaB/EtQ4RmMEw3tmQzuYnZA2/Ja1g== minimatch@^3.0.4: version "3.0.4" diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index fc003585c9b..3b9917c412c 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -16,7 +16,7 @@ "Programming Languages" ], "dependencies": { - "jsonc-parser": "^2.0.1", + "jsonc-parser": "^2.1.1", "rimraf": "^2.6.3", "semver": "5.5.1", "vscode-extension-telemetry": "0.1.1", @@ -758,4 +758,4 @@ } ] } -} \ No newline at end of file +} diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index 683f002c727..682bc093c6c 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -1027,10 +1027,10 @@ json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= -jsonc-parser@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.0.1.tgz#9d23cd2709714fff508a1a6679d82135bee1ae60" - integrity sha512-9w/QyN9qF1dTlffzkmyITa6qAYt6sDArlVZqaP+CXnRh66V73wImQGG8GIBkuas0XLAxddWEWYQ8PPFoK5KNQA== +jsonc-parser@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.1.1.tgz#83dc3d7a6e7186346b889b1280eefa04446c6d3e" + integrity sha512-VC0CjnWJylKB1iov4u76/W/5Ef0ydDkjtYWxoZ9t3HdWlSnZQwZL5MgFikaB/EtQ4RmMEw3tmQzuYnZA2/Ja1g== jsonify@~0.0.0: version "0.0.0" diff --git a/package.json b/package.json index 959edd7a376..47d7fb2d519 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.38.0", - "distro": "2f7403275ca8931a7265b58ac35e33096ce0d074", + "distro": "116231da62de1019aab221a7b307f1d41edb5774", "author": { "name": "Microsoft Corporation" }, diff --git a/scripts/code.bat b/scripts/code.bat index 8c058365dca..f4689608e4a 100644 --- a/scripts/code.bat +++ b/scripts/code.bat @@ -33,6 +33,7 @@ set VSCODE_CLI=1 set ELECTRON_DEFAULT_ERROR_MODE=1 set ELECTRON_ENABLE_LOGGING=1 set ELECTRON_ENABLE_STACK_DUMPING=1 +set VSCODE_LOGS= :: Launch Code diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 3967d3b3f08..1639bb949fe 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -1186,22 +1186,14 @@ export function animate(fn: () => void): IDisposable { return toDisposable(() => stepDisposable.dispose()); } - - -const _location = URI.parse(window.location.href); +RemoteAuthorities.setPreferredWebSchema(/^https:/.test(window.location.href) ? 'https' : 'http'); export function asDomUri(uri: URI): URI { if (!uri) { return uri; } if (Schemas.vscodeRemote === uri.scheme) { - if (platform.isWeb) { - // rewrite vscode-remote-uris to uris of the window location - // so that they can be intercepted by the service worker - return _location.with({ path: '/vscode-remote', query: JSON.stringify(uri) }); - } else { - return RemoteAuthorities.rewrite(uri.authority, uri.path); - } + return RemoteAuthorities.rewrite(uri.authority, uri.path); } return uri; } diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 5d94200e15a..fa6956fdac0 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -183,9 +183,11 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende allowedAttributes: { 'a': ['href', 'name', 'target', 'data-href'], 'iframe': ['allowfullscreen', 'frameborder', 'src'], - 'img': ['src', 'title', 'alt', 'width', 'height'] + 'img': ['src', 'title', 'alt', 'width', 'height'], + 'div': ['class', 'data-code'] } }); + signalInnerHTML!(); return element; diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index 2a2964beeef..fcc27e402a7 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -24,6 +24,8 @@ export interface IFindInputOptions extends IFindInputStyles { readonly validation?: IInputValidator; readonly label: string; readonly flexibleHeight?: boolean; + readonly flexibleWidth?: boolean; + readonly flexibleMaxHeight?: number; readonly appendCaseSensitiveLabel?: string; readonly appendWholeWordsLabel?: string; @@ -119,6 +121,8 @@ export class FindInput extends Widget { const appendRegexLabel = options.appendRegexLabel || ''; const history = options.history || []; const flexibleHeight = !!options.flexibleHeight; + const flexibleWidth = !!options.flexibleWidth; + const flexibleMaxHeight = options.flexibleMaxHeight; this.domNode = document.createElement('div'); dom.addClass(this.domNode, 'monaco-findInput'); @@ -142,7 +146,9 @@ export class FindInput extends Widget { inputValidationErrorForeground: this.inputValidationErrorForeground, inputValidationErrorBorder: this.inputValidationErrorBorder, history, - flexibleHeight + flexibleHeight, + flexibleWidth, + flexibleMaxHeight })); this.regex = this._register(new RegexCheckbox({ @@ -194,11 +200,7 @@ export class FindInput extends Widget { })); if (this._showOptionButtons) { - const paddingRight = (this.caseSensitive.width() + this.wholeWords.width() + this.regex.width()) + 'px'; - this.inputBox.inputElement.style.paddingRight = paddingRight; - if (this.inputBox.mirrorElement) { - this.inputBox.mirrorElement.style.paddingRight = paddingRight; - } + this.inputBox.paddingRight = this.caseSensitive.width() + this.wholeWords.width() + this.regex.width(); } // Arrow-Key support to navigate between options diff --git a/src/vs/base/browser/ui/findinput/replaceInput.ts b/src/vs/base/browser/ui/findinput/replaceInput.ts new file mode 100644 index 00000000000..d597fb8bd49 --- /dev/null +++ b/src/vs/base/browser/ui/findinput/replaceInput.ts @@ -0,0 +1,373 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./findInput'; + +import * as nls from 'vs/nls'; +import * as dom from 'vs/base/browser/dom'; +import { IMessage as InputBoxMessage, IInputValidator, IInputBoxStyles, HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputBox'; +import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; +import { Widget } from 'vs/base/browser/ui/widget'; +import { Event, Emitter } from 'vs/base/common/event'; +import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { IMouseEvent } from 'vs/base/browser/mouseEvent'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { Color } from 'vs/base/common/color'; +import { ICheckboxStyles, Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; +import { IFindInputCheckboxOpts } from 'vs/base/browser/ui/findinput/findInputCheckboxes'; + +export interface IReplaceInputOptions extends IReplaceInputStyles { + readonly placeholder?: string; + readonly width?: number; + readonly validation?: IInputValidator; + readonly label: string; + readonly flexibleHeight?: boolean; + readonly flexibleWidth?: boolean; + readonly flexibleMaxHeight?: number; + + readonly history?: string[]; +} + +export interface IReplaceInputStyles extends IInputBoxStyles { + inputActiveOptionBorder?: Color; +} + +const NLS_DEFAULT_LABEL = nls.localize('defaultLabel', "input"); +const NLS_PRESERVE_CASE_LABEL = nls.localize('label.preserveCaseCheckbox', "Preserve Case"); + +export class PreserveCaseCheckbox extends Checkbox { + constructor(opts: IFindInputCheckboxOpts) { + super({ + // TODO: does this need its own icon? + actionClassName: 'monaco-case-sensitive', + title: NLS_PRESERVE_CASE_LABEL + opts.appendTitle, + isChecked: opts.isChecked, + inputActiveOptionBorder: opts.inputActiveOptionBorder + }); + } +} + +export class ReplaceInput extends Widget { + + static readonly OPTION_CHANGE: string = 'optionChange'; + + private contextViewProvider: IContextViewProvider | undefined; + private placeholder: string; + private validation?: IInputValidator; + private label: string; + private fixFocusOnOptionClickEnabled = true; + + private inputActiveOptionBorder?: Color; + private inputBackground?: Color; + private inputForeground?: Color; + private inputBorder?: Color; + + private inputValidationInfoBorder?: Color; + private inputValidationInfoBackground?: Color; + private inputValidationInfoForeground?: Color; + private inputValidationWarningBorder?: Color; + private inputValidationWarningBackground?: Color; + private inputValidationWarningForeground?: Color; + private inputValidationErrorBorder?: Color; + private inputValidationErrorBackground?: Color; + private inputValidationErrorForeground?: Color; + + private preserveCase: PreserveCaseCheckbox; + private cachedOptionsWidth: number = 0; + public domNode: HTMLElement; + public inputBox: HistoryInputBox; + + private readonly _onDidOptionChange = this._register(new Emitter()); + public readonly onDidOptionChange: Event = this._onDidOptionChange.event; + + private readonly _onKeyDown = this._register(new Emitter()); + public readonly onKeyDown: Event = this._onKeyDown.event; + + private readonly _onMouseDown = this._register(new Emitter()); + public readonly onMouseDown: Event = this._onMouseDown.event; + + private readonly _onInput = this._register(new Emitter()); + public readonly onInput: Event = this._onInput.event; + + private readonly _onKeyUp = this._register(new Emitter()); + public readonly onKeyUp: Event = this._onKeyUp.event; + + private _onPreserveCaseKeyDown = this._register(new Emitter()); + public readonly onPreserveCaseKeyDown: Event = this._onPreserveCaseKeyDown.event; + + constructor(parent: HTMLElement | null, contextViewProvider: IContextViewProvider | undefined, private readonly _showOptionButtons: boolean, options: IReplaceInputOptions) { + super(); + this.contextViewProvider = contextViewProvider; + this.placeholder = options.placeholder || ''; + this.validation = options.validation; + this.label = options.label || NLS_DEFAULT_LABEL; + + this.inputActiveOptionBorder = options.inputActiveOptionBorder; + this.inputBackground = options.inputBackground; + this.inputForeground = options.inputForeground; + this.inputBorder = options.inputBorder; + + this.inputValidationInfoBorder = options.inputValidationInfoBorder; + this.inputValidationInfoBackground = options.inputValidationInfoBackground; + this.inputValidationInfoForeground = options.inputValidationInfoForeground; + this.inputValidationWarningBorder = options.inputValidationWarningBorder; + this.inputValidationWarningBackground = options.inputValidationWarningBackground; + this.inputValidationWarningForeground = options.inputValidationWarningForeground; + this.inputValidationErrorBorder = options.inputValidationErrorBorder; + this.inputValidationErrorBackground = options.inputValidationErrorBackground; + this.inputValidationErrorForeground = options.inputValidationErrorForeground; + + const flexibleHeight = !!options.flexibleHeight; + const flexibleWidth = !!options.flexibleWidth; + const flexibleMaxHeight = options.flexibleMaxHeight; + + this.buildDomNode(options.history || [], flexibleHeight, flexibleWidth, flexibleMaxHeight); + + if (parent) { + parent.appendChild(this.domNode); + } + + this.onkeydown(this.inputBox.inputElement, (e) => this._onKeyDown.fire(e)); + this.onkeyup(this.inputBox.inputElement, (e) => this._onKeyUp.fire(e)); + this.oninput(this.inputBox.inputElement, (e) => this._onInput.fire()); + this.onmousedown(this.inputBox.inputElement, (e) => this._onMouseDown.fire(e)); + } + + public enable(): void { + dom.removeClass(this.domNode, 'disabled'); + this.inputBox.enable(); + this.preserveCase.enable(); + } + + public disable(): void { + dom.addClass(this.domNode, 'disabled'); + this.inputBox.disable(); + this.preserveCase.disable(); + } + + public setFocusInputOnOptionClick(value: boolean): void { + this.fixFocusOnOptionClickEnabled = value; + } + + public setEnabled(enabled: boolean): void { + if (enabled) { + this.enable(); + } else { + this.disable(); + } + } + + public clear(): void { + this.clearValidation(); + this.setValue(''); + this.focus(); + } + + public getValue(): string { + return this.inputBox.value; + } + + public setValue(value: string): void { + if (this.inputBox.value !== value) { + this.inputBox.value = value; + } + } + + public onSearchSubmit(): void { + this.inputBox.addToHistory(); + } + + public style(styles: IReplaceInputStyles): void { + this.inputActiveOptionBorder = styles.inputActiveOptionBorder; + this.inputBackground = styles.inputBackground; + this.inputForeground = styles.inputForeground; + this.inputBorder = styles.inputBorder; + + this.inputValidationInfoBackground = styles.inputValidationInfoBackground; + this.inputValidationInfoForeground = styles.inputValidationInfoForeground; + this.inputValidationInfoBorder = styles.inputValidationInfoBorder; + this.inputValidationWarningBackground = styles.inputValidationWarningBackground; + this.inputValidationWarningForeground = styles.inputValidationWarningForeground; + this.inputValidationWarningBorder = styles.inputValidationWarningBorder; + this.inputValidationErrorBackground = styles.inputValidationErrorBackground; + this.inputValidationErrorForeground = styles.inputValidationErrorForeground; + this.inputValidationErrorBorder = styles.inputValidationErrorBorder; + + this.applyStyles(); + } + + protected applyStyles(): void { + if (this.domNode) { + const checkBoxStyles: ICheckboxStyles = { + inputActiveOptionBorder: this.inputActiveOptionBorder, + }; + this.preserveCase.style(checkBoxStyles); + + const inputBoxStyles: IInputBoxStyles = { + inputBackground: this.inputBackground, + inputForeground: this.inputForeground, + inputBorder: this.inputBorder, + inputValidationInfoBackground: this.inputValidationInfoBackground, + inputValidationInfoForeground: this.inputValidationInfoForeground, + inputValidationInfoBorder: this.inputValidationInfoBorder, + inputValidationWarningBackground: this.inputValidationWarningBackground, + inputValidationWarningForeground: this.inputValidationWarningForeground, + inputValidationWarningBorder: this.inputValidationWarningBorder, + inputValidationErrorBackground: this.inputValidationErrorBackground, + inputValidationErrorForeground: this.inputValidationErrorForeground, + inputValidationErrorBorder: this.inputValidationErrorBorder + }; + this.inputBox.style(inputBoxStyles); + } + } + + public select(): void { + this.inputBox.select(); + } + + public focus(): void { + this.inputBox.focus(); + } + + public getPreserveCase(): boolean { + return this.preserveCase.checked; + } + + public setPreserveCase(value: boolean): void { + this.preserveCase.checked = value; + } + + public focusOnPreserve(): void { + this.preserveCase.focus(); + } + + private _lastHighlightFindOptions: number = 0; + public highlightFindOptions(): void { + dom.removeClass(this.domNode, 'highlight-' + (this._lastHighlightFindOptions)); + this._lastHighlightFindOptions = 1 - this._lastHighlightFindOptions; + dom.addClass(this.domNode, 'highlight-' + (this._lastHighlightFindOptions)); + } + + private buildDomNode(history: string[], flexibleHeight: boolean, flexibleWidth: boolean, flexibleMaxHeight: number | undefined): void { + this.domNode = document.createElement('div'); + dom.addClass(this.domNode, 'monaco-findInput'); + + this.inputBox = this._register(new HistoryInputBox(this.domNode, this.contextViewProvider, { + ariaLabel: this.label || '', + placeholder: this.placeholder || '', + validationOptions: { + validation: this.validation + }, + inputBackground: this.inputBackground, + inputForeground: this.inputForeground, + inputBorder: this.inputBorder, + inputValidationInfoBackground: this.inputValidationInfoBackground, + inputValidationInfoForeground: this.inputValidationInfoForeground, + inputValidationInfoBorder: this.inputValidationInfoBorder, + inputValidationWarningBackground: this.inputValidationWarningBackground, + inputValidationWarningForeground: this.inputValidationWarningForeground, + inputValidationWarningBorder: this.inputValidationWarningBorder, + inputValidationErrorBackground: this.inputValidationErrorBackground, + inputValidationErrorForeground: this.inputValidationErrorForeground, + inputValidationErrorBorder: this.inputValidationErrorBorder, + history, + flexibleHeight, + flexibleWidth, + flexibleMaxHeight + })); + + this.preserveCase = this._register(new PreserveCaseCheckbox({ + appendTitle: '', + isChecked: false, + inputActiveOptionBorder: this.inputActiveOptionBorder + })); + this._register(this.preserveCase.onChange(viaKeyboard => { + this._onDidOptionChange.fire(viaKeyboard); + if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { + this.inputBox.focus(); + } + this.validate(); + })); + this._register(this.preserveCase.onKeyDown(e => { + this._onPreserveCaseKeyDown.fire(e); + })); + + if (this._showOptionButtons) { + this.cachedOptionsWidth = this.preserveCase.width(); + } else { + this.cachedOptionsWidth = 0; + } + + // Arrow-Key support to navigate between options + let indexes = [this.preserveCase.domNode]; + this.onkeydown(this.domNode, (event: IKeyboardEvent) => { + if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Escape)) { + let index = indexes.indexOf(document.activeElement); + if (index >= 0) { + let newIndex: number = -1; + if (event.equals(KeyCode.RightArrow)) { + newIndex = (index + 1) % indexes.length; + } else if (event.equals(KeyCode.LeftArrow)) { + if (index === 0) { + newIndex = indexes.length - 1; + } else { + newIndex = index - 1; + } + } + + if (event.equals(KeyCode.Escape)) { + indexes[index].blur(); + } else if (newIndex >= 0) { + indexes[newIndex].focus(); + } + + dom.EventHelper.stop(event, true); + } + } + }); + + + let controls = document.createElement('div'); + controls.className = 'controls'; + controls.style.display = this._showOptionButtons ? 'block' : 'none'; + controls.appendChild(this.preserveCase.domNode); + + this.domNode.appendChild(controls); + } + + public validate(): void { + if (this.inputBox) { + this.inputBox.validate(); + } + } + + public showMessage(message: InputBoxMessage): void { + if (this.inputBox) { + this.inputBox.showMessage(message); + } + } + + public clearMessage(): void { + if (this.inputBox) { + this.inputBox.hideMessage(); + } + } + + private clearValidation(): void { + if (this.inputBox) { + this.inputBox.hideMessage(); + } + } + + public set width(newWidth: number) { + this.inputBox.paddingRight = this.cachedOptionsWidth; + this.inputBox.width = newWidth; + this.domNode.style.width = newWidth + 'px'; + } + + public dispose(): void { + super.dispose(); + } +} diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index 7af2a58f46d..430f250ce38 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -32,6 +32,7 @@ export interface IInputOptions extends IInputBoxStyles { readonly type?: string; readonly validationOptions?: IInputValidationOptions; readonly flexibleHeight?: boolean; + readonly flexibleWidth?: boolean; readonly flexibleMaxHeight?: number; readonly actions?: ReadonlyArray; } @@ -173,6 +174,13 @@ export class InputBox extends Widget { this.mirror.innerHTML = ' '; this.scrollableElement = new ScrollableElement(this.element, { vertical: ScrollbarVisibility.Auto }); + + if (this.options.flexibleWidth) { + this.input.setAttribute('wrap', 'off'); + this.mirror.style.whiteSpace = 'pre'; + this.mirror.style.wordWrap = 'initial'; + } + dom.append(container, this.scrollableElement.getDomNode()); this._register(this.scrollableElement); @@ -321,12 +329,36 @@ export class InputBox extends Widget { } public set width(width: number) { - this.input.style.width = width + 'px'; + if (this.options.flexibleHeight && this.options.flexibleWidth) { + // textarea with horizontal scrolling + let horizontalPadding = 0; + if (this.mirror) { + const paddingLeft = parseFloat(this.mirror.style.paddingLeft || '') || 0; + const paddingRight = parseFloat(this.mirror.style.paddingRight || '') || 0; + horizontalPadding = paddingLeft + paddingRight; + } + this.input.style.width = (width - horizontalPadding) + 'px'; + } else { + this.input.style.width = width + 'px'; + } + if (this.mirror) { this.mirror.style.width = width + 'px'; } } + public set paddingRight(paddingRight: number) { + if (this.options.flexibleHeight && this.options.flexibleWidth) { + this.input.style.width = `calc(100% - ${paddingRight}px)`; + } else { + this.input.style.paddingRight = paddingRight + 'px'; + } + + if (this.mirror) { + this.mirror.style.paddingRight = paddingRight + 'px'; + } + } + private updateScrollDimensions(): void { if (typeof this.cachedContentHeight !== 'number' || typeof this.cachedHeight !== 'number') { return; diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 4f5394f773d..8ae8867bf64 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -683,6 +683,22 @@ export class ListView implements ISpliceable, IDisposable { this.scrollableElement.setScrollPosition({ scrollTop }); } + getScrollLeft(): number { + const scrollPosition = this.scrollableElement.getScrollPosition(); + return scrollPosition.scrollLeft; + } + + setScrollLeftt(scrollLeft: number): void { + if (this.scrollableElementUpdateDisposable) { + this.scrollableElementUpdateDisposable.dispose(); + this.scrollableElementUpdateDisposable = null; + this.scrollableElement.setScrollDimensions({ scrollWidth: this.scrollWidth }); + } + + this.scrollableElement.setScrollPosition({ scrollLeft }); + } + + get scrollTop(): number { return this.getScrollTop(); } diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 6e82c6de867..15b21e3b9b0 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -1307,6 +1307,14 @@ export class List implements ISpliceable, IDisposable { this.view.setScrollTop(scrollTop); } + get scrollLeft(): number { + return this.view.getScrollLeft(); + } + + set scrollLeft(scrollLeft: number) { + this.view.setScrollLeftt(scrollLeft); + } + get scrollHeight(): number { return this.view.scrollHeight; } diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 7402d493a98..1aa9c5363e2 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -1319,6 +1319,14 @@ export abstract class AbstractTree implements IDisposable this.view.scrollTop = scrollTop; } + get scrollLeft(): number { + return this.view.scrollTop; + } + + set scrollLeft(scrollLeft: number) { + this.view.scrollLeft = scrollLeft; + } + get scrollHeight(): number { return this.view.scrollHeight; } diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index b26b80d2c49..9c6bea61a41 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -400,6 +400,14 @@ export class AsyncDataTree implements IDisposable this.tree.scrollTop = scrollTop; } + get scrollLeft(): number { + return this.tree.scrollLeft; + } + + set scrollLeft(scrollLeft: number) { + this.tree.scrollLeft = scrollLeft; + } + get scrollHeight(): number { return this.tree.scrollHeight; } diff --git a/src/vs/base/browser/ui/tree/indexTreeModel.ts b/src/vs/base/browser/ui/tree/indexTreeModel.ts index a568881e7c2..bdf19be09f5 100644 --- a/src/vs/base/browser/ui/tree/indexTreeModel.ts +++ b/src/vs/base/browser/ui/tree/indexTreeModel.ts @@ -219,7 +219,7 @@ export class IndexTreeModel, TFilterData = voi const result = this._setListNodeCollapsed(node, listIndex, revealed, collapsed!, recursive || false); - if (this.autoExpandSingleChildren && !collapsed! && !recursive) { + if (node !== this.root && this.autoExpandSingleChildren && !collapsed! && !recursive) { let onlyVisibleChildIndex = -1; for (let i = 0; i < node.children.length; i++) { diff --git a/src/vs/base/common/filters.ts b/src/vs/base/common/filters.ts index d01c32668ca..fe233f874bf 100644 --- a/src/vs/base/common/filters.ts +++ b/src/vs/base/common/filters.ts @@ -549,7 +549,7 @@ export function fuzzyScore(pattern: string, patternLow: string, patternPos: numb const patternStartPos = patternPos; const wordStartPos = wordPos; - // There will be a mach, fill in tables + // There will be a match, fill in tables for (patternPos = patternStartPos + 1; patternPos <= patternLen; patternPos++) { for (wordPos = 1; wordPos <= wordLen; wordPos++) { @@ -573,6 +573,11 @@ export function fuzzyScore(pattern: string, patternLow: string, patternPos: numb } else { score = 5; } + } else if (isSeparatorAtPos(wordLow, wordPos - 1) && (wordPos === 1 || !isSeparatorAtPos(wordLow, wordPos - 2))) { + // hitting a separator: `. <-> foo.bar` + // ^ + score = 5; + } else if (isSeparatorAtPos(wordLow, wordPos - 2) || isWhitespaceAtPos(wordLow, wordPos - 2)) { // post separator: `foo <-> bar_foo` // ^^^ diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 9c83d42dc7c..4f93e06df0b 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; +import * as platform from 'vs/base/common/platform'; export namespace Schemas { @@ -58,11 +59,17 @@ class RemoteAuthoritiesImpl { private readonly _hosts: { [authority: string]: string; }; private readonly _ports: { [authority: string]: number; }; private readonly _connectionTokens: { [authority: string]: string; }; + private _preferredWebSchema: 'http' | 'https'; constructor() { this._hosts = Object.create(null); this._ports = Object.create(null); this._connectionTokens = Object.create(null); + this._preferredWebSchema = 'http'; + } + + public setPreferredWebSchema(schema: 'http' | 'https') { + this._preferredWebSchema = schema; } public set(authority: string, host: string, port: number): void { @@ -79,9 +86,9 @@ class RemoteAuthoritiesImpl { const port = this._ports[authority]; const connectionToken = this._connectionTokens[authority]; return URI.from({ - scheme: Schemas.vscodeRemoteResource, + scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource, authority: `${host}:${port}`, - path: `/vscode-remote2`, + path: `/vscode-remote-resource`, query: `path=${encodeURIComponent(path)}&tkn=${encodeURIComponent(connectionToken)}` }); } diff --git a/src/vs/base/test/common/filters.test.ts b/src/vs/base/test/common/filters.test.ts index 25cc50d5780..42b74143661 100644 --- a/src/vs/base/test/common/filters.test.ts +++ b/src/vs/base/test/common/filters.test.ts @@ -366,6 +366,10 @@ suite('Filters', () => { assertMatches('f', ':foo', ':^foo', fuzzyScore); }); + test('Separator only match should not be weak #79558', function () { + assertMatches('.', 'foo.bar', 'foo^.bar', fuzzyScore); + }); + test('Cannot set property \'1\' of undefined, #26511', function () { let word = new Array(123).join('a'); let pattern = new Array(120).join('a'); diff --git a/src/vs/code/browser/workbench/workbench.html b/src/vs/code/browser/workbench/workbench.html index 8f8f45fb15f..9bf87281e2c 100644 --- a/src/vs/code/browser/workbench/workbench.html +++ b/src/vs/code/browser/workbench/workbench.html @@ -25,8 +25,8 @@ - + - + diff --git a/src/vs/code/browser/workbench/workbench.js b/src/vs/code/browser/workbench/workbench.js index 240ca34df05..5050cb4e5b4 100644 --- a/src/vs/code/browser/workbench/workbench.js +++ b/src/vs/code/browser/workbench/workbench.js @@ -8,15 +8,15 @@ (function () { require.config({ - baseUrl: `${window.location.origin}/out`, + baseUrl: `${window.location.origin}/static/out`, paths: { - 'vscode-textmate': `${window.location.origin}/node_modules/vscode-textmate/release/main`, - 'onigasm-umd': `${window.location.origin}/node_modules/onigasm-umd/release/main`, - 'xterm': `${window.location.origin}/node_modules/xterm/lib/xterm.js`, - 'xterm-addon-search': `${window.location.origin}/node_modules/xterm-addon-search/lib/xterm-addon-search.js`, - 'xterm-addon-web-links': `${window.location.origin}/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`, - 'semver-umd': `${window.location.origin}/node_modules/semver-umd/lib/semver-umd.js`, - '@microsoft/applicationinsights-web': `${window.location.origin}/node_modules/@microsoft/applicationinsights-web/dist/applicationinsights-web.js`, + 'vscode-textmate': `${window.location.origin}/static/node_modules/vscode-textmate/release/main`, + 'onigasm-umd': `${window.location.origin}/static/node_modules/onigasm-umd/release/main`, + 'xterm': `${window.location.origin}/static/node_modules/xterm/lib/xterm.js`, + 'xterm-addon-search': `${window.location.origin}/static/node_modules/xterm-addon-search/lib/xterm-addon-search.js`, + 'xterm-addon-web-links': `${window.location.origin}/static/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`, + 'semver-umd': `${window.location.origin}/static/node_modules/semver-umd/lib/semver-umd.js`, + '@microsoft/applicationinsights-web': `${window.location.origin}/static/node_modules/@microsoft/applicationinsights-web/dist/applicationinsights-web.js`, } }); diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index 7632af1b45c..07056377d38 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -4,41 +4,39 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { LinkedList } from 'vs/base/common/linkedList'; import { parse } from 'vs/base/common/marshalling'; import { Schemas } from 'vs/base/common/network'; import * as resources from 'vs/base/common/resources'; +import { equalsIgnoreCase } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; -import { IOpenerService, IOpener } from 'vs/platform/opener/common/opener'; -import { equalsIgnoreCase } from 'vs/base/common/strings'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { LinkedList } from 'vs/base/common/linkedList'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { localize } from 'vs/nls'; -import { IProductService } from 'vs/platform/product/common/product'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import Severity from 'vs/base/common/severity'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { IOpener, IOpenerService, IValidator } from 'vs/platform/opener/common/opener'; -export class OpenerService implements IOpenerService { +export class OpenerService extends Disposable implements IOpenerService { _serviceBrand!: ServiceIdentifier; - private readonly _opener = new LinkedList(); + private readonly _openers = new LinkedList(); + private readonly _validators = new LinkedList(); constructor( @ICodeEditorService private readonly _editorService: ICodeEditorService, @ICommandService private readonly _commandService: ICommandService, - @IStorageService private readonly _storageService: IStorageService, - @IDialogService private readonly _dialogService: IDialogService, - @IProductService private readonly _productService: IProductService ) { - // + super(); } registerOpener(opener: IOpener): IDisposable { - const remove = this._opener.push(opener); + const remove = this._openers.push(opener); + return { dispose: remove }; + } + + registerValidator(validator: IValidator): IDisposable { + const remove = this._validators.push(validator); return { dispose: remove }; } @@ -47,8 +45,16 @@ export class OpenerService implements IOpenerService { if (!resource.scheme) { return Promise.resolve(false); } + + // check with contributed validators + for (const validator of this._validators.toArray()) { + if (!(await validator.shouldOpen(resource))) { + return false; + } + } + // check with contributed openers - for (const opener of this._opener.toArray()) { + for (const opener of this._openers.toArray()) { const handled = await opener.open(resource, options); if (handled) { return true; @@ -60,7 +66,7 @@ export class OpenerService implements IOpenerService { private _doOpen(resource: URI, options?: { openToSide?: boolean, openExternal?: boolean }): Promise { - const { scheme, authority, path, query, fragment } = resource; + const { scheme, path, query, fragment } = resource; if (equalsIgnoreCase(scheme, Schemas.mailto) || (options && options.openExternal)) { // open default mail application @@ -68,48 +74,8 @@ export class OpenerService implements IOpenerService { } if (equalsIgnoreCase(scheme, Schemas.http) || equalsIgnoreCase(scheme, Schemas.https)) { - let trustedDomains: string[] = ['https://code.visualstudio.com']; - try { - const trustedDomainsSrc = this._storageService.get('http.trustedDomains', StorageScope.GLOBAL); - if (trustedDomainsSrc) { - trustedDomains = JSON.parse(trustedDomainsSrc); - } - } catch (err) { } - - const domainToOpen = `${scheme}://${authority}`; - - if (isDomainTrusted(domainToOpen, trustedDomains)) { - return this._doOpenExternal(resource); - } else { - return this._dialogService.show( - Severity.Info, - localize( - 'openExternalLinkAt', - 'Do you want {0} to open the external website?\n{1}', - this._productService.nameShort, - resource.toString(true) - ), - [ - localize('openLink', 'Open Link'), - localize('cancel', 'Cancel'), - localize('configureTrustedDomains', 'Configure Trusted Domains') - ], - { - cancelId: 1 - }).then((choice) => { - if (choice === 0) { - return this._doOpenExternal(resource); - } else if (choice === 2) { - return this._commandService.executeCommand('workbench.action.configureTrustedDomains', domainToOpen).then((pickedDomains: string[]) => { - if (pickedDomains.indexOf(domainToOpen) !== -1) { - return this._doOpenExternal(resource); - } - return Promise.resolve(false); - }); - } - return Promise.resolve(false); - }); - } + // open link in default browser + return this._doOpenExternal(resource); } else if (equalsIgnoreCase(scheme, Schemas.command)) { // run command or bail out if command isn't known if (!CommandsRegistry.getCommand(path)) { @@ -158,22 +124,8 @@ export class OpenerService implements IOpenerService { return Promise.resolve(true); } -} -/** - * Check whether a domain like https://www.microsoft.com matches - * the list of trusted domains. - */ -function isDomainTrusted(domain: string, trustedDomains: string[]) { - for (let i = 0; i < trustedDomains.length; i++) { - if (trustedDomains[i] === '*') { - return true; - } - - if (trustedDomains[i] === domain) { - return true; - } + dispose() { + this._validators.clear(); } - - return false; } diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 7480fccf86b..4689f472001 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -367,6 +367,10 @@ export let completionKindFromString: { }; })(); +export const enum CompletionItemKindModifier { + Deprecated = 1 +} + export const enum CompletionItemInsertTextRule { /** * Adjust whitespace/indentation of multiline insert texts to @@ -396,6 +400,11 @@ export interface CompletionItem { * an icon is chosen by the editor. */ kind: CompletionItemKind; + /** + * A modifier to the `kind` which affect how the item + * is rendered, e.g. Deprecated is rendered with a strikeout + */ + kindModifier?: CompletionItemKindModifier; /** * A human-readable string with additional information * about this item, like type or symbol information. @@ -464,7 +473,7 @@ export interface CompletionItem { /** * @internal */ - [key: string]: any; + _id?: [number, number]; } export interface CompletionList { diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 46d295d2398..76ee0d96933 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -581,6 +581,10 @@ export enum CompletionItemKind { Snippet = 25 } +export enum CompletionItemKindModifier { + Deprecated = 1 +} + export enum CompletionItemInsertTextRule { /** * Adjust whitespace/indentation of multiline insert texts to diff --git a/src/vs/editor/contrib/find/findController.ts b/src/vs/editor/contrib/find/findController.ts index ed3544d10ff..664bc8c8bf3 100644 --- a/src/vs/editor/contrib/find/findController.ts +++ b/src/vs/editor/contrib/find/findController.ts @@ -12,19 +12,20 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, EditorCommand, ServicesAccessor, registerEditorAction, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { CONTEXT_FIND_INPUT_FOCUSED, CONTEXT_FIND_WIDGET_VISIBLE, FIND_IDS, FindModelBoundToEditorModel, ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleSearchScopeKeybinding, ToggleWholeWordKeybinding } from 'vs/editor/contrib/find/findModel'; +import { CONTEXT_FIND_INPUT_FOCUSED, CONTEXT_FIND_WIDGET_VISIBLE, FIND_IDS, FindModelBoundToEditorModel, ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleSearchScopeKeybinding, ToggleWholeWordKeybinding, CONTEXT_REPLACE_INPUT_FOCUSED } from 'vs/editor/contrib/find/findModel'; import { FindOptionsWidget } from 'vs/editor/contrib/find/findOptionsWidget'; import { FindReplaceState, FindReplaceStateChangedEvent, INewFindReplaceState } from 'vs/editor/contrib/find/findState'; import { FindWidget, IFindController } from 'vs/editor/contrib/find/findWidget'; import { MenuId } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { optional } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { INotificationService } from 'vs/platform/notification/common/notification'; const SEARCH_STRING_MAX_LENGTH = 524288; @@ -75,7 +76,7 @@ export class CommonFindController extends Disposable implements editorCommon.IEd protected _state: FindReplaceState; protected _updateHistoryDelayer: Delayer; private _model: FindModelBoundToEditorModel | null; - private readonly _storageService: IStorageService; + protected readonly _storageService: IStorageService; private readonly _clipboardService: IClipboardService; protected readonly _contextKeyService: IContextKeyService; @@ -383,10 +384,11 @@ export class FindController extends CommonFindController implements IFindControl @IContextKeyService _contextKeyService: IContextKeyService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @IThemeService private readonly _themeService: IThemeService, - @IStorageService storageService: IStorageService, - @optional(IClipboardService) clipboardService: IClipboardService + @INotificationService private readonly _notificationService: INotificationService, + @IStorageService _storageService: IStorageService, + @optional(IClipboardService) clipboardService: IClipboardService, ) { - super(editor, _contextKeyService, storageService, clipboardService); + super(editor, _contextKeyService, _storageService, clipboardService); this._widget = null; this._findOptionsWidget = null; } @@ -422,7 +424,7 @@ export class FindController extends CommonFindController implements IFindControl } private _createFindWidget() { - this._widget = this._register(new FindWidget(this._editor, this, this._state, this._contextViewService, this._keybindingService, this._contextKeyService, this._themeService)); + this._widget = this._register(new FindWidget(this._editor, this, this._state, this._contextViewService, this._keybindingService, this._contextKeyService, this._themeService, this._storageService, this._notificationService)); this._findOptionsWidget = this._register(new FindOptionsWidget(this._editor, this._state, this._keybindingService, this._themeService)); } } @@ -540,6 +542,27 @@ export class NextMatchFindAction extends MatchFindAction { } } +export class NextMatchFindAction2 extends MatchFindAction { + + constructor() { + super({ + id: FIND_IDS.NextMatchFindAction, + label: nls.localize('findNextMatchAction', "Find Next"), + alias: 'Find Next', + precondition: undefined, + kbOpts: { + kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_FIND_INPUT_FOCUSED), + primary: KeyCode.Enter, + weight: KeybindingWeight.EditorContrib + } + }); + } + + protected _run(controller: CommonFindController): boolean { + return controller.moveToNextMatch(); + } +} + export class PreviousMatchFindAction extends MatchFindAction { constructor() { @@ -562,6 +585,27 @@ export class PreviousMatchFindAction extends MatchFindAction { } } +export class PreviousMatchFindAction2 extends MatchFindAction { + + constructor() { + super({ + id: FIND_IDS.PreviousMatchFindAction, + label: nls.localize('findPreviousMatchAction', "Find Previous"), + alias: 'Find Previous', + precondition: undefined, + kbOpts: { + kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_FIND_INPUT_FOCUSED), + primary: KeyMod.Shift | KeyCode.Enter, + weight: KeybindingWeight.EditorContrib + } + }); + } + + protected _run(controller: CommonFindController): boolean { + return controller.moveToPrevMatch(); + } +} + export abstract class SelectionMatchFindAction extends EditorAction { public run(accessor: ServicesAccessor | null, editor: ICodeEditor): void { let controller = CommonFindController.get(editor); @@ -695,7 +739,9 @@ registerEditorContribution(FindController); registerEditorAction(StartFindAction); registerEditorAction(StartFindWithSelectionAction); registerEditorAction(NextMatchFindAction); +registerEditorAction(NextMatchFindAction2); registerEditorAction(PreviousMatchFindAction); +registerEditorAction(PreviousMatchFindAction2); registerEditorAction(NextSelectionMatchFindAction); registerEditorAction(PreviousSelectionMatchFindAction); registerEditorAction(StartFindReplaceAction); @@ -781,6 +827,17 @@ registerEditorCommand(new FindCommand({ } })); +registerEditorCommand(new FindCommand({ + id: FIND_IDS.ReplaceOneAction, + precondition: CONTEXT_FIND_WIDGET_VISIBLE, + handler: x => x.replace(), + kbOpts: { + weight: KeybindingWeight.EditorContrib + 5, + kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_REPLACE_INPUT_FOCUSED), + primary: KeyCode.Enter + } +})); + registerEditorCommand(new FindCommand({ id: FIND_IDS.ReplaceAllAction, precondition: CONTEXT_FIND_WIDGET_VISIBLE, @@ -792,6 +849,20 @@ registerEditorCommand(new FindCommand({ } })); +registerEditorCommand(new FindCommand({ + id: FIND_IDS.ReplaceAllAction, + precondition: CONTEXT_FIND_WIDGET_VISIBLE, + handler: x => x.replaceAll(), + kbOpts: { + weight: KeybindingWeight.EditorContrib + 5, + kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_REPLACE_INPUT_FOCUSED), + primary: undefined, + mac: { + primary: KeyMod.CtrlCmd | KeyCode.Enter, + } + } +})); + registerEditorCommand(new FindCommand({ id: FIND_IDS.SelectAllMatchesAction, precondition: CONTEXT_FIND_WIDGET_VISIBLE, diff --git a/src/vs/editor/contrib/find/findWidget.css b/src/vs/editor/contrib/find/findWidget.css index 7e1140442ea..d6c6f602e48 100644 --- a/src/vs/editor/contrib/find/findWidget.css +++ b/src/vs/editor/contrib/find/findWidget.css @@ -32,8 +32,8 @@ .monaco-editor .find-widget { position: absolute; z-index: 10; - top: -44px; /* find input height + shadow (10px) */ - height: 34px; /* find input height */ + top: -44px; + height: 33px; overflow: hidden; line-height: 19px; transition: top 200ms linear; @@ -47,12 +47,10 @@ /* Find widget when replace is toggled on */ .monaco-editor .find-widget.replaceToggled { top: -74px; /* find input height + replace input height + shadow (10px) */ - height: 64px; /* find input height + replace input height */ } .monaco-editor .find-widget.replaceToggled > .replace-part { display: flex; display: -webkit-flex; - align-items: center; } .monaco-editor .find-widget.visible, @@ -60,13 +58,26 @@ top: 0; } +/* Multiple line find widget */ + +.monaco-editor .find-widget.multipleline { + top: unset; + bottom: 10px; +} + +.monaco-editor .find-widget.multipleline.visible, +.monaco-editor .find-widget.multipleline.replaceToggled.visible { + top: 0px; + bottom: unset; +} + .monaco-editor .find-widget .monaco-inputbox .input { background-color: transparent; /* Style to compensate for //winjs */ min-height: 0; } -.monaco-editor .find-widget .replace-input .input { +.monaco-editor .find-widget .monaco-findInput .input { font-size: 13px; } @@ -76,29 +87,38 @@ font-size: 12px; display: flex; display: -webkit-flex; - align-items: center; } .monaco-editor .find-widget > .find-part .monaco-inputbox, .monaco-editor .find-widget > .replace-part .monaco-inputbox { - height: 25px; + min-height: 25px; } -.monaco-editor .find-widget > .find-part .monaco-inputbox > .wrapper > .input { - width: 100% !important; - padding-right: 66px; -} -.monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .input { +.monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .mirror { padding-right: 22px; } .monaco-editor .find-widget > .find-part .monaco-inputbox > .wrapper > .input, -.monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .input { +.monaco-editor .find-widget > .find-part .monaco-inputbox > .wrapper > .mirror, +.monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .input, +.monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .mirror { padding-top: 2px; padding-bottom: 2px; } +.monaco-editor .find-widget > .find-part .find-actions { + height: 25px; + display: flex; + align-items: center; +} + +.monaco-editor .find-widget > .replace-part .replace-actions { + height: 25px; + display: flex; + align-items: center; +} + .monaco-editor .find-widget .monaco-findInput { vertical-align: middle; display: flex; @@ -106,6 +126,16 @@ flex:1; } +.monaco-editor .find-widget .monaco-findInput .monaco-scrollable-element { + /* Make sure textarea inherits the width correctly */ + width: 100%; +} + +.monaco-editor .find-widget .monaco-findInput .monaco-scrollable-element .scrollbar.vertical { + /* Hide vertical scrollbar */ + opacity: 0; +} + .monaco-editor .find-widget .matchesCount { display: flex; display: -webkit-flex; @@ -237,15 +267,17 @@ display: none; } -.monaco-editor .find-widget > .replace-part > .replace-input { +.monaco-editor .find-widget > .replace-part > .monaco-findInput { position: relative; display: flex; display: -webkit-flex; vertical-align: middle; - width: auto !important; + flex: auto; + flex-grow: 0; + flex-shrink: 0; } -.monaco-editor .find-widget > .replace-part > .replace-input > .controls { +.monaco-editor .find-widget > .replace-part > .monaco-findInput > .controls { position: absolute; top: 3px; right: 2px; diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index a25d3b5b464..2ea8ba03dd7 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -9,11 +9,12 @@ import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; +import { alert as alertFn } from 'vs/base/browser/ui/aria/aria'; import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput'; -import { HistoryInputBox, IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox'; +import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox'; +import { ReplaceInput } from 'vs/base/browser/ui/findinput/replaceInput'; import { IHorizontalSashLayoutProvider, ISashEvent, Orientation, Sash } from 'vs/base/browser/ui/sash/sash'; import { Widget } from 'vs/base/browser/ui/widget'; -import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; import { Delayer } from 'vs/base/common/async'; import { Color } from 'vs/base/common/color'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -30,9 +31,10 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { contrastBorder, editorFindMatch, editorFindMatchBorder, editorFindMatchHighlight, editorFindMatchHighlightBorder, editorFindRangeHighlight, editorFindRangeHighlightBorder, editorWidgetBackground, editorWidgetBorder, editorWidgetResizeBorder, errorForeground, inputActiveOptionBorder, inputActiveOptionBackground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry'; import { ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { ContextScopedFindInput, ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget'; +import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/browser/contextScopedHistoryWidget'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; -import { alert as alertFn } from 'vs/base/browser/ui/aria/aria'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { INotificationService } from 'vs/platform/notification/common/notification'; export interface IFindController { replace(): void; @@ -48,7 +50,6 @@ const NLS_TOGGLE_SELECTION_FIND_TITLE = nls.localize('label.toggleSelectionFind' const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close"); const NLS_REPLACE_INPUT_LABEL = nls.localize('label.replace', "Replace"); const NLS_REPLACE_INPUT_PLACEHOLDER = nls.localize('placeholder.replace', "Replace"); -const NLS_PRESERVE_CASE_LABEL = nls.localize('label.preserveCaseCheckbox', "Preserve Case"); const NLS_REPLACE_BTN_LABEL = nls.localize('label.replaceButton', "Replace"); const NLS_REPLACE_ALL_BTN_LABEL = nls.localize('label.replaceAllButton', "Replace All"); const NLS_TOGGLE_REPLACE_MODE_BTN_LABEL = nls.localize('label.toggleReplaceButton', "Toggle Replace mode"); @@ -59,14 +60,12 @@ const NLS_NO_RESULTS = nls.localize('label.noResults', "No Results"); const FIND_WIDGET_INITIAL_WIDTH = 411; const PART_WIDTH = 275; const FIND_INPUT_AREA_WIDTH = PART_WIDTH - 54; -const REPLACE_INPUT_AREA_WIDTH = FIND_INPUT_AREA_WIDTH; let MAX_MATCHES_COUNT_WIDTH = 69; let FIND_ALL_CONTROLS_WIDTH = 17/** Find Input margin-left */ + (MAX_MATCHES_COUNT_WIDTH + 3 + 1) /** Match Results */ + 23 /** Button */ * 4 + 2/** sash */; -const FIND_INPUT_AREA_HEIGHT = 34; // The height of Find Widget when Replace Input is not visible. -const FIND_REPLACE_AREA_HEIGHT = 64; // The height of Find Widget when Replace Input is visible. - +const FIND_INPUT_AREA_HEIGHT = 33; // The height of Find Widget when Replace Input is not visible. +const ctrlEnterReplaceAllWarningPromptedKey = 'ctrlEnterReplaceAll.windows.donotask'; export class FindWidgetViewZone implements IViewZone { public readonly afterLineNumber: number; @@ -84,6 +83,22 @@ export class FindWidgetViewZone implements IViewZone { } } +function stopPropagationForMultiLineUpwards(event: IKeyboardEvent, value: string, textarea: HTMLTextAreaElement | null) { + const isMultiline = !!value.match(/\n/); + if (textarea && isMultiline && textarea.selectionStart > 0) { + event.stopPropagation(); + return; + } +} + +function stopPropagationForMultiLineDownwards(event: IKeyboardEvent, value: string, textarea: HTMLTextAreaElement | null) { + const isMultiline = !!value.match(/\n/); + if (textarea && isMultiline && textarea.selectionEnd < textarea.value.length) { + event.stopPropagation(); + return; + } +} + export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSashLayoutProvider { private static readonly ID = 'editor.contrib.findWidget'; private readonly _codeEditor: ICodeEditor; @@ -92,10 +107,13 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas private readonly _contextViewProvider: IContextViewProvider; private readonly _keybindingService: IKeybindingService; private readonly _contextKeyService: IContextKeyService; + private readonly _storageService: IStorageService; + private readonly _notificationService: INotificationService; private _domNode!: HTMLElement; + private _cachedHeight: number | null; private _findInput!: FindInput; - private _replaceInputBox!: HistoryInputBox; + private _replaceInput!: ReplaceInput; private _toggleReplaceBtn!: SimpleButton; private _matchesCount!: HTMLElement; @@ -103,13 +121,13 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas private _nextBtn!: SimpleButton; private _toggleSelectionFind!: SimpleCheckbox; private _closeBtn!: SimpleButton; - private _preserveCase!: Checkbox; private _replaceBtn!: SimpleButton; private _replaceAllBtn!: SimpleButton; private _isVisible: boolean; private _isReplaceVisible: boolean; private _ignoreChangeEvent: boolean; + private _ctrlEnterReplaceAllWarningPrompted: boolean; private readonly _findFocusTracker: dom.IFocusTracker; private readonly _findInputFocused: IContextKey; @@ -129,7 +147,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas contextViewProvider: IContextViewProvider, keybindingService: IKeybindingService, contextKeyService: IContextKeyService, - themeService: IThemeService + themeService: IThemeService, + storageService: IStorageService, + notificationService: INotificationService, ) { super(); this._codeEditor = codeEditor; @@ -138,6 +158,10 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._contextViewProvider = contextViewProvider; this._keybindingService = keybindingService; this._contextKeyService = contextKeyService; + this._storageService = storageService; + this._notificationService = notificationService; + + this._ctrlEnterReplaceAllWarningPrompted = !!storageService.getBoolean(ctrlEnterReplaceAllWarningPromptedKey, StorageScope.GLOBAL); this._isVisible = false; this._isReplaceVisible = false; @@ -149,6 +173,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._buildDomNode(); this._updateButtons(); this._tryUpdateWidgetWidth(); + this._findInput.inputBox.layout(); this._register(this._codeEditor.onDidChangeConfiguration((e: IConfigurationChangedEvent) => { if (e.readOnly) { @@ -203,7 +228,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas })); this._replaceInputFocused = CONTEXT_REPLACE_INPUT_FOCUSED.bindTo(contextKeyService); - this._replaceFocusTracker = this._register(dom.trackFocus(this._replaceInputBox.inputElement)); + this._replaceFocusTracker = this._register(dom.trackFocus(this._replaceInput.inputBox.inputElement)); this._register(this._replaceFocusTracker.onDidFocus(() => { this._replaceInputFocused.set(true); this._updateSearchScope(); @@ -264,6 +289,12 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas private _onStateChanged(e: FindReplaceStateChangedEvent): void { if (e.searchString) { + if (this._state.searchString.indexOf('\n') >= 0) { + dom.addClass(this._domNode, 'multipleline'); + } else { + dom.removeClass(this._domNode, 'multipleline'); + } + try { this._ignoreChangeEvent = true; this._findInput.setValue(this._state.searchString); @@ -273,7 +304,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._updateButtons(); } if (e.replaceString) { - this._replaceInputBox.value = this._state.replaceString; + this._replaceInput.inputBox.value = this._state.replaceString; } if (e.isRevealed) { if (this._state.isRevealed) { @@ -286,8 +317,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas if (this._state.isReplaceRevealed) { if (!this._codeEditor.getConfiguration().readOnly && !this._isReplaceVisible) { this._isReplaceVisible = true; - this._replaceInputBox.width = this._findInput.inputBox.width; + this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode); this._updateButtons(); + this._replaceInput.inputBox.layout(); } } else { if (this._isReplaceVisible) { @@ -296,6 +328,12 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } } } + if ((e.isRevealed || e.isReplaceRevealed) && (this._state.isRevealed || this._state.isReplaceRevealed)) { + if (this._tryUpdateHeight()) { + this._showViewZone(); + } + } + if (e.isRegex) { this._findInput.setRegex(this._state.isRegex); } @@ -337,7 +375,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._findInput.inputBox.addToHistory(); } if (this._state.replaceString) { - this._replaceInputBox.addToHistory(); + this._replaceInput.inputBox.addToHistory(); } } @@ -402,7 +440,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas private _updateButtons(): void { this._findInput.setEnabled(this._isVisible); - this._replaceInputBox.setEnabled(this._isVisible && this._isReplaceVisible); + this._replaceInput.setEnabled(this._isVisible && this._isReplaceVisible); this._updateToggleSelectionFindButton(); this._closeBtn.setEnabled(this._isVisible); @@ -512,12 +550,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } this._codeEditor.changeViewZones((accessor) => { - if (this._state.isReplaceRevealed) { - viewZone.heightInPx = FIND_REPLACE_AREA_HEIGHT; - } else { - viewZone.heightInPx = FIND_INPUT_AREA_HEIGHT; - } - + viewZone.heightInPx = this._getHeight(); this._viewZoneId = accessor.addZone(viewZone); // scroll top adjust to make sure the editor doesn't scroll when adding viewzone at the beginning. this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() + viewZone.heightInPx); @@ -525,30 +558,47 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } private _showViewZone(adjustScroll: boolean = true) { - const viewZone = this._viewZone; - if (!this._isVisible || !viewZone) { + if (!this._isVisible) { return; } + const addExtraSpaceOnTop = this._codeEditor.getConfiguration().contribInfo.find.addExtraSpaceOnTop; + + if (!addExtraSpaceOnTop) { + return; + } + + if (this._viewZone === undefined) { + this._viewZone = new FindWidgetViewZone(0); + } + + const viewZone = this._viewZone; + this._codeEditor.changeViewZones((accessor) => { - let scrollAdjustment = FIND_INPUT_AREA_HEIGHT; - if (this._viewZoneId !== undefined) { - if (this._state.isReplaceRevealed) { - viewZone.heightInPx = FIND_REPLACE_AREA_HEIGHT; - scrollAdjustment = FIND_REPLACE_AREA_HEIGHT - FIND_INPUT_AREA_HEIGHT; - } else { - viewZone.heightInPx = FIND_INPUT_AREA_HEIGHT; - scrollAdjustment = FIND_INPUT_AREA_HEIGHT - FIND_REPLACE_AREA_HEIGHT; + // the view zone already exists, we need to update the height + const newHeight = this._getHeight(); + if (newHeight === viewZone.heightInPx) { + return; } - accessor.removeZone(this._viewZoneId); - } else { - viewZone.heightInPx = FIND_INPUT_AREA_HEIGHT; - } - this._viewZoneId = accessor.addZone(viewZone); - if (adjustScroll) { - this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() + scrollAdjustment); + let scrollAdjustment = newHeight - viewZone.heightInPx; + viewZone.heightInPx = newHeight; + accessor.layoutZone(this._viewZoneId); + + if (adjustScroll) { + this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() + scrollAdjustment); + } + + return; + } else { + const scrollAdjustment = this._getHeight(); + viewZone.heightInPx = scrollAdjustment; + this._viewZoneId = accessor.addZone(viewZone); + + if (adjustScroll) { + this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() + scrollAdjustment); + } } }); } @@ -584,8 +634,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas inputValidationErrorBorder: theme.getColor(inputValidationErrorBorder), }; this._findInput.style(inputStyles); - this._replaceInputBox.style(inputStyles); - this._preserveCase.style(inputStyles); + this._replaceInput.style(inputStyles); } private _tryUpdateWidgetWidth() { @@ -615,7 +664,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas if (widgetWidth > FIND_WIDGET_INITIAL_WIDTH) { // as the widget is resized by users, we may need to change the max width of the widget as the editor width changes. this._domNode.style.maxWidth = `${editorWidth - 28 - minimapWidth - 15}px`; - this._replaceInputBox.inputElement.style.width = `${dom.getTotalWidth(this._findInput.inputBox.inputElement)}px`; + this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode); return; } } @@ -639,13 +688,47 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } if (this._resized) { - let findInputWidth = dom.getTotalWidth(this._findInput.inputBox.inputElement); + this._findInput.inputBox.layout(); + let findInputWidth = this._findInput.inputBox.width; if (findInputWidth > 0) { - this._replaceInputBox.inputElement.style.width = `${findInputWidth}px`; + this._replaceInput.width = findInputWidth; } } } + private _getHeight(): number { + let totalheight = 0; + + // find input margin top + totalheight += 4; + + // find input height + totalheight += this._findInput.inputBox.height + 2 /** input box border */; + + if (this._isReplaceVisible) { + // replace input margin + totalheight += 4; + + totalheight += this._replaceInput.inputBox.height + 2 /** input box border */; + } + + // margin bottom + totalheight += 4; + return totalheight; + } + + private _tryUpdateHeight(): boolean { + const totalHeight = this._getHeight(); + if (this._cachedHeight !== null && this._cachedHeight === totalHeight) { + return false; + } + + this._cachedHeight = totalHeight; + this._domNode.style.height = `${totalHeight}px`; + + return true; + } + // ----- Public public focusFindInput(): void { @@ -655,9 +738,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } public focusReplaceInput(): void { - this._replaceInputBox.select(); + this._replaceInput.select(); // Edge browser requires focus() in addition to select() - this._replaceInputBox.focus(); + this._replaceInput.focus(); } public highlightFindOptions(): void { @@ -692,22 +775,25 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } private _onFindInputKeyDown(e: IKeyboardEvent): void { + if (e.equals(KeyMod.WinCtrl | KeyCode.Enter)) { + const inputElement = this._findInput.inputBox.inputElement; + const start = inputElement.selectionStart; + const end = inputElement.selectionEnd; + const content = inputElement.value; - if (e.equals(KeyCode.Enter)) { - this._codeEditor.getAction(FIND_IDS.NextMatchFindAction).run().then(undefined, onUnexpectedError); - e.preventDefault(); - return; - } - - if (e.equals(KeyMod.Shift | KeyCode.Enter)) { - this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction).run().then(undefined, onUnexpectedError); - e.preventDefault(); - return; + if (start && end) { + const value = content.substr(0, start) + '\n' + content.substr(end); + this._findInput.inputBox.value = value; + inputElement.setSelectionRange(start + 1, start + 1); + this._findInput.inputBox.layout(); + e.preventDefault(); + return; + } } if (e.equals(KeyCode.Tab)) { if (this._isReplaceVisible) { - this._replaceInputBox.focus(); + this._replaceInput.focus(); } else { this._findInput.focusOnCaseSensitive(); } @@ -720,20 +806,43 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas e.preventDefault(); return; } + + if (e.equals(KeyCode.UpArrow)) { + return stopPropagationForMultiLineUpwards(e, this._findInput.getValue(), this._findInput.domNode.querySelector('textarea')); + } + + if (e.equals(KeyCode.DownArrow)) { + return stopPropagationForMultiLineDownwards(e, this._findInput.getValue(), this._findInput.domNode.querySelector('textarea')); + } } private _onReplaceInputKeyDown(e: IKeyboardEvent): void { + if (e.equals(KeyMod.WinCtrl | KeyCode.Enter)) { + if (platform.isWindows && platform.isNative && !this._ctrlEnterReplaceAllWarningPrompted) { + // this is the first time when users press Ctrl + Enter to replace all + this._notificationService.info( + nls.localize('ctrlEnter.keybindingChanged', + 'Ctrl+Enter now inserts line break instead of replacing all. You can modify the keybinding for editor.action.replaceAll to override this behavior.') + ); - if (e.equals(KeyCode.Enter)) { - this._controller.replace(); - e.preventDefault(); - return; - } + this._ctrlEnterReplaceAllWarningPrompted = true; + this._storageService.store(ctrlEnterReplaceAllWarningPromptedKey, true, StorageScope.GLOBAL); - if (e.equals(KeyMod.CtrlCmd | KeyCode.Enter)) { - this._controller.replaceAll(); - e.preventDefault(); - return; + } + + const inputElement = this._replaceInput.inputBox.inputElement; + const start = inputElement.selectionStart; + const end = inputElement.selectionEnd; + const content = inputElement.value; + + if (start && end) { + const value = content.substr(0, start) + '\n' + content.substr(end); + this._replaceInput.inputBox.value = value; + inputElement.setSelectionRange(start + 1, start + 1); + this._replaceInput.inputBox.layout(); + e.preventDefault(); + return; + } } if (e.equals(KeyCode.Tab)) { @@ -753,6 +862,14 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas e.preventDefault(); return; } + + if (e.equals(KeyCode.UpArrow)) { + return stopPropagationForMultiLineUpwards(e, this._replaceInput.inputBox.value, this._replaceInput.inputBox.element.querySelector('textarea')); + } + + if (e.equals(KeyCode.DownArrow)) { + return stopPropagationForMultiLineDownwards(e, this._replaceInput.inputBox.value, this._replaceInput.inputBox.element.querySelector('textarea')); + } } // ----- sash @@ -777,6 +894,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } private _buildDomNode(): void { + const flexibleHeight = platform.isNative; + const flexibleWidth = platform.isNative; // Find input this._findInput = this._register(new ContextScopedFindInput(null, this._contextViewProvider, { width: FIND_INPUT_AREA_WIDTH, @@ -796,7 +915,10 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } catch (e) { return { content: e.message }; } - } + }, + flexibleHeight, + flexibleWidth, + flexibleMaxHeight: 118 }, this._contextKeyService, true)); this._findInput.setRegex(!!this._state.isRegex); this._findInput.setCaseSensitive(!!this._state.matchCase); @@ -818,11 +940,24 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._register(this._findInput.onCaseSensitiveKeyDown((e) => { if (e.equals(KeyMod.Shift | KeyCode.Tab)) { if (this._isReplaceVisible) { - this._replaceInputBox.focus(); + this._replaceInput.focus(); e.preventDefault(); } } })); + this._register(this._findInput.onRegexKeyDown((e) => { + if (e.equals(KeyCode.Tab)) { + if (this._isReplaceVisible) { + this._replaceInput.focusOnPreserve(); + e.preventDefault(); + } + } + })); + this._register(this._findInput.inputBox.onDidHeightChange((e) => { + if (this._tryUpdateHeight()) { + this._showViewZone(); + } + })); if (platform.isLinux) { this._register(this._findInput.onMouseDown((e) => this._onFindInputMouseDown(e))); } @@ -852,13 +987,16 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas let findPart = document.createElement('div'); findPart.className = 'find-part'; findPart.appendChild(this._findInput.domNode); - findPart.appendChild(this._matchesCount); - findPart.appendChild(this._prevBtn.domNode); - findPart.appendChild(this._nextBtn.domNode); + const actionsContainer = document.createElement('div'); + actionsContainer.className = 'find-actions'; + findPart.appendChild(actionsContainer); + actionsContainer.appendChild(this._matchesCount); + actionsContainer.appendChild(this._prevBtn.domNode); + actionsContainer.appendChild(this._nextBtn.domNode); // Toggle selection button this._toggleSelectionFind = this._register(new SimpleCheckbox({ - parent: findPart, + parent: actionsContainer, title: NLS_TOGGLE_SELECTION_FIND_TITLE + this._keybindingLabelFor(FIND_IDS.ToggleSearchScopeCommand), onChange: () => { if (this._toggleSelectionFind.checked) { @@ -898,34 +1036,45 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } })); - findPart.appendChild(this._closeBtn.domNode); + actionsContainer.appendChild(this._closeBtn.domNode); // Replace input - let replaceInput = document.createElement('div'); - replaceInput.className = 'replace-input'; - replaceInput.style.width = REPLACE_INPUT_AREA_WIDTH + 'px'; - this._replaceInputBox = this._register(new ContextScopedHistoryInputBox(replaceInput, undefined, { - ariaLabel: NLS_REPLACE_INPUT_LABEL, + this._replaceInput = this._register(new ContextScopedReplaceInput(null, undefined, { + label: NLS_REPLACE_INPUT_LABEL, placeholder: NLS_REPLACE_INPUT_PLACEHOLDER, - history: [] - }, this._contextKeyService)); - - - this._register(dom.addStandardDisposableListener(this._replaceInputBox.inputElement, 'keydown', (e) => this._onReplaceInputKeyDown(e))); - this._register(this._replaceInputBox.onDidChange(() => { - this._state.change({ replaceString: this._replaceInputBox.value }, false); + history: [], + flexibleHeight, + flexibleWidth, + flexibleMaxHeight: 118 + }, this._contextKeyService, true)); + this._replaceInput.setPreserveCase(!!this._state.preserveCase); + this._register(this._replaceInput.onKeyDown((e) => this._onReplaceInputKeyDown(e))); + this._register(this._replaceInput.inputBox.onDidChange(() => { + this._state.change({ replaceString: this._replaceInput.inputBox.value }, false); })); - - this._preserveCase = this._register(new Checkbox({ - actionClassName: 'monaco-preserve-case', - title: NLS_PRESERVE_CASE_LABEL, - isChecked: false, + this._register(this._replaceInput.inputBox.onDidHeightChange((e) => { + if (this._isReplaceVisible && this._tryUpdateHeight()) { + this._showViewZone(); + } })); - this._preserveCase.checked = !!this._state.preserveCase; - this._register(this._preserveCase.onChange(viaKeyboard => { - if (!viaKeyboard) { - this._state.change({ preserveCase: !this._state.preserveCase }, false); - this._replaceInputBox.focus(); + this._register(this._replaceInput.onDidOptionChange(() => { + this._state.change({ + preserveCase: this._replaceInput.getPreserveCase() + }, true); + })); + this._register(this._replaceInput.onPreserveCaseKeyDown((e) => { + if (e.equals(KeyCode.Tab)) { + if (this._prevBtn.isEnabled()) { + this._prevBtn.focus(); + } else if (this._nextBtn.isEnabled()) { + this._nextBtn.focus(); + } else if (this._toggleSelectionFind.isEnabled()) { + this._toggleSelectionFind.focus(); + } else if (this._closeBtn.isEnabled()) { + this._closeBtn.focus(); + } + + e.preventDefault(); } })); @@ -953,17 +1102,16 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } })); - let controls = document.createElement('div'); - controls.className = 'controls'; - controls.style.display = 'block'; - controls.appendChild(this._preserveCase.domNode); - replaceInput.appendChild(controls); - let replacePart = document.createElement('div'); replacePart.className = 'replace-part'; - replacePart.appendChild(replaceInput); - replacePart.appendChild(this._replaceBtn.domNode); - replacePart.appendChild(this._replaceAllBtn.domNode); + replacePart.appendChild(this._replaceInput.domNode); + + const replaceActionsContainer = document.createElement('div'); + replaceActionsContainer.className = 'replace-actions'; + replacePart.appendChild(replaceActionsContainer); + + replaceActionsContainer.appendChild(this._replaceBtn.domNode); + replaceActionsContainer.appendChild(this._replaceAllBtn.domNode); // Toggle replace button this._toggleReplaceBtn = this._register(new SimpleButton({ @@ -972,7 +1120,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas onTrigger: () => { this._state.change({ isReplaceRevealed: !this._isReplaceVisible }, false); if (this._isReplaceVisible) { - this._replaceInputBox.width = this._findInput.inputBox.width; + this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode); + this._replaceInput.inputBox.layout(); } this._showViewZone(); } @@ -1015,9 +1164,13 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas return; } this._domNode.style.width = `${width}px`; + this._findInput.inputBox.width = inputBoxWidth; if (this._isReplaceVisible) { - this._replaceInputBox.width = inputBoxWidth; + this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode); } + + this._findInput.inputBox.layout(); + this._tryUpdateHeight(); })); } @@ -1077,6 +1230,10 @@ class SimpleCheckbox extends Widget { return this._domNode; } + public isEnabled(): boolean { + return (this._domNode.tabIndex >= 0); + } + public get checked(): boolean { return this._checkbox.checked; } @@ -1086,7 +1243,7 @@ class SimpleCheckbox extends Widget { } public focus(): void { - this._checkbox.focus(); + this._domNode.focus(); } private enable(): void { diff --git a/src/vs/editor/contrib/suggest/media/suggest.css b/src/vs/editor/contrib/suggest/media/suggest.css index 1fc61b27488..d01d1b6bb5b 100644 --- a/src/vs/editor/contrib/suggest/media/suggest.css +++ b/src/vs/editor/contrib/suggest/media/suggest.css @@ -97,6 +97,10 @@ font-weight: bold; } +.monaco-editor .suggest-widget-deprecated span { + text-decoration: line-through; +} + /** Icon styles **/ .monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .header > .close, diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 57b6ed5638a..b5731357b0e 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -30,7 +30,7 @@ import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { TimeoutTimer, CancelablePromise, createCancelablePromise, disposableTimeout } from 'vs/base/common/async'; -import { CompletionItemKind, completionKindToCssClass } from 'vs/editor/common/modes'; +import { CompletionItemKind, completionKindToCssClass, CompletionItemKindModifier } from 'vs/editor/common/modes'; import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -38,6 +38,7 @@ import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { FileKind } from 'vs/platform/files/common/files'; import { MarkdownString } from 'vs/base/common/htmlContent'; +import { flatten } from 'vs/base/common/arrays'; const expandSuggestionDocsByDefault = false; @@ -60,7 +61,6 @@ export const editorSuggestWidgetForeground = registerColor('editorSuggestWidget. export const editorSuggestWidgetSelectedBackground = registerColor('editorSuggestWidget.selectedBackground', { dark: listFocusBackground, light: listFocusBackground, hc: listFocusBackground }, nls.localize('editorSuggestWidgetSelectedBackground', 'Background color of the selected entry in the suggest widget.')); export const editorSuggestWidgetHighlightForeground = registerColor('editorSuggestWidget.highlightForeground', { dark: listHighlightForeground, light: listHighlightForeground, hc: listHighlightForeground }, nls.localize('editorSuggestWidgetHighlightForeground', 'Color of the match highlights in the suggest widget.')); - const colorRegExp = /^(#([\da-f]{3}){1,2}|(rgb|hsl)a\(\s*(\d{1,3}%?\s*,\s*){3}(1|0?\.\d+)\)|(rgb|hsl)\(\s*\d{1,3}%?(\s*,\s*\d{1,3}%?){2}\s*\))$/i; function extractColor(item: CompletionItem, out: string[]): boolean { if (item.completion.label.match(colorRegExp)) { @@ -173,18 +173,18 @@ class Renderer implements IListRenderer } else if (suggestion.kind === CompletionItemKind.File && this._themeService.getIconTheme().hasFileIcons) { // special logic for 'file' completion items data.icon.className = 'icon hide'; - labelOptions.extraClasses = ([] as string[]).concat( + labelOptions.extraClasses = flatten([ getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: suggestion.label }), FileKind.FILE), getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: suggestion.detail }), FileKind.FILE) - ); + ]); } else if (suggestion.kind === CompletionItemKind.Folder && this._themeService.getIconTheme().hasFolderIcons) { // special logic for 'folder' completion items data.icon.className = 'icon hide'; - labelOptions.extraClasses = ([] as string[]).concat( + labelOptions.extraClasses = flatten([ getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: suggestion.label }), FileKind.FOLDER), getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: suggestion.detail }), FileKind.FOLDER) - ); + ]); } else { // normal icon data.icon.className = 'icon hide'; @@ -193,6 +193,10 @@ class Renderer implements IListRenderer ]; } + if (suggestion.kindModifier && suggestion.kindModifier & CompletionItemKindModifier.Deprecated) { + labelOptions.extraClasses = (labelOptions.extraClasses || []).concat(['suggest-widget-deprecated']); + } + data.iconLabel.setLabel(suggestion.label, undefined, labelOptions); data.typeLabel.textContent = (suggestion.detail || '').replace(/\n.*$/m, ''); diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index ddba66741d9..8750db8f943 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -38,9 +38,6 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { clearAllFontInfos } from 'vs/editor/browser/config/configuration'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IProductService } from 'vs/platform/product/common/product'; -import { IStorageService } from 'vs/platform/storage/common/storage'; type Omit = Pick>; @@ -54,13 +51,7 @@ function withAllStandaloneServices(domElement: H } if (!services.has(IOpenerService)) { - services.set(IOpenerService, new OpenerService( - services.get(ICodeEditorService), - services.get(ICommandService), - services.get(IStorageService), - services.get(IDialogService), - services.get(IProductService) - )); + services.set(IOpenerService, new OpenerService(services.get(ICodeEditorService), services.get(ICommandService))); } let result = callback(services); diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index e8e9dffbe84..30209af005c 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -562,6 +562,7 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { // enums DocumentHighlightKind: standaloneEnums.DocumentHighlightKind, CompletionItemKind: standaloneEnums.CompletionItemKind, + CompletionItemKindModifier: standaloneEnums.CompletionItemKindModifier, CompletionItemInsertTextRule: standaloneEnums.CompletionItemInsertTextRule, SymbolKind: standaloneEnums.SymbolKind, IndentAction: standaloneEnums.IndentAction, diff --git a/src/vs/editor/test/browser/services/openerService.test.ts b/src/vs/editor/test/browser/services/openerService.test.ts index 3e1ef9e80cb..6824acf4d84 100644 --- a/src/vs/editor/test/browser/services/openerService.test.ts +++ b/src/vs/editor/test/browser/services/openerService.test.ts @@ -7,18 +7,13 @@ import { URI } from 'vs/base/common/uri'; import { OpenerService } from 'vs/editor/browser/services/openerService'; import { TestCodeEditorService } from 'vs/editor/test/browser/editorTestServices'; import { CommandsRegistry, ICommandService, NullCommandService } from 'vs/platform/commands/common/commands'; -import { deepClone } from 'vs/base/common/objects'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IProductService } from 'vs/platform/product/common/product'; -import { IStorageService } from 'vs/platform/storage/common/storage'; suite('OpenerService', function () { - const editorService = new TestCodeEditorService(); - let lastCommand: { id: string, args: any[] } | undefined; + let lastCommand: { id: string; args: any[] } | undefined; - const commandService = new class implements ICommandService { + const commandService = new (class implements ICommandService { _serviceBrand: any; onWillExecuteCommand = () => ({ dispose: () => { } }); onDidExecuteCommand = () => ({ dispose: () => { } }); @@ -26,79 +21,20 @@ suite('OpenerService', function () { lastCommand = { id, args }; return Promise.resolve(undefined); } - }; - - function getStorageService(trustedDomainsSetting: string[]) { - let _settings = deepClone(trustedDomainsSetting); - - return new class implements IStorageService { - get = () => JSON.stringify(_settings); - store = (key: string, val: string) => _settings = JSON.parse(val); - - // Don't care - _serviceBrand: any; - - onDidChangeStorage = () => ({ dispose: () => { } }); - onWillSaveState = () => ({ dispose: () => { } }); - - getBoolean = () => true; - getNumber = () => 0; - remove = () => { }; - logStorage = () => { }; - }; - } - - function getDialogService() { - return new class implements IDialogService { - _showInvoked = 0; - show = () => { - this._showInvoked++; - return Promise.resolve({} as any); - } - get confirmInvoked() { return this._showInvoked; } - - // Don't care - _serviceBrand: any; - confirm = () => { - return Promise.resolve({} as any); - } - }; - } - - function getProductService(): IProductService { - return new class { - nameShort: 'VS Code'; - - _serviceBrand: any; - } as IProductService; - } - + })(); setup(function () { lastCommand = undefined; }); test('delegate to editorService, scheme:///fff', function () { - const openerService = new OpenerService( - editorService, - NullCommandService, - getStorageService([]), - getDialogService(), - getProductService() - ); + const openerService = new OpenerService(editorService, NullCommandService); openerService.open(URI.parse('another:///somepath')); assert.equal(editorService.lastInput!.options!.selection, undefined); }); test('delegate to editorService, scheme:///fff#L123', function () { - - const openerService = new OpenerService( - editorService, - NullCommandService, - getStorageService([]), - getDialogService(), - getProductService() - ); + const openerService = new OpenerService(editorService, NullCommandService); openerService.open(URI.parse('file:///somepath#L23')); assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23); @@ -120,14 +56,7 @@ suite('OpenerService', function () { }); test('delegate to editorService, scheme:///fff#123,123', function () { - - const openerService = new OpenerService( - editorService, - NullCommandService, - getStorageService([]), - getDialogService(), - getProductService() - ); + const openerService = new OpenerService(editorService, NullCommandService); openerService.open(URI.parse('file:///somepath#23')); assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23); @@ -145,14 +74,7 @@ suite('OpenerService', function () { }); test('delegate to commandsService, command:someid', function () { - - const openerService = new OpenerService( - editorService, - commandService, - getStorageService([]), - getDialogService(), - getProductService() - ); + const openerService = new OpenerService(editorService, commandService); const id = `aCommand${Math.random()}`; CommandsRegistry.registerCommand(id, function () { }); @@ -173,69 +95,107 @@ suite('OpenerService', function () { assert.equal(lastCommand!.args[1], true); }); - test('links are protected by dialog.show', function () { - const dialogService = getDialogService(); - const openerService = new OpenerService( - editorService, - commandService, - getStorageService([]), - dialogService, - getProductService() - ); + test('links are protected by validators', async function () { + const openerService = new OpenerService(editorService, commandService); - openerService.open(URI.parse('https://www.microsoft.com')); - assert.equal(dialogService.confirmInvoked, 1); + openerService.registerValidator({ shouldOpen: () => Promise.resolve(false) }); + + const httpResult = await openerService.open(URI.parse('https://www.microsoft.com')); + const httpsResult = await openerService.open(URI.parse('https://www.microsoft.com')); + assert.equal(httpResult, false); + assert.equal(httpsResult, false); }); - test('links on the whitelisted domains can be opened without dialog.show', function () { - const dialogService = getDialogService(); - const openerService = new OpenerService( - editorService, - commandService, - getStorageService(['https://microsoft.com']), - dialogService, - getProductService() - ); + test('links validated by validators go to openers', async function () { + const openerService = new OpenerService(editorService, commandService); - openerService.open(URI.parse('https://microsoft.com')); - openerService.open(URI.parse('https://microsoft.com/')); - openerService.open(URI.parse('https://microsoft.com/en-us/')); - openerService.open(URI.parse('https://microsoft.com/en-us/?foo=bar')); - openerService.open(URI.parse('https://microsoft.com/en-us/?foo=bar#baz')); + openerService.registerValidator({ shouldOpen: () => Promise.resolve(true) }); - assert.equal(dialogService.confirmInvoked, 0); + let openCount = 0; + openerService.registerOpener({ + open: (resource: URI) => { + openCount++; + return Promise.resolve(true); + } + }); + + await openerService.open(URI.parse('http://microsoft.com')); + assert.equal(openCount, 1); + await openerService.open(URI.parse('https://microsoft.com')); + assert.equal(openCount, 2); }); - test('variations of links are protected by dialog confirmation', function () { - const dialogService = getDialogService(); - const openerService = new OpenerService( - editorService, - commandService, - getStorageService(['https://microsoft.com']), - dialogService, - getProductService() - ); + test('links validated by multiple validators', async function () { + const openerService = new OpenerService(editorService, commandService); - openerService.open(URI.parse('http://microsoft.com')); - openerService.open(URI.parse('https://www.microsoft.com')); + let v1 = 0; + openerService.registerValidator({ + shouldOpen: () => { + v1++; + return Promise.resolve(true); + } + }); - assert.equal(dialogService.confirmInvoked, 2); + let v2 = 0; + openerService.registerValidator({ + shouldOpen: () => { + v2++; + return Promise.resolve(true); + } + }); + + let openCount = 0; + openerService.registerOpener({ + open: (resource: URI) => { + openCount++; + return Promise.resolve(true); + } + }); + + await openerService.open(URI.parse('http://microsoft.com')); + assert.equal(openCount, 1); + assert.equal(v1, 1); + assert.equal(v2, 1); + await openerService.open(URI.parse('https://microsoft.com')); + assert.equal(openCount, 2); + assert.equal(v1, 2); + assert.equal(v2, 2); }); - test('* removes all link protection', function () { - const dialogService = getDialogService(); - const openerService = new OpenerService( - editorService, - commandService, - getStorageService(['*']), - dialogService, - getProductService() - ); + test('links invalidated by first validator do not continue validating', async function () { + const openerService = new OpenerService(editorService, commandService); - openerService.open(URI.parse('https://code.visualstudio.com/')); - openerService.open(URI.parse('https://www.microsoft.com')); - openerService.open(URI.parse('https://www.github.com')); + let v1 = 0; + openerService.registerValidator({ + shouldOpen: () => { + v1++; + return Promise.resolve(false); + } + }); - assert.equal(dialogService.confirmInvoked, 0); + let v2 = 0; + openerService.registerValidator({ + shouldOpen: () => { + v2++; + return Promise.resolve(true); + } + }); + + let openCount = 0; + openerService.registerOpener({ + open: (resource: URI) => { + openCount++; + return Promise.resolve(true); + } + }); + + await openerService.open(URI.parse('http://microsoft.com')); + assert.equal(openCount, 0); + assert.equal(v1, 1); + assert.equal(v2, 0); + await openerService.open(URI.parse('https://microsoft.com')); + assert.equal(openCount, 0); + assert.equal(v1, 2); + assert.equal(v2, 0); }); }); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 9845081a9e9..545a3d799c3 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4786,6 +4786,10 @@ declare namespace monaco.languages { Snippet = 25 } + export enum CompletionItemKindModifier { + Deprecated = 1 + } + export enum CompletionItemInsertTextRule { /** * Adjust whitespace/indentation of multiline insert texts to @@ -4814,6 +4818,11 @@ declare namespace monaco.languages { * an icon is chosen by the editor. */ kind: CompletionItemKind; + /** + * A modifier to the `kind` which affect how the item + * is rendered, e.g. Deprecated is rendered with a strikeout + */ + kindModifier?: CompletionItemKindModifier; /** * A human-readable string with additional information * about this item, like type or symbol information. diff --git a/src/vs/platform/browser/contextScopedHistoryWidget.ts b/src/vs/platform/browser/contextScopedHistoryWidget.ts index 5e5e8603683..8625db10ee8 100644 --- a/src/vs/platform/browser/contextScopedHistoryWidget.ts +++ b/src/vs/platform/browser/contextScopedHistoryWidget.ts @@ -10,6 +10,7 @@ import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview import { IHistoryNavigationWidget } from 'vs/base/browser/history'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { ReplaceInput, IReplaceInputOptions } from 'vs/base/browser/ui/findinput/replaceInput'; export const HistoryNavigationWidgetContext = 'historyNavigationWidget'; export const HistoryNavigationEnablementContext = 'historyNavigationEnabled'; @@ -60,6 +61,16 @@ export class ContextScopedFindInput extends FindInput { super(container, contextViewProvider, showFindOptions, options); this._register(createAndBindHistoryNavigationWidgetScopedContextKeyService(contextKeyService, { target: this.inputBox.element, historyNavigator: this.inputBox }).scopedContextKeyService); } +} + +export class ContextScopedReplaceInput extends ReplaceInput { + + constructor(container: HTMLElement | null, contextViewProvider: IContextViewProvider | undefined, options: IReplaceInputOptions, + @IContextKeyService contextKeyService: IContextKeyService, showReplaceOptions: boolean = false + ) { + super(container, contextViewProvider, showReplaceOptions, options); + this._register(createAndBindHistoryNavigationWidgetScopedContextKeyService(contextKeyService, { target: this.inputBox.element, historyNavigator: this.inputBox }).scopedContextKeyService); + } } diff --git a/src/vs/platform/log/common/fileLogService.ts b/src/vs/platform/log/common/fileLogService.ts new file mode 100644 index 00000000000..01859d4f7b2 --- /dev/null +++ b/src/vs/platform/log/common/fileLogService.ts @@ -0,0 +1,134 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ILogService, LogLevel, AbstractLogService } from 'vs/platform/log/common/log'; +import { URI } from 'vs/base/common/uri'; +import { IFileService } from 'vs/platform/files/common/files'; +import { Queue } from 'vs/base/common/async'; +import { VSBuffer } from 'vs/base/common/buffer'; + +export class FileLogService extends AbstractLogService implements ILogService { + + _serviceBrand: any; + + private readonly queue: Queue; + + constructor( + private readonly name: string, + private readonly resource: URI, + level: LogLevel, + @IFileService private readonly fileService: IFileService + ) { + super(); + this.setLevel(level); + this.queue = this._register(new Queue()); + } + + trace(): void { + if (this.getLevel() <= LogLevel.Trace) { + this._log(LogLevel.Trace, this.format(arguments)); + } + } + + debug(): void { + if (this.getLevel() <= LogLevel.Debug) { + this._log(LogLevel.Debug, this.format(arguments)); + } + } + + info(): void { + if (this.getLevel() <= LogLevel.Info) { + this._log(LogLevel.Info, this.format(arguments)); + } + } + + warn(): void { + if (this.getLevel() <= LogLevel.Warning) { + this._log(LogLevel.Warning, this.format(arguments)); + } + } + + error(): void { + if (this.getLevel() <= LogLevel.Error) { + const arg = arguments[0]; + + if (arg instanceof Error) { + const array = Array.prototype.slice.call(arguments) as any[]; + array[0] = arg.stack; + this._log(LogLevel.Error, this.format(array)); + } else { + this._log(LogLevel.Error, this.format(arguments)); + } + } + } + + critical(): void { + if (this.getLevel() <= LogLevel.Critical) { + this._log(LogLevel.Critical, this.format(arguments)); + } + } + + flush(): Promise { + return this.queue.queue(() => Promise.resolve()); + } + + log(level: LogLevel, args: any[]): void { + this._log(level, this.format(args)); + } + + private _log(level: LogLevel, message: string): void { + this.queue.queue(async () => { + let content = await this.loadContent(); + content += `[${this.getCurrentTimestamp()}] [${this.name}] [${this.stringifyLogLevel(level)}] ${message}\n`; + await this.fileService.writeFile(this.resource, VSBuffer.fromString(content)); + }); + } + + private getCurrentTimestamp(): string { + const toTwoDigits = (v: number) => v < 10 ? `0${v}` : v; + const toThreeDigits = (v: number) => v < 10 ? `00${v}` : v < 100 ? `0${v}` : v; + const currentTime = new Date(); + return `${currentTime.getFullYear()}-${toTwoDigits(currentTime.getMonth() + 1)}-${toTwoDigits(currentTime.getDate())} ${toTwoDigits(currentTime.getHours())}:${toTwoDigits(currentTime.getMinutes())}:${toTwoDigits(currentTime.getSeconds())}.${toThreeDigits(currentTime.getMilliseconds())}`; + } + + private async loadContent(): Promise { + try { + const content = await this.fileService.readFile(this.resource); + return content.value.toString(); + } catch (e) { + return ''; + } + } + + private stringifyLogLevel(level: LogLevel): string { + switch (level) { + case LogLevel.Critical: return 'critical'; + case LogLevel.Debug: return 'debug'; + case LogLevel.Error: return 'error'; + case LogLevel.Info: return 'info'; + case LogLevel.Trace: return 'trace'; + case LogLevel.Warning: return 'warning'; + } + return ''; + } + + private format(args: any): string { + let result = ''; + + for (let i = 0; i < args.length; i++) { + let a = args[i]; + + if (typeof a === 'object') { + try { + a = JSON.stringify(a); + } catch (e) { } + } + + result += (i > 0 ? ' ' : '') + a; + } + + return result; + } +} diff --git a/src/vs/platform/opener/common/opener.ts b/src/vs/platform/opener/common/opener.ts index 17cea7f5b3a..8c92cb2d1bd 100644 --- a/src/vs/platform/opener/common/opener.ts +++ b/src/vs/platform/opener/common/opener.ts @@ -14,6 +14,10 @@ export interface IOpener { open(resource: URI, options?: { openExternal?: boolean }): Promise; } +export interface IValidator { + shouldOpen(resource: URI): Promise; +} + export interface IOpenerService { _serviceBrand: any; @@ -23,6 +27,12 @@ export interface IOpenerService { */ registerOpener(opener: IOpener): IDisposable; + /** + * Register a participant that can validate if the URI resource be opened. + * validators are run before openers. + */ + registerValidator(validator: IValidator): IDisposable; + /** * Opens a resource, like a webaddress, a document uri, or executes command. * @@ -36,5 +46,6 @@ export interface IOpenerService { export const NullOpenerService: IOpenerService = Object.freeze({ _serviceBrand: undefined, registerOpener() { return { dispose() { } }; }, + registerValidator() { return { dispose() { } }; }, open() { return Promise.resolve(false); }, }); diff --git a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts index 5db4ac5683e..3f8958b7235 100644 --- a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts +++ b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ResolvedAuthority, IRemoteAuthorityResolverService, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { RemoteAuthorities } from 'vs/base/common/network'; export class RemoteAuthorityResolverService implements IRemoteAuthorityResolverService { @@ -15,13 +16,14 @@ export class RemoteAuthorityResolverService implements IRemoteAuthorityResolverS resolveAuthority(authority: string): Promise { if (authority.indexOf(':') >= 0) { const pieces = authority.split(':'); - return Promise.resolve({ - authority: { authority, host: pieces[0], port: parseInt(pieces[1], 10) } - }); + return Promise.resolve(this._createResolvedAuthority(authority, pieces[0], parseInt(pieces[1], 10))); } - return Promise.resolve({ - authority: { authority, host: authority, port: 80 } - }); + return Promise.resolve(this._createResolvedAuthority(authority, authority, 80)); + } + + private _createResolvedAuthority(authority: string, host: string, port: number): ResolverResult { + RemoteAuthorities.set(authority, host, port); + return { authority: { authority, host, port } }; } clearResolvedAuthority(authority: string): void { diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 9dcf41062f4..1628e28fb27 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1141,4 +1141,15 @@ declare module 'vscode' { } //#endregion + + //#region Deprecated support + + export interface CompletionItem { + /** + * Indicates if this item is deprecated. + */ + deprecated?: boolean; + } + + //#endregion } diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 5cc6f7fa4c9..769df9337d9 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -330,6 +330,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha return { label: data.a, kind: data.b, + kindModifier: data.n ? modes.CompletionItemKindModifier.Deprecated : undefined, detail: data.c, documentation: data.d, sortText: data.e, @@ -365,7 +366,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha }; if (supportsResolveDetails) { provider.resolveCompletionItem = (model, position, suggestion, token) => { - return this._proxy.$resolveCompletionItem(handle, model.uri, position, suggestion._id, token).then(result => { + return this._proxy.$resolveCompletionItem(handle, model.uri, position, suggestion._id!, token).then(result => { if (!result) { return suggestion; } diff --git a/src/vs/workbench/api/browser/mainThreadLogService.ts b/src/vs/workbench/api/browser/mainThreadLogService.ts index b19ef2e78b1..f8eef4dd91c 100644 --- a/src/vs/workbench/api/browser/mainThreadLogService.ts +++ b/src/vs/workbench/api/browser/mainThreadLogService.ts @@ -3,20 +3,46 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { ILogService } from 'vs/platform/log/common/log'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IExtHostContext, ExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; +import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { ILogService, LogLevel } from 'vs/platform/log/common/log'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { IExtHostContext, ExtHostContext, MainThreadLogShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { UriComponents, URI } from 'vs/base/common/uri'; +import { FileLogService } from 'vs/platform/log/common/fileLogService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { basename } from 'vs/base/common/path'; -@extHostCustomer -export class MainThreadLogService extends Disposable { +@extHostNamedCustomer(MainContext.MainThreadLog) +export class MainThreadLogService implements MainThreadLogShape { + + private readonly _loggers = new Map(); + private readonly _logListener: IDisposable; constructor( extHostContext: IExtHostContext, - @ILogService logService: ILogService, + @ILogService private readonly _logService: ILogService, + @IInstantiationService private readonly _instaService: IInstantiationService, ) { - super(); - this._register(logService.onDidChangeLogLevel(level => extHostContext.getProxy(ExtHostContext.ExtHostLogService).$setLevel(level))); + const proxy = extHostContext.getProxy(ExtHostContext.ExtHostLogService); + this._logListener = _logService.onDidChangeLogLevel(level => { + proxy.$setLevel(level); + this._loggers.forEach(value => value.setLevel(level)); + }); } -} \ No newline at end of file + dispose(): void { + this._logListener.dispose(); + this._loggers.forEach(value => value.dispose()); + this._loggers.clear(); + } + + $log(file: UriComponents, level: LogLevel, message: any[]): void { + const uri = URI.revive(file); + let logger = this._loggers.get(uri.toString()); + if (!logger) { + logger = this._instaService.createInstance(FileLogService, basename(file.path), URI.revive(file), this._logService.getLevel()); + this._loggers.set(uri.toString(), logger); + } + logger.log(level, message); + } +} diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b10008dbe56..f7f61cf6255 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -934,6 +934,7 @@ export interface ISuggestDataDto { k/* commitCharacters */?: string[]; l/* additionalTextEdits */?: ISingleEditOperation[]; m/* command */?: modes.Command; + n/* deprecated */?: boolean; // not-standard x?: ChainedCacheId; } @@ -1283,6 +1284,10 @@ export interface ExtHostLogServiceShape { $setLevel(level: LogLevel): void; } +export interface MainThreadLogShape { + $log(file: UriComponents, level: LogLevel, args: any[]): void; +} + export interface ExtHostOutputServiceShape { $setVisibleChannel(channelId: string | null): void; } @@ -1325,6 +1330,7 @@ export const MainContext = { MainThreadKeytar: createMainId('MainThreadKeytar'), MainThreadLanguageFeatures: createMainId('MainThreadLanguageFeatures'), MainThreadLanguages: createMainId('MainThreadLanguages'), + MainThreadLog: createMainId('MainThread'), MainThreadMessageService: createMainId('MainThreadMessageService'), MainThreadOutputService: createMainId('MainThreadOutputService'), MainThreadProgress: createMainId('MainThreadProgress'), diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 55c0234ee56..0f0afd3b587 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -736,6 +736,7 @@ class SuggestAdapter { k: item.commitCharacters, l: item.additionalTextEdits && item.additionalTextEdits.map(typeConvert.TextEdit.from), m: this._commands.toInternal(item.command, disposables), + n: item.deprecated }; // 'insertText'-logic diff --git a/src/vs/workbench/api/worker/extHostExtensionService.ts b/src/vs/workbench/api/worker/extHostExtensionService.ts index a40ce65b7bf..4fcb6db76f8 100644 --- a/src/vs/workbench/api/worker/extHostExtensionService.ts +++ b/src/vs/workbench/api/worker/extHostExtensionService.ts @@ -8,7 +8,6 @@ import { ExtensionActivationTimesBuilder } from 'vs/workbench/api/common/extHost import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; import { endsWith, startsWith } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; -import { Schemas } from 'vs/base/common/network'; import { joinPath } from 'vs/base/common/resources'; import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor'; @@ -128,7 +127,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { const next = joinPath(parent, '..', ensureSuffix(mod, '.js')); moduleStack.push(next); const trap = ExportsTrap.Instance.add(next.toString()); - importScripts(asDomUri(next).toString(true)); + importScripts(next.toString(true)); moduleStack.pop(); return trap.claim(); @@ -139,7 +138,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { module = module.with({ path: ensureSuffix(module.path, '.js') }); moduleStack.push(module); const trap = ExportsTrap.Instance.add(module.toString()); - importScripts(asDomUri(module).toString(true)); + importScripts(module.toString(true)); moduleStack.pop(); return Promise.resolve(trap.claim()); @@ -153,16 +152,6 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { } } -// todo@joh this is a copy of `dom.ts#asDomUri` -function asDomUri(uri: URI): URI { - if (Schemas.vscodeRemote === uri.scheme) { - // rewrite vscode-remote-uris to uris of the window location - // so that they can be intercepted by the service worker - return URI.parse(window.location.href).with({ path: '/vscode-remote', query: JSON.stringify(uri) }); - } - return uri; -} - function ensureSuffix(path: string, suffix: string): string { return endsWith(path, suffix) ? path : path + suffix; } diff --git a/src/vs/workbench/api/worker/extHostLogService.ts b/src/vs/workbench/api/worker/extHostLogService.ts index 79211c61945..d29f41ec939 100644 --- a/src/vs/workbench/api/worker/extHostLogService.ts +++ b/src/vs/workbench/api/worker/extHostLogService.ts @@ -4,24 +4,33 @@ *--------------------------------------------------------------------------------------------*/ import { ILogService, LogLevel, AbstractLogService } from 'vs/platform/log/common/log'; -import { ExtHostLogServiceShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostLogServiceShape, MainThreadLogShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { IExtHostOutputService } from 'vs/workbench/api/common/extHostOutput'; -import * as vscode from 'vscode'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { joinPath } from 'vs/base/common/resources'; +import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions'; +import { UriComponents } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; export class ExtHostLogService extends AbstractLogService implements ILogService, ExtHostLogServiceShape { _serviceBrand: any; - private readonly _logChannel: vscode.OutputChannel; + private readonly _proxy: MainThreadLogShape; + private readonly _logFile: UriComponents; constructor( + @IExtHostRpcService rpc: IExtHostRpcService, @IExtHostInitDataService initData: IExtHostInitDataService, @IExtHostOutputService extHostOutputService: IExtHostOutputService ) { super(); + const logFile = joinPath(initData.logsLocation, `${ExtensionHostLogFileName}.log`); + this._proxy = rpc.getProxy(MainContext.MainThreadLog); + this._logFile = logFile.toJSON(); this.setLevel(initData.logLevel); - this._logChannel = extHostOutputService.createOutputChannel('Log (Worker Extension Host)'); + extHostOutputService.createOutputChannelFromLogFile(localize('name', "Worker Extension Host"), logFile); } $setLevel(level: LogLevel): void { @@ -30,55 +39,37 @@ export class ExtHostLogService extends AbstractLogService implements ILogService trace(_message: string, ..._args: any[]): void { if (this.getLevel() <= LogLevel.Trace) { - this._logChannel.appendLine(this._format(arguments)); + this._proxy.$log(this._logFile, LogLevel.Trace, Array.from(arguments)); } } debug(_message: string, ..._args: any[]): void { if (this.getLevel() <= LogLevel.Debug) { - this._logChannel.appendLine(this._format(arguments)); + this._proxy.$log(this._logFile, LogLevel.Debug, Array.from(arguments)); } } info(_message: string, ..._args: any[]): void { if (this.getLevel() <= LogLevel.Info) { - this._logChannel.appendLine(this._format(arguments)); + this._proxy.$log(this._logFile, LogLevel.Info, Array.from(arguments)); } } warn(_message: string, ..._args: any[]): void { if (this.getLevel() <= LogLevel.Warning) { - this._logChannel.appendLine(this._format(arguments)); + this._proxy.$log(this._logFile, LogLevel.Warning, Array.from(arguments)); } } error(_message: string | Error, ..._args: any[]): void { if (this.getLevel() <= LogLevel.Error) { - this._logChannel.appendLine(this._format(arguments)); + this._proxy.$log(this._logFile, LogLevel.Error, Array.from(arguments)); } } critical(_message: string | Error, ..._args: any[]): void { if (this.getLevel() <= LogLevel.Critical) { - this._logChannel.appendLine(String(arguments)); + this._proxy.$log(this._logFile, LogLevel.Critical, Array.from(arguments)); } } - - private _format(args: any): string { - let result = ''; - - for (let i = 0; i < args.length; i++) { - let a = args[i]; - - if (typeof a === 'object') { - try { - a = JSON.stringify(a); - } catch (e) { } - } - - result += (i > 0 ? ' ' : '') + a; - } - - return result; - } } diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index dcb44e8c137..3ea46019652 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -8,7 +8,6 @@ import { domContentLoaded, addDisposableListener, EventType, addClass } from 'vs import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ILogService } from 'vs/platform/log/common/log'; import { Disposable } from 'vs/base/common/lifecycle'; -import { SimpleLogService } from 'vs/workbench/browser/web.simpleservices'; import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { Workbench } from 'vs/workbench/browser/workbench'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; @@ -42,6 +41,9 @@ import { getThemeTypeSelector, DARK, HIGH_CONTRAST, LIGHT } from 'vs/platform/th import { InMemoryUserDataProvider } from 'vs/workbench/services/userData/common/inMemoryUserDataProvider'; import { registerWindowDriver } from 'vs/platform/driver/browser/driver'; import { StaticExtensionsService, IStaticExtensionsService } from 'vs/workbench/services/extensions/common/staticExtensions'; +import { BufferLogService } from 'vs/platform/log/common/bufferLog'; +import { INMEMORY_LOG_SCHEME, InMemoryLogProvider } from 'vs/workbench/services/log/common/inMemoryLogProvider'; +import { FileLogService } from 'vs/platform/log/common/fileLogService'; class CodeRendererMain extends Disposable { @@ -117,13 +119,14 @@ class CodeRendererMain extends Disposable { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // Log - const logService = new SimpleLogService(); + const logFile = URI.file(`window.log`).with({ scheme: INMEMORY_LOG_SCHEME }); + const logService = new BufferLogService(); serviceCollection.set(ILogService, logService); const payload = await this.resolveWorkspaceInitializationPayload(); // Environment - const environmentService = new BrowserWorkbenchEnvironmentService(payload.id, this.configuration); + const environmentService = new BrowserWorkbenchEnvironmentService({ workspaceId: payload.id, logFile, ...this.configuration }); serviceCollection.set(IWorkbenchEnvironmentService, environmentService); // Product @@ -146,6 +149,10 @@ class CodeRendererMain extends Disposable { const fileService = this._register(new FileService(logService)); serviceCollection.set(IFileService, fileService); + // InMemory Log + fileService.registerProvider(INMEMORY_LOG_SCHEME, new InMemoryLogProvider()); + logService.logger = new FileLogService('window', logFile, logService.getLevel(), fileService); + // Static Extensions const staticExtensions = new StaticExtensionsService(this.configuration.staticExtensions || []); serviceCollection.set(IStaticExtensionsService, staticExtensions); diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index ce24f3df7bf..73f112884c8 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -8,10 +8,9 @@ import * as browser from 'vs/base/browser/browser'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IExtensionTipsService, ExtensionRecommendationReason, IExtensionRecommendation } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IURLHandler, IURLService } from 'vs/platform/url/common/url'; -import { ConsoleLogService, ILogService } from 'vs/platform/log/common/log'; +import { ILogService } from 'vs/platform/log/common/log'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IUpdateService, State } from 'vs/platform/update/common/update'; @@ -31,7 +30,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { toStoreData, restoreRecentlyOpened } from 'vs/platform/history/common/historyStorage'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IProductService } from 'vs/platform/product/common/product'; import Severity from 'vs/base/common/severity'; @@ -40,45 +38,6 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService // tslint:disable-next-line: import-patterns import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/common/workspaceStats'; -//#region Extension Tips - -export class SimpleExtensionTipsService implements IExtensionTipsService { - _serviceBrand: any; - - onRecommendationChange = Event.None; - - getAllRecommendationsWithReason(): { [id: string]: { reasonId: ExtensionRecommendationReason; reasonText: string; }; } { - return Object.create(null); - } - - getFileBasedRecommendations(): IExtensionRecommendation[] { - return []; - } - - getOtherRecommendations(): Promise { - return Promise.resolve([]); - } - - getWorkspaceRecommendations(): Promise { - return Promise.resolve([]); - } - - getKeymapRecommendations(): IExtensionRecommendation[] { - return []; - } - - toggleIgnoredRecommendation(extensionId: string, shouldIgnore: boolean): void { - } - - getAllIgnoredRecommendations(): { global: string[]; workspace: string[]; } { - return { global: [], workspace: [] }; - } -} - -registerSingleton(IExtensionTipsService, SimpleExtensionTipsService, true); - -//#endregion - //#region Extension URL Handler export const IExtensionUrlHandler = createDecorator('inactiveExtensionUrlHandler'); @@ -102,12 +61,6 @@ registerSingleton(IExtensionUrlHandler, SimpleExtensionURLHandler, true); //#endregion -//#region Log - -export class SimpleLogService extends ConsoleLogService { } - -//#endregion - //#region Update export class SimpleUpdateService implements IUpdateService { @@ -182,7 +135,6 @@ export class SimpleWindowService extends Disposable implements IWindowService { @IStorageService private readonly storageService: IStorageService, @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService, @ILogService private readonly logService: ILogService, - @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService ) { super(); @@ -378,7 +330,7 @@ export class SimpleWindowService extends Disposable implements IWindowService { for (let i = 0; i < _uris.length; i++) { const uri = _uris[i]; if ('folderUri' in uri) { - const newAddress = `${document.location.origin}/?folder=${uri.folderUri.path}${this.workbenchEnvironmentService.configuration.connectionToken ? `&tkn=${this.workbenchEnvironmentService.configuration.connectionToken}` : ''}`; + const newAddress = `${document.location.origin}/?folder=${uri.folderUri.path}`; if (openFolderInNewWindow) { window.open(newAddress); } else { @@ -465,7 +417,6 @@ export class SimpleWindowsService implements IWindowsService { readonly onRecentlyOpenedChange: Event = Event.None; constructor( - @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, @IDialogService private readonly dialogService: IDialogService, @IProductService private readonly productService: IProductService, @IClipboardService private readonly clipboardService: IClipboardService @@ -657,11 +608,6 @@ export class SimpleWindowsService implements IWindowsService { addQueryParameter('ibe', ibe); } - // add connection token - if (this.workbenchEnvironmentService.configuration.connectionToken) { - addQueryParameter('tkn', this.workbenchEnvironmentService.configuration.connectionToken); - } - window.open(newAddress); return Promise.resolve(); diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts index 8ba218619a2..f036bfb8c58 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts @@ -9,7 +9,7 @@ import * as dom from 'vs/base/browser/dom'; import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput'; import { Widget } from 'vs/base/browser/ui/widget'; import { Delayer } from 'vs/base/common/async'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { KeyCode } from 'vs/base/common/keyCodes'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox'; import { SimpleButton } from 'vs/editor/contrib/find/findWidget'; @@ -42,8 +42,7 @@ export abstract class SimpleFindWidget extends Widget { @IContextViewService private readonly _contextViewService: IContextViewService, @IContextKeyService contextKeyService: IContextKeyService, private readonly _state: FindReplaceState = new FindReplaceState(), - showOptionButtons?: boolean, - private readonly _invertDefaultDirection: boolean = false + showOptionButtons?: boolean ) { super(); @@ -94,20 +93,6 @@ export abstract class SimpleFindWidget extends Widget { this.findFirst(); })); - this._register(this._findInput.onKeyDown((e) => { - if (e.equals(KeyCode.Enter)) { - this.find(this._invertDefaultDirection); - e.preventDefault(); - return; - } - - if (e.equals(KeyMod.Shift | KeyCode.Enter)) { - this.find(!this._invertDefaultDirection); - e.preventDefault(); - return; - } - })); - this.prevBtn = this._register(new SimpleButton({ label: NLS_PREVIOUS_MATCH_BTN_LABEL, className: 'previous', diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 4786ae44870..c89c4b83247 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -593,13 +593,13 @@ class CallStackDataSource implements IAsyncDataSource { + async getChildren(element: IDebugModel | CallStackItem): Promise { if (isDebugModel(element)) { const sessions = element.getSessions(); if (sessions.length === 0) { return Promise.resolve([]); } - if (sessions.length > 1 || this.debugService.getViewModel().isMultiSessionView()) { + if (sessions.length > 1) { return Promise.resolve(sessions.filter(s => !s.parentSession)); } @@ -609,9 +609,10 @@ class CallStackDataSource implements IAsyncDataSource s.parentSession === element); const threads: CallStackItem[] = element.getAllThreads(); - if (threads.length === 1 && childSessions.length === 0) { + if (threads.length === 1) { // Do not show thread when there is only one to be compact. - return this.getThreadChildren(threads[0]); + const children = await this.getThreadChildren(threads[0]); + return children.concat(childSessions); } return Promise.resolve(threads.concat(childSessions)); diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index f06919be1d9..b7f66a4a69b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -245,6 +245,9 @@ export class DebugHoverWidget implements IContentWidget { this.layoutTreeAndContainer(); this.editor.layoutContentWidget(this); this.scrollbar.scanDomNode(); + this.tree.scrollTop = 0; + this.tree.scrollLeft = 0; + if (focus) { this.editor.render(); this.tree.domFocus(); diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 7a0b0fe82c4..86c6bb089b6 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -479,11 +479,11 @@ export class DebugService implements IDebugService { }); } - private launchOrAttachToSession(session: IDebugSession, focus = true): Promise { + private launchOrAttachToSession(session: IDebugSession, forceFocus = false): Promise { const dbgr = this.configurationManager.getDebugger(session.configuration.type); return session.initialize(dbgr!).then(() => { return session.launchOrAttach(session.configuration).then(() => { - if (focus) { + if (forceFocus || !this.viewModel.focusedSession) { this.focusStackFrame(undefined, undefined, session); } }); @@ -572,7 +572,7 @@ export class DebugService implements IDebugService { return runTasks().then(taskResult => taskResult === TaskRunResult.Success ? this.extensionHostDebugService.reload(session.getId()) : undefined); } - const shouldFocus = this.viewModel.focusedSession && session.getId() === this.viewModel.focusedSession.getId(); + const shouldFocus = !!this.viewModel.focusedSession && session.getId() === this.viewModel.focusedSession.getId(); // If the restart is automatic -> disconnect, otherwise -> terminate #55064 return (isAutoRestart ? session.disconnect(true) : session.terminate(true)).then(() => { diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 5146eac7f78..ba384a37df3 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -131,6 +131,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { private registerListeners(): void { this._register(this.debugService.onDidChangeState(() => this.updateScheduler.schedule())); this._register(this.debugService.getViewModel().onDidFocusSession(() => this.updateScheduler.schedule())); + this._register(this.debugService.onDidNewSession(() => this.updateScheduler.schedule())); this._register(this.configurationService.onDidChangeConfiguration(e => this.onDidConfigurationChange(e))); this._register(this.actionBar.actionRunner.onDidRun((e: IRunEvent) => { // check for error diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 272fa9d0591..17e83bed319 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -50,7 +50,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { getDefaultValue } from 'vs/platform/configuration/common/configurationRegistry'; import { isUndefined } from 'vs/base/common/types'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { IWebviewService, Webview } from 'vs/workbench/contrib/webview/browser/webview'; +import { IWebviewService, Webview, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED } from 'vs/workbench/contrib/webview/browser/webview'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { generateUuid } from 'vs/base/common/uuid'; import { platform } from 'vs/base/common/process'; @@ -525,6 +525,12 @@ export class ExtensionEditor extends BaseEditor { } } + runFindAction(previous: boolean): void { + if (this.activeElement && (this.activeElement).runFindAction) { + (this.activeElement).runFindAction(previous); + } + } + private onNavbarChange(extension: IExtension, { id, focus }: { id: string | null, focus: boolean }, template: IExtensionEditorTemplate): void { if (this.editorLoadComplete) { /* __GDPR__ @@ -1314,28 +1320,66 @@ export class ExtensionEditor extends BaseEditor { } } +const contextKeyExpr = ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', ExtensionEditor.ID), ContextKeyExpr.not('editorFocus')); class ShowExtensionEditorFindCommand extends Command { public runCommand(accessor: ServicesAccessor, args: any): void { - const extensionEditor = this.getExtensionEditor(accessor); + const extensionEditor = getExtensionEditor(accessor); if (extensionEditor) { extensionEditor.showFind(); } } - - private getExtensionEditor(accessor: ServicesAccessor): ExtensionEditor | null { - const activeControl = accessor.get(IEditorService).activeControl as ExtensionEditor; - if (activeControl instanceof ExtensionEditor) { - return activeControl; - } - return null; - } } -const showCommand = new ShowExtensionEditorFindCommand({ +(new ShowExtensionEditorFindCommand({ id: 'editor.action.extensioneditor.showfind', - precondition: ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', ExtensionEditor.ID), ContextKeyExpr.not('editorFocus')), + precondition: contextKeyExpr, kbOpts: { primary: KeyMod.CtrlCmd | KeyCode.KEY_F, weight: KeybindingWeight.EditorContrib } -}); -showCommand.register(); +})).register(); + +class StartExtensionEditorFindNextCommand extends Command { + public runCommand(accessor: ServicesAccessor, args: any): void { + const extensionEditor = getExtensionEditor(accessor); + if (extensionEditor) { + extensionEditor.runFindAction(false); + } + } +} +(new StartExtensionEditorFindNextCommand({ + id: 'editor.action.extensioneditor.findNext', + precondition: ContextKeyExpr.and( + contextKeyExpr, + KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED), + kbOpts: { + primary: KeyCode.Enter, + weight: KeybindingWeight.EditorContrib + } +})).register(); + +class StartExtensionEditorFindPreviousCommand extends Command { + public runCommand(accessor: ServicesAccessor, args: any): void { + const extensionEditor = getExtensionEditor(accessor); + if (extensionEditor) { + extensionEditor.runFindAction(true); + } + } +} +(new StartExtensionEditorFindPreviousCommand({ + id: 'editor.action.extensioneditor.findPrevious', + precondition: ContextKeyExpr.and( + contextKeyExpr, + KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED), + kbOpts: { + primary: KeyMod.Shift | KeyCode.Enter, + weight: KeybindingWeight.EditorContrib + } +})).register(); + +function getExtensionEditor(accessor: ServicesAccessor): ExtensionEditor | null { + const activeControl = accessor.get(IEditorService).activeControl as ExtensionEditor; + if (activeControl instanceof ExtensionEditor) { + return activeControl; + } + return null; +} diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.web.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.web.contribution.ts deleted file mode 100644 index 439f218cfb3..00000000000 --- a/src/vs/workbench/contrib/extensions/browser/extensions.web.contribution.ts +++ /dev/null @@ -1,11 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IExtensionTipsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; - -// Singletons -registerSingleton(IExtensionTipsService, ExtensionTipsService); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index ec19e774ab1..53632cb694a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -3025,7 +3025,6 @@ export class InstallLocalExtensionsInRemoteAction extends Action { private extensions: IExtension[] | undefined = undefined; constructor( - private readonly selectAndInstall: boolean, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @@ -3047,9 +3046,7 @@ export class InstallLocalExtensionsInRemoteAction extends Action { get label(): string { if (this.extensionManagementServerService.remoteExtensionManagementServer) { - return this.selectAndInstall ? - localize('select and install local extensions', "Install Local Extensions in {0}...", this.extensionManagementServerService.remoteExtensionManagementServer.label) - : localize('install local extensions', "Install Local Extensions in {0}", this.extensionManagementServerService.remoteExtensionManagementServer.label); + return localize('select and install local extensions', "Install Local Extensions in {0}...", this.extensionManagementServerService.remoteExtensionManagementServer.label); } return ''; } @@ -3065,12 +3062,7 @@ export class InstallLocalExtensionsInRemoteAction extends Action { } async run(): Promise { - if (this.selectAndInstall) { - return this.selectAndInstallLocalExtensions(); - } else { - const extensionsToInstall = await this.queryExtensionsToInstall(); - return this.installLocalExtensions(extensionsToInstall); - } + return this.selectAndInstallLocalExtensions(); } private async queryExtensionsToInstall(): Promise { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 733aa5fa5b1..9eabbf3220f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -876,7 +876,7 @@ export class ServerExtensionsView extends ExtensionsListView { getActions(): IAction[] { if (this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManagementServerService.localExtensionManagementServer === this.server) { - const installLocalExtensionsInRemoteAction = this._register(this.instantiationService.createInstance(InstallLocalExtensionsInRemoteAction, false)); + const installLocalExtensionsInRemoteAction = this._register(this.instantiationService.createInstance(InstallLocalExtensionsInRemoteAction)); installLocalExtensionsInRemoteAction.class = 'octicon octicon-cloud-download'; return [installLocalExtensionsInRemoteAction]; } diff --git a/src/vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller.ts b/src/vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller.ts index b140853a820..84c47be3064 100644 --- a/src/vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller.ts +++ b/src/vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller.ts @@ -22,7 +22,7 @@ export class RemoteExtensionsInstaller extends Disposable implements IWorkbenchC ) { super(); if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { - const installLocalExtensionsInRemoteAction = instantiationService.createInstance(InstallLocalExtensionsInRemoteAction, true); + const installLocalExtensionsInRemoteAction = instantiationService.createInstance(InstallLocalExtensionsInRemoteAction); CommandsRegistry.registerCommand('workbench.extensions.installLocalExtensions', () => installLocalExtensionsInRemoteAction.run()); let disposable = Disposable.None; const appendMenuItem = () => { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts b/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts index f1708ff4d49..eb1f7705faf 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts @@ -11,6 +11,7 @@ import { IDecorationsProvider, IDecorationData } from 'vs/workbench/services/dec import { listInvalidItemForeground } from 'vs/platform/theme/common/colorRegistry'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; +import { explorerRootErrorEmitter } from 'vs/workbench/contrib/files/browser/views/explorerViewer'; export class ExplorerDecorationsProvider implements IDecorationsProvider { readonly label: string = localize('label', "Explorer"); @@ -30,6 +31,9 @@ export class ExplorerDecorationsProvider implements IDecorationsProvider { this._onDidChange.fire([change.item.resource]); } })); + this.toDispose.add(explorerRootErrorEmitter.event((resource => { + this._onDidChange.fire([resource]); + }))); } get onDidChange(): Event { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index c5bd34e7ac6..5a2c00e5786 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -50,6 +50,7 @@ import { first } from 'vs/base/common/arrays'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { dispose } from 'vs/base/common/lifecycle'; +import { timeout } from 'vs/base/common/async'; export class ExplorerView extends ViewletPanel { static readonly ID: string = 'workbench.explorer.fileView'; @@ -522,6 +523,8 @@ export class ExplorerView extends ViewletPanel { while (item && item.resource.toString() !== resource.toString()) { await this.tree.expand(item); + // Tree returns too early from the expand, need to wait for next tick #77106 + await timeout(0); item = first(values(item.children), i => isEqualOrParent(resource, i.resource)); } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index d8d9f52f320..016249db38c 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -46,6 +46,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; import { findValidPasteFileTarget } from 'vs/workbench/contrib/files/browser/fileActions'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; +import { Emitter } from 'vs/base/common/event'; export class ExplorerDelegate implements IListVirtualDelegate { @@ -60,6 +61,7 @@ export class ExplorerDelegate implements IListVirtualDelegate { } } +export const explorerRootErrorEmitter = new Emitter(); export class ExplorerDataSource implements IAsyncDataSource { constructor( @@ -87,8 +89,9 @@ export class ExplorerDataSource implements IAsyncDataSource(WorkbenchActionExtensions.WorkbenchActions); const devCategory = nls.localize('developer', "Developer"); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SetLogLevelAction, SetLogLevelAction.ID, SetLogLevelAction.LABEL), 'Developer: Set Log Level...', devCategory); \ No newline at end of file +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SetLogLevelAction, SetLogLevelAction.ID, SetLogLevelAction.LABEL), 'Developer: Set Log Level...', devCategory); + +class LogOutputChannels extends Disposable implements IWorkbenchContribution { + + constructor( + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @ILogService private readonly logService: ILogService, + @IFileService private readonly fileService: IFileService + ) { + super(); + if (isWeb) { + this.registerWebContributions(); + } else { + this.registerNativeContributions(); + } + } + + private registerWebContributions(): void { + Registry.as(OutputExt.OutputChannels).registerChannel({ id: Constants.rendererLogChannelId, label: nls.localize('rendererLog', "Window"), file: this.environmentService.logFile, log: true }); + } + + private registerNativeContributions(): void { + this.registerLogChannel(Constants.mainLogChannelId, nls.localize('mainLog', "Main"), URI.file(join(this.environmentService.logsPath, `main.log`))); + this.registerLogChannel(Constants.sharedLogChannelId, nls.localize('sharedLog', "Shared"), URI.file(join(this.environmentService.logsPath, `sharedprocess.log`))); + this.registerLogChannel(Constants.rendererLogChannelId, nls.localize('rendererLog', "Window"), this.environmentService.logFile); + + const registerTelemetryChannel = (level: LogLevel) => { + if (level === LogLevel.Trace && !Registry.as(OutputExt.OutputChannels).getChannel(Constants.telemetryLogChannelId)) { + this.registerLogChannel(Constants.telemetryLogChannelId, nls.localize('telemetryLog', "Telemetry"), URI.file(join(this.environmentService.logsPath, `telemetry.log`))); + } + }; + registerTelemetryChannel(this.logService.getLevel()); + this.logService.onDidChangeLogLevel(registerTelemetryChannel); + + const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); + const devCategory = nls.localize('developer', "Developer"); + workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenLogsFolderAction, OpenLogsFolderAction.ID, OpenLogsFolderAction.LABEL), 'Developer: Open Logs Folder', devCategory); + } + + private async registerLogChannel(id: string, label: string, file: URI): Promise { + const outputChannelRegistry = Registry.as(OutputExt.OutputChannels); + const exists = await this.fileService.exists(file); + if (exists) { + outputChannelRegistry.registerChannel({ id, label, file, log: true }); + return; + } + + const watcher = this.fileService.watch(dirname(file)); + const disposable = this.fileService.onFileChanges(e => { + if (e.contains(file, FileChangeType.ADDED)) { + watcher.dispose(); + disposable.dispose(); + outputChannelRegistry.registerChannel({ id, label, file, log: true }); + } + }); + } + +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LogOutputChannels, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts b/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts deleted file mode 100644 index d8d381fa525..00000000000 --- a/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts +++ /dev/null @@ -1,68 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { join } from 'vs/base/common/path'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IOutputChannelRegistry, Extensions as OutputExt, } from 'vs/workbench/contrib/output/common/output'; -import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; -import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { OpenLogsFolderAction } from 'vs/workbench/contrib/logs/common/logsActions'; -import { ILogService, LogLevel } from 'vs/platform/log/common/log'; -import { IFileService, FileChangeType } from 'vs/platform/files/common/files'; -import { dirname } from 'vs/base/common/resources'; - -class LogOutputChannels extends Disposable implements IWorkbenchContribution { - - constructor( - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @ILogService logService: ILogService, - @IFileService private readonly fileService: IFileService - ) { - super(); - this.registerLogChannel(Constants.mainLogChannelId, nls.localize('mainLog', "Main"), URI.file(join(environmentService.logsPath, `main.log`))); - this.registerLogChannel(Constants.sharedLogChannelId, nls.localize('sharedLog', "Shared"), URI.file(join(environmentService.logsPath, `sharedprocess.log`))); - this.registerLogChannel(Constants.rendererLogChannelId, nls.localize('rendererLog', "Window"), URI.file(join(environmentService.logsPath, `renderer${environmentService.configuration.windowId}.log`))); - - const registerTelemetryChannel = (level: LogLevel) => { - if (level === LogLevel.Trace && !Registry.as(OutputExt.OutputChannels).getChannel(Constants.telemetryLogChannelId)) { - this.registerLogChannel(Constants.telemetryLogChannelId, nls.localize('telemetryLog', "Telemetry"), URI.file(join(environmentService.logsPath, `telemetry.log`))); - } - }; - registerTelemetryChannel(logService.getLevel()); - logService.onDidChangeLogLevel(registerTelemetryChannel); - - const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); - const devCategory = nls.localize('developer', "Developer"); - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenLogsFolderAction, OpenLogsFolderAction.ID, OpenLogsFolderAction.LABEL), 'Developer: Open Logs Folder', devCategory); - } - - private async registerLogChannel(id: string, label: string, file: URI): Promise { - const outputChannelRegistry = Registry.as(OutputExt.OutputChannels); - const exists = await this.fileService.exists(file); - if (exists) { - outputChannelRegistry.registerChannel({ id, label, file, log: true }); - return; - } - - const watcher = this.fileService.watch(dirname(file)); - const disposable = this.fileService.onFileChanges(e => { - if (e.contains(file, FileChangeType.ADDED)) { - watcher.dispose(); - disposable.dispose(); - outputChannelRegistry.registerChannel({ id, label, file, log: true }); - } - }); - } - -} - -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LogOutputChannels, LifecyclePhase.Restored); \ No newline at end of file diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index a45a2680f32..eb7ff5278a6 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -109,7 +109,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor ) { super(KeybindingsEditor.ID, telemetryService, themeService, storageService); this.delayedFiltering = new Delayer(300); - this._register(keybindingsService.onDidUpdateKeybindings(() => this.render(true))); + this._register(keybindingsService.onDidUpdateKeybindings(() => this.render(!!this.keybindingFocusContextKey.get()))); this.keybindingsEditorContextKey = CONTEXT_KEYBINDINGS_EDITOR.bindTo(this.contextKeyService); this.searchFocusContextKey = CONTEXT_KEYBINDINGS_SEARCH_FOCUS.bindTo(this.contextKeyService); @@ -537,7 +537,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor } this.unAssignedKeybindingItemToRevealAndFocus = null; } else if (currentSelectedIndex !== -1 && currentSelectedIndex < this.listEntries.length) { - this.selectEntry(currentSelectedIndex); + this.selectEntry(currentSelectedIndex, preserveFocus); } else if (this.editorService.activeControl === this && !preserveFocus) { this.focus(); } @@ -597,11 +597,13 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor return -1; } - private selectEntry(keybindingItemEntry: IKeybindingItemEntry | number): void { + private selectEntry(keybindingItemEntry: IKeybindingItemEntry | number, focus: boolean = true): void { const index = typeof keybindingItemEntry === 'number' ? keybindingItemEntry : this.getIndexOf(keybindingItemEntry); if (index !== -1) { - this.keybindingsList.getHTMLElement().focus(); - this.keybindingsList.setFocus([index]); + if (focus) { + this.keybindingsList.getHTMLElement().focus(); + this.keybindingsList.setFocus([index]); + } this.keybindingsList.setSelection([index]); } } diff --git a/src/vs/workbench/contrib/resources/browser/resourceServiceWorker.ts b/src/vs/workbench/contrib/resources/browser/resourceServiceWorker.ts index 48b238d10f3..3534ef147df 100644 --- a/src/vs/workbench/contrib/resources/browser/resourceServiceWorker.ts +++ b/src/vs/workbench/contrib/resources/browser/resourceServiceWorker.ts @@ -34,7 +34,7 @@ self.addEventListener('activate', event => { //#region --- fetching/caching const _cacheName = 'vscode-extension-resources'; -const _resourcePrefix = '/vscode-remote'; +const _resourcePrefix = '/vscode-remote-resource'; const _pendingFetch = new Map(); self.addEventListener('message', event => { diff --git a/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerMain.ts b/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerMain.ts index 0dc883addcc..89e7d32eae6 100644 --- a/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerMain.ts +++ b/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerMain.ts @@ -10,7 +10,7 @@ // statement. // trigger service worker updates -const _tag = '52278406-3ca9-48af-a8fb-8495add5bb4e'; +const _tag = '23549971-9b8d-41bb-92ae-d7f6a68c9702'; // loader world const baseUrl = '../../../../../'; diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 99c526eedca..b47f264dcb9 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -530,7 +530,7 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindNext, FindNe }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Find next', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindNext, FindNext.ID, FindNext.LABEL, { primary: KeyCode.F3, - mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_G, secondary: [KeyCode.F3] } + mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_G, secondary: [KeyCode.F3, KeyMod.Shift | KeyCode.Enter] } }, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Find next'); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindPrevious, FindPrevious.ID_TERMINAL_FOCUS, FindPrevious.LABEL, { primary: KeyMod.Shift | KeyCode.F3, @@ -538,7 +538,7 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindPrevious, Fi }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Find previous', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindPrevious, FindPrevious.ID, FindPrevious.LABEL, { primary: KeyMod.Shift | KeyCode.F3, - mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_G, secondary: [KeyMod.Shift | KeyCode.F3] }, + mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_G, secondary: [KeyMod.Shift | KeyCode.F3, KeyCode.Enter] }, }, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Find previous'); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts b/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts index 87ca61f2d55..a3d01c1c6e4 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts @@ -19,7 +19,7 @@ export class TerminalFindWidget extends SimpleFindWidget { @IContextKeyService private readonly _contextKeyService: IContextKeyService, @ITerminalService private readonly _terminalService: ITerminalService ) { - super(_contextViewService, _contextKeyService, findState, true, true); + super(_contextViewService, _contextKeyService, findState, true); this._register(findState.onFindReplaceStateChange(() => { this.show(); })); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index bfa25469cea..daed8080612 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -26,6 +26,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur export class TerminalService extends CommonTerminalService implements ITerminalService { private _configHelper: IBrowserTerminalConfigHelper; + private _terminalContainer: HTMLElement | undefined; public get configHelper(): ITerminalConfigHelper { return this._configHelper; } diff --git a/src/vs/workbench/contrib/terminal/common/terminalService.ts b/src/vs/workbench/contrib/terminal/common/terminalService.ts index d2dc8ec3c59..1fbdcb6237b 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalService.ts @@ -35,7 +35,6 @@ export abstract class TerminalService implements ITerminalService { protected _isShuttingDown: boolean; protected _terminalFocusContextKey: IContextKey; protected _findWidgetVisible: IContextKey; - protected _terminalContainer: HTMLElement | undefined; protected _terminalTabs: ITerminalTab[] = []; protected _backgroundedTerminalInstances: ITerminalInstance[] = []; protected get _terminalInstances(): ITerminalInstance[] { diff --git a/src/vs/workbench/contrib/url/common/url.contribution.ts b/src/vs/workbench/contrib/url/common/url.contribution.ts index 475265542b1..7d8a5f9e8fc 100644 --- a/src/vs/workbench/contrib/url/common/url.contribution.ts +++ b/src/vs/workbench/contrib/url/common/url.contribution.ts @@ -14,17 +14,24 @@ import { Action } from 'vs/base/common/actions'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IProductService } from 'vs/platform/product/common/product'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { equalsIgnoreCase } from 'vs/base/common/strings'; +import { Schemas } from 'vs/base/common/network'; +import Severity from 'vs/base/common/severity'; export class OpenUrlAction extends Action { - static readonly ID = 'workbench.action.url.openUrl'; - static readonly LABEL = localize('openUrl', "Open URL"); + static readonly LABEL = localize('openUrl', 'Open URL'); constructor( id: string, label: string, @IURLService private readonly urlService: IURLService, - @IQuickInputService private readonly quickInputService: IQuickInputService, + @IQuickInputService private readonly quickInputService: IQuickInputService ) { super(id, label); } @@ -45,7 +52,7 @@ Registry.as(ActionExtensions.WorkbenchActions).registe const VSCODE_DOMAIN = 'https://code.visualstudio.com'; -const configureTrustedDomainsHandler = ( +const configureTrustedDomainsHandler = async ( quickInputService: IQuickInputService, storageService: IStorageService, domainToConfigure?: string @@ -66,7 +73,7 @@ const configureTrustedDomainsHandler = ( type: 'item', label: d, id: d, - picked: true, + picked: true }; }); @@ -91,23 +98,24 @@ const configureTrustedDomainsHandler = ( specialQuickPickItems.push(domainToConfigureItem); } - const quickPickItems: (IQuickPickItem | IQuickPickSeparator)[] = domainQuickPickItems.length === 0 - ? specialQuickPickItems - : [...specialQuickPickItems, { type: 'separator' }, ...domainQuickPickItems]; + const quickPickItems: (IQuickPickItem | IQuickPickSeparator)[] = + domainQuickPickItems.length === 0 + ? specialQuickPickItems + : [...specialQuickPickItems, { type: 'separator' }, ...domainQuickPickItems]; - return quickInputService.pick(quickPickItems, { + const pickedResult = await quickInputService.pick(quickPickItems, { canPickMany: true, activeItem: domainToConfigureItem - }).then(result => { - if (result) { - const pickedDomains = result.map(r => r.id); - storageService.store('http.trustedDomains', JSON.stringify(pickedDomains), StorageScope.GLOBAL); - - return pickedDomains; - } - - return []; }); + + if (pickedResult) { + const pickedDomains: string[] = pickedResult.map(r => r.id!); + storageService.store('http.trustedDomains', JSON.stringify(pickedDomains), StorageScope.GLOBAL); + + return pickedDomains; + } + + return []; }; const configureTrustedDomainCommand = { @@ -131,3 +139,93 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { title: configureTrustedDomainCommand.description.description } }); + +class OpenerValidatorContributions implements IWorkbenchContribution { + constructor( + @IOpenerService private readonly _openerService: IOpenerService, + @IStorageService private readonly _storageService: IStorageService, + @IDialogService private readonly _dialogService: IDialogService, + @IProductService private readonly _productService: IProductService, + @IQuickInputService private readonly _quickInputService: IQuickInputService + ) { + this._openerService.registerValidator({ shouldOpen: r => this.validateLink(r) }); + } + + async validateLink(resource: URI): Promise { + const { scheme, authority } = resource; + + if (!equalsIgnoreCase(scheme, Schemas.http) && !equalsIgnoreCase(scheme, Schemas.https)) { + return true; + } + + let trustedDomains: string[] = [VSCODE_DOMAIN]; + try { + const trustedDomainsSrc = this._storageService.get('http.trustedDomains', StorageScope.GLOBAL); + if (trustedDomainsSrc) { + trustedDomains = JSON.parse(trustedDomainsSrc); + } + } catch (err) { } + + const domainToOpen = `${scheme}://${authority}`; + + if (isDomainTrusted(domainToOpen, trustedDomains)) { + return true; + } else { + const choice = await this._dialogService.show( + Severity.Info, + localize( + 'openExternalLinkAt', + 'Do you want {0} to open the external website?\n{1}', + this._productService.nameShort, + resource.toString(true) + ), + [ + localize('openLink', 'Open Link'), + localize('cancel', 'Cancel'), + localize('configureTrustedDomains', 'Configure Trusted Domains') + ], + { + cancelId: 1 + } + ); + + // Open Link + if (choice === 0) { + return true; + } + // Configure Trusted Domains + else if (choice === 2) { + const pickedDomains = await configureTrustedDomainsHandler(this._quickInputService, this._storageService, domainToOpen); + if (pickedDomains.indexOf(domainToOpen) !== -1) { + return true; + } + return false; + } + + return false; + } + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution( + OpenerValidatorContributions, + LifecyclePhase.Restored +); + +/** + * Check whether a domain like https://www.microsoft.com matches + * the list of trusted domains. + */ +function isDomainTrusted(domain: string, trustedDomains: string[]) { + for (let i = 0; i < trustedDomains.length; i++) { + if (trustedDomains[i] === '*') { + return true; + } + + if (trustedDomains[i] === domain) { + return true; + } + } + + return false; +} diff --git a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts index 94d8bfa1f36..ead4c72d910 100644 --- a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts +++ b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts @@ -153,6 +153,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd reload(): void { this.withWebview(webview => webview.reload()); } showFind(): void { this.withWebview(webview => webview.showFind()); } hideFind(): void { this.withWebview(webview => webview.hideFind()); } + runFindAction(previous: boolean): void { this.withWebview(webview => webview.runFindAction(previous)); } public getInnerWebview() { return this._webview.value; diff --git a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts index 56017d4348f..e6bf13abce6 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts @@ -15,8 +15,8 @@ import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } fro import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; import { WebviewEditorInputFactory } from 'vs/workbench/contrib/webview/browser/webviewEditorInputFactory'; -import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, webviewDeveloperCategory } from 'vs/workbench/contrib/webview/browser/webview'; -import { HideWebViewEditorFindCommand, ReloadWebviewAction, ShowWebViewEditorFindWidgetCommand } from '../browser/webviewCommands'; +import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, webviewDeveloperCategory, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED } from 'vs/workbench/contrib/webview/browser/webview'; +import { HideWebViewEditorFindCommand, ReloadWebviewAction, ShowWebViewEditorFindWidgetCommand, WebViewEditorFindNextCommand, WebViewEditorFindPreviousCommand } from '../browser/webviewCommands'; import { WebviewEditor } from '../browser/webviewEditor'; import { WebviewEditorInput } from '../browser/webviewEditorInput'; import { IWebviewEditorService, WebviewEditorService } from '../browser/webviewEditorService'; @@ -58,6 +58,28 @@ function registerWebViewCommands(editorId: string): void { weight: KeybindingWeight.EditorContrib } })).register(); + + (new WebViewEditorFindNextCommand({ + id: WebViewEditorFindNextCommand.ID, + precondition: ContextKeyExpr.and( + contextKeyExpr, + KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED), + kbOpts: { + primary: KeyCode.Enter, + weight: KeybindingWeight.EditorContrib + } + })).register(); + + (new WebViewEditorFindPreviousCommand({ + id: WebViewEditorFindPreviousCommand.ID, + precondition: ContextKeyExpr.and( + contextKeyExpr, + KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED), + kbOpts: { + primary: KeyMod.Shift | KeyCode.Enter, + weight: KeybindingWeight.EditorContrib + } + })).register(); } registerWebViewCommands(WebviewEditor.ID); diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index ea7ecb5a720..e7ef6086eaf 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -16,6 +16,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' * Set when the find widget in a webview is visible. */ export const KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE = new RawContextKey('webviewFindWidgetVisible', false); +export const KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED = new RawContextKey('webviewFindWidgetFocused', false); export const IWebviewService = createDecorator('webviewService'); @@ -83,6 +84,7 @@ export interface Webview extends IDisposable { showFind(): void; hideFind(): void; + runFindAction(previous: boolean): void; } export interface WebviewElement extends Webview { diff --git a/src/vs/workbench/contrib/webview/browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/browser/webviewCommands.ts index a5888e3e668..62a3eecece5 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewCommands.ts @@ -32,6 +32,27 @@ export class HideWebViewEditorFindCommand extends Command { } } +export class WebViewEditorFindNextCommand extends Command { + public static readonly ID = 'editor.action.webvieweditor.findNext'; + + public runCommand(accessor: ServicesAccessor, args: any): void { + const webViewEditor = getActiveWebviewEditor(accessor); + if (webViewEditor) { + webViewEditor.find(false); + } + } +} + +export class WebViewEditorFindPreviousCommand extends Command { + public static readonly ID = 'editor.action.webvieweditor.findPrevious'; + + public runCommand(accessor: ServicesAccessor, args: any): void { + const webViewEditor = getActiveWebviewEditor(accessor); + if (webViewEditor) { + webViewEditor.find(true); + } + } +} export class ReloadWebviewAction extends Action { static readonly ID = 'workbench.action.webview.reloadWebviewAction'; static readonly LABEL = nls.localize('refreshWebviewLabel', "Reload Webviews"); @@ -62,4 +83,4 @@ function getActiveWebviewEditor(accessor: ServicesAccessor): WebviewEditor | nul const editorService = accessor.get(IEditorService); const activeControl = editorService.activeControl as WebviewEditor; return activeControl.isWebviewEditor ? activeControl : null; -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts index b8d7c6dd48e..33d33303076 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts @@ -25,7 +25,6 @@ export class WebviewEditor extends BaseEditor { private readonly _scopedContextKeyService = this._register(new MutableDisposable()); private _findWidgetVisible: IContextKey; - private _editorFrame?: HTMLElement; private _content?: HTMLElement; @@ -79,6 +78,12 @@ export class WebviewEditor extends BaseEditor { this.withWebview(webview => webview.hideFind()); } + public find(previous: boolean) { + this.withWebview(webview => { + webview.runFindAction(previous); + }); + } + public reload() { this.withWebview(webview => webview.reload()); } diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index 7a0015be053..b18f47f0f96 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -268,6 +268,10 @@ export class IFrameWebview extends Disposable implements Webview { throw new Error('Method not implemented.'); } + runFindAction(previous: boolean): void { + throw new Error('Method not implemented.'); + } + public set state(state: string | undefined) { this.content = { html: this.content.html, diff --git a/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts b/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts index 0d8e6804bc0..b6c68c732bb 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts @@ -4,8 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { SimpleFindWidget } from 'vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED } from 'vs/workbench/contrib/webview/browser/webview'; export interface WebviewFindDelegate { find(value: string, previous: boolean): void; @@ -15,6 +16,7 @@ export interface WebviewFindDelegate { } export class WebviewFindWidget extends SimpleFindWidget { + protected _findWidgetFocused: IContextKey; constructor( private readonly _delegate: WebviewFindDelegate, @@ -22,6 +24,7 @@ export class WebviewFindWidget extends SimpleFindWidget { @IContextKeyService contextKeyService: IContextKeyService ) { super(contextViewService, contextKeyService); + this._findWidgetFocused = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED.bindTo(contextKeyService); } public find(previous: boolean) { @@ -47,9 +50,13 @@ export class WebviewFindWidget extends SimpleFindWidget { return false; } - protected onFocusTrackerFocus() { } + protected onFocusTrackerFocus() { + this._findWidgetFocused.set(true); + } - protected onFocusTrackerBlur() { } + protected onFocusTrackerBlur() { + this._findWidgetFocused.reset(); + } protected onFindInputFocusTrackerFocus() { } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index bdd1c7e3956..1fd54f62df6 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -640,6 +640,12 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview { } } + public runFindAction(previous: boolean) { + if (this._webviewFindWidget) { + this._webviewFindWidget.find(previous); + } + } + public reload() { this.doUpdateContent(); } diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index bf36c64c500..ca6f5cf469a 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -272,7 +272,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Respect option to reveal an editor if it is already visible in any group if (options && options.revealIfVisible) { for (const group of groupsByLastActive) { - if (input.matches(group.activeEditor)) { + if (group.isActive(input)) { targetGroup = group; break; } @@ -280,11 +280,16 @@ export class EditorService extends Disposable implements EditorServiceImpl { } // Respect option to reveal an editor if it is open (not necessarily visible) - if ((options && options.revealIfOpened) || this.configurationService.getValue('workbench.editor.revealIfOpen')) { - for (const group of groupsByLastActive) { - if (group.isOpened(input)) { - targetGroup = group; - break; + if (!targetGroup) { + if ((options && options.revealIfOpened) || this.configurationService.getValue('workbench.editor.revealIfOpen')) { + for (const group of groupsByLastActive) { + if (group.isOpened(input) && group.isActive(input)) { + targetGroup = group; + break; + } + if (group.isOpened(input) && !targetGroup) { + targetGroup = group; + } } } } diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 6cbe59689db..ec7417e7e7b 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -62,11 +62,9 @@ export class BrowserWindowConfiguration implements IWindowConfiguration { termProgram?: string; } -export interface IBrowserWindowConfiguration { +interface IBrowserWorkbenchEnvironemntConstructionOptions extends IWorkbenchConstructionOptions { workspaceId: string; - remoteAuthority?: string; - webviewEndpoint?: string; - connectionToken?: string; + logFile: URI; } export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironmentService { @@ -75,8 +73,9 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment readonly configuration: IWindowConfiguration = new BrowserWindowConfiguration(); - constructor(workspaceId: string, public readonly options: IWorkbenchConstructionOptions) { + constructor(readonly options: IBrowserWorkbenchEnvironemntConstructionOptions) { this.args = { _: [] }; + this.logFile = options.logFile; this.appRoot = '/web/'; this.appNameLong = 'Visual Studio Code - Web'; @@ -88,8 +87,8 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment this.keyboardLayoutResource = joinPath(this.userRoamingDataHome, 'keyboardLayout.json'); this.localeResource = joinPath(this.userRoamingDataHome, 'locale.json'); this.backupHome = joinPath(this.userRoamingDataHome, BACKUPS); - this.configuration.backupWorkspaceResource = joinPath(this.backupHome, workspaceId); - this.configuration.connectionToken = options.connectionToken || this.getConnectionTokenFromLocation(); + this.configuration.backupWorkspaceResource = joinPath(this.backupHome, options.workspaceId); + this.configuration.connectionToken = options.connectionToken || getCookieValue('vscode-tkn'); this.logsPath = '/web/logs'; @@ -183,6 +182,7 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment driverVerbose: boolean; webviewEndpoint?: string; galleryMachineIdResource?: URI; + readonly logFile: URI; get webviewResourceRoot(): string { return this.webviewEndpoint ? this.webviewEndpoint + '/vscode-resource{{resource}}' : 'vscode-resource:{{resource}}'; @@ -191,21 +191,12 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment get webviewCspSource(): string { return this.webviewEndpoint ? this.webviewEndpoint : 'vscode-resource:'; } - - private getConnectionTokenFromLocation(): string | undefined { - // TODO: Check with @alexd where the token will be: search or hash? - let connectionToken: string | undefined = undefined; - if (document.location.search) { - connectionToken = this.getConnectionToken(document.location.search); - } - if (!connectionToken && document.location.hash) { - connectionToken = this.getConnectionToken(document.location.hash); - } - return connectionToken; - } - - private getConnectionToken(str: string): string | undefined { - const m = str.match(/[#&?]tkn=([^&]+)/); - return m ? m[1] : undefined; - } +} + +/** + * See https://stackoverflow.com/a/25490531 + */ +function getCookieValue(name: string): string | undefined { + const m = document.cookie.match('(^|[^;]+)\\s*' + name + '\\s*=\\s*([^;]+)'); + return m ? m.pop() : undefined; } diff --git a/src/vs/workbench/services/environment/common/environmentService.ts b/src/vs/workbench/services/environment/common/environmentService.ts index 72c98796b9a..967ad5e43cf 100644 --- a/src/vs/workbench/services/environment/common/environmentService.ts +++ b/src/vs/workbench/services/environment/common/environmentService.ts @@ -7,6 +7,7 @@ import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/co import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; +import { URI } from 'vs/base/common/uri'; export const IWorkbenchEnvironmentService = createDecorator('environmentService'); @@ -16,5 +17,7 @@ export interface IWorkbenchEnvironmentService extends IEnvironmentService { readonly configuration: IWindowConfiguration; + readonly logFile: URI; + readonly options?: IWorkbenchConstructionOptions; } diff --git a/src/vs/workbench/services/environment/node/environmentService.ts b/src/vs/workbench/services/environment/node/environmentService.ts index 2ebf10365a2..0a46f3fa8e5 100644 --- a/src/vs/workbench/services/environment/node/environmentService.ts +++ b/src/vs/workbench/services/environment/node/environmentService.ts @@ -11,6 +11,7 @@ import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { toBackupWorkspaceResource } from 'vs/workbench/services/backup/common/backup'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { join } from 'vs/base/common/path'; export class WorkbenchEnvironmentService extends EnvironmentService implements IWorkbenchEnvironmentService { @@ -31,4 +32,7 @@ export class WorkbenchEnvironmentService extends EnvironmentService implements I @memoize get userRoamingDataHome(): URI { return this.appSettingsHome.with({ scheme: Schemas.userData }); } + + @memoize + get logFile(): URI { return URI.file(join(this.logsPath, `renderer${this.configuration.windowId}.log`)); } } diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index 78076e31480..80dc149b2ac 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -28,6 +28,7 @@ import { Schemas } from 'vs/base/common/network'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IStaticExtensionsService } from 'vs/workbench/services/extensions/common/staticExtensions'; import { DeltaExtensionsResult } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; +import { INMEMORY_LOG_SCHEME } from 'vs/workbench/services/log/common/inMemoryLogProvider'; export class ExtensionService extends AbstractExtensionService implements IExtensionService { @@ -86,7 +87,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten const result: ExtensionHostProcessManager[] = []; const webExtensions = this.getExtensions().then(extensions => extensions.filter(ext => isWebExtension(ext, this._configService))); - const webHostProcessWorker = this._instantiationService.createInstance(WebWorkerExtensionHostStarter, true, webExtensions, URI.parse('empty:value')); //todo@joh + const webHostProcessWorker = this._instantiationService.createInstance(WebWorkerExtensionHostStarter, true, webExtensions, URI.from({ scheme: INMEMORY_LOG_SCHEME, path: '/' })); const webHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, false, webHostProcessWorker, null, initialActivationEvents); result.push(webHostProcessManager); diff --git a/src/vs/workbench/services/keybinding/browser/keymapService.ts b/src/vs/workbench/services/keybinding/browser/keymapService.ts index c90e58fc0a9..e3744643efd 100644 --- a/src/vs/workbench/services/keybinding/browser/keymapService.ts +++ b/src/vs/workbench/services/keybinding/browser/keymapService.ts @@ -176,6 +176,7 @@ export class BrowserKeyboardMapperFactoryBase { } setActiveKeyMapping(keymap: IKeyboardMapping | null) { + let keymapUpdated = false; let matchedKeyboardLayout = this.getMatchedKeymapInfo(keymap); if (matchedKeyboardLayout) { // let score = matchedKeyboardLayout.score; @@ -209,18 +210,21 @@ export class BrowserKeyboardMapperFactoryBase { if (!this._activeKeymapInfo) { this._activeKeymapInfo = matchedKeyboardLayout.result; + keymapUpdated = true; } else if (keymap) { if (matchedKeyboardLayout.result.getScore(keymap) > this._activeKeymapInfo.getScore(keymap)) { this._activeKeymapInfo = matchedKeyboardLayout.result; + keymapUpdated = true; } } } if (!this._activeKeymapInfo) { this._activeKeymapInfo = this.getUSStandardLayout(); + keymapUpdated = true; } - if (!this._activeKeymapInfo) { + if (!this._activeKeymapInfo || !keymapUpdated) { return; } diff --git a/src/vs/workbench/services/log/common/inMemoryLogProvider.ts b/src/vs/workbench/services/log/common/inMemoryLogProvider.ts new file mode 100644 index 00000000000..6a01a1dbe1b --- /dev/null +++ b/src/vs/workbench/services/log/common/inMemoryLogProvider.ts @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { IFileSystemProviderWithFileReadWriteCapability, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileOverwriteOptions, FileType, FileDeleteOptions, FileWriteOptions, FileChangeType } from 'vs/platform/files/common/files'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Event, Emitter } from 'vs/base/common/event'; +import { VSBuffer } from 'vs/base/common/buffer'; + +export const INMEMORY_LOG_SCHEME = 'vscode-logs-inmemory'; + +interface ILog { + content: string; + version: number; +} + +export class InMemoryLogProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability { + + readonly capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.FileReadWrite; + readonly onDidChangeCapabilities: Event = Event.None; + + private readonly _onDidChangeFile: Emitter = this._register(new Emitter()); + readonly onDidChangeFile: Event = this._onDidChangeFile.event; + + private readonly logs: Map = new Map(); + + constructor( + ) { + super(); + } + + watch(resource: URI, opts: IWatchOptions): IDisposable { + return Disposable.None; + } + + mkdir(resource: URI): Promise { + return Promise.reject(new Error('Not Supported')); + } + + rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise { + return Promise.reject(new Error('Not Supported')); + } + + readdir(resource: URI): Promise<[string, FileType][]> { + return Promise.reject(new Error('Not Supported')); + } + + delete(resource: URI, opts: FileDeleteOptions): Promise { + return Promise.reject(new Error('Not Supported')); + } + + async stat(resource: URI): Promise { + const log = this.logs.get(resource.toString()); + return { + ctime: 0, + mtime: log ? log.version : 0, + size: log ? log.content.length : 0, + type: FileType.File + }; + } + + async readFile(resource: URI): Promise { + const log = this.logs.get(resource.toString()); + return VSBuffer.fromString(log ? log.content : '').buffer; + } + + async writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise { + const log = this.logs.get(resource.toString()) || { content: '', version: 0 }; + log.content = VSBuffer.wrap(content).toString(); + log.version = log.version + 1; + this.logs.set(resource.toString(), log); + this._onDidChangeFile.fire([{ resource, type: FileChangeType.UPDATED }]); + } + +} diff --git a/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts b/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts index 854e9582f5a..777fcedf37b 100644 --- a/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts +++ b/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts @@ -47,7 +47,7 @@ suite('FileUserDataProvider', () => { userDataResource = URI.file(userDataPath).with({ scheme: Schemas.userData }); await Promise.all([pfs.mkdirp(userDataPath), pfs.mkdirp(backupsPath)]); - const environmentService = new BrowserWorkbenchEnvironmentService('workspaceId', { remoteAuthority: 'remote' }); + const environmentService = new BrowserWorkbenchEnvironmentService({ remoteAuthority: 'remote', workspaceId: 'workspaceId', logFile: URI.file('logFile') }); environmentService.userRoamingDataHome = userDataResource; const userDataFileSystemProvider = new FileUserDataProvider(URI.file(userDataPath), URI.file(backupsPath), diskFileSystemProvider, environmentService); @@ -321,7 +321,7 @@ suite('FileUserDataProvider - Watching', () => { localUserDataResource = URI.file(userDataPath); userDataResource = localUserDataResource.with({ scheme: Schemas.userData }); - const environmentService = new BrowserWorkbenchEnvironmentService('workspaceId', { remoteAuthority: 'remote' }); + const environmentService = new BrowserWorkbenchEnvironmentService({ remoteAuthority: 'remote', workspaceId: 'workspaceId', logFile: URI.file('logFile') }); environmentService.userRoamingDataHome = userDataResource; const userDataFileSystemProvider = new FileUserDataProvider(localUserDataResource, localBackupsResource, new TestFileSystemProvider(fileEventEmitter.event), environmentService); diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index b44e341f9c1..edfd8a273f3 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -95,9 +95,6 @@ registerSingleton(IStaticExtensionsService, class extends StaticExtensionsServic // Localizations import 'vs/workbench/contrib/localizations/browser/localizations.contribution'; -// Logs -import 'vs/workbench/contrib/logs/electron-browser/logs.contribution'; - // Stats import 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService'; import 'vs/workbench/contrib/stats/electron-browser/stats.contribution'; diff --git a/test/electron/renderer.html b/test/electron/renderer.html index 72454389597..a2d4bcc5ad9 100644 --- a/test/electron/renderer.html +++ b/test/electron/renderer.html @@ -11,6 +11,18 @@