diff --git a/.github/commands.json b/.github/commands.json index de0643d56c9..65936b422a9 100644 --- a/.github/commands.json +++ b/.github/commands.json @@ -133,6 +133,18 @@ "action": "updateLabels", "addLabel": "~needs more info" }, + { + "type": "comment", + "name": "needsPerfInfo", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "addLabel": "needs more info", + "comment": "Thanks for creating this issue regarding performance! Please follow this guide to help us diagnose performance issues: https://github.com/microsoft/vscode/wiki/Performance-Issues \n\nHappy Coding!" + }, { "type": "comment", "name": "jsDebugLogs", diff --git a/extensions/debug-auto-launch/src/extension.ts b/extensions/debug-auto-launch/src/extension.ts index b47641f2db2..1016f0af44a 100644 --- a/extensions/debug-auto-launch/src/extension.ts +++ b/extensions/debug-auto-launch/src/extension.ts @@ -315,12 +315,10 @@ function updateStatusBar(context: vscode.ExtensionContext, state: State, busy = } if (!statusItem) { - statusItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); + statusItem = vscode.window.createStatusBarItem('status.debug.autoAttach', vscode.StatusBarAlignment.Left); + statusItem.name = localize('status.name.auto.attach', "Debug Auto Attach"); statusItem.command = TOGGLE_COMMAND; - statusItem.tooltip = localize( - 'status.tooltip.auto.attach', - 'Automatically attach to node.js processes in debug mode', - ); + statusItem.tooltip = localize('status.tooltip.auto.attach', "Automatically attach to node.js processes in debug mode"); context.subscriptions.push(statusItem); } diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts index fa81fd97d5d..937f47a681a 100644 --- a/extensions/github-authentication/src/githubServer.ts +++ b/extensions/github-authentication/src/githubServer.ts @@ -49,7 +49,7 @@ export class GitHubServer { // TODO@joaomoreno TODO@RMacfarlane private async isNoCorsEnvironment(): Promise { - const uri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate`)); + const uri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/dummy`)); return uri.scheme === 'https' && /^vscode\./.test(uri.authority); } @@ -179,7 +179,8 @@ export class GitHubServer { private updateStatusBarItem(isStart?: boolean) { if (isStart && !this._statusBarItem) { - this._statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); + this._statusBarItem = vscode.window.createStatusBarItem('status.git.signIn', vscode.StatusBarAlignment.Left); + this._statusBarItem.name = localize('status.git.signIn.name', "GitHub Sign-in"); this._statusBarItem.text = this.type === AuthProviderType.github ? localize('signingIn', "$(mark-github) Signing in to github.com...") : localize('signingInEnterprise', "$(mark-github) Signing in to {0}...", this.getServerUri().authority); diff --git a/extensions/image-preview/src/binarySizeStatusBarEntry.ts b/extensions/image-preview/src/binarySizeStatusBarEntry.ts index 0c983d37cd2..ce375fc19b8 100644 --- a/extensions/image-preview/src/binarySizeStatusBarEntry.ts +++ b/extensions/image-preview/src/binarySizeStatusBarEntry.ts @@ -39,12 +39,7 @@ class BinarySize { export class BinarySizeStatusBarEntry extends PreviewStatusBarEntry { constructor() { - super({ - id: 'imagePreview.binarySize', - name: localize('sizeStatusBar.name', "Image Binary Size"), - alignment: vscode.StatusBarAlignment.Right, - priority: 100, - }); + super('status.imagePreview.binarySize', localize('sizeStatusBar.name', "Image Binary Size"), vscode.StatusBarAlignment.Right, 100); } public show(owner: string, size: number | undefined) { diff --git a/extensions/image-preview/src/ownedStatusBarEntry.ts b/extensions/image-preview/src/ownedStatusBarEntry.ts index 51c9e25503c..31165f67d69 100644 --- a/extensions/image-preview/src/ownedStatusBarEntry.ts +++ b/extensions/image-preview/src/ownedStatusBarEntry.ts @@ -11,9 +11,10 @@ export abstract class PreviewStatusBarEntry extends Disposable { protected readonly entry: vscode.StatusBarItem; - constructor(options: vscode.StatusBarItemOptions) { + constructor(id: string, name: string, alignment: vscode.StatusBarAlignment, priority: number) { super(); - this.entry = this._register(vscode.window.createStatusBarItem(options)); + this.entry = this._register(vscode.window.createStatusBarItem(id, alignment, priority)); + this.entry.name = name; } protected showItem(owner: string, text: string) { diff --git a/extensions/image-preview/src/sizeStatusBarEntry.ts b/extensions/image-preview/src/sizeStatusBarEntry.ts index e74eea0fe60..68e5c34d232 100644 --- a/extensions/image-preview/src/sizeStatusBarEntry.ts +++ b/extensions/image-preview/src/sizeStatusBarEntry.ts @@ -12,12 +12,7 @@ const localize = nls.loadMessageBundle(); export class SizeStatusBarEntry extends PreviewStatusBarEntry { constructor() { - super({ - id: 'imagePreview.size', - name: localize('sizeStatusBar.name', "Image Size"), - alignment: vscode.StatusBarAlignment.Right, - priority: 101 /* to the left of editor status (100) */, - }); + super('status.imagePreview.size', localize('sizeStatusBar.name', "Image Size"), vscode.StatusBarAlignment.Right, 101 /* to the left of editor status (100) */); } public show(owner: string, text: string) { diff --git a/extensions/image-preview/src/zoomStatusBarEntry.ts b/extensions/image-preview/src/zoomStatusBarEntry.ts index 18adc19d6d2..a4fcdc2b604 100644 --- a/extensions/image-preview/src/zoomStatusBarEntry.ts +++ b/extensions/image-preview/src/zoomStatusBarEntry.ts @@ -19,12 +19,7 @@ export class ZoomStatusBarEntry extends OwnedStatusBarEntry { public readonly onDidChangeScale = this._onDidChangeScale.event; constructor() { - super({ - id: 'imagePreview.zoom', - name: localize('zoomStatusBar.name', "Image Zoom"), - alignment: vscode.StatusBarAlignment.Right, - priority: 102 /* to the left of editor size entry (101) */, - }); + super('status.imagePreview.zoom', localize('zoomStatusBar.name', "Image Zoom"), vscode.StatusBarAlignment.Right, 102 /* to the left of editor size entry (101) */); this._register(vscode.commands.registerCommand(selectZoomLevelCommandId, async () => { type MyPickItem = vscode.QuickPickItem & { scale: Scale }; diff --git a/extensions/json-language-features/client/src/jsonClient.ts b/extensions/json-language-features/client/src/jsonClient.ts index c925a9181f6..611327188ef 100644 --- a/extensions/json-language-features/client/src/jsonClient.ts +++ b/extensions/json-language-features/client/src/jsonClient.ts @@ -101,12 +101,8 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua const documentSelector = ['json', 'jsonc']; - const schemaResolutionErrorStatusBarItem = window.createStatusBarItem({ - id: 'status.json.resolveError', - name: localize('json.resolveError', "JSON: Schema Resolution Error"), - alignment: StatusBarAlignment.Right, - priority: 0, - }); + const schemaResolutionErrorStatusBarItem = window.createStatusBarItem('status.json.resolveError', StatusBarAlignment.Right, 0); + schemaResolutionErrorStatusBarItem.name = localize('json.resolveError', "JSON: Schema Resolution Error"); schemaResolutionErrorStatusBarItem.text = '$(alert)'; toDispose.push(schemaResolutionErrorStatusBarItem); diff --git a/extensions/markdown-basics/cgmanifest.json b/extensions/markdown-basics/cgmanifest.json index 0d320ab3376..f3f0717c5ad 100644 --- a/extensions/markdown-basics/cgmanifest.json +++ b/extensions/markdown-basics/cgmanifest.json @@ -33,7 +33,7 @@ "git": { "name": "microsoft/vscode-markdown-tm-grammar", "repositoryUrl": "https://github.com/microsoft/vscode-markdown-tm-grammar", - "commitHash": "399ff6f608a7bef3f68713be23cdcb4c6d475804" + "commitHash": "a612b96d62aa1ce305c4a55dc9d577316fab39da" } }, "license": "MIT", diff --git a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json index 66f8114869a..aaa4c774b40 100644 --- a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json +++ b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/399ff6f608a7bef3f68713be23cdcb4c6d475804", + "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/a612b96d62aa1ce305c4a55dc9d577316fab39da", "name": "Markdown", "scopeName": "text.html.markdown", "patterns": [ @@ -63,7 +63,7 @@ "while": "(^|\\G)\\s*(>) ?" }, "fenced_code_block_css": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(css|css.erb)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(css|css.erb)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -96,7 +96,7 @@ ] }, "fenced_code_block_basic": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(html|htm|shtml|xhtml|inc|tmpl|tpl)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(html|htm|shtml|xhtml|inc|tmpl|tpl)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -129,7 +129,7 @@ ] }, "fenced_code_block_ini": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(ini|conf)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(ini|conf)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -162,7 +162,7 @@ ] }, "fenced_code_block_java": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(java|bsh)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(java|bsh)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -195,7 +195,7 @@ ] }, "fenced_code_block_lua": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(lua)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(lua)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -228,7 +228,7 @@ ] }, "fenced_code_block_makefile": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(Makefile|makefile|GNUmakefile|OCamlMakefile)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(Makefile|makefile|GNUmakefile|OCamlMakefile)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -261,7 +261,7 @@ ] }, "fenced_code_block_perl": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(perl|pl|pm|pod|t|PL|psgi|vcl)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(perl|pl|pm|pod|t|PL|psgi|vcl)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -294,7 +294,7 @@ ] }, "fenced_code_block_r": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(R|r|s|S|Rprofile|\\{\\.r.+?\\})((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(R|r|s|S|Rprofile|\\{\\.r.+?\\})((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -327,7 +327,7 @@ ] }, "fenced_code_block_ruby": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(ruby|rb|rbx|rjs|Rakefile|rake|cgi|fcgi|gemspec|irbrc|Capfile|ru|prawn|Cheffile|Gemfile|Guardfile|Hobofile|Vagrantfile|Appraisals|Rantfile|Berksfile|Berksfile.lock|Thorfile|Puppetfile)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(ruby|rb|rbx|rjs|Rakefile|rake|cgi|fcgi|gemspec|irbrc|Capfile|ru|prawn|Cheffile|Gemfile|Guardfile|Hobofile|Vagrantfile|Appraisals|Rantfile|Berksfile|Berksfile.lock|Thorfile|Puppetfile)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -360,7 +360,7 @@ ] }, "fenced_code_block_php": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(php|php3|php4|php5|phpt|phtml|aw|ctp)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(php|php3|php4|php5|phpt|phtml|aw|ctp)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -396,7 +396,7 @@ ] }, "fenced_code_block_sql": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(sql|ddl|dml)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(sql|ddl|dml)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -429,7 +429,7 @@ ] }, "fenced_code_block_vs_net": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(vb)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(vb)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -462,7 +462,7 @@ ] }, "fenced_code_block_xml": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(xml|xsd|tld|jsp|pt|cpt|dtml|rss|opml)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(xml|xsd|tld|jsp|pt|cpt|dtml|rss|opml)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -495,7 +495,7 @@ ] }, "fenced_code_block_xsl": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(xsl|xslt)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(xsl|xslt)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -528,7 +528,7 @@ ] }, "fenced_code_block_yaml": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(yaml|yml)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(yaml|yml)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -561,7 +561,7 @@ ] }, "fenced_code_block_dosbatch": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(bat|batch)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(bat|batch)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -594,7 +594,7 @@ ] }, "fenced_code_block_clojure": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(clj|cljs|clojure)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(clj|cljs|clojure)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -627,7 +627,7 @@ ] }, "fenced_code_block_coffee": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(coffee|Cakefile|coffee.erb)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(coffee|Cakefile|coffee.erb)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -660,7 +660,7 @@ ] }, "fenced_code_block_c": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(c|h)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(c|h)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -693,7 +693,7 @@ ] }, "fenced_code_block_cpp": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(cpp|c\\+\\+|cxx)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(cpp|c\\+\\+|cxx)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -726,7 +726,7 @@ ] }, "fenced_code_block_diff": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(patch|diff|rej)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(patch|diff|rej)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -759,7 +759,7 @@ ] }, "fenced_code_block_dockerfile": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(dockerfile|Dockerfile)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(dockerfile|Dockerfile)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -792,7 +792,7 @@ ] }, "fenced_code_block_git_commit": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(COMMIT_EDITMSG|MERGE_MSG)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(COMMIT_EDITMSG|MERGE_MSG)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -825,7 +825,7 @@ ] }, "fenced_code_block_git_rebase": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(git-rebase-todo)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(git-rebase-todo)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -858,7 +858,7 @@ ] }, "fenced_code_block_go": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(go|golang)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(go|golang)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -891,7 +891,7 @@ ] }, "fenced_code_block_groovy": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(groovy|gvy)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(groovy|gvy)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -924,7 +924,7 @@ ] }, "fenced_code_block_pug": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(jade|pug)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(jade|pug)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -957,7 +957,7 @@ ] }, "fenced_code_block_js": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(js|jsx|javascript|es6|mjs|cjs|\\{\\.js.+?\\})((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(js|jsx|javascript|es6|mjs|cjs|\\{\\.js.+?\\})((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -990,7 +990,7 @@ ] }, "fenced_code_block_js_regexp": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(regexp)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(regexp)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1023,7 +1023,7 @@ ] }, "fenced_code_block_json": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(json|json5|sublime-settings|sublime-menu|sublime-keymap|sublime-mousemap|sublime-theme|sublime-build|sublime-project|sublime-completions)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(json|json5|sublime-settings|sublime-menu|sublime-keymap|sublime-mousemap|sublime-theme|sublime-build|sublime-project|sublime-completions)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1056,7 +1056,7 @@ ] }, "fenced_code_block_jsonc": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(jsonc)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(jsonc)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1089,7 +1089,7 @@ ] }, "fenced_code_block_less": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(less)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(less)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1122,7 +1122,7 @@ ] }, "fenced_code_block_objc": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(objectivec|objective-c|mm|objc|obj-c|m|h)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(objectivec|objective-c|mm|objc|obj-c|m|h)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1155,7 +1155,7 @@ ] }, "fenced_code_block_swift": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(swift)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(swift)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1188,7 +1188,7 @@ ] }, "fenced_code_block_scss": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(scss)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(scss)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1221,7 +1221,7 @@ ] }, "fenced_code_block_perl6": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(perl6|p6|pl6|pm6|nqp)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(perl6|p6|pl6|pm6|nqp)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1254,7 +1254,7 @@ ] }, "fenced_code_block_powershell": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(powershell|ps1|psm1|psd1)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(powershell|ps1|psm1|psd1)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1287,7 +1287,7 @@ ] }, "fenced_code_block_python": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(python|py|py3|rpy|pyw|cpy|SConstruct|Sconstruct|sconstruct|SConscript|gyp|gypi|\\{\\.python.+?\\})((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(python|py|py3|rpy|pyw|cpy|SConstruct|Sconstruct|sconstruct|SConscript|gyp|gypi|\\{\\.python.+?\\})((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1320,7 +1320,7 @@ ] }, "fenced_code_block_regexp_python": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(re)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(re)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1353,7 +1353,7 @@ ] }, "fenced_code_block_rust": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(rust|rs|\\{\\.rust.+?\\})((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(rust|rs|\\{\\.rust.+?\\})((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1386,7 +1386,7 @@ ] }, "fenced_code_block_scala": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(scala|sbt)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(scala|sbt)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1419,7 +1419,7 @@ ] }, "fenced_code_block_shell": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(shell|sh|bash|zsh|bashrc|bash_profile|bash_login|profile|bash_logout|.textmate_init|\\{\\.bash.+?\\})((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(shell|sh|bash|zsh|bashrc|bash_profile|bash_login|profile|bash_logout|.textmate_init|\\{\\.bash.+?\\})((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1452,7 +1452,7 @@ ] }, "fenced_code_block_ts": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(typescript|ts)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(typescript|ts)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1485,7 +1485,7 @@ ] }, "fenced_code_block_tsx": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(tsx)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(tsx)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1518,7 +1518,7 @@ ] }, "fenced_code_block_csharp": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(cs|csharp|c#)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(cs|csharp|c#)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1551,7 +1551,7 @@ ] }, "fenced_code_block_fsharp": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(fs|fsharp|f#)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(fs|fsharp|f#)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1584,7 +1584,7 @@ ] }, "fenced_code_block_dart": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(dart)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(dart)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1617,7 +1617,7 @@ ] }, "fenced_code_block_handlebars": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(handlebars|hbs)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(handlebars|hbs)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1650,7 +1650,7 @@ ] }, "fenced_code_block_markdown": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(markdown|md)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(markdown|md)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1683,7 +1683,7 @@ ] }, "fenced_code_block_log": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(log)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(log)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1716,7 +1716,7 @@ ] }, "fenced_code_block_erlang": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(erlang)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(erlang)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1749,7 +1749,7 @@ ] }, "fenced_code_block_elixir": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(elixir)((\\s+|:|\\{)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(elixir)((\\s+|:|\\{|\\?)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { diff --git a/extensions/package.json b/extensions/package.json index f590e5cab37..9b5fd61b741 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -4,7 +4,7 @@ "license": "MIT", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "^4.3.0-dev.20210507" + "typescript": "^4.3.1-rc" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/typescript-language-features/src/commands/commandManager.ts b/extensions/typescript-language-features/src/commands/commandManager.ts index 7bf1d7e33aa..b7b58bf7ede 100644 --- a/extensions/typescript-language-features/src/commands/commandManager.ts +++ b/extensions/typescript-language-features/src/commands/commandManager.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; export interface Command { - readonly id: string | string[]; + readonly id: string; execute(...args: any[]): void; } @@ -22,17 +22,9 @@ export class CommandManager { } public register(command: T): T { - for (const id of Array.isArray(command.id) ? command.id : [command.id]) { - this.registerCommand(id, command.execute, command); + if (!this.commands.has(command.id)) { + this.commands.set(command.id, vscode.commands.registerCommand(command.id, command.execute, command)); } return command; } - - private registerCommand(id: string, impl: (...args: any[]) => void, thisArg?: any) { - if (this.commands.has(id)) { - return; - } - - this.commands.set(id, vscode.commands.registerCommand(id, impl, thisArg)); - } -} \ No newline at end of file +} diff --git a/extensions/typescript-language-features/src/languageFeatures/organizeImports.ts b/extensions/typescript-language-features/src/languageFeatures/organizeImports.ts index 3b8111b7c1f..10a399bad5b 100644 --- a/extensions/typescript-language-features/src/languageFeatures/organizeImports.ts +++ b/extensions/typescript-language-features/src/languageFeatures/organizeImports.ts @@ -29,7 +29,7 @@ class OrganizeImportsCommand implements Command { private readonly telemetryReporter: TelemetryReporter, ) { } - public async execute(file: string): Promise { + public async execute(file: string, sortOnly = false): Promise { /* __GDPR__ "organizeImports.execute" : { "${include}": [ @@ -45,7 +45,8 @@ class OrganizeImportsCommand implements Command { args: { file } - } + }, + skipDestructiveCodeActions: sortOnly, }; const response = await this.client.interruptGetErr(() => this.client.execute('organizeImports', args, nulToken)); if (response.type !== 'response' || !response.body) { @@ -57,23 +58,42 @@ class OrganizeImportsCommand implements Command { } } -export class OrganizeImportsCodeActionProvider implements vscode.CodeActionProvider { - public static readonly minVersion = API.v280; +class ImportsCodeActionProvider implements vscode.CodeActionProvider { + + static register( + client: ITypeScriptServiceClient, + minVersion: API, + kind: vscode.CodeActionKind, + title: string, + sortOnly: boolean, + commandManager: CommandManager, + fileConfigurationManager: FileConfigurationManager, + telemetryReporter: TelemetryReporter, + selector: DocumentSelector + ): vscode.Disposable { + return conditionalRegistration([ + requireMinVersion(client, minVersion), + requireSomeCapability(client, ClientCapability.Semantic), + ], () => { + const provider = new ImportsCodeActionProvider(client, kind, title, sortOnly, commandManager, fileConfigurationManager, telemetryReporter); + return vscode.languages.registerCodeActionsProvider(selector.semantic, provider, { + providedCodeActionKinds: [kind] + }); + }); + } public constructor( private readonly client: ITypeScriptServiceClient, + private readonly kind: vscode.CodeActionKind, + private readonly title: string, + private readonly sortOnly: boolean, commandManager: CommandManager, private readonly fileConfigManager: FileConfigurationManager, telemetryReporter: TelemetryReporter, - ) { commandManager.register(new OrganizeImportsCommand(client, telemetryReporter)); } - public readonly metadata: vscode.CodeActionProviderMetadata = { - providedCodeActionKinds: [vscode.CodeActionKind.SourceOrganizeImports] - }; - public provideCodeActions( document: vscode.TextDocument, _range: vscode.Range, @@ -85,16 +105,14 @@ export class OrganizeImportsCodeActionProvider implements vscode.CodeActionProvi return []; } - if (!context.only || !context.only.contains(vscode.CodeActionKind.SourceOrganizeImports)) { + if (!context.only || !context.only.contains(this.kind)) { return []; } this.fileConfigManager.ensureConfigurationForDocument(document, token); - const action = new vscode.CodeAction( - localize('organizeImportsAction.title', "Organize Imports"), - vscode.CodeActionKind.SourceOrganizeImports); - action.command = { title: '', command: OrganizeImportsCommand.Id, arguments: [file] }; + const action = new vscode.CodeAction(this.title, this.kind); + action.command = { title: '', command: OrganizeImportsCommand.Id, arguments: [file, this.sortOnly] }; return [action]; } } @@ -106,13 +124,28 @@ export function register( fileConfigurationManager: FileConfigurationManager, telemetryReporter: TelemetryReporter, ) { - return conditionalRegistration([ - requireMinVersion(client, OrganizeImportsCodeActionProvider.minVersion), - requireSomeCapability(client, ClientCapability.Semantic), - ], () => { - const organizeImportsProvider = new OrganizeImportsCodeActionProvider(client, commandManager, fileConfigurationManager, telemetryReporter); - return vscode.languages.registerCodeActionsProvider(selector.semantic, - organizeImportsProvider, - organizeImportsProvider.metadata); - }); + return vscode.Disposable.from( + ImportsCodeActionProvider.register( + client, + API.v280, + vscode.CodeActionKind.SourceOrganizeImports, + localize('organizeImportsAction.title', "Organize Imports"), + false, + commandManager, + fileConfigurationManager, + telemetryReporter, + selector + ), + ImportsCodeActionProvider.register( + client, + API.v430, + vscode.CodeActionKind.Source.append('sortImports'), + localize('sortImportsAction.title', "Sort Imports"), + true, + commandManager, + fileConfigurationManager, + telemetryReporter, + selector + ), + ); } diff --git a/extensions/typescript-language-features/src/tsServer/versionStatus.ts b/extensions/typescript-language-features/src/tsServer/versionStatus.ts index a868c3e2502..2560f3e8be9 100644 --- a/extensions/typescript-language-features/src/tsServer/versionStatus.ts +++ b/extensions/typescript-language-features/src/tsServer/versionStatus.ts @@ -135,12 +135,8 @@ export default class VersionStatus extends Disposable { ) { super(); - this._statusBarEntry = this._register(vscode.window.createStatusBarItem({ - id: 'status.typescript', - name: localize('projectInfo.name', "TypeScript: Project Info"), - alignment: vscode.StatusBarAlignment.Right, - priority: 99 /* to the right of editor status (100) */ - })); + this._statusBarEntry = this._register(vscode.window.createStatusBarItem('status.typescript', vscode.StatusBarAlignment.Right, 99 /* to the right of editor status (100) */)); + this._statusBarEntry.name = localize('projectInfo.name', "TypeScript: Project Info"); const command = new ProjectStatusCommand(this._client, () => this._state); commandManager.register(command); diff --git a/extensions/typescript-language-features/src/utils/largeProjectStatus.ts b/extensions/typescript-language-features/src/utils/largeProjectStatus.ts index 223d7cb4716..346f95c6979 100644 --- a/extensions/typescript-language-features/src/utils/largeProjectStatus.ts +++ b/extensions/typescript-language-features/src/utils/largeProjectStatus.ts @@ -23,12 +23,8 @@ class ExcludeHintItem { constructor( private readonly telemetryReporter: TelemetryReporter ) { - this._item = vscode.window.createStatusBarItem({ - id: 'status.typescript.exclude', - name: localize('statusExclude', "TypeScript: Configure Excludes"), - alignment: vscode.StatusBarAlignment.Right, - priority: 98 /* to the right of typescript version status (99) */ - }); + this._item = vscode.window.createStatusBarItem('status.typescript.exclude', vscode.StatusBarAlignment.Right, 98 /* to the right of typescript version status (99) */); + this._item.name = localize('statusExclude', "TypeScript: Configure Excludes"); this._item.command = 'js.projectStatus.command'; } diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts index b672c7705ac..0a403549ce2 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { workspace, window, commands, ViewColumn, TextEditorViewColumnChangeEvent, Uri, Selection, Position, CancellationTokenSource, TextEditorSelectionChangeKind, QuickPickItem, TextEditor } from 'vscode'; +import { workspace, window, commands, ViewColumn, TextEditorViewColumnChangeEvent, Uri, Selection, Position, CancellationTokenSource, TextEditorSelectionChangeKind, QuickPickItem, TextEditor, StatusBarAlignment } from 'vscode'; import { join } from 'path'; import { closeAllEditors, pathEquals, createRandomFile, assertNoRpc } from '../utils'; @@ -638,4 +638,22 @@ suite('vscode API - window', () => { }); }); + + test('createStatusBar', async function () { + const statusBarEntryWithoutId = window.createStatusBarItem(StatusBarAlignment.Left, 100); + assert.strictEqual(statusBarEntryWithoutId.id, 'vscode.vscode-api-tests'); + assert.strictEqual(statusBarEntryWithoutId.alignment, StatusBarAlignment.Left); + assert.strictEqual(statusBarEntryWithoutId.priority, 100); + assert.strictEqual(statusBarEntryWithoutId.name, undefined); + statusBarEntryWithoutId.name = 'Test Name'; + assert.strictEqual(statusBarEntryWithoutId.name, 'Test Name'); + + const statusBarEntryWithId = window.createStatusBarItem('testId', StatusBarAlignment.Right, 200); + assert.strictEqual(statusBarEntryWithId.alignment, StatusBarAlignment.Right); + assert.strictEqual(statusBarEntryWithId.priority, 200); + assert.strictEqual(statusBarEntryWithId.id, 'testId'); + assert.strictEqual(statusBarEntryWithId.name, undefined); + statusBarEntryWithId.name = 'Test Name'; + assert.strictEqual(statusBarEntryWithId.name, 'Test Name'); + }); }); diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 18e5f36add7..3d37fa10930 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -24,10 +24,10 @@ fast-plist@0.1.2: resolved "https://registry.yarnpkg.com/fast-plist/-/fast-plist-0.1.2.tgz#a45aff345196006d406ca6cdcd05f69051ef35b8" integrity sha1-pFr/NFGWAG1AbKbNzQX2kFHvNbg= -typescript@^4.3.0-dev.20210507: - version "4.3.0-dev.20210507" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.0-dev.20210507.tgz#07fdc0479bb1b215865aabb01ed1d920cf844ea0" - integrity sha512-SEZV+XOg8exwPXlTmxPT94v9kasblelh4TjL1I12FBv0DiorBHDtUs8GC2h2sg8zJOgFwj06QXiaLLGL5RhzDw== +typescript@^4.3.1-rc: + version "4.3.1-rc" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.1-rc.tgz#925149c8d8514e20a6bd8d4bd7f42adac67ab59c" + integrity sha512-L3uJ0gcntaRaKni9aV2amYB+pCDVodKe/B5+IREyvtKGsDOF7cYjchHb/B894skqkgD52ykRuWatIZMqEsHIqA== vscode-grammar-updater@^1.0.3: version "1.0.3" diff --git a/remote/yarn.lock b/remote/yarn.lock index 69069f99d84..efeb6c53ebc 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -437,9 +437,9 @@ socks@^2.3.3: smart-buffer "^4.1.0" spdlog@^0.13.0: - version "0.13.4" - resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.13.4.tgz#7393d436f077fca1d07500741e50cbf8928a838a" - integrity sha512-tdzk9ysc640emskx+pE/A2JdJ5IAr440ZIsNjRlD9aPK6U6IQ94VUGpl7u0NHamAB8O1H7RxLgtHyXT32V+RaA== + version "0.13.5" + resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.13.5.tgz#a31027dcccbe032e9a53579f42cb45428af08bad" + integrity sha512-D1xA5tRXw7eZOoFBCAnOxCxLN3JpHVDjpPJG/xjJ0nFZvtfOUTAzK66MVxJCDht/ZFwjLcBAltvzjfz4JTuSEw== dependencies: bindings "^1.5.0" mkdirp "^0.5.5" diff --git a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts index 5d28a61c896..6e93aa1e4b8 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts @@ -11,6 +11,7 @@ import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { applyZoom, zoomIn, zoomOut } from 'vs/platform/windows/electron-sandbox/window'; import { $, reset, safeInnerHtml, windowOpenNoOpener } from 'vs/base/browser/dom'; import { Button } from 'vs/base/browser/ui/button/button'; +import { Delayer } from 'vs/base/common/async'; import { groupBy } from 'vs/base/common/collections'; import { debounce } from 'vs/base/common/decorators'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -62,6 +63,7 @@ export class IssueReporter extends Disposable { private receivedPerformanceInfo = false; private shouldQueueSearch = false; private hasBeenSubmitted = false; + private delayedSubmit = new Delayer(300); private readonly previewButton!: Button; @@ -356,7 +358,11 @@ export class IssueReporter extends Disposable { this.searchIssues(title, fileOnExtension, fileOnMarketplace); }); - this.previewButton.onDidClick(() => this.createIssue()); + this.previewButton.onDidClick(async () => { + this.delayedSubmit.trigger(async () => { + this.createIssue(); + }); + }); function sendWorkbenchCommand(commandId: string) { ipcRenderer.send('vscode:workbenchCommand', { id: commandId, from: 'issueReporter' }); @@ -383,9 +389,11 @@ export class IssueReporter extends Disposable { const cmdOrCtrlKey = isMacintosh ? e.metaKey : e.ctrlKey; // Cmd/Ctrl+Enter previews issue and closes window if (cmdOrCtrlKey && e.keyCode === 13) { - if (await this.createIssue()) { - ipcRenderer.send('vscode:closeIssueReporter'); - } + this.delayedSubmit.trigger(async () => { + if (await this.createIssue()) { + ipcRenderer.send('vscode:closeIssueReporter'); + } + }); } // Cmd/Ctrl + w closes issue window diff --git a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInput.css b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInput.css index fc302f49d68..c48737cfaf4 100644 --- a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInput.css +++ b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInput.css @@ -14,7 +14,7 @@ .vs .quick-input-widget .monaco-list-row.focused .monaco-highlighted-label .highlight, .vs .quick-input-widget .monaco-list-row.focused .monaco-highlighted-label .highlight { - color: #33B6FF; + color: #9DDDFF; } .vs-dark .quick-input-widget .monaco-highlighted-label .highlight, diff --git a/src/vs/editor/standalone/common/themes.ts b/src/vs/editor/standalone/common/themes.ts index 24084dacb5e..cf0b7945091 100644 --- a/src/vs/editor/standalone/common/themes.ts +++ b/src/vs/editor/standalone/common/themes.ts @@ -74,7 +74,7 @@ export const vs: IStandaloneThemeData = { [editorIndentGuides]: '#D3D3D3', [editorActiveIndentGuides]: '#939393', [editorSelectionHighlight]: '#ADD6FF4D', - [listFocusHighlightForeground]: '#33B6FF' + [listFocusHighlightForeground]: '#9DDDFF' } }; /* -------------------------------- End vs theme -------------------------------- */ diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 1b5db388b7f..779b2e1286c 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -63,13 +63,13 @@ export const OPTIONS: OptionDescriptions> = { 'verbose': { type: 'boolean', cat: 't', description: localize('verbose', "Print verbose output (implies --wait).") }, 'log': { type: 'string', cat: 't', args: 'level', description: localize('log', "Log level to use. Default is 'info'. Allowed values are 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'.") }, 'status': { type: 'boolean', alias: 's', cat: 't', description: localize('status', "Print process usage and diagnostics information.") }, - 'prof-startup': { type: 'boolean', cat: 't', description: localize('prof-startup', "Run CPU profiler during startup") }, + 'prof-startup': { type: 'boolean', cat: 't', description: localize('prof-startup', "Run CPU profiler during startup.") }, 'prof-append-timers': { type: 'string' }, 'prof-startup-prefix': { type: 'string' }, 'prof-v8-extensions': { type: 'boolean' }, 'disable-extensions': { type: 'boolean', deprecates: 'disableExtensions', cat: 't', description: localize('disableExtensions', "Disable all installed extensions.") }, 'disable-extension': { type: 'string[]', cat: 't', args: 'extension-id', description: localize('disableExtension', "Disable an extension.") }, - 'sync': { type: 'string', cat: 't', description: localize('turn sync', "Turn sync on or off"), args: ['on', 'off'] }, + 'sync': { type: 'string', cat: 't', description: localize('turn sync', "Turn sync on or off."), args: ['on', 'off'] }, 'inspect-extensions': { type: 'string', deprecates: 'debugPluginHost', args: 'port', cat: 't', description: localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection URI.") }, 'inspect-brk-extensions': { type: 'string', deprecates: 'debugBrkPluginHost', args: 'port', cat: 't', description: localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection URI.") }, diff --git a/src/vs/platform/log/node/spdlogLog.ts b/src/vs/platform/log/node/spdlogLog.ts index e9fd6a709ee..61c411cc675 100644 --- a/src/vs/platform/log/node/spdlogLog.ts +++ b/src/vs/platform/log/node/spdlogLog.ts @@ -11,6 +11,7 @@ async function createSpdLogLogger(name: string, logfilePath: string, filesize: n // Do not crash if spdlog cannot be loaded try { const _spdlog = await import('spdlog'); + _spdlog.setFlushOn(LogLevel.Info); return _spdlog.createAsyncRotatingLogger(name, logfilePath, filesize, filecount); } catch (e) { console.error(e); @@ -20,6 +21,7 @@ async function createSpdLogLogger(name: string, logfilePath: string, filesize: n export function createRotatingLogger(name: string, filename: string, filesize: number, filecount: number): Promise { const _spdlog: typeof spdlog = require.__$__nodeRequire('spdlog'); + _spdlog.setFlushOn(LogLevel.Info); return _spdlog.createRotatingLogger(name, filename, filesize, filecount); } @@ -38,7 +40,6 @@ function log(logger: spdlog.Logger, level: LogLevel, message: string): void { case LogLevel.Critical: logger.critical(message); break; default: throw new Error('Invalid log level'); } - logger.flush(); } export class SpdLogLogger extends AbstractMessageLogger implements ILogger { diff --git a/src/vs/platform/windows/electron-main/window.ts b/src/vs/platform/windows/electron-main/window.ts index 12c49860d48..c597738aa69 100644 --- a/src/vs/platform/windows/electron-main/window.ts +++ b/src/vs/platform/windows/electron-main/window.ts @@ -451,13 +451,16 @@ export class CodeWindow extends Disposable implements ICodeWindow { return false; }; + const isRequestFromSafeContext = (details: Electron.OnBeforeRequestListenerDetails | Electron.OnHeadersReceivedListenerDetails): boolean => { + return details.resourceType === 'xhr' || isSafeFrame(details.frame); + }; + this._win.webContents.session.webRequest.onBeforeRequest((details, callback) => { const uri = URI.parse(details.url); if (uri.path.endsWith('.svg')) { const isSafeResourceUrl = supportedSvgSchemes.has(uri.scheme) || uri.path.includes(Schemas.vscodeRemoteResource); if (!isSafeResourceUrl) { - const isSafeContext = isSafeFrame(details.frame); - return callback({ cancel: !isSafeContext }); + return callback({ cancel: !isRequestFromSafeContext(details) }); } } @@ -483,8 +486,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { // remote extension schemes have the following format // http://127.0.0.1:/vscode-remote-resource?path= if (!uri.path.includes(Schemas.vscodeRemoteResource) && contentTypes.some(contentType => contentType.toLowerCase().includes('image/svg'))) { - const isSafeContext = isSafeFrame(details.frame); - return callback({ cancel: !isSafeContext }); + return callback({ cancel: !isRequestFromSafeContext(details) }); } } diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 40cf4af251f..118837f3aad 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -2355,7 +2355,7 @@ declare module 'vscode' { * list of kinds may either be generic, such as `[CodeActionKind.Refactor]`, or list out every kind provided, * such as `[CodeActionKind.Refactor.Extract.append('function'), CodeActionKind.Refactor.Extract.append('constant'), ...]`. */ - readonly providedCodeActionKinds?: ReadonlyArray; + readonly providedCodeActionKinds?: readonly CodeActionKind[]; /** * Static documentation for a class of code actions. @@ -5584,6 +5584,14 @@ declare module 'vscode' { */ export interface StatusBarItem { + /** + * The identifier of this item. + * + * *Note*: if no identifier was provided by the {@link window.createStatusBarItem `window.createStatusBarItem`} + * method, the identifier will match the {@link Extension.id extension identifier}. + */ + readonly id: string; + /** * The alignment of this item. */ @@ -5595,6 +5603,13 @@ declare module 'vscode' { */ readonly priority?: number; + /** + * The name of the entry, like 'Python Language Indicator', 'Git Status' etc. + * Try to keep the length of the name short, yet descriptive enough that + * users can understand what the status bar item is about. + */ + name: string | undefined; + /** * The text to show for the entry. You can embed icons in the text by leveraging the syntax: * @@ -8804,6 +8819,16 @@ declare module 'vscode' { */ export function createStatusBarItem(alignment?: StatusBarAlignment, priority?: number): StatusBarItem; + /** + * Creates a status bar {@link StatusBarItem item}. + * + * @param id The unique identifier of the item. + * @param alignment The alignment of the item. + * @param priority The priority of the item. Higher values mean the item should be shown more to the left. + * @return A new status bar item. + */ + export function createStatusBarItem(id: string, alignment?: StatusBarAlignment, priority?: number): StatusBarItem; + /** * Creates a {@link Terminal} with a backing shell process. The cwd of the terminal will be the workspace * directory if it exists. diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 67845179a46..0fcaa4e05f2 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -938,51 +938,6 @@ declare module 'vscode' { } //#endregion - //#region Status bar item with ID and Name: https://github.com/microsoft/vscode/issues/74972 - - /** - * Options to configure the status bar item. - */ - export interface StatusBarItemOptions { - - /** - * An identifier for the item that should be unique. - */ - id: string; - - /** - * A human readable name of the item that explains the purpose - * of the item to the user. - */ - name: string; - - /** - * The alignment of the item. - */ - alignment?: StatusBarAlignment; - - /** - * The priority of the item. Higher values mean the item should be shown more to the left. - */ - priority?: number; - } - - export namespace window { - - /** - * Creates a status bar {@link StatusBarItem item}. - * - * @param options The options of the item. If not provided, some default values - * will be assumed. For example, the {@link StatusBarItemOptions.id `StatusBarItemOptions.id`} - * will be the id of the extension and the {@link StatusBarItemOptions.name `StatusBarItemOptions.name`} - * will be the extension name. - * @return A new status bar item. - */ - export function createStatusBarItem(options?: StatusBarItemOptions): StatusBarItem; - } - - //#endregion - //#region Custom editor move https://github.com/microsoft/vscode/issues/86146 // TODO: Also for custom editor diff --git a/src/vs/workbench/api/browser/mainThreadStatusBar.ts b/src/vs/workbench/api/browser/mainThreadStatusBar.ts index 3ba387186db..69f8d2cf92a 100644 --- a/src/vs/workbench/api/browser/mainThreadStatusBar.ts +++ b/src/vs/workbench/api/browser/mainThreadStatusBar.ts @@ -27,7 +27,7 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape { this.entries.clear(); } - $setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: Command | undefined, color: string | ThemeColor | undefined, backgroundColor: string | ThemeColor | undefined, alignment: MainThreadStatusBarAlignment, priority: number | undefined, accessibilityInformation: IAccessibilityInformation): void { + $setEntry(entryId: number, id: string, name: string, text: string, tooltip: string | undefined, command: Command | undefined, color: string | ThemeColor | undefined, backgroundColor: string | ThemeColor | undefined, alignment: MainThreadStatusBarAlignment, priority: number | undefined, accessibilityInformation: IAccessibilityInformation): void { // if there are icons in the text use the tooltip for the aria label let ariaLabel: string; let role: string | undefined = undefined; @@ -37,23 +37,23 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape { } else { ariaLabel = getCodiconAriaLabel(text); } - const entry: IStatusbarEntry = { text, tooltip, command, color, backgroundColor, ariaLabel, role }; + const entry: IStatusbarEntry = { name, text, tooltip, command, color, backgroundColor, ariaLabel, role }; if (typeof priority === 'undefined') { priority = 0; } // Reset existing entry if alignment or priority changed - let existingEntry = this.entries.get(id); + let existingEntry = this.entries.get(entryId); if (existingEntry && (existingEntry.alignment !== alignment || existingEntry.priority !== priority)) { dispose(existingEntry.accessor); - this.entries.delete(id); + this.entries.delete(entryId); existingEntry = undefined; } // Create new entry if not existing if (!existingEntry) { - this.entries.set(id, { accessor: this.statusbarService.addEntry(entry, statusId, statusName, alignment, priority), alignment, priority }); + this.entries.set(entryId, { accessor: this.statusbarService.addEntry(entry, id, alignment, priority), alignment, priority }); } // Otherwise update diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 5a605ced71d..55a5897d3af 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; @@ -150,7 +149,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostNotebookRenderers = rpcProtocol.set(ExtHostContext.ExtHostNotebookRenderers, new ExtHostNotebookRenderers(rpcProtocol, extHostNotebook)); const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors)); const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService)); - const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors, { ...initData.environment, remote: initData.remote })); + const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors, initData)); const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol, extHostLogService)); const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, uriTransformer, extHostDocuments, extHostCommands, extHostDiagnostics, extHostLogService, extHostApiDeprecation)); const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures)); @@ -163,7 +162,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostTheming = rpcProtocol.set(ExtHostContext.ExtHostTheming, new ExtHostTheming(rpcProtocol)); const extHostAuthentication = rpcProtocol.set(ExtHostContext.ExtHostAuthentication, new ExtHostAuthentication(rpcProtocol)); const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol, extHostCommands)); - const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, { ...initData.environment, remote: initData.remote }, extHostWorkspace, extHostLogService, extHostApiDeprecation)); + const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, { remote: initData.remote }, extHostWorkspace, extHostLogService, extHostApiDeprecation)); const extHostWebviewPanels = rpcProtocol.set(ExtHostContext.ExtHostWebviewPanels, new ExtHostWebviewPanels(rpcProtocol, extHostWebviews, extHostWorkspace)); const extHostCustomEditors = rpcProtocol.set(ExtHostContext.ExtHostCustomEditors, new ExtHostCustomEditors(rpcProtocol, extHostDocuments, extensionStoragePaths, extHostWebviews, extHostWebviewPanels)); const extHostWebviewViews = rpcProtocol.set(ExtHostContext.ExtHostWebviewViews, new ExtHostWebviewViews(rpcProtocol, extHostWebviews)); @@ -599,23 +598,21 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I showSaveDialog(options) { return extHostDialogs.showSaveDialog(options); }, - createStatusBarItem(alignmentOrOptions?: vscode.StatusBarAlignment | vscode.StatusBarItemOptions, priority?: number): vscode.StatusBarItem { - let id: string; - let name: string; + createStatusBarItem(alignmentOrId?: vscode.StatusBarAlignment | string, priorityOrAlignment?: number | vscode.StatusBarAlignment, priorityArg?: number): vscode.StatusBarItem { + let id: string | undefined; let alignment: number | undefined; + let priority: number | undefined; - if (alignmentOrOptions && typeof alignmentOrOptions !== 'number') { - id = alignmentOrOptions.id; - name = alignmentOrOptions.name; - alignment = alignmentOrOptions.alignment; - priority = alignmentOrOptions.priority; + if (typeof alignmentOrId === 'string') { + id = alignmentOrId; + alignment = priorityOrAlignment; + priority = priorityArg; } else { - id = extension.identifier.value; - name = nls.localize('extensionLabel', "{0} (Extension)", extension.displayName || extension.name); - alignment = alignmentOrOptions; + alignment = alignmentOrId; + priority = priorityOrAlignment; } - return extHostStatusBar.createStatusBarEntry(id, name, alignment, priority); + return extHostStatusBar.createStatusBarEntry(extension, id, alignment, priority); }, setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable): vscode.Disposable { return extHostStatusBar.setStatusBarMessage(text, timeoutOrThenable); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 68dedb83461..e9473957ba6 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -74,8 +74,6 @@ export interface IEnvironment { extensionTestsLocationURI?: URI; globalStorageHome: URI; workspaceStorageHome: URI; - webviewResourceRoot: string; - webviewCspSource: string; useHostProxy?: boolean; } diff --git a/src/vs/workbench/api/common/extHostCodeInsets.ts b/src/vs/workbench/api/common/extHostCodeInsets.ts index 7db034cd81f..1ee7661d9fd 100644 --- a/src/vs/workbench/api/common/extHostCodeInsets.ts +++ b/src/vs/workbench/api/common/extHostCodeInsets.ts @@ -10,7 +10,7 @@ import { ExtHostTextEditor } from 'vs/workbench/api/common/extHostTextEditor'; import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors'; import type * as vscode from 'vscode'; import { ExtHostEditorInsetsShape, MainThreadEditorInsetsShape } from './extHost.protocol'; -import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; +import { asWebviewUri, webviewGenericCspSource, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; import { generateUuid } from 'vs/base/common/uuid'; export class ExtHostEditorInsets implements ExtHostEditorInsetsShape { @@ -66,11 +66,11 @@ export class ExtHostEditorInsets implements ExtHostEditorInsetsShape { private _options: vscode.WebviewOptions = Object.create(null); asWebviewUri(resource: vscode.Uri): vscode.Uri { - return asWebviewUri(that._initData, this._uuid, resource); + return asWebviewUri(this._uuid, resource, that._initData.remote.authority); } get cspSource(): string { - return that._initData.webviewCspSource; + return webviewGenericCspSource; } set options(value: vscode.WebviewOptions) { diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index f95d887f8f2..cc0be03859d 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -190,7 +190,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { return that._proxy.$postMessage(handle, editor && that._extHostNotebook.getIdByEditor(editor), message); }, asWebviewUri(uri: URI) { - return asWebviewUri({ ...that._initData.environment, remote: that._initData.remote }, String(handle), uri); + return asWebviewUri(String(handle), uri, that._initData.remote.authority); }, // --- priority updateNotebookAffinity(notebook, priority) { diff --git a/src/vs/workbench/api/common/extHostStatusBar.ts b/src/vs/workbench/api/common/extHostStatusBar.ts index 9c875cdc084..f85f182011e 100644 --- a/src/vs/workbench/api/common/extHostStatusBar.ts +++ b/src/vs/workbench/api/common/extHostStatusBar.ts @@ -10,8 +10,10 @@ import { MainContext, MainThreadStatusBarShape, IMainContext, ICommandDto } from import { localize } from 'vs/nls'; import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; export class ExtHostStatusBarEntry implements vscode.StatusBarItem { + private static ID_GEN = 0; private static ALLOWED_BACKGROUND_COLORS = new Map( @@ -21,17 +23,20 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { #proxy: MainThreadStatusBarShape; #commands: CommandsConverter; - private _id: number; + private _entryId: number; + + private _extension?: IExtensionDescription; + + private _id?: string; private _alignment: number; private _priority?: number; + private _disposed: boolean = false; private _visible: boolean = false; - private _statusId: string; - private _statusName: string; - private _text: string = ''; private _tooltip?: string; + private _name?: string; private _color?: string | ThemeColor; private _backgroundColor?: ThemeColor; private readonly _internalCommandRegistration = new DisposableStore(); @@ -43,19 +48,23 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { private _timeoutHandle: any; private _accessibilityInformation?: vscode.AccessibilityInformation; - constructor(proxy: MainThreadStatusBarShape, commands: CommandsConverter, id: string, name: string, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number) { + constructor(proxy: MainThreadStatusBarShape, commands: CommandsConverter, extension: IExtensionDescription, id?: string, alignment?: ExtHostStatusBarAlignment, priority?: number); + constructor(proxy: MainThreadStatusBarShape, commands: CommandsConverter, extension: IExtensionDescription | undefined, id: string, alignment?: ExtHostStatusBarAlignment, priority?: number); + constructor(proxy: MainThreadStatusBarShape, commands: CommandsConverter, extension?: IExtensionDescription, id?: string, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number) { this.#proxy = proxy; this.#commands = commands; - this._id = ExtHostStatusBarEntry.ID_GEN++; - this._statusId = id; - this._statusName = name; + this._entryId = ExtHostStatusBarEntry.ID_GEN++; + + this._extension = extension; + + this._id = id; this._alignment = alignment; this._priority = priority; } - public get id(): number { - return this._id; + public get id(): string { + return this._id ?? this._extension!.identifier.value; } public get alignment(): vscode.StatusBarAlignment { @@ -70,6 +79,10 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { return this._text; } + public get name(): string | undefined { + return this._name; + } + public get tooltip(): string | undefined { return this._tooltip; } @@ -95,6 +108,11 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { this.update(); } + public set name(name: string | undefined) { + this._name = name; + this.update(); + } + public set tooltip(tooltip: string | undefined) { this._tooltip = tooltip; this.update(); @@ -149,7 +167,7 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { public hide(): void { clearTimeout(this._timeoutHandle); this._visible = false; - this.#proxy.$dispose(this.id); + this.#proxy.$dispose(this._entryId); } private update(): void { @@ -163,6 +181,28 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { this._timeoutHandle = setTimeout(() => { this._timeoutHandle = undefined; + // If the id is not set, derive it from the extension identifier, + // otherwise make sure to prefix it with the extension identifier + // to get a more unique value across extensions. + let id: string; + if (this._extension) { + if (this._id) { + id = `${this._extension.identifier.value}.${this._id}`; + } else { + id = this._extension.identifier.value; + } + } else { + id = this._id!; + } + + // If the name is not set, derive it from the extension descriptor + let name: string; + if (this._name) { + name = this._name; + } else { + name = localize('extensionLabel', "{0} (Extension)", this._extension!.displayName || this._extension!.name); + } + // If a background color is set, the foreground is determined let color = this._color; if (this._backgroundColor) { @@ -170,7 +210,7 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { } // Set to status bar - this.#proxy.$setEntry(this.id, this._statusId, this._statusName, this._text, this._tooltip, this._command?.internal, color, + this.#proxy.$setEntry(this._entryId, id, name, this._text, this._tooltip, this._command?.internal, color, this._backgroundColor, this._alignment === ExtHostStatusBarAlignment.Left ? MainThreadStatusBarAlignment.LEFT : MainThreadStatusBarAlignment.RIGHT, this._priority, this._accessibilityInformation); }, 0); @@ -188,7 +228,8 @@ class StatusBarMessage { private _messages: { message: string }[] = []; constructor(statusBar: ExtHostStatusBar) { - this._item = statusBar.createStatusBarEntry('status.extensionMessage', localize('status.extensionMessage', "Extension Status"), ExtHostStatusBarAlignment.Left, Number.MIN_VALUE); + this._item = statusBar.createStatusBarEntry(undefined, 'status.extensionMessage', ExtHostStatusBarAlignment.Left, Number.MIN_VALUE); + this._item.name = localize('status.extensionMessage', "Extension Status"); } dispose() { @@ -232,12 +273,13 @@ export class ExtHostStatusBar { this._statusMessage = new StatusBarMessage(this); } - createStatusBarEntry(id: string, name: string, alignment?: ExtHostStatusBarAlignment, priority?: number): vscode.StatusBarItem { - return new ExtHostStatusBarEntry(this._proxy, this._commands, id, name, alignment, priority); + createStatusBarEntry(extension: IExtensionDescription | undefined, id: string, alignment?: ExtHostStatusBarAlignment, priority?: number): vscode.StatusBarItem; + createStatusBarEntry(extension: IExtensionDescription, id?: string, alignment?: ExtHostStatusBarAlignment, priority?: number): vscode.StatusBarItem; + createStatusBarEntry(extension: IExtensionDescription, id: string, alignment?: ExtHostStatusBarAlignment, priority?: number): vscode.StatusBarItem { + return new ExtHostStatusBarEntry(this._proxy, this._commands, extension, id, alignment, priority); } setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable): Disposable { - const d = this._statusMessage.setMessage(text); let handle: any; diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index d7cd5f11644..b7bbde06834 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -12,7 +12,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; import { serializeWebviewMessage, deserializeWebviewMessage } from 'vs/workbench/api/common/extHostWebviewMessaging'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; -import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; +import { asWebviewUri, webviewGenericCspSource, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; import type * as vscode from 'vscode'; import * as extHostProtocol from './extHost.protocol'; @@ -69,12 +69,11 @@ export class ExtHostWebview implements vscode.Webview { public asWebviewUri(resource: vscode.Uri): vscode.Uri { this.#hasCalledAsWebviewUri = true; - return asWebviewUri(this.#initData, this.#handle, resource); + return asWebviewUri(this.#handle, resource, this.#initData.remote.authority); } public get cspSource(): string { - return this.#initData.webviewCspSource - .replace('{{uuid}}', this.#handle); + return webviewGenericCspSource; } public get html(): string { diff --git a/src/vs/workbench/api/common/shared/webview.ts b/src/vs/workbench/api/common/shared/webview.ts index f1de57db839..13ec3d02943 100644 --- a/src/vs/workbench/api/common/shared/webview.ts +++ b/src/vs/workbench/api/common/shared/webview.ts @@ -3,16 +3,26 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import type * as vscode from 'vscode'; export interface WebviewInitData { - readonly isExtensionDevelopmentDebug: boolean; - readonly webviewResourceRoot: string; - readonly webviewCspSource: string; readonly remote: { readonly authority: string | undefined }; } +/** + * Location where we load resources from + * + * There endpoints can be hardcoded because we never expect to actually hit them. Instead these requests + * should always go to a service worker. + */ +export const webviewResourceOrigin = (id: string) => `https://${id}.vscode-webview-test.com`; + +export const webviewResourceRoot = (id: string) => `${webviewResourceOrigin(id)}/vscode-resource/{{resource}}`; + +export const webviewGenericCspSource = 'https://*.vscode-webview-test.com'; + /** * Construct a uri that can load resources inside a webview * @@ -20,16 +30,32 @@ export interface WebviewInitData { * we know where to load the resource from (remote or truly local): * * ```txt - * /remote-authority?/scheme/resource-authority/path... + * scheme/resource-authority/path... * ``` + * + * @param uuid Unique id of the webview. + * @param resource Uri of the resource to load. + * @param fromAuthority Optional remote authority that specifies where `resource` should be resolved from. */ export function asWebviewUri( - initData: WebviewInitData, uuid: string, resource: vscode.Uri, + fromAuthority: string | undefined ): vscode.Uri { - const uri = initData.webviewResourceRoot - .replace('{{resource}}', (initData.remote.authority ?? '') + '/' + resource.scheme + '/' + encodeURIComponent(resource.authority) + resource.path) + if (resource.scheme === Schemas.http || resource.scheme === Schemas.https) { + return resource; + } + + if (fromAuthority && resource.scheme === Schemas.file) { + resource = URI.from({ + scheme: Schemas.vscodeRemote, + authority: fromAuthority, + path: resource.path, + }); + } + + const uri = webviewResourceRoot(uuid) + .replace('{{resource}}', resource.scheme + '/' + encodeURIComponent(resource.authority) + resource.path) .replace('{{uuid}}', uuid); return URI.parse(uri).with({ fragment: resource.fragment, diff --git a/src/vs/workbench/api/node/extHostOutputService.ts b/src/vs/workbench/api/node/extHostOutputService.ts index 8daae98da4e..78a955a7330 100644 --- a/src/vs/workbench/api/node/extHostOutputService.ts +++ b/src/vs/workbench/api/node/extHostOutputService.ts @@ -32,7 +32,6 @@ class OutputAppender { append(content: string): void { this.appender.critical(content); - this.flush(); } flush(): void { diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index e199ec8a9b0..651f3cf61f5 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -1366,6 +1366,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.workbenchGrid.setViewVisible(this.activityBarPartView, !hidden); } + setBannerHidden(hidden: boolean): void { + this.workbenchGrid.setViewVisible(this.bannerPartView, !hidden); + } + setEditorHidden(hidden: boolean, skipLayout?: boolean): void { this.state.editor.hidden = hidden; @@ -1806,7 +1810,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi { type: 'leaf', data: { type: Parts.BANNER_PART }, - size: bannerHeight + size: bannerHeight, + visible: false }, { type: 'branch', diff --git a/src/vs/workbench/browser/parts/banner/bannerPart.ts b/src/vs/workbench/browser/parts/banner/bannerPart.ts index 4a042ca9d19..0a9e336bb29 100644 --- a/src/vs/workbench/browser/parts/banner/bannerPart.ts +++ b/src/vs/workbench/browser/parts/banner/bannerPart.ts @@ -10,7 +10,7 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Codicon, registerCodicon } from 'vs/base/common/codicons'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IStorageService, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IStorageService } from 'vs/platform/storage/common/storage'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { Part } from 'vs/workbench/browser/part'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; @@ -96,9 +96,9 @@ export class BannerPart extends Part implements IBannerService { constructor( @IThemeService themeService: IThemeService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @IStorageService storageService: IStorageService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IStorageService private readonly storageService: IStorageService, ) { super(Parts.BANNER_PART, { hasTitle: false }, themeService, storageService, layoutService); @@ -131,8 +131,8 @@ export class BannerPart extends Part implements IBannerService { clearNode(this.element); // Remember choice - if (item.scope) { - this.storageService.store(item.id, true, item.scope, StorageTarget.USER); + if (typeof item.onClose === 'function') { + item.onClose(); } this.item = undefined; @@ -178,6 +178,7 @@ export class BannerPart extends Part implements IBannerService { this.visible = visible; this.focusedActionIndex = -1; + this.layoutService.setBannerHidden(!visible); this._onDidChangeSize.fire(undefined); } } @@ -210,10 +211,6 @@ export class BannerPart extends Part implements IBannerService { } show(item: IBannerItem): void { - if (item.scope && this.storageService.getBoolean(item.id, item.scope, false)) { - return; - } - if (item.id === this.item?.id) { this.setVisibility(true); return; diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 4c2e352c455..75d2c9bc6a4 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -404,13 +404,14 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { if (!this.tabFocusModeElement.value) { const text = localize('tabFocusModeEnabled', "Tab Moves Focus"); this.tabFocusModeElement.value = this.statusbarService.addEntry({ + name: localize('status.editor.tabFocusMode', "Accessibility Mode"), text, ariaLabel: text, tooltip: localize('disableTabMode', "Disable Accessibility Mode"), command: 'editor.action.toggleTabFocusMode', backgroundColor: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_BACKGROUND), color: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_FOREGROUND) - }, 'status.editor.tabFocusMode', localize('status.editor.tabFocusMode', "Accessibility Mode"), StatusbarAlignment.RIGHT, 100.7); + }, 'status.editor.tabFocusMode', StatusbarAlignment.RIGHT, 100.7); } } else { this.tabFocusModeElement.clear(); @@ -422,13 +423,14 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { if (!this.columnSelectionModeElement.value) { const text = localize('columnSelectionModeEnabled', "Column Selection"); this.columnSelectionModeElement.value = this.statusbarService.addEntry({ + name: localize('status.editor.columnSelectionMode', "Column Selection Mode"), text, ariaLabel: text, tooltip: localize('disableColumnSelectionMode', "Disable Column Selection Mode"), command: 'editor.action.toggleColumnSelection', backgroundColor: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_BACKGROUND), color: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_FOREGROUND) - }, 'status.editor.columnSelectionMode', localize('status.editor.columnSelectionMode', "Column Selection Mode"), StatusbarAlignment.RIGHT, 100.8); + }, 'status.editor.columnSelectionMode', StatusbarAlignment.RIGHT, 100.8); } } else { this.columnSelectionModeElement.clear(); @@ -440,12 +442,13 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { if (!this.screenRedearModeElement.value) { const text = localize('screenReaderDetected', "Screen Reader Optimized"); this.screenRedearModeElement.value = this.statusbarService.addEntry({ + name: localize('status.editor.screenReaderMode', "Screen Reader Mode"), text, ariaLabel: text, command: 'showEditorScreenReaderNotification', backgroundColor: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_BACKGROUND), color: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_FOREGROUND) - }, 'status.editor.screenReaderMode', localize('status.editor.screenReaderMode', "Screen Reader Mode"), StatusbarAlignment.RIGHT, 100.6); + }, 'status.editor.screenReaderMode', StatusbarAlignment.RIGHT, 100.6); } } else { this.screenRedearModeElement.clear(); @@ -459,13 +462,14 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } const props: IStatusbarEntry = { + name: localize('status.editor.selection', "Editor Selection"), text, ariaLabel: text, tooltip: localize('gotoLine', "Go to Line/Column"), command: 'workbench.action.gotoLine' }; - this.updateElement(this.selectionElement, props, 'status.editor.selection', localize('status.editor.selection', "Editor Selection"), StatusbarAlignment.RIGHT, 100.5); + this.updateElement(this.selectionElement, props, 'status.editor.selection', StatusbarAlignment.RIGHT, 100.5); } private updateIndentationElement(text: string | undefined): void { @@ -475,13 +479,14 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } const props: IStatusbarEntry = { + name: localize('status.editor.indentation', "Editor Indentation"), text, ariaLabel: text, tooltip: localize('selectIndentation', "Select Indentation"), command: 'changeEditorIndentation' }; - this.updateElement(this.indentationElement, props, 'status.editor.indentation', localize('status.editor.indentation', "Editor Indentation"), StatusbarAlignment.RIGHT, 100.4); + this.updateElement(this.indentationElement, props, 'status.editor.indentation', StatusbarAlignment.RIGHT, 100.4); } private updateEncodingElement(text: string | undefined): void { @@ -491,13 +496,14 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } const props: IStatusbarEntry = { + name: localize('status.editor.encoding', "Editor Encoding"), text, ariaLabel: text, tooltip: localize('selectEncoding', "Select Encoding"), command: 'workbench.action.editor.changeEncoding' }; - this.updateElement(this.encodingElement, props, 'status.editor.encoding', localize('status.editor.encoding', "Editor Encoding"), StatusbarAlignment.RIGHT, 100.3); + this.updateElement(this.encodingElement, props, 'status.editor.encoding', StatusbarAlignment.RIGHT, 100.3); } private updateEOLElement(text: string | undefined): void { @@ -507,13 +513,14 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } const props: IStatusbarEntry = { + name: localize('status.editor.eol', "Editor End of Line"), text, ariaLabel: text, tooltip: localize('selectEOL', "Select End of Line Sequence"), command: 'workbench.action.editor.changeEOL' }; - this.updateElement(this.eolElement, props, 'status.editor.eol', localize('status.editor.eol', "Editor End of Line"), StatusbarAlignment.RIGHT, 100.2); + this.updateElement(this.eolElement, props, 'status.editor.eol', StatusbarAlignment.RIGHT, 100.2); } private updateModeElement(text: string | undefined): void { @@ -523,13 +530,14 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } const props: IStatusbarEntry = { + name: localize('status.editor.mode', "Editor Language"), text, ariaLabel: text, tooltip: localize('selectLanguageMode', "Select Language Mode"), command: 'workbench.action.editor.changeLanguageMode' }; - this.updateElement(this.modeElement, props, 'status.editor.mode', localize('status.editor.mode', "Editor Language"), StatusbarAlignment.RIGHT, 100.1); + this.updateElement(this.modeElement, props, 'status.editor.mode', StatusbarAlignment.RIGHT, 100.1); } private updateMetadataElement(text: string | undefined): void { @@ -539,17 +547,18 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } const props: IStatusbarEntry = { + name: localize('status.editor.info', "File Information"), text, ariaLabel: text, tooltip: localize('fileInfo', "File Information") }; - this.updateElement(this.metadataElement, props, 'status.editor.info', localize('status.editor.info', "File Information"), StatusbarAlignment.RIGHT, 100); + this.updateElement(this.metadataElement, props, 'status.editor.info', StatusbarAlignment.RIGHT, 100); } - private updateElement(element: MutableDisposable, props: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority: number) { + private updateElement(element: MutableDisposable, props: IStatusbarEntry, id: string, alignment: StatusbarAlignment, priority: number) { if (!element.value) { - element.value = this.statusbarService.addEntry(props, id, name, alignment, priority); + element.value = this.statusbarService.addEntry(props, id, alignment, priority); } else { element.value.update(props); } @@ -923,9 +932,9 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable { const line = splitLines(this.currentMarker.message)[0]; const text = `${this.getType(this.currentMarker)} ${line}`; if (!this.statusBarEntryAccessor.value) { - this.statusBarEntryAccessor.value = this.statusbarService.addEntry({ text: '', ariaLabel: '' }, 'statusbar.currentProblem', localize('currentProblem', "Current Problem"), StatusbarAlignment.LEFT); + this.statusBarEntryAccessor.value = this.statusbarService.addEntry({ name: localize('currentProblem', "Current Problem"), text: '', ariaLabel: '' }, 'statusbar.currentProblem', StatusbarAlignment.LEFT); } - this.statusBarEntryAccessor.value.update({ text, ariaLabel: text }); + this.statusBarEntryAccessor.value.update({ name: localize('currentProblem', "Current Problem"), text, ariaLabel: text }); } else { this.statusBarEntryAccessor.clear(); } diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index e2d0ed9a806..028cf936329 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -290,7 +290,7 @@ export abstract class TitleControl extends Themable { return false; } - const editorOptions: ITextEditorOptions = { + let editorOptions: ITextEditorOptions = { viewState: (() => { if (this.group.activeEditor === editor) { const activeControl = this.group.activeEditorPane?.getControl(); @@ -304,6 +304,14 @@ export abstract class TitleControl extends Themable { sticky: this.group.isSticky(editor) }; + // If it's a custom editor or a notebook add the viewtype + if ((editor as object).hasOwnProperty('viewType')) { + interface EditorInputWithViewType extends IEditorInput { + viewType: string; + } + editorOptions = { ...editorOptions, override: (editor as EditorInputWithViewType).viewType }; + } + this.instantiationService.invokeFunction(fillResourceDataTransfers, [resource], () => editorOptions, e); return true; diff --git a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts index 228208bd180..063cf045525 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts @@ -71,6 +71,7 @@ export class NotificationsStatus extends Disposable { // Show the bell with a dot if there are unread or in-progress notifications const statusProperties: IStatusbarEntry = { + name: localize('status.notifications', "Notifications"), text: `${notificationsInProgress > 0 || this.newNotificationsCount > 0 ? '$(bell-dot)' : '$(bell)'}`, ariaLabel: localize('status.notifications', "Notifications"), command: this.isNotificationsCenterVisible ? HIDE_NOTIFICATIONS_CENTER : SHOW_NOTIFICATIONS_CENTER, @@ -82,7 +83,6 @@ export class NotificationsStatus extends Disposable { this.notificationsCenterStatusItem = this.statusbarService.addEntry( statusProperties, 'status.notifications', - localize('status.notifications', "Notifications"), StatusbarAlignment.RIGHT, -Number.MAX_VALUE /* towards the far end of the right hand side */ ); @@ -180,9 +180,12 @@ export class NotificationsStatus extends Disposable { let statusMessageEntry: IStatusbarEntryAccessor; let showHandle: any = setTimeout(() => { statusMessageEntry = this.statusbarService.addEntry( - { text: message, ariaLabel: message }, + { + name: localize('status.message', "Status Message"), + text: message, + ariaLabel: message + }, 'status.message', - localize('status.message', "Status Message"), StatusbarAlignment.LEFT, -Number.MAX_VALUE /* far right on left hand side */ ); diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index a2e0dcc2ffc..aa59b862c83 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -66,7 +66,6 @@ interface IStatusbarEntryPriority { interface IPendingStatusbarEntry { id: string; - name: string; entry: IStatusbarEntry; alignment: StatusbarAlignment; priority: IStatusbarEntryPriority; @@ -445,7 +444,7 @@ export class StatusbarPart extends Part implements IStatusbarService { this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateStyles())); } - addEntry(entry: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, primaryPriority = 0): IStatusbarEntryAccessor { + addEntry(entry: IStatusbarEntry, id: string, alignment: StatusbarAlignment, primaryPriority = 0): IStatusbarEntryAccessor { const priority: IStatusbarEntryPriority = { primary: primaryPriority, secondary: hash(id) // derive from identifier to accomplish uniqueness @@ -454,15 +453,15 @@ export class StatusbarPart extends Part implements IStatusbarService { // As long as we have not been created into a container yet, record all entries // that are pending so that they can get created at a later point if (!this.element) { - return this.doAddPendingEntry(entry, id, name, alignment, priority); + return this.doAddPendingEntry(entry, id, alignment, priority); } // Otherwise add to view - return this.doAddEntry(entry, id, name, alignment, priority); + return this.doAddEntry(entry, id, alignment, priority); } - private doAddPendingEntry(entry: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority: IStatusbarEntryPriority): IStatusbarEntryAccessor { - const pendingEntry: IPendingStatusbarEntry = { entry, id, name, alignment, priority }; + private doAddPendingEntry(entry: IStatusbarEntry, id: string, alignment: StatusbarAlignment, priority: IStatusbarEntryPriority): IStatusbarEntryAccessor { + const pendingEntry: IPendingStatusbarEntry = { entry, id, alignment, priority }; this.pendingEntries.push(pendingEntry); const accessor: IStatusbarEntryAccessor = { @@ -486,7 +485,7 @@ export class StatusbarPart extends Part implements IStatusbarService { return accessor; } - private doAddEntry(entry: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority: IStatusbarEntryPriority): IStatusbarEntryAccessor { + private doAddEntry(entry: IStatusbarEntry, id: string, alignment: StatusbarAlignment, priority: IStatusbarEntryPriority): IStatusbarEntryAccessor { // Create item const itemContainer = this.doCreateStatusItem(id, alignment, ...coalesce([entry.showBeak ? 'has-beak' : undefined])); @@ -496,7 +495,15 @@ export class StatusbarPart extends Part implements IStatusbarService { this.appendOneStatusbarEntry(itemContainer, alignment, priority); // Add to view model - const viewModelEntry: IStatusbarViewModelEntry = { id, name, alignment, priority, container: itemContainer, labelContainer: item.labelContainer }; + const viewModelEntry: IStatusbarViewModelEntry = new class implements IStatusbarViewModelEntry { + readonly id = id; + readonly alignment = alignment; + readonly priority = priority; + readonly container = itemContainer; + readonly labelContainer = item.labelContainer; + + get name() { return item.name; } + }; const viewModelEntryDispose = this.viewModel.add(viewModelEntry); return { @@ -580,7 +587,7 @@ export class StatusbarPart extends Part implements IStatusbarService { while (this.pendingEntries.length) { const pending = this.pendingEntries.shift(); if (pending) { - pending.accessor = this.addEntry(pending.entry, pending.id, pending.name, pending.alignment, pending.priority.primary); + pending.accessor = this.addEntry(pending.entry, pending.id, pending.alignment, pending.priority.primary); } } } @@ -815,6 +822,7 @@ class StatusbarEntryItem extends Disposable { private readonly label: StatusBarCodiconLabel; private entry: IStatusbarEntry | undefined = undefined; + get name(): string { return assertIsDefined(this.entry).name; } private readonly foregroundListener = this._register(new MutableDisposable()); private readonly backgroundListener = this._register(new MutableDisposable()); diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 29cb607489a..8e906203cc8 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -859,12 +859,18 @@ class TreeRenderer extends Disposable implements ITreeRenderer { - return this.hoverService.showHover(options); + return this.hoverService.showHover({ + ...options, + linkHandler: (url: string) => { + return openerService.open(url, { allowCommands: (!isString(options.text) && options.text.isTrusted) }); + } + }); }, delay: this.configurationService.getValue('workbench.hover.delay') }; diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 05a3439914b..c862f63c9cc 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -338,7 +338,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ id: CONTINUE_ID, weight: KeybindingWeight.WorkbenchContrib + 10, // Use a stronger weight to get priority over start debugging F5 shortcut primary: KeyCode.F5, - when: CONTEXT_IN_DEBUG_MODE, + when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'), handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { getThreadAndRun(accessor, context, thread => thread.continue()); } @@ -389,7 +389,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ id: DEBUG_START_COMMAND_ID, weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.F5, - when: ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing))), + when: ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.isEqualTo('inactive')), handler: async (accessor: ServicesAccessor, debugStartOptions?: { config?: Partial; noDebug?: boolean }) => { const debugService = accessor.get(IDebugService); let { launch, name, getConfig } = debugService.getConfigurationManager().selectedConfiguration; diff --git a/src/vs/workbench/contrib/debug/browser/debugStatus.ts b/src/vs/workbench/contrib/debug/browser/debugStatus.ts index a83612abdca..91806d76519 100644 --- a/src/vs/workbench/contrib/debug/browser/debugStatus.ts +++ b/src/vs/workbench/contrib/debug/browser/debugStatus.ts @@ -23,7 +23,7 @@ export class DebugStatusContribution implements IWorkbenchContribution { ) { const addStatusBarEntry = () => { - this.entryAccessor = this.statusBarService.addEntry(this.entry, 'status.debug', nls.localize('status.debug', "Debug"), StatusbarAlignment.LEFT, 30 /* Low Priority */); + this.entryAccessor = this.statusBarService.addEntry(this.entry, 'status.debug', StatusbarAlignment.LEFT, 30 /* Low Priority */); }; const setShowInStatusBar = () => { @@ -65,6 +65,7 @@ export class DebugStatusContribution implements IWorkbenchContribution { } return { + name: nls.localize('status.debug', "Debug"), text: '$(debug-alt-small) ' + text, ariaLabel: nls.localize('debugTarget', "Debug: {0}", text), tooltip: nls.localize('selectAndStartDebug', "Select and start debug configuration"), diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts index e93f8f483df..ac64051f0aa 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts @@ -82,6 +82,7 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio if (visible) { const indicator: IStatusbarEntry = { + name: nls.localize('status.profiler', "Extension Profiler"), text: nls.localize('profilingExtensionHost', "Profiling Extension Host"), showProgress: true, ariaLabel: nls.localize('profilingExtensionHost', "Profiling Extension Host"), @@ -98,7 +99,7 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio this.profilingStatusBarIndicatorLabelUpdater.value = toDisposable(() => clearInterval(handle)); if (!this.profilingStatusBarIndicator) { - this.profilingStatusBarIndicator = this._statusbarService.addEntry(indicator, 'status.profiler', nls.localize('status.profiler', "Extension Profiler"), StatusbarAlignment.RIGHT); + this.profilingStatusBarIndicator = this._statusbarService.addEntry(indicator, 'status.profiler', StatusbarAlignment.RIGHT); } else { this.profilingStatusBarIndicator.update(indicator); } diff --git a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts index 07b502f176f..7708f4a8f65 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts @@ -80,7 +80,7 @@ export class FeedbackStatusbarConribution extends Disposable implements IWorkben private createFeedbackStatusEntry(): void { // Status entry - this.entry = this._register(this.statusbarService.addEntry(this.getStatusEntry(), 'status.feedback', localize('status.feedback', "Tweet Feedback"), StatusbarAlignment.RIGHT, -100 /* towards the end of the right hand side */)); + this.entry = this._register(this.statusbarService.addEntry(this.getStatusEntry(), 'status.feedback', StatusbarAlignment.RIGHT, -100 /* towards the end of the right hand side */)); // Command to toggle CommandsRegistry.registerCommand(FeedbackStatusbarConribution.TOGGLE_FEEDBACK_COMMAND, () => this.toggleFeedback()); @@ -136,6 +136,7 @@ export class FeedbackStatusbarConribution extends Disposable implements IWorkben private getStatusEntry(showBeak?: boolean): IStatusbarEntry { return { + name: localize('status.feedback.name', "Feedback"), text: '$(feedback)', ariaLabel: localize('status.feedback', "Tweet Feedback"), tooltip: localize('status.feedback', "Tweet Feedback"), diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index e0d049f9a56..14b184c6cdb 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -374,7 +374,7 @@ class MarkersStatusBarContributions extends Disposable implements IWorkbenchCont @IStatusbarService private readonly statusbarService: IStatusbarService ) { super(); - this.markersStatusItem = this._register(this.statusbarService.addEntry(this.getMarkersItem(), 'status.problems', localize('status.problems', "Problems"), StatusbarAlignment.LEFT, 50 /* Medium Priority */)); + this.markersStatusItem = this._register(this.statusbarService.addEntry(this.getMarkersItem(), 'status.problems', StatusbarAlignment.LEFT, 50 /* Medium Priority */)); this.markerService.onMarkerChanged(() => this.markersStatusItem.update(this.getMarkersItem())); } @@ -382,6 +382,7 @@ class MarkersStatusBarContributions extends Disposable implements IWorkbenchCont const markersStatistics = this.markerService.getStatistics(); const tooltip = this.getMarkersTooltip(markersStatistics); return { + name: localize('status.problems', "Problems"), text: this.getMarkersText(markersStatistics), ariaLabel: tooltip, tooltip, diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts b/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts index f6334bcde3f..4a415850196 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts @@ -289,13 +289,13 @@ export class KernelStatus extends Disposable implements IWorkbenchContribution { const tooltip = kernel.description ?? kernel.detail ?? kernel.label; this._kernelInfoElement.add(this._statusbarService.addEntry( { + name: nls.localize('notebook.info', "Notebook Kernel Info"), text: `$(notebook-kernel-select) ${kernel.label}`, ariaLabel: kernel.label, tooltip: isSuggested ? nls.localize('tooltop', "{0} (suggestion)", tooltip) : tooltip, command: SELECT_KERNEL_ID, }, 'notebook.selectKernel', - nls.localize('notebook.info', "Notebook Kernel Info"), StatusbarAlignment.RIGHT, 10 )); @@ -307,13 +307,13 @@ export class KernelStatus extends Disposable implements IWorkbenchContribution { // multiple kernels -> show selection hint this._kernelInfoElement.add(this._statusbarService.addEntry( { + name: nls.localize('notebook.select', "Notebook Kernel Selection"), text: nls.localize('kernel.select.label', "Select Kernel"), ariaLabel: nls.localize('kernel.select.label', "Select Kernel"), command: SELECT_KERNEL_ID, backgroundColor: { id: 'statusBarItem.prominentBackground' } }, 'notebook.selectKernel', - nls.localize('notebook.select', "Notebook Kernel Selection"), StatusbarAlignment.RIGHT, 10 )); @@ -361,12 +361,11 @@ export class ActiveCellStatus extends Disposable implements IWorkbenchContributi return; } - const entry = { text: newText, ariaLabel: newText }; + const entry = { name: nls.localize('notebook.activeCellStatusName', "Notebook Editor Selections"), text: newText, ariaLabel: newText }; if (!this._accessor.value) { this._accessor.value = this._statusbarService.addEntry( entry, 'notebook.activeCellStatus', - nls.localize('notebook.activeCellStatusName', "Notebook Editor Selections"), StatusbarAlignment.RIGHT, 100 ); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts index d7b4f046c7f..9472f2455ba 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts @@ -143,10 +143,10 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD setMarkdownCellEditState(cellId: string, editState: CellEditState): void { // throw new Error('Method not implemented.'); } - markdownCellDragStart(cellId: string, position: { clientY: number }): void { + markdownCellDragStart(cellId: string, event: { dragOffsetY: number }): void { // throw new Error('Method not implemented.'); } - markdownCellDrag(cellId: string, position: { clientY: number }): void { + markdownCellDrag(cellId: string, event: { dragOffsetY: number }): void { // throw new Error('Method not implemented.'); } markdownCellDragEnd(cellId: string): void { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index d188dea2b16..b4dbbc4020b 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -170,16 +170,16 @@ export interface ICommonNotebookEditor { triggerScroll(event: IMouseWheelEvent): void; getCellByInfo(cellInfo: ICommonCellInfo): IGenericCellViewModel; getCellById(cellId: string): IGenericCellViewModel | undefined; - toggleNotebookCellSelection(cell: IGenericCellViewModel): void; + toggleNotebookCellSelection(cell: IGenericCellViewModel, selectFromPrevious: boolean): void; focusNotebookCell(cell: IGenericCellViewModel, focus: 'editor' | 'container' | 'output', options?: IFocusNotebookCellOptions): void; focusNextNotebookCell(cell: IGenericCellViewModel, focus: 'editor' | 'container' | 'output'): void; updateOutputHeight(cellInfo: ICommonCellInfo, output: IDisplayOutputViewModel, height: number, isInit: boolean, source?: string): void; scheduleOutputHeightAck(cellInfo: ICommonCellInfo, outputId: string, height: number): void; updateMarkdownCellHeight(cellId: string, height: number, isInit: boolean): void; setMarkdownCellEditState(cellId: string, editState: CellEditState): void; - markdownCellDragStart(cellId: string, position: { clientY: number }): void; - markdownCellDrag(cellId: string, position: { clientY: number }): void; - markdownCellDrop(cellId: string, position: { clientY: number, ctrlKey: boolean, altKey: boolean }): void; + markdownCellDragStart(cellId: string, event: { dragOffsetY: number }): void; + markdownCellDrag(cellId: string, event: { dragOffsetY: number }): void; + markdownCellDrop(cellId: string, event: { dragOffsetY: number, ctrlKey: boolean, altKey: boolean }): void; markdownCellDragEnd(cellId: string): void; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index a4c92f90a75..22840d525ad 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2050,19 +2050,38 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } } - toggleNotebookCellSelection(cell: ICellViewModel): void { + toggleNotebookCellSelection(selectedCell: ICellViewModel, selectFromPrevious: boolean): void { const currentSelections = this._list.getSelectedElements(); + const isSelected = currentSelections.includes(selectedCell); - const isSelected = currentSelections.includes(cell); + const previousSelection = selectFromPrevious ? currentSelections[currentSelections.length - 1] ?? selectedCell : selectedCell; + const selectedIndex = this._list.getViewIndex(selectedCell)!; + const previousIndex = this._list.getViewIndex(previousSelection)!; + + const cellsInSelectionRange = this.getCellsInRange(selectedIndex, previousIndex); if (isSelected) { // Deselect - this._list.selectElements(currentSelections.filter(current => current !== cell)); + this._list.selectElements(currentSelections.filter(current => !cellsInSelectionRange.includes(current))); } else { // Add to selection - this._list.selectElements([...currentSelections, cell]); + this.focusElement(selectedCell); + this._list.selectElements([...currentSelections.filter(current => !cellsInSelectionRange.includes(current)), ...cellsInSelectionRange]); } } + private getCellsInRange(fromInclusive: number, toInclusive: number): ICellViewModel[] { + const selectedCellsInRange: ICellViewModel[] = []; + for (let index = 0; index < this._list.length; ++index) { + const cell = this._list.element(index); + if (cell) { + if ((index >= fromInclusive && index <= toInclusive) || (index >= toInclusive && index <= fromInclusive)) { + selectedCellsInRange.push(cell); + } + } + } + return selectedCellsInRange; + } + focusNotebookCell(cell: ICellViewModel, focusItem: 'editor' | 'container' | 'output', options?: IFocusNotebookCellOptions) { if (this._isDisposed) { return; @@ -2430,24 +2449,24 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } } - markdownCellDragStart(cellId: string, ctx: { clientY: number }): void { + markdownCellDragStart(cellId: string, event: { dragOffsetY: number }): void { const cell = this.getCellById(cellId); if (cell instanceof MarkdownCellViewModel) { - this._dndController?.startExplicitDrag(cell, ctx); + this._dndController?.startExplicitDrag(cell, event.dragOffsetY); } } - markdownCellDrag(cellId: string, ctx: { clientY: number }): void { + markdownCellDrag(cellId: string, event: { dragOffsetY: number }): void { const cell = this.getCellById(cellId); if (cell instanceof MarkdownCellViewModel) { - this._dndController?.explicitDrag(cell, ctx); + this._dndController?.explicitDrag(cell, event.dragOffsetY); } } - markdownCellDrop(cellId: string, ctx: { clientY: number, ctrlKey: boolean, altKey: boolean }): void { + markdownCellDrop(cellId: string, event: { dragOffsetY: number, ctrlKey: boolean, altKey: boolean }): void { const cell = this.getCellById(cellId); if (cell instanceof MarkdownCellViewModel) { - this._dndController?.explicitDrop(cell, ctx); + this._dndController?.explicitDrop(cell, event); } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index b8ff078934f..58dc2410754 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -132,17 +132,13 @@ export interface IToggleMarkdownPreviewMessage extends BaseToWebviewMessage { export interface ICellDragStartMessage extends BaseToWebviewMessage { type: 'cell-drag-start'; readonly cellId: string; - readonly position: { - readonly clientY: number; - }; + readonly dragOffsetY: number; } export interface ICellDragMessage extends BaseToWebviewMessage { type: 'cell-drag'; readonly cellId: string; - readonly position: { - readonly clientY: number; - }; + readonly dragOffsetY: number; } export interface ICellDropMessage extends BaseToWebviewMessage { @@ -150,9 +146,7 @@ export interface ICellDropMessage extends BaseToWebviewMessage { readonly cellId: string; readonly ctrlKey: boolean readonly altKey: boolean; - readonly position: { - readonly clientY: number; - }; + readonly dragOffsetY: number; } export interface ICellDragEndMessage extends BaseToWebviewMessage { @@ -794,12 +788,7 @@ export class BackLayerWebView extends Disposable { private asWebviewUri(uri: URI, fromExtension: URI | undefined) { const remoteAuthority = fromExtension?.scheme === Schemas.vscodeRemote ? fromExtension.authority : undefined; - return asWebviewUri({ - isExtensionDevelopmentDebug: this.environmentService.isExtensionDevelopment, - webviewCspSource: this.environmentService.webviewCspSource, - webviewResourceRoot: this.environmentService.webviewResourceRoot, - remote: { authority: remoteAuthority } - }, this.id, uri); + return asWebviewUri(this.id, uri, remoteAuthority); } postKernelMessage(message: any) { @@ -1037,8 +1026,8 @@ var requirejs = (function() { const cell = this.notebookEditor.getCellById(data.cellId); if (cell) { if (data.shiftKey || (isMacintosh ? data.metaKey : data.ctrlKey)) { - // Add to selection - this.notebookEditor.toggleNotebookCellSelection(cell); + // Modify selection + this.notebookEditor.toggleNotebookCellSelection(cell, /* fromPrevious */ data.shiftKey); } else { // Normal click this.notebookEditor.focusNotebookCell(cell, 'container', { skipReveal: true }); @@ -1098,18 +1087,18 @@ var requirejs = (function() { } case 'cell-drag-start': { - this.notebookEditor.markdownCellDragStart(data.cellId, data.position); + this.notebookEditor.markdownCellDragStart(data.cellId, data); break; } case 'cell-drag': { - this.notebookEditor.markdownCellDrag(data.cellId, data.position); + this.notebookEditor.markdownCellDrag(data.cellId, data); break; } case 'cell-drop': { this.notebookEditor.markdownCellDrop(data.cellId, { - clientY: data.position.clientY, + dragOffsetY: data.dragOffsetY, ctrlKey: data.ctrlKey, altKey: data.altKey, }); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellDnd.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellDnd.ts index 7f9cd47a3cd..03eea9d8ec5 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellDnd.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellDnd.ts @@ -195,7 +195,7 @@ export class CellDragAndDropController extends Disposable { } } - private _dropImpl(draggedCell: ICellViewModel, dropDirection: 'above' | 'below', ctx: { clientY: number, ctrlKey: boolean, altKey: boolean }, draggedOverCell: ICellViewModel) { + private _dropImpl(draggedCell: ICellViewModel, dropDirection: 'above' | 'below', ctx: { ctrlKey: boolean, altKey: boolean }, draggedOverCell: ICellViewModel) { const cellTop = this.list.getAbsoluteTopOfElement(draggedOverCell); const cellHeight = this.list.elementHeight(draggedOverCell); const insertionIndicatorAbsolutePos = dropDirection === 'above' ? cellTop : cellTop + cellHeight; @@ -323,18 +323,18 @@ export class CellDragAndDropController extends Disposable { })); } - public startExplicitDrag(cell: ICellViewModel, position: { clientY: number }) { + public startExplicitDrag(cell: ICellViewModel, _dragOffsetY: number) { this.currentDraggedCell = cell; this.setInsertIndicatorVisibility(true); } - public explicitDrag(cell: ICellViewModel, position: { clientY: number }) { - const target = this.list.elementAt(position.clientY); + public explicitDrag(cell: ICellViewModel, dragOffsetY: number) { + const target = this.list.elementAt(dragOffsetY); if (target && target !== cell) { const cellTop = this.list.getAbsoluteTopOfElement(target); const cellHeight = this.list.elementHeight(target); - const dropDirection = this.getExplicitDragDropDirection(position.clientY, cellTop, cellHeight); + const dropDirection = this.getExplicitDragDropDirection(dragOffsetY, cellTop, cellHeight); const insertionIndicatorAbsolutePos = dropDirection === 'above' ? cellTop : cellTop + cellHeight; this.updateInsertIndicator(dropDirection, insertionIndicatorAbsolutePos); } @@ -344,16 +344,19 @@ export class CellDragAndDropController extends Disposable { return; } - const viewRect = this.notebookEditor.getDomNode().getBoundingClientRect(); - const eventPositionInView = position.clientY - this.list.scrollTop; + const notebookViewRect = this.notebookEditor.getDomNode().getBoundingClientRect(); + const eventPositionInView = dragOffsetY - this.list.scrollTop; - const scrollMargin = 0.2; - const maxScrollPerFrame = 20; - const eventPositionRatio = eventPositionInView / viewRect.height; - if (eventPositionRatio < scrollMargin) { - this.list.scrollTop -= maxScrollPerFrame * (1 - eventPositionRatio / scrollMargin); - } else if (eventPositionRatio > 1 - scrollMargin) { - this.list.scrollTop += maxScrollPerFrame * (1 - ((1 - eventPositionRatio) / scrollMargin)); + // Percentage from the top/bottom of the screen where we start scrolling while dragging + const notebookViewScrollMargins = 0.2; + + const maxScrollDeltaPerFrame = 20; + + const eventPositionRatio = eventPositionInView / notebookViewRect.height; + if (eventPositionRatio < notebookViewScrollMargins) { + this.list.scrollTop -= maxScrollDeltaPerFrame * (1 - eventPositionRatio / notebookViewScrollMargins); + } else if (eventPositionRatio > 1 - notebookViewScrollMargins) { + this.list.scrollTop += maxScrollDeltaPerFrame * (1 - ((1 - eventPositionRatio) / notebookViewScrollMargins)); } } @@ -361,24 +364,23 @@ export class CellDragAndDropController extends Disposable { this.setInsertIndicatorVisibility(false); } - public explicitDrop(cell: ICellViewModel, ctx: { clientY: number, ctrlKey: boolean, altKey: boolean }) { + public explicitDrop(cell: ICellViewModel, ctx: { dragOffsetY: number, ctrlKey: boolean, altKey: boolean }) { this.currentDraggedCell = undefined; this.setInsertIndicatorVisibility(false); - const target = this.list.elementAt(ctx.clientY); + const target = this.list.elementAt(ctx.dragOffsetY); if (!target || target === cell) { return; } const cellTop = this.list.getAbsoluteTopOfElement(target); const cellHeight = this.list.elementHeight(target); - const dropDirection = this.getExplicitDragDropDirection(ctx.clientY, cellTop, cellHeight); + const dropDirection = this.getExplicitDragDropDirection(ctx.dragOffsetY, cellTop, cellHeight); this._dropImpl(cell, dropDirection, ctx, target); } private getExplicitDragDropDirection(clientY: number, cellTop: number, cellHeight: number) { - const dragOffset = this.list.scrollTop + clientY; - const dragPosInElement = dragOffset - cellTop; + const dragPosInElement = clientY - cellTop; const dragPosRatio = dragPosInElement / cellHeight; return this.getDropInsertDirection(dragPosRatio); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index c05e38d2bc4..424b8bf972e 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -1171,7 +1171,7 @@ async function webviewPreloads(style: PreloadStyles, rendererData: readonly Rend cellId: drag.cellId, ctrlKey: e.ctrlKey, altKey: e.altKey, - position: { clientY: e.clientY }, + dragOffsetY: e.clientY, }); }); } @@ -1187,7 +1187,7 @@ async function webviewPreloads(style: PreloadStyles, rendererData: readonly Rend postNotebookMessage('cell-drag-start', { cellId: cellId, - position: { clientY: e.clientY }, + dragOffsetY: e.clientY, }); // Continuously send updates while dragging instead of relying on `updateDrag`. @@ -1199,7 +1199,7 @@ async function webviewPreloads(style: PreloadStyles, rendererData: readonly Rend postNotebookMessage('cell-drag', { cellId: cellId, - position: { clientY: this.currentDrag.clientY }, + dragOffsetY: this.currentDrag.clientY, }); requestAnimationFrame(trySendDragUpdate); }; diff --git a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts index 04da8ba66b7..7f474ee7440 100644 --- a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -177,7 +177,7 @@ function _createTestNotebookEditor(instantiationService: TestInstantiationServic const viewContext = new ViewContext(new NotebookOptions(instantiationService.get(IConfigurationService)), new NotebookEventDispatcher()); const viewModel: NotebookViewModel = instantiationService.createInstance(NotebookViewModel, viewType, model.notebook, viewContext, null); - const cellList = createNotebookCellList(instantiationService); + const cellList = createNotebookCellList(instantiationService, viewContext); cellList.attachViewModel(viewModel); const listViewInfoAccessor = new ListViewInfoAccessor(cellList); @@ -275,7 +275,7 @@ export async function withTestNotebook(cells: [source: string, lang: st return res; } -export function createNotebookCellList(instantiationService: TestInstantiationService) { +export function createNotebookCellList(instantiationService: TestInstantiationService, viewContext?: ViewContext) { const delegate: IListVirtualDelegate = { getHeight(element: CellViewModel) { return element.getHeight(17); }, getTemplateId() { return 'template'; } @@ -293,6 +293,7 @@ export function createNotebookCellList(instantiationService: TestInstantiationSe 'NotebookCellList', DOM.$('container'), DOM.$('body'), + viewContext ?? new ViewContext(new NotebookOptions(instantiationService.get(IConfigurationService)), new NotebookEventDispatcher()), delegate, [renderer], instantiationService.get(IContextKeyService), diff --git a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts index d3a90dcfcc2..9b789932749 100644 --- a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts +++ b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts @@ -32,6 +32,8 @@ export class KeyboardLayoutPickerContribution extends Disposable implements IWor ) { super(); + const name = nls.localize('status.workbench.keyboardLayout', "Keyboard Layout"); + let layout = this.keyboardLayoutService.getCurrentKeyboardLayout(); if (layout) { let layoutInfo = parseKeyboardLayoutDescription(layout); @@ -39,12 +41,12 @@ export class KeyboardLayoutPickerContribution extends Disposable implements IWor this.pickerElement.value = this.statusbarService.addEntry( { + name, text, ariaLabel: text, command: KEYBOARD_LAYOUT_OPEN_PICKER }, 'status.workbench.keyboardLayout', - nls.localize('status.workbench.keyboardLayout', "Keyboard Layout"), StatusbarAlignment.RIGHT ); } @@ -56,6 +58,7 @@ export class KeyboardLayoutPickerContribution extends Disposable implements IWor if (this.pickerElement.value) { const text = nls.localize('keyboardLayout', "Layout: {0}", layoutInfo.label); this.pickerElement.value.update({ + name, text, ariaLabel: text, command: KEYBOARD_LAYOUT_OPEN_PICKER @@ -64,12 +67,12 @@ export class KeyboardLayoutPickerContribution extends Disposable implements IWor const text = nls.localize('keyboardLayout', "Layout: {0}", layoutInfo.label); this.pickerElement.value = this.statusbarService.addEntry( { + name, text, ariaLabel: text, command: KEYBOARD_LAYOUT_OPEN_PICKER }, 'status.workbench.keyboardLayout', - nls.localize('status.workbench.keyboardLayout', "Keyboard Layout"), StatusbarAlignment.RIGHT ); } diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index 06e6fba025e..be7329df4c1 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -822,7 +822,7 @@ class EditSettingRenderer extends Disposable { private onEditSettingClicked(editPreferenceWidget: EditPreferenceWidget, e: IEditorMouseEvent): void { EventHelper.stop(e.event, true); - const anchor = { x: e.event.posx, y: e.event.posy + 10 }; + const anchor = { x: e.event.posx, y: e.event.posy }; const actions = this.getSettings(editPreferenceWidget.getLine()).length === 1 ? this.getActions(editPreferenceWidget.preferences[0], this.getConfigurationsMap()[editPreferenceWidget.preferences[0].key]) : editPreferenceWidget.preferences.map(setting => new SubmenuAction(`preferences.submenu.${setting.key}`, setting.key, this.getActions(setting, this.getConfigurationsMap()[setting.key]))); this.contextMenuService.showContextMenu({ diff --git a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts index b437ae61a92..33a5e367bc0 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts @@ -123,7 +123,7 @@ export class ForwardedPortsView extends Disposable implements IWorkbenchContribu private updateStatusBar() { if (!this.entryAccessor) { - this._register(this.entryAccessor = this.statusbarService.addEntry(this.entry, 'status.forwardedPorts', nls.localize('status.forwardedPorts', "Forwarded Ports"), StatusbarAlignment.LEFT, 40)); + this._register(this.entryAccessor = this.statusbarService.addEntry(this.entry, 'status.forwardedPorts', StatusbarAlignment.LEFT, 40)); } else { this.entryAccessor.update(this.entry); } @@ -143,6 +143,7 @@ export class ForwardedPortsView extends Disposable implements IWorkbenchContribu allTunnels.map(forwarded => forwarded.remotePort).join(', ')); } return { + name: nls.localize('status.forwardedPorts', "Forwarded Ports"), text: `$(radio-tower) ${text}`, ariaLabel: tooltip, tooltip, diff --git a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts index 99ba6ad9d14..342ce7934eb 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts @@ -317,6 +317,7 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr const ariaLabel = getCodiconAriaLabel(text); const properties: IStatusbarEntry = { + name, backgroundColor: themeColorFromId(STATUS_BAR_HOST_NAME_BACKGROUND), color: themeColorFromId(STATUS_BAR_HOST_NAME_FOREGROUND), ariaLabel, @@ -329,7 +330,7 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr if (this.remoteStatusEntry) { this.remoteStatusEntry.update(properties); } else { - this.remoteStatusEntry = this.statusbarService.addEntry(properties, 'status.host', name, StatusbarAlignment.LEFT, Number.MAX_VALUE /* first entry */); + this.remoteStatusEntry = this.statusbarService.addEntry(properties, 'status.host', StatusbarAlignment.LEFT, Number.MAX_VALUE /* first entry */); } } diff --git a/src/vs/workbench/contrib/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts index fcfe819b884..8e4de79194b 100644 --- a/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/src/vs/workbench/contrib/scm/browser/activity.ts @@ -154,11 +154,12 @@ export class SCMStatusController implements IWorkbenchContribution { ariaLabel = ariaLabel ? `${ariaLabel}, ${label}` : label; disposables.add(this.statusbarService.addEntry({ + name: localize('status.scm', "Source Control"), text: command.title, ariaLabel: `${ariaLabel}${command.tooltip ? ` - ${command.tooltip}` : ''}`, tooltip, command: command.id ? command : undefined - }, 'status.scm', localize('status.scm', "Source Control"), MainThreadStatusBarAlignment.LEFT, 10000)); + }, 'status.scm', MainThreadStatusBarAlignment.LEFT, 10000)); } this.statusBarDisposable = disposables; diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index e1f1eb3aa5f..b3bceff3270 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -130,6 +130,7 @@ export class TaskStatusBarContributions extends Disposable implements IWorkbench } } else { const itemProps: IStatusbarEntry = { + name: nls.localize('status.runningTasks', "Running Tasks"), text: `$(tools) ${tasks.length}`, ariaLabel: nls.localize('numberOfRunningTasks', "{0} running tasks", tasks.length), tooltip: nls.localize('runningTasks', "Show Running Tasks"), @@ -137,7 +138,7 @@ export class TaskStatusBarContributions extends Disposable implements IWorkbench }; if (!this.runningTasksStatusItem) { - this.runningTasksStatusItem = this.statusbarService.addEntry(itemProps, 'status.runningTasks', nls.localize('status.runningTasks', "Running Tasks"), StatusbarAlignment.LEFT, 49 /* Medium Priority, next to Markers */); + this.runningTasksStatusItem = this.statusbarService.addEntry(itemProps, 'status.runningTasks', StatusbarAlignment.LEFT, 49 /* Medium Priority, next to Markers */); } else { this.runningTasksStatusItem.update(itemProps); } diff --git a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts index b491ab10216..05e62f5dd7a 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts @@ -22,7 +22,9 @@ interface TerminalData { const TASK_TERMINAL_STATUS_ID = 'task_terminal_status'; const ACTIVE_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.play, severity: Severity.Info, tooltip: nls.localize('taskTerminalStatus.active', "Task is running") }; const SUCCEEDED_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.check, severity: Severity.Info, tooltip: nls.localize('taskTerminalStatus.succeeded', "Task succeeded") }; +const SUCCEEDED_INACTIVE_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.check, severity: Severity.Info, tooltip: nls.localize('taskTerminalStatus.succeededInactive', "Task succeeded and waiting...") }; const FAILED_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.error, severity: Severity.Error, tooltip: nls.localize('taskTerminalStatus.errors', "Task has errors") }; +const FAILED_INACTIVE_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.error, severity: Severity.Error, tooltip: nls.localize('taskTerminalStatus.errorsInactive', "Task has errors and is waiting...") }; export class TaskTerminalStatus extends Disposable { private terminalMap: Map = new Map(); @@ -76,9 +78,9 @@ export class TaskTerminalStatus extends Disposable { } terminalData.terminal.statusList.remove(terminalData.status); if (terminalData.problemMatcher.numberOfMatches === 0) { - terminalData.terminal.statusList.add(SUCCEEDED_TASK_STATUS); + terminalData.terminal.statusList.add(SUCCEEDED_INACTIVE_TASK_STATUS); } else { - terminalData.terminal.statusList.add(FAILED_TASK_STATUS); + terminalData.terminal.statusList.add(FAILED_INACTIVE_TASK_STATUS); } } diff --git a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts index 7fabfa93909..1653f9bff32 100644 --- a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts @@ -23,9 +23,9 @@ import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; -import { IRequestService } from 'vs/platform/request/common/request'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { WebviewPortMappingManager } from 'vs/platform/webview/common/webviewPortMapping'; +import { asWebviewUri, webviewResourceOrigin } from 'vs/workbench/api/common/shared/webview'; import { loadLocalResource, WebviewResourceResponse } from 'vs/workbench/contrib/webview/browser/resourceLoading'; import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing'; import { areWebviewContentOptionsEqual, WebviewContentOptions, WebviewExtensionDescription, WebviewMessageReceivedEvent, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; @@ -100,7 +100,6 @@ export abstract class BaseWebview extends Disposable { private readonly _fileService: IFileService; private readonly _logService: ILogService; private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService; - private readonly _requestService: IRequestService; private readonly _telemetryService: ITelemetryService; private readonly _tunnelService: ITunnelService; protected readonly _environmentService: IWorkbenchEnvironmentService; @@ -122,7 +121,6 @@ export abstract class BaseWebview extends Disposable { menuService: IMenuService, notificationService: INotificationService, remoteAuthorityResolverService: IRemoteAuthorityResolverService, - requestService: IRequestService, telemetryService: ITelemetryService, tunnelService: ITunnelService, } @@ -133,7 +131,6 @@ export abstract class BaseWebview extends Disposable { this._fileService = services.fileService; this._logService = services.logService; this._remoteAuthorityResolverService = services.remoteAuthorityResolverService; - this._requestService = services.requestService; this._telemetryService = services.telemetryService; this._tunnelService = services.tunnelService; @@ -244,19 +241,19 @@ export abstract class BaseWebview extends Disposable { this._register(this.on(WebviewMessageChannels.loadResource, (entry: { id: number, path: string, query: string, ifNoneMatch?: string }) => { const rawPath = entry.path; - // ext-authority / scheme / path-authority / ...path - const match = rawPath.match(/^\/([^\/]*)\/([^\/]*)\/([^\/]*)(\/.+)$/); + // scheme / path-authority / ...path + const match = rawPath.match(/^\/([^\/]*)\/([^\/]*)(\/.+)$/); if (!match) { throw new Error('Could not parse resource url'); } - const [_, remoteAuthority, scheme, pathAuthority, paths] = match; + const [_, scheme, pathAuthority, paths] = match; const uri = URI.parse(`${scheme}://${decodeURIComponent(pathAuthority)}${paths}`).with({ query: decodeURIComponent(entry.query), }); - this.loadResource(entry.id, rawPath, uri, decodeURIComponent(remoteAuthority), entry.ifNoneMatch); + this.loadResource(entry.id, rawPath, uri, entry.ifNoneMatch); })); this._register(this.on(WebviewMessageChannels.loadLocalhost, (entry: any) => { @@ -377,16 +374,28 @@ export abstract class BaseWebview extends Disposable { }); } - protected abstract get webviewResourceEndpoint(): string; + protected get webviewResourceOrigin(): string { + return webviewResourceOrigin(this.id); + } private rewriteVsCodeResourceUrls(value: string): string { - const remoteAuthority = this.extension?.location.scheme === Schemas.vscodeRemote ? this.extension.location.authority : ''; + const remoteAuthority = this.extension?.location.scheme === Schemas.vscodeRemote ? this.extension.location.authority : undefined; return value .replace(/(["'])(?:vscode-resource):(\/\/([^\s\/'"]+?)(?=\/))?([^\s'"]+?)(["'])/gi, (_match, startQuote, _1, scheme, path, endQuote) => { - return `${startQuote}${this.webviewResourceEndpoint}/vscode-resource/${remoteAuthority}/${scheme ?? 'file'}/${path}${endQuote}`; + const uri = URI.from({ + scheme: scheme || 'file', + path: path, + }); + const webviewUri = asWebviewUri(this.id, uri, remoteAuthority).toString(); + return `${startQuote}${webviewUri}${endQuote}`; }) .replace(/(["'])(?:vscode-webview-resource):(\/\/[^\s\/'"]+\/([^\s\/'"]+?)(?=\/))?([^\s'"]+?)(["'])/gi, (_match, startQuote, _1, scheme, path, endQuote) => { - return `${startQuote}${this.webviewResourceEndpoint}/vscode-resource/${remoteAuthority}/${scheme ?? 'file'}/${path}${endQuote}`; + const uri = URI.from({ + scheme: scheme || 'file', + path: path, + }); + const webviewUri = asWebviewUri(this.id, uri, remoteAuthority).toString(); + return `${startQuote}${webviewUri}${endQuote}`; }); } @@ -433,7 +442,7 @@ export abstract class BaseWebview extends Disposable { contents: this.content.html, options: this.content.options, state: this.content.state, - resourceEndpoint: this.webviewResourceEndpoint, + resourceEndpoint: this.webviewResourceOrigin, ...this.extraContentOptions }); } @@ -512,15 +521,12 @@ export abstract class BaseWebview extends Disposable { } } - private async loadResource(id: number, requestPath: string, uri: URI, remoteAuthority: string | undefined, ifNoneMatch: string | undefined) { + private async loadResource(id: number, requestPath: string, uri: URI, ifNoneMatch: string | undefined) { try { - const remoteConnectionData = remoteAuthority ? this._remoteAuthorityResolverService.getConnectionData(remoteAuthority) : null; - - const result = await loadLocalResource(uri, ifNoneMatch, { + const result = await loadLocalResource(uri, { + ifNoneMatch, roots: this.content.options.localResourceRoots || [], - remoteConnectionData, - remoteAuthority: remoteAuthority, - }, this._fileService, this._requestService, this._logService, this._resourceLoadingCts.token); + }, this._fileService, this._logService, this._resourceLoadingCts.token); switch (result.type) { case WebviewResourceResponse.Type.Success: diff --git a/src/vs/workbench/contrib/webview/browser/pre/host.js b/src/vs/workbench/contrib/webview/browser/pre/host.js index 8310f417ca0..7e773adefab 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/host.js +++ b/src/vs/workbench/contrib/webview/browser/pre/host.js @@ -6,8 +6,13 @@ import { createWebviewManager } from './main.js'; -const id = document.location.search.match(/\bid=([\w-]+)/)[1]; -const onElectron = /platform=electron/.test(document.location.search); +const searchParams = new URL(location.toString()).searchParams; +const id = searchParams.get('id'); +if (!id) { + throw new Error('Could not resolve webview id. Webview will not work.\nThis is usually caused by incorrectly trying to navigate in a webview'); +} + +const onElectron = searchParams.get('platform') === 'electron'; const hostMessaging = new class HostMessaging { constructor() { diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index a31f57f26ce..a5faa4d3316 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -4,10 +4,12 @@ *--------------------------------------------------------------------------------------------*/ // @ts-check +/// + /** * @typedef {{ * postMessage: (channel: string, data?: any) => void, - * onMessage: (channel: string, handler: any) => void, + * onMessage: (channel: string, handler: (event: MessageEvent, data: any) => void) => void, * focusIframeOnCreate?: boolean, * ready?: Promise, * onIframeLoaded?: (iframe: HTMLIFrameElement) => void, @@ -56,6 +58,18 @@ const getPendingFrame = () => { return /** @type {HTMLIFrameElement} */ (document.getElementById('pending-frame')); }; +/** + * @template T + * @param {T | undefined | null} obj + * @return {T} + */ +function assertIsDefined(obj) { + if (typeof obj === 'undefined' || obj === null) { + throw new Error('Found unexpected null'); + } + return obj; +} + const vscodePostMessageFuncName = '__vscode_post_message__'; const defaultStyles = document.createElement('style'); @@ -202,6 +216,9 @@ const workerReady = new Promise(async (resolve, reject) => { async registration => { await navigator.serviceWorker.ready; + /** + * @param {MessageEvent} event + */ const versionHandler = (event) => { if (event.data.channel !== 'version') { return; @@ -218,7 +235,7 @@ const workerReady = new Promise(async (resolve, reject) => { } }; navigator.serviceWorker.addEventListener('message', versionHandler); - registration.active.postMessage({ channel: 'version' }); + assertIsDefined(registration.active).postMessage({ channel: 'version' }); }, error => { reject(new Error(`Could not register service workers: ${error}.`)); @@ -231,6 +248,7 @@ const workerReady = new Promise(async (resolve, reject) => { export async function createWebviewManager(host) { // state let firstLoad = true; + /** @type {any} */ let loadTimeout; let styleVersion = 0; @@ -241,7 +259,7 @@ export async function createWebviewManager(host) { /** @type {number | undefined} */ initialScrollProgress: undefined, - /** @type {{ [key: string]: string }} */ + /** @type {{ [key: string]: string } | undefined} */ styles: undefined, /** @type {string | undefined} */ @@ -253,13 +271,13 @@ export async function createWebviewManager(host) { host.onMessage('did-load-resource', (_event, data) => { navigator.serviceWorker.ready.then(registration => { - registration.active.postMessage({ channel: 'did-load-resource', data }, data.data?.buffer ? [data.data.buffer] : []); + assertIsDefined(registration.active).postMessage({ channel: 'did-load-resource', data }, data.data?.buffer ? [data.data.buffer] : []); }); }); host.onMessage('did-load-localhost', (_event, data) => { navigator.serviceWorker.ready.then(registration => { - registration.active.postMessage({ channel: 'did-load-localhost', data }); + assertIsDefined(registration.active).postMessage({ channel: 'did-load-localhost', data }); }); }); @@ -282,7 +300,9 @@ export async function createWebviewManager(host) { if (body) { body.classList.remove('vscode-light', 'vscode-dark', 'vscode-high-contrast'); - body.classList.add(initData.activeTheme); + if (initData.activeTheme) { + body.classList.add(initData.activeTheme); + } body.dataset.vscodeThemeKind = initData.activeTheme; body.dataset.vscodeThemeName = initData.themeName || ''; @@ -435,6 +455,9 @@ export async function createWebviewManager(host) { let isHandlingScroll = false; + /** + * @param {WheelEvent} event + */ const handleWheel = (event) => { if (isHandlingScroll) { return; @@ -450,15 +473,21 @@ export async function createWebviewManager(host) { }); }; + /** + * @param {Event} event + */ const handleInnerScroll = (event) => { - if (!event.target || !event.target.body) { - return; - } if (isHandlingScroll) { return; } - const progress = event.currentTarget.scrollY / event.target.body.clientHeight; + const target = /** @type {HTMLDocument | null} */ (event.target); + const currentTarget = /** @type {Window | null} */ (event.currentTarget); + if (!target || !currentTarget || !target.body) { + return; + } + + const progress = currentTarget.scrollY / target.body.clientHeight; if (isNaN(progress)) { return; } @@ -475,6 +504,19 @@ export async function createWebviewManager(host) { }; /** + * @typedef {{ + * contents: string; + * options: { + * readonly allowScripts: boolean; + * readonly allowMultipleAPIAcquire: boolean; + * } + * state: any; + * resourceEndpoint: string; + * }} ContentUpdateData + */ + + /** + * @param {ContentUpdateData} data * @return {string} */ function toContentHtml(data) { @@ -484,7 +526,10 @@ export async function createWebviewManager(host) { newDocument.querySelectorAll('a').forEach(a => { if (!a.title) { - a.title = a.getAttribute('href'); + const href = a.getAttribute('href'); + if (typeof href === 'string') { + a.title = href; + } } }); @@ -509,8 +554,11 @@ export async function createWebviewManager(host) { try { // Attempt to rewrite CSPs that hardcode old-style resource endpoint const endpointUrl = new URL(data.resourceEndpoint); - const newCsp = csp.getAttribute('content').replace(/(vscode-webview-resource|vscode-resource):(?=(\s|;|$))/g, endpointUrl.origin); - csp.setAttribute('content', newCsp); + const cspContent = csp.getAttribute('content'); + if (cspContent) { + const newCsp = cspContent.replace(/(vscode-webview-resource|vscode-resource):(?=(\s|;|$))/g, endpointUrl.origin); + csp.setAttribute('content', newCsp); + } } catch (e) { console.error(`Could not rewrite csp: ${e}`); } @@ -563,7 +611,7 @@ export async function createWebviewManager(host) { // update iframe-contents let updateId = 0; - host.onMessage('content', async (_event, data) => { + host.onMessage('content', async (_event, /** @type {ContentUpdateData} */ data) => { const currentUpdateId = ++updateId; try { @@ -586,18 +634,19 @@ export async function createWebviewManager(host) { const frame = getActiveFrame(); const wasFirstLoad = firstLoad; // keep current scrollY around and use later + /** @type {(body: HTMLElement, window: Window) => void} */ let setInitialScrollPosition; if (firstLoad) { firstLoad = false; setInitialScrollPosition = (body, window) => { - if (!isNaN(initData.initialScrollProgress)) { + if (typeof initData.initialScrollProgress === 'number' && !isNaN(initData.initialScrollProgress)) { if (window.scrollY === 0) { window.scroll(0, body.clientHeight * initData.initialScrollProgress); } } }; } else { - const scrollY = frame && frame.contentDocument && frame.contentDocument.body ? frame.contentWindow.scrollY : 0; + const scrollY = frame && frame.contentDocument && frame.contentDocument.body ? assertIsDefined(frame.contentWindow).scrollY : 0; setInitialScrollPosition = (body, window) => { if (window.scrollY === 0) { window.scroll(0, scrollY); @@ -656,15 +705,16 @@ export async function createWebviewManager(host) { return; } - if (newFrame.contentDocument.readyState !== 'loading') { + const contentDocument = assertIsDefined(newFrame.contentDocument); + if (contentDocument.readyState !== 'loading') { clearInterval(interval); - onFrameLoaded(newFrame.contentDocument); + onFrameLoaded(contentDocument); } }, 10); } else { - newFrame.contentWindow.addEventListener('DOMContentLoaded', e => { + assertIsDefined(newFrame.contentWindow).addEventListener('DOMContentLoaded', e => { const contentDocument = e.target ? (/** @type {HTMLDocument} */ (e.target)) : undefined; - onFrameLoaded(contentDocument); + onFrameLoaded(assertIsDefined(contentDocument)); }); } @@ -692,7 +742,7 @@ export async function createWebviewManager(host) { newFrame.setAttribute('id', 'active-frame'); newFrame.style.visibility = 'visible'; if (host.focusIframeOnCreate) { - newFrame.contentWindow.focus(); + assertIsDefined(newFrame.contentWindow).focus(); } contentWindow.addEventListener('scroll', handleInnerScroll); @@ -720,10 +770,12 @@ export async function createWebviewManager(host) { loadTimeout = setTimeout(() => { clearTimeout(loadTimeout); loadTimeout = undefined; - onLoad(newFrame.contentDocument, newFrame.contentWindow); + onLoad(assertIsDefined(newFrame.contentDocument), assertIsDefined(newFrame.contentWindow)); }, 200); - newFrame.contentWindow.addEventListener('load', function (e) { + const contentWindow = assertIsDefined(newFrame.contentWindow); + + contentWindow.addEventListener('load', function (e) { const contentDocument = /** @type {Document} */ (e.target); if (loadTimeout) { @@ -734,11 +786,16 @@ export async function createWebviewManager(host) { }); // Bubble out various events - newFrame.contentWindow.addEventListener('click', handleInnerClick); - newFrame.contentWindow.addEventListener('auxclick', handleAuxClick); - newFrame.contentWindow.addEventListener('keydown', handleInnerKeydown); - newFrame.contentWindow.addEventListener('keyup', handleInnerUp); - newFrame.contentWindow.addEventListener('contextmenu', e => { + contentWindow.addEventListener('click', handleInnerClick); + contentWindow.addEventListener('auxclick', handleAuxClick); + contentWindow.addEventListener('keydown', handleInnerKeydown); + contentWindow.addEventListener('keyup', handleInnerUp); + contentWindow.addEventListener('contextmenu', e => { + if (e.defaultPrevented) { + // Extension code has already handled this event + return; + } + e.preventDefault(); host.postMessage('did-context-menu', { clientX: e.clientX, @@ -760,7 +817,7 @@ export async function createWebviewManager(host) { if (!pending) { const target = getActiveFrame(); if (target) { - target.contentWindow.postMessage(data.message, '*', data.transfer); + assertIsDefined(target.contentWindow).postMessage(data.message, '*', data.transfer); return; } } @@ -776,7 +833,7 @@ export async function createWebviewManager(host) { if (!target) { return; } - target.contentDocument.execCommand(data); + assertIsDefined(target.contentDocument).execCommand(data); }); trackFocus({ @@ -784,7 +841,7 @@ export async function createWebviewManager(host) { onBlur: () => host.postMessage('did-blur') }); - (/** @type {any} */ (window))[vscodePostMessageFuncName] = (command, data, transfer) => { + (/** @type {any} */ (window))[vscodePostMessageFuncName] = (/** @type {string} */ command, /** @type {any} */ data) => { switch (command) { case 'onmessage': case 'do-update-state': diff --git a/src/vs/workbench/contrib/webview/browser/pre/service-worker.js b/src/vs/workbench/contrib/webview/browser/pre/service-worker.js index e61fc59ad84..2589f552df0 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/service-worker.js +++ b/src/vs/workbench/contrib/webview/browser/pre/service-worker.js @@ -25,7 +25,7 @@ const resourceOrigin = searchParams.get('vscode-resource-origin') ?? sw.origin; /** * Root path for resources */ -const resourceRoot = rootPath + '/vscode-resource'; +const resourceRoot = '/vscode-resource'; const serviceWorkerFetchIgnoreSubdomain = searchParams.get('serviceWorkerFetchIgnoreSubdomain') ?? false; @@ -66,9 +66,15 @@ class RequestStore { create() { const requestId = ++this.requestPool; + /** @type {undefined | ((x: T) => void)} */ let resolve; + + /** @type {Promise} */ const promise = new Promise(r => resolve = r); - const entry = { resolve, promise }; + + /** @type {RequestStoreEntry} */ + const entry = { resolve: /** @type {(x: T) => void} */ (resolve), promise }; + this.map.set(requestId, entry); const dispose = () => { @@ -156,7 +162,6 @@ sw.addEventListener('message', async (event) => { } case 'did-load-localhost': { - const webviewId = getWebviewIdForClient(event.source); const data = event.data.data; if (!localhostRequestStore.resolve(data.id, data.location)) { console.log('Could not resolve unknown localhost', data.origin); @@ -170,7 +175,6 @@ sw.addEventListener('message', async (event) => { sw.addEventListener('fetch', (event) => { const requestUrl = new URL(event.request.url); - if (serviceWorkerFetchIgnoreSubdomain && requestUrl.pathname.startsWith(resourceRoot + '/')) { // #121981 const ignoreFirstSubdomainRegex = /(.*):\/\/.*?\.(.*)/; @@ -205,11 +209,16 @@ sw.addEventListener('activate', (event) => { async function processResourceRequest(event, requestUrl) { const client = await sw.clients.get(event.clientId); if (!client) { - console.log('Could not find inner client for request'); + console.error('Could not find inner client for request'); return notFound(); } const webviewId = getWebviewIdForClient(client); + if (!webviewId) { + console.error('Could not resolve webview id'); + return notFound(); + } + const resourcePath = requestUrl.pathname.startsWith(resourceRoot + '/') ? requestUrl.pathname.slice(resourceRoot.length) : requestUrl.pathname; /** @@ -229,18 +238,18 @@ async function processResourceRequest(event, requestUrl) { } } - const cacheHeaders = entry.etag ? { - 'ETag': entry.etag, - 'Cache-Control': 'no-cache' - } : {}; - + /** @type {Record} */ + const headers = { + 'Content-Type': entry.mime, + 'Access-Control-Allow-Origin': '*', + }; + if (entry.etag) { + headers['ETag'] = entry.etag; + headers['Cache-Control'] = 'no-cache'; + } const response = new Response(entry.body, { status: 200, - headers: { - 'Content-Type': entry.mime, - 'Access-Control-Allow-Origin': '*', - ...cacheHeaders - } + headers }); if (entry.etag) { @@ -273,23 +282,30 @@ async function processResourceRequest(event, requestUrl) { } /** - * @param {*} event + * @param {FetchEvent} event * @param {URL} requestUrl + * @return {Promise} */ async function processLocalhostRequest(event, requestUrl) { const client = await sw.clients.get(event.clientId); if (!client) { // This is expected when requesting resources on other localhost ports // that are not spawned by vs code - return undefined; + return fetch(event.request); } const webviewId = getWebviewIdForClient(client); + if (!webviewId) { + console.error('Could not resolve webview id'); + return fetch(event.request); + } + const origin = requestUrl.origin; /** - * @param {string} redirectOrigin + * @param {string | undefined} redirectOrigin + * @return {Promise} */ - const resolveRedirect = (redirectOrigin) => { + const resolveRedirect = async (redirectOrigin) => { if (!redirectOrigin) { return fetch(event.request); } @@ -318,16 +334,24 @@ async function processLocalhostRequest(event, requestUrl) { return promise.then(resolveRedirect); } +/** + * @param {Client} client + * @returns {string | null} + */ function getWebviewIdForClient(client) { const requesterClientUrl = new URL(client.url); - return requesterClientUrl.search.match(/\bid=([a-z0-9-]+)/i)[1]; + return requesterClientUrl.searchParams.get('id'); } +/** + * @param {string} webviewId + * @returns {Promise} + */ async function getOuterIframeClient(webviewId) { const allClients = await sw.clients.matchAll({ includeUncontrolled: true }); return allClients.find(client => { const clientUrl = new URL(client.url); const hasExpectedPathName = (clientUrl.pathname === `${rootPath}/` || clientUrl.pathname === `${rootPath}/index.html` || clientUrl.pathname === `${rootPath}/electron-browser-index.html`); - return hasExpectedPathName && clientUrl.search.match(new RegExp('\\bid=' + webviewId)); + return hasExpectedPathName && clientUrl.searchParams.get('id') === webviewId; }); } diff --git a/src/vs/workbench/contrib/webview/browser/resourceLoading.ts b/src/vs/workbench/contrib/webview/browser/resourceLoading.ts index 79e68e5e6bf..5b6ebc58ac2 100644 --- a/src/vs/workbench/contrib/webview/browser/resourceLoading.ts +++ b/src/vs/workbench/contrib/webview/browser/resourceLoading.ts @@ -9,11 +9,8 @@ import { isUNC } from 'vs/base/common/extpath'; import { Schemas } from 'vs/base/common/network'; import { sep } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; -import { IHeaders } from 'vs/base/parts/request/common/request'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; -import { IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { IRequestService } from 'vs/platform/request/common/request'; import { getWebviewContentMimeType } from 'vs/platform/webview/common/mimeTypes'; export namespace WebviewResourceResponse { @@ -45,20 +42,17 @@ export namespace WebviewResourceResponse { export async function loadLocalResource( requestUri: URI, - ifNoneMatch: string | undefined, options: { + ifNoneMatch: string | undefined, roots: ReadonlyArray; - remoteConnectionData?: IRemoteConnectionData | null; - remoteAuthority: string | undefined; }, fileService: IFileService, - requestService: IRequestService, logService: ILogService, token: CancellationToken, ): Promise { logService.debug(`loadLocalResource - being. requestUri=${requestUri}`); - const resourceToLoad = getResourceToLoad(requestUri, options.roots, options.remoteAuthority); + const resourceToLoad = getResourceToLoad(requestUri, options.roots); logService.debug(`loadLocalResource - found resource to load. requestUri=${requestUri}, resourceToLoad=${resourceToLoad}`); @@ -68,33 +62,8 @@ export async function loadLocalResource( const mime = getWebviewContentMimeType(requestUri); // Use the original path for the mime - if (resourceToLoad.scheme === Schemas.http || resourceToLoad.scheme === Schemas.https) { - const headers: IHeaders = {}; - if (ifNoneMatch) { - headers['If-None-Match'] = ifNoneMatch; - } - - const response = await requestService.request({ - url: resourceToLoad.toString(true), - headers: headers - }, token); - - logService.debug(`loadLocalResource - Loaded over http(s). requestUri=${requestUri}, response=${response.res.statusCode}`); - - switch (response.res.statusCode) { - case 200: - return new WebviewResourceResponse.StreamSuccess(response.stream, response.res.headers['etag'], mime); - - case 304: // Not modified - return new WebviewResourceResponse.NotModified(mime); - - default: - return WebviewResourceResponse.Failed; - } - } - try { - const result = await fileService.readFileStream(resourceToLoad, { etag: ifNoneMatch }); + const result = await fileService.readFileStream(resourceToLoad, { etag: options.ifNoneMatch }); return new WebviewResourceResponse.StreamSuccess(result.value, result.etag, mime); } catch (err) { if (err instanceof FileOperationError) { @@ -117,32 +86,16 @@ export async function loadLocalResource( function getResourceToLoad( requestUri: URI, roots: ReadonlyArray, - remoteAuthority: string | undefined, ): URI | undefined { for (const root of roots) { if (containsResource(root, requestUri)) { - return normalizeResourcePath(requestUri, remoteAuthority); + return normalizeResourcePath(requestUri); } } return undefined; } -function normalizeResourcePath(resource: URI, remoteAuthority: string | undefined): URI { - // If the uri was from a remote authority, make we go to the remote to load it - if (remoteAuthority && resource.scheme === Schemas.file) { - return URI.from({ - scheme: Schemas.vscodeRemote, - authority: remoteAuthority, - path: '/vscode-resource', - query: JSON.stringify({ - requestResourcePath: resource.path - }) - }); - } - return resource; -} - function containsResource(root: URI, resource: URI): boolean { let rootPath = root.fsPath + (root.fsPath.endsWith(sep) ? '' : sep); let resourceFsPath = resource.fsPath; @@ -154,3 +107,18 @@ function containsResource(root: URI, resource: URI): boolean { return resourceFsPath.startsWith(rootPath); } + +function normalizeResourcePath(resource: URI): URI { + // Rewrite remote uris to a path that the remote file system can understand + if (resource.scheme === Schemas.vscodeRemote) { + return URI.from({ + scheme: Schemas.vscodeRemote, + authority: resource.authority, + path: '/vscode-resource', + query: JSON.stringify({ + requestResourcePath: resource.path + }) + }); + } + return resource; +} diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index 6235a5f49bc..3dd09f3048f 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -32,6 +32,11 @@ export interface IWebviewService { */ readonly activeWebview: Webview | undefined; + /** + * All webviews. + */ + readonly webviews: Iterable; + /** * Fired when the currently focused webview changes. */ diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index 4cc864ed41f..58b84ff93d4 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -13,7 +13,6 @@ import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; -import { IRequestService } from 'vs/platform/request/common/request'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { BaseWebview, WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/baseWebviewElement'; import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing'; @@ -37,7 +36,6 @@ export class IFrameWebview extends BaseWebview implements Web @IMenuService menuService: IMenuService, @INotificationService notificationService: INotificationService, @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, - @IRequestService requestService: IRequestService, @ITelemetryService telemetryService: ITelemetryService, @ITunnelService tunnelService: ITunnelService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @@ -48,7 +46,6 @@ export class IFrameWebview extends BaseWebview implements Web logService, telemetryService, environmentService, - requestService, fileService, tunnelService, remoteAuthorityResolverService, @@ -106,7 +103,8 @@ export class IFrameWebview extends BaseWebview implements Web extensionId: extension?.id.value ?? '', // The extensionId and purpose in the URL are used for filtering in js-debug: purpose: options.purpose, serviceWorkerFetchIgnoreSubdomain: options.serviceWorkerFetchIgnoreSubdomain, - ...extraParams + ...extraParams, + 'vscode-resource-origin': this.webviewResourceOrigin, } as const; const queryString = (Object.keys(params) as Array) @@ -124,10 +122,6 @@ export class IFrameWebview extends BaseWebview implements Web return endpoint; } - protected get webviewResourceEndpoint(): string { - return this.webviewContentEndpoint; - } - public mountTo(parent: HTMLElement) { if (this.element) { parent.appendChild(this.element); diff --git a/src/vs/workbench/contrib/webview/browser/webviewService.ts b/src/vs/workbench/contrib/webview/browser/webviewService.ts index 25e922e271d..d99b5796ae6 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewService.ts @@ -34,6 +34,12 @@ export class WebviewService extends Disposable implements IWebviewService { } } + private _webviews = new Set(); + + public get webviews(): Iterable { + return this._webviews.values(); + } + private readonly _onDidChangeActiveWebview = this._register(new Emitter()); public readonly onDidChangeActiveWebview = this._onDidChangeActiveWebview.event; @@ -44,7 +50,7 @@ export class WebviewService extends Disposable implements IWebviewService { extension: WebviewExtensionDescription | undefined, ): WebviewElement { const webview = this._instantiationService.createInstance(IFrameWebview, id, options, contentOptions, extension, this._webviewThemeDataProvider); - this.addWebviewListeners(webview); + this.registerNewWebview(webview); return webview; } @@ -55,11 +61,13 @@ export class WebviewService extends Disposable implements IWebviewService { extension: WebviewExtensionDescription | undefined, ): WebviewOverlay { const webview = this._instantiationService.createInstance(DynamicWebviewEditorOverlay, id, options, contentOptions, extension); - this.addWebviewListeners(webview); + this.registerNewWebview(webview); return webview; } - protected addWebviewListeners(webview: Webview) { + protected registerNewWebview(webview: Webview) { + this._webviews.add(webview); + webview.onDidFocus(() => { this.updateActiveWebview(webview); }); @@ -71,6 +79,9 @@ export class WebviewService extends Disposable implements IWebviewService { }; webview.onDidBlur(onBlur); - webview.onDidDispose(onBlur); + webview.onDidDispose(() => { + onBlur(); + this._webviews.delete(webview); + }); } } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index 244e5b7c9ed..fb055a5aedb 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -19,7 +19,6 @@ import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; -import { IRequestService } from 'vs/platform/request/common/request'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { webviewPartitionId } from 'vs/platform/webview/common/webviewManagerService'; import { BaseWebview, WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/baseWebviewElement'; @@ -62,7 +61,6 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme @IMenuService menuService: IMenuService, @INotificationService notificationService: INotificationService, @IFileService fileService: IFileService, - @IRequestService requestService: IRequestService, @ITunnelService tunnelService: ITunnelService, @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, ) { @@ -74,7 +72,6 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme environmentService, fileService, menuService, - requestService, tunnelService, remoteAuthorityResolverService }); @@ -162,7 +159,7 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme // and not the `vscode-file` URI because preload scripts are loaded // via node.js from the main side and only allow `file:` protocol this.element!.preload = FileAccess.asFileUri('./pre/electron-index.js', require).toString(true); - this.element!.src = `${Schemas.vscodeWebview}://${this.id}/electron-browser-index.html?platform=electron&id=${this.id}&vscode-resource-origin=${encodeURIComponent(this.webviewResourceEndpoint)}`; + this.element!.src = `${Schemas.vscodeWebview}://${this.id}/electron-browser-index.html?platform=electron&id=${this.id}&vscode-resource-origin=${encodeURIComponent(this.webviewResourceOrigin)}`; } protected createElement(options: WebviewOptions) { @@ -195,10 +192,6 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme super.contentOptions = options; } - protected override get webviewResourceEndpoint(): string { - return `https://${this.id}.vscode-webview-test.com`; - } - protected readonly extraContentOptions = {}; public mountTo(parent: HTMLElement) { diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts index ffb4870d5d4..f1603eebd35 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts @@ -28,7 +28,7 @@ export class ElectronWebviewService extends WebviewService { ): WebviewElement { const useIframes = this._configService.getValue('webview.experimental.useIframes') ?? !options.enableFindWidget; const webview = this._instantiationService.createInstance(useIframes ? ElectronIframeWebview : ElectronWebviewBasedWebview, id, options, contentOptions, extension, this._webviewThemeDataProvider); - this.addWebviewListeners(webview); + this.registerNewWebview(webview); return webview; } @@ -39,7 +39,7 @@ export class ElectronWebviewService extends WebviewService { extension: WebviewExtensionDescription | undefined, ): WebviewOverlay { const webview = this._instantiationService.createInstance(DynamicWebviewEditorOverlay, id, options, contentOptions, extension); - this.addWebviewListeners(webview); + this.registerNewWebview(webview); return webview; } } diff --git a/src/vs/workbench/contrib/webview/electron-sandbox/iframeWebviewElement.ts b/src/vs/workbench/contrib/webview/electron-sandbox/iframeWebviewElement.ts index 0140d50fa7d..6c66a01bc11 100644 --- a/src/vs/workbench/contrib/webview/electron-sandbox/iframeWebviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-sandbox/iframeWebviewElement.ts @@ -13,7 +13,6 @@ import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; -import { IRequestService } from 'vs/platform/request/common/request'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/baseWebviewElement'; import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing'; @@ -38,7 +37,6 @@ export class ElectronIframeWebview extends IFrameWebview { @IContextMenuService contextMenuService: IContextMenuService, @ITunnelService tunnelService: ITunnelService, @IFileService fileService: IFileService, - @IRequestService requestService: IRequestService, @ITelemetryService telemetryService: ITelemetryService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IRemoteAuthorityResolverService _remoteAuthorityResolverService: IRemoteAuthorityResolverService, @@ -51,7 +49,7 @@ export class ElectronIframeWebview extends IFrameWebview { ) { super(id, options, contentOptions, extension, webviewThemeDataProvider, contextMenuService, - configurationService, fileService, logService, menuService, notificationService, _remoteAuthorityResolverService, requestService, telemetryService, tunnelService, environmentService); + configurationService, fileService, logService, menuService, notificationService, _remoteAuthorityResolverService, telemetryService, tunnelService, environmentService); this._webviewKeyboardHandler = new WindowIgnoreMenuShortcutsManager(configurationService, mainProcessService, nativeHostService); @@ -66,8 +64,7 @@ export class ElectronIframeWebview extends IFrameWebview { protected override initElement(extension: WebviewExtensionDescription | undefined, options: WebviewOptions) { super.initElement(extension, options, { - platform: 'electron', - 'vscode-resource-origin': this.webviewResourceEndpoint, + platform: 'electron' }); } @@ -79,10 +76,6 @@ export class ElectronIframeWebview extends IFrameWebview { return endpoint; } - protected override get webviewResourceEndpoint(): string { - return `https://${this.id}.vscode-webview-test.com`; - } - protected override async doPostMessage(channel: string, data?: any): Promise { this.element?.contentWindow!.postMessage({ channel, args: data }, '*'); } diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewCommands.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewCommands.ts index ee06e2a50d3..2a045adb649 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewCommands.ts @@ -11,7 +11,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CATEGORIES } from 'vs/workbench/common/actions'; -import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_ENABLED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, Webview } from 'vs/workbench/contrib/webview/browser/webview'; +import { IWebviewService, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_ENABLED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewEditor } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditor'; import { WebviewInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -118,11 +118,9 @@ export class ReloadWebviewAction extends Action2 { } public async run(accessor: ServicesAccessor): Promise { - const editorService = accessor.get(IEditorService); - for (const editor of editorService.visibleEditors) { - if (editor instanceof WebviewInput) { - editor.webview.reload(); - } + const webviewService = accessor.get(IWebviewService); + for (const webview of webviewService.webviews) { + webview.reload(); } } } diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts index a31e1564393..bc06cd1a87c 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts @@ -56,7 +56,6 @@ import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/to import { ResourceMap } from 'vs/base/common/map'; import { IFileService } from 'vs/platform/files/common/files'; import { joinPath } from 'vs/base/common/resources'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { asWebviewUri } from 'vs/workbench/api/common/shared/webview'; import { Schemas } from 'vs/base/common/network'; @@ -132,7 +131,6 @@ export class GettingStartedPage extends EditorPane { @IExtensionService private readonly extensionService: IExtensionService, @IInstantiationService private readonly instantiationService: IInstantiationService, @INotificationService private readonly notificationService: INotificationService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IEditorGroupsService private readonly groupsService: IEditorGroupsService, @IContextKeyService contextService: IContextKeyService, @IQuickInputService private quickInputService: IQuickInputService, @@ -494,12 +492,7 @@ export class GettingStartedPage extends EditorPane { if (src.startsWith('https://')) { return `src="${src}"`; } const path = joinPath(base, src); - const transformed = asWebviewUri({ - isExtensionDevelopmentDebug: this.environmentService.isExtensionDevelopment, - webviewResourceRoot: this.environmentService.webviewResourceRoot, - webviewCspSource: this.environmentService.webviewCspSource, - remote: { authority: undefined }, - }, this.webviewID, path).toString(); + const transformed = asWebviewUri(this.webviewID, path, undefined).toString(); return `src="${transformed}"`; }); diff --git a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts index 902e74d93d5..0dbac3b6736 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts @@ -16,7 +16,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService, workspaceTrustToString } from 'vs/platform/workspace/common/workspaceTrust'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { Codicon } from 'vs/base/common/codicons'; +import { Codicon, registerCodicon } from 'vs/base/common/codicons'; import { ThemeColor } from 'vs/workbench/api/common/extHostTypes'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -47,7 +47,13 @@ import { getVirtualWorkspaceScheme } from 'vs/platform/remote/common/remoteHosts import { verifyMicrosoftInternalDomain } from 'vs/platform/telemetry/common/commonProperties'; const BANNER_RESTRICTED_MODE = 'workbench.banner.restrictedMode'; +const BANNER_VIRTUAL_WORKSPACE = 'workbench.banner.virtualWorkspace'; +const BANNER_VIRTUAL_AND_RESTRICTED = 'workbench.banner.virtualAndRestricted'; const STARTUP_PROMPT_SHOWN_KEY = 'workspace.trust.startupPrompt.shown'; +const BANNER_RESTRICTED_MODE_DISMISSED_KEY = 'workbench.banner.restrictedMode.dismissed'; +const BANNER_VIRTUAL_WORKSPACE_DISMISSED_KEY = 'workbench.banner.virtualWorkspace.dismissed'; + +const infoIcon = registerCodicon('workspace-banner-warning-icon', Codicon.info); /* * Trust Request UX Handler @@ -149,6 +155,7 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben private showModalOnStart(): void { if (this.workspaceTrustManagementService.isWorkpaceTrusted()) { + this.updateWorkbenchIndicators(true); return; } @@ -194,17 +201,56 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben private createStatusbarEntry(): void { const entry = this.getStatusbarEntry(this.workspaceTrustManagementService.isWorkpaceTrusted()); - this.statusbarEntryAccessor.value = this.statusbarService.addEntry(entry, this.entryId, localize('status.WorkspaceTrust', "Workspace Trust"), StatusbarAlignment.LEFT, 0.99 * Number.MAX_VALUE /* Right of remote indicator */); + this.statusbarEntryAccessor.value = this.statusbarService.addEntry(entry, this.entryId, StatusbarAlignment.LEFT, 0.99 * Number.MAX_VALUE /* Right of remote indicator */); this.statusbarService.updateEntryVisibility(this.entryId, false); } - private getBannerItem(): IBannerItem { - return { - id: BANNER_RESTRICTED_MODE, - icon: shieldIcon, - ariaLabel: localize('restrictedModeBannerAriaLabel', "Restricted Mode is intended for safe code browsing. Trust this folder to enable all features. Use navigation keys to access banner actions."), - message: localize('restrictedModeBannerMessage', "Restricted Mode is intended for safe code browsing. Trust this folder to enable all features."), - actions: [ + private getBannerItem(isVirtualWorkspace: boolean, restrictedMode: boolean): IBannerItem | undefined { + + const dismissedVirtual = this.storageService.getBoolean(BANNER_VIRTUAL_WORKSPACE_DISMISSED_KEY, StorageScope.WORKSPACE, false); + const dismissedRestricted = this.storageService.getBoolean(BANNER_RESTRICTED_MODE_DISMISSED_KEY, StorageScope.WORKSPACE, false); + + // all important info has been dismissed + if (dismissedVirtual && dismissedRestricted) { + return undefined; + } + + // don't show restricted mode only banner + if (dismissedRestricted && !isVirtualWorkspace) { + return undefined; + } + + // don't show virtual workspace only banner + if (dismissedVirtual && !restrictedMode) { + return undefined; + } + + const choose = (virtual: any, restricted: any, virtualAndRestricted: any) => { + return (isVirtualWorkspace && !dismissedVirtual) && (restrictedMode && !dismissedRestricted) ? virtualAndRestricted : ((isVirtualWorkspace && !dismissedVirtual) ? virtual : restricted); + }; + + const id = choose(BANNER_VIRTUAL_WORKSPACE, BANNER_RESTRICTED_MODE, BANNER_VIRTUAL_AND_RESTRICTED); + const icon = choose(infoIcon, shieldIcon, infoIcon); + const ariaLabel = choose( + localize('virtualBannerAriaLabel', "Some features are not available because the current workspace is backed by a virtual file system. Use navigation keys to access banner actions."), + localize('restrictedModeBannerAriaLabel', "Restricted Mode is intended for safe code browsing. Trust this folder to enable all features. Use navigation keys to access banner actions."), + localize('virtualAndRestrictedModeBannerAriaLabel', "Some features are not available because the current workspace is backed by a virtual file system and is not trusted. You can trust this workspace to enable some of these features. Use navigation keys to access banner actions."), + ); + + const message = choose( + localize('virtualBannerMessage', "Some features are not available because the current workspace is backed by a virtual file system."), + localize('restrictedModeBannerMessage', "Restricted Mode is intended for safe code browsing. Trust this folder to enable all features."), + localize('virtualAndRestrictedModeBannerMessage', "Some features are not available because the current workspace is backed by a virtual file system and is not trusted. You can trust this workspace to enabled some of these features."), + ); + + const actions = choose( + [ + { + label: localize('virtualBannerLearnMore', "Learn More"), + href: 'https://aka.ms/vscode-virtual-workspaces' + } + ], + [ { label: localize('restrictedModeBannerManage', "Manage"), href: 'command:workbench.trust.manage' @@ -214,7 +260,33 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben href: 'https://aka.ms/vscode-workspace-trust' } ], - scope: StorageScope.WORKSPACE, + [ + { + label: localize('virtualAndRestrictedModeBannerManage', "Manage Trust"), + href: 'command:workbench.trust.manage' + }, + { + label: localize('virtualBannerLearnMore', "Learn More"), + href: 'https://aka.ms/vscode-virtual-workspaces' + } + ] + ); + + return { + id, + icon, + ariaLabel, + message, + actions, + onClose: () => { + if (isVirtualWorkspace) { + this.storageService.store(BANNER_VIRTUAL_WORKSPACE_DISMISSED_KEY, true, StorageScope.WORKSPACE, StorageTarget.MACHINE); + } + + if (restrictedMode) { + this.storageService.store(BANNER_RESTRICTED_MODE_DISMISSED_KEY, true, StorageScope.WORKSPACE, StorageTarget.MACHINE); + } + } }; } @@ -224,6 +296,7 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben const color = new ThemeColor(STATUS_BAR_PROMINENT_ITEM_FOREGROUND); return { + name: localize('status.WorkspaceTrust', "Workspace Trust"), text: trusted ? `$(shield)` : `$(shield) ${text}`, ariaLabel: trusted ? localize('status.ariaTrusted', "This workspace is trusted.") : localize('status.ariaUntrusted', "Restricted Mode: Some features are disabled because this workspace is not trusted."), tooltip: trusted ? localize('status.tooltipTrusted', "This workspace is trusted.") : localize('status.tooltipUntrusted', "Some features are disabled because this workspace is not trusted."), @@ -244,10 +317,20 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben private updateWorkbenchIndicators(trusted: boolean): void { this.updateStatusbarEntry(trusted); - if (!trusted) { - this.bannerService.show(this.getBannerItem()); - } else { - this.bannerService.hide(BANNER_RESTRICTED_MODE); + + const isVirtualWorkspace = getVirtualWorkspaceScheme(this.workspaceContextService.getWorkspace()) !== undefined; + const bannerItem = this.getBannerItem(isVirtualWorkspace, !trusted); + + if (bannerItem) { + if (!isVirtualWorkspace) { + if (!trusted) { + this.bannerService.show(bannerItem); + } else { + this.bannerService.hide(BANNER_RESTRICTED_MODE); + } + } else { + this.bannerService.show(bannerItem); + } } } diff --git a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.css b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.css index 63f05bb1fd4..b26d4cc9aa5 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.css +++ b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.css @@ -217,8 +217,7 @@ } .workspace-trust-editor .workspace-trust-settings { - padding-top: 20px; - padding-bottom: 20px; + padding: 20px 14px; } .workspace-trust-editor .workspace-trust-settings .workspace-trusted-folders-title { @@ -268,3 +267,14 @@ padding-left: 8px; padding-right: 8px; } + +.workspace-trust-editor .workspace-trust-settings .monaco-table-tr .monaco-table-td .actions .monaco-action-bar { + display: none; + flex: 1; +} + +.workspace-trust-editor .workspace-trust-settings .monaco-list-row.selected .monaco-table-tr .monaco-table-td .actions .monaco-action-bar, +.workspace-trust-editor .workspace-trust-settings .monaco-table .monaco-list-row.focused .monaco-table-tr .monaco-table-td .actions .monaco-action-bar, +.workspace-trust-editor .workspace-trust-settings .monaco-list-row:hover .monaco-table-tr .monaco-table-td .actions .monaco-action-bar { + display: flex; +} diff --git a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts index 08da9da034b..82016b8c8d1 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts @@ -22,6 +22,7 @@ import { Schemas } from 'vs/base/common/network'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; +import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { ExtensionUntrustedWorkpaceSupportType } from 'vs/platform/extensions/common/extensions'; @@ -31,6 +32,7 @@ import { WorkbenchTable } from 'vs/platform/list/browser/listService'; import { IPromptChoiceWithMenu } from 'vs/platform/notification/common/notification'; import { Link } from 'vs/platform/opener/browser/link'; import product from 'vs/platform/product/common/product'; +import { Registry } from 'vs/platform/registry/common/platform'; import { getVirtualWorkspaceScheme } from 'vs/platform/remote/common/remoteHosts'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -47,7 +49,7 @@ import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/deb import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; import { getInstalledExtensions, IExtensionStatus } from 'vs/workbench/contrib/extensions/common/extensionsUtils'; import { settingsEditIcon, settingsRemoveIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons'; -import { filterSettingsRequireWorkspaceTrust, IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; +import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { WorkspaceTrustEditorInput } from 'vs/workbench/services/workspaces/browser/workspaceTrustEditorInput'; @@ -117,8 +119,8 @@ class WorkspaceTrustedUrisTable extends Disposable { label: '', tooltip: '', weight: 0, - minimumWidth: 80, - maximumWidth: 80, + minimumWidth: 55, + maximumWidth: 55, templateId: TrustedUriActionsColumnRenderer.TEMPLATE_ID, project(row: ITrustedUriItem): ITrustedUriItem { return row; } }, @@ -173,9 +175,12 @@ class WorkspaceTrustedUrisTable extends Disposable { } } + private get currentWorkspaceUri(): URI { + return this.workspaceService.getWorkspace().folders[0]?.uri || URI.file('/'); + } + private get trustedUriEntries(): ITrustedUriItem[] { const currentWorkspace = this.workspaceService.getWorkspace(); - const currentWorkspaceUri = currentWorkspace.folders[0]?.uri || URI.file('/'); const currentWorkspaceUris = currentWorkspace.folders.map(folder => folder.uri); if (currentWorkspace.configuration) { currentWorkspaceUris.push(currentWorkspace.configuration); @@ -194,7 +199,7 @@ class WorkspaceTrustedUrisTable extends Disposable { parentOfWorkspaceItem: relatedToCurrentWorkspace }; }); - entries.push({ uri: currentWorkspaceUri, entryType: TrustedUriItemType.Add, parentOfWorkspaceItem: false }); + entries.push({ uri: this.currentWorkspaceUri, entryType: TrustedUriItemType.Add, parentOfWorkspaceItem: false }); return entries; } @@ -231,9 +236,11 @@ class WorkspaceTrustedUrisTable extends Disposable { } async edit(item: ITrustedUriItem) { - if (item.uri.scheme === Schemas.file || item.uri.scheme === Schemas.vscodeRemote) { + const canUseOpenDialog = item.uri.scheme === Schemas.file || + (item.uri.scheme === this.currentWorkspaceUri.scheme && this.uriService.extUri.isEqualAuthority(this.currentWorkspaceUri.authority, item.uri.authority)); + if (canUseOpenDialog) { const uri = await this.fileDialogService.showOpenDialog({ - canSelectFiles: true, + canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: item.uri, @@ -478,8 +485,8 @@ class TrustedUriHostColumnRenderer implements ITableRenderer(Extensions.Configuration); + const settingsRequiringTrustedWorkspaceCount = restrictedSettings.default.filter(key => { + const property = configurationRegistry.getConfigurationProperties()[key]; + + // cannot be configured in workspace + if (property.scope === ConfigurationScope.APPLICATION || property.scope === ConfigurationScope.MACHINE) { + return false; + } + + // If deprecated include only those configured in the workspace + if (property.deprecationMessage || property.markdownDeprecationMessage) { + if (restrictedSettings.workspace?.includes(key)) { + return true; + } + if (restrictedSettings.workspaceFolder) { + for (const workspaceFolderSettings of restrictedSettings.workspaceFolder.values()) { + if (workspaceFolderSettings.includes(key)) { + return true; + } + } + } + return false; + } + + return true; + }).length; // Features List const installedExtensions = await this.instantiationService.invokeFunction(getInstalledExtensions); @@ -700,10 +733,10 @@ export class WorkspaceTrustEditor extends EditorPane { private createConfigurationElement(parent: HTMLElement): void { this.configurationContainer = append(parent, $('.workspace-trust-settings')); const configurationTitle = append(this.configurationContainer, $('.workspace-trusted-folders-title')); - configurationTitle.innerText = localize('trustedFolders', "Trusted Folders"); + configurationTitle.innerText = localize('trustedFoldersAndWorkspaces', "Trusted Folders & Workspaces"); const configurationDescription = append(this.configurationContainer, $('.workspace-trusted-folders-description')); - configurationDescription.innerText = localize('trustedFoldersDescription', "You trust the following folders and their children."); + configurationDescription.innerText = localize('trustedFoldersDescription', "You trust the following folders, their children, and workspace files."); this.workpaceTrustedUrisTable = this._register(this.instantiationService.createInstance(WorkspaceTrustedUrisTable, this.configurationContainer)); diff --git a/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts b/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts index e168d463051..c27cd8e4f63 100644 --- a/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts +++ b/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts @@ -12,7 +12,7 @@ import { IAddressProvider } from 'vs/platform/remote/common/remoteAgentConnectio import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExtensionService, NullExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { isWindows } from 'vs/base/common/platform'; -import { IWebviewService, WebviewContentOptions, WebviewElement, WebviewExtensionDescription, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; +import { IWebviewService, Webview, WebviewContentOptions, WebviewElement, WebviewExtensionDescription, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { ITunnelProvider, ITunnelService, RemoteTunnel, TunnelProviderFeatures } from 'vs/platform/remote/common/tunnel'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { joinPath } from 'vs/base/common/resources'; @@ -270,6 +270,7 @@ class SimpleWebviewService implements IWebviewService { declare readonly _serviceBrand: undefined; readonly activeWebview = undefined; + readonly webviews: Webview[] = []; readonly onDidChangeActiveWebview = Event.None; createWebviewElement(id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, extension: WebviewExtensionDescription | undefined): WebviewElement { throw new Error('Method not implemented.'); } diff --git a/src/vs/workbench/services/banner/browser/bannerService.ts b/src/vs/workbench/services/banner/browser/bannerService.ts index 859bf6035e8..15f9fe15afd 100644 --- a/src/vs/workbench/services/banner/browser/bannerService.ts +++ b/src/vs/workbench/services/banner/browser/bannerService.ts @@ -7,16 +7,15 @@ import { Codicon } from 'vs/base/common/codicons'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILinkDescriptor } from 'vs/platform/opener/browser/link'; -import { StorageScope } from 'vs/platform/storage/common/storage'; export interface IBannerItem { readonly id: string; readonly icon: Codicon; readonly message: string | MarkdownString; - readonly scope?: StorageScope; /* Used to remember that the banner has been closed. */ readonly actions?: ILinkDescriptor[]; readonly ariaLabel?: string; + readonly onClose?: () => void; } export const IBannerService = createDecorator('bannerService'); diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index 9cff28447ca..105b65223d7 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -3,13 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; import { ResourceMap } from 'vs/base/common/map'; -import { Registry } from 'vs/platform/registry/common/platform'; export const FOLDER_CONFIG_FOLDER_NAME = '.vscode'; export const FOLDER_SETTINGS_NAME = 'settings'; @@ -48,14 +47,6 @@ export interface IConfigurationCache { } -export function filterSettingsRequireWorkspaceTrust(settings: ReadonlyArray): ReadonlyArray { - const configurationRegistry = Registry.as(Extensions.Configuration); - return settings.filter(key => { - const property = configurationRegistry.getConfigurationProperties()[key]; - return property.restricted && property.scope !== ConfigurationScope.APPLICATION && property.scope !== ConfigurationScope.MACHINE; - }); -} - export type RestrictedSettings = { default: ReadonlyArray; userLocal?: ReadonlyArray; diff --git a/src/vs/workbench/services/editor/browser/editorOverrideService.ts b/src/vs/workbench/services/editor/browser/editorOverrideService.ts index 5ca97d5b24c..990a4acf6d9 100644 --- a/src/vs/workbench/services/editor/browser/editorOverrideService.ts +++ b/src/vs/workbench/services/editor/browser/editorOverrideService.ts @@ -161,8 +161,8 @@ export class EditorOverrideService extends Disposable implements IEditorOverride return toDisposable(() => remove()); } - hasContributionPoint(schemeOrGlob: string): boolean { - return this._contributionPoints.has(schemeOrGlob); + hasContributionPoint(glob: string): boolean { + return this._contributionPoints.has(glob); } getAssociationsForResource(resource: URI): EditorAssociations { @@ -223,12 +223,15 @@ export class EditorOverrideService extends Disposable implements IEditorOverride } private findMatchingContributions(resource: URI): ContributionPoint[] { + // The user setting should be respected even if the editor doesn't specify that resource in package.json + const userSettings = this.getAssociationsForResource(resource); let contributions: ContributionPoint[] = []; // Then all glob patterns for (const key of this._contributionPoints.keys()) { const contributionPoints = this._contributionPoints.get(key)!; for (const contributionPoint of contributionPoints) { - if (globMatchesResource(key, resource)) { + const foundInSettings = userSettings.find(setting => setting.viewType === contributionPoint.editorInfo.id); + if (foundInSettings || globMatchesResource(key, resource)) { contributions.push(contributionPoint); } } diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 92da6861768..a4046f66004 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -236,17 +236,6 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment return (this.webviewEndpoint).replace('{{commit}}', this.productService.commit || '23a2409675bc1bde94f3532bc7c5826a6e99e4b6'); } - @memoize - get webviewResourceRoot(): string { - return `${this.webviewExternalEndpoint}/vscode-resource/{{resource}}`; - } - - @memoize - get webviewCspSource(): string { - const uri = URI.parse(this.webviewEndpoint.replace('{{uuid}}', '*')); - return `${uri.scheme}://${uri.authority}`; - } - @memoize get telemetryLogResource(): URI { return joinPath(this.options.logsPath, 'telemetry.log'); } get disableTelemetry(): boolean { return false; } diff --git a/src/vs/workbench/services/environment/common/environmentService.ts b/src/vs/workbench/services/environment/common/environmentService.ts index 6604c9823ae..f8950d42da8 100644 --- a/src/vs/workbench/services/environment/common/environmentService.ts +++ b/src/vs/workbench/services/environment/common/environmentService.ts @@ -36,8 +36,6 @@ export interface IWorkbenchEnvironmentService extends IEnvironmentService { readonly extensionEnabledProposedApi?: string[]; readonly webviewExternalEndpoint: string; - readonly webviewResourceRoot: string; - readonly webviewCspSource: string; readonly skipReleaseNotes: boolean; diff --git a/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts b/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts index 9fb4fb45d75..948a3aa8935 100644 --- a/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts +++ b/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts @@ -68,21 +68,6 @@ export class NativeWorkbenchEnvironmentService extends AbstractNativeEnvironment @memoize get webviewExternalEndpoint(): string { return `${Schemas.vscodeWebview}://{{uuid}}`; } - @memoize - get webviewResourceRoot(): string { - // On desktop, this endpoint is only used for the service worker to identify resource loads and - // should never actually be requested. - // - // Required due to https://github.com/electron/electron/issues/28528 - return 'https://{{uuid}}.vscode-webview-test.com/vscode-resource/{{resource}}'; - } - - @memoize - get webviewCspSource(): string { - const uri = URI.parse(this.webviewResourceRoot.replace('{{uuid}}', '*')); - return `${uri.scheme}://${uri.authority}`; - } - @memoize get skipReleaseNotes(): boolean { return !!this.args['skip-release-notes']; } diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts index fee40e289d8..bf44e6b8d70 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts @@ -360,8 +360,6 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, globalStorageHome: this._environmentService.globalStorageHome, workspaceStorageHome: this._environmentService.workspaceStorageHome, - webviewResourceRoot: this._environmentService.webviewResourceRoot, - webviewCspSource: this._environmentService.webviewCspSource, }, workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? undefined : { configuration: workspace.configuration || undefined, diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts index 4b07928a83d..d238526f526 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts @@ -235,9 +235,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI, extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, globalStorageHome: remoteInitData.globalStorageHome, - workspaceStorageHome: remoteInitData.workspaceStorageHome, - webviewResourceRoot: this._environmentService.webviewResourceRoot, - webviewCspSource: this._environmentService.webviewCspSource, + workspaceStorageHome: remoteInitData.workspaceStorageHome }, workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : { configuration: workspace.configuration, diff --git a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts index a29459cf692..245da60c35b 100644 --- a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts @@ -476,8 +476,6 @@ export class LocalProcessExtensionHost implements IExtensionHost { extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, globalStorageHome: this._environmentService.globalStorageHome, workspaceStorageHome: this._environmentService.workspaceStorageHome, - webviewResourceRoot: this._environmentService.webviewResourceRoot, - webviewCspSource: this._environmentService.webviewCspSource, }, workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? undefined : { configuration: withNullAsUndefined(workspace.configuration), diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 25edebb2c71..83d72d6341e 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -163,6 +163,11 @@ export interface IWorkbenchLayoutService extends ILayoutService { */ setActivityBarHidden(hidden: boolean): void; + /** + * Set banner hidden or not + */ + setBannerHidden(hidden: boolean): void; + /** * * Set editor area hidden or not diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index 82b7a7dfc45..fbdb00a85f6 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -152,6 +152,7 @@ export class ProgressService extends Disposable implements IProgressService { } const statusEntryProperties: IStatusbarEntry = { + name: localize('status.progress', "Progress Message"), text, showProgress: true, ariaLabel: text, @@ -162,7 +163,7 @@ export class ProgressService extends Disposable implements IProgressService { if (this.windowProgressStatusEntry) { this.windowProgressStatusEntry.update(statusEntryProperties); } else { - this.windowProgressStatusEntry = this.statusbarService.addEntry(statusEntryProperties, 'status.progress', localize('status.progress', "Progress Message"), StatusbarAlignment.LEFT); + this.windowProgressStatusEntry = this.statusbarService.addEntry(statusEntryProperties, 'status.progress', StatusbarAlignment.LEFT); } } diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 51210735438..43e7c540559 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -585,7 +585,7 @@ export class TunnelModel extends Disposable { existingTunnel.name = attributes?.label ?? name; this._onForwardPort.fire(); } - if (attributes?.protocol !== existingTunnel.localUri.scheme) { + if (attributes?.protocol && attributes.protocol !== existingTunnel.localUri.scheme) { await this.close(existingTunnel.remoteHost, existingTunnel.remotePort); await this.forward({ host: existingTunnel.remoteHost, port: existingTunnel.remotePort }, local, name, source, elevateIfNeeded, isPublic, restore, attributes); } diff --git a/src/vs/workbench/services/statusbar/common/statusbar.ts b/src/vs/workbench/services/statusbar/common/statusbar.ts index ee19a350567..7ef7ee73e64 100644 --- a/src/vs/workbench/services/statusbar/common/statusbar.ts +++ b/src/vs/workbench/services/statusbar/common/statusbar.ts @@ -21,6 +21,12 @@ export const enum StatusbarAlignment { */ export interface IStatusbarEntry { + /** + * The (short) name to show for the entry like 'Language Indicator', + * 'Git Status' etc. + */ + readonly name: string; + /** * The text to show for the entry. You can embed icons in the text by leveraging the syntax: * @@ -79,12 +85,11 @@ export interface IStatusbarService { * to update or remove the statusbar entry. * * @param id identifier of the entry is needed to allow users to hide entries via settings - * @param name human readable name the entry is about * @param alignment either LEFT or RIGHT * @param priority items get arranged from highest priority to lowest priority from left to right * in their respective alignment slot */ - addEntry(entry: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority?: number): IStatusbarEntryAccessor; + addEntry(entry: IStatusbarEntry, id: string, alignment: StatusbarAlignment, priority?: number): IStatusbarEntryAccessor; /** * An event that is triggered when an entry's visibility is changed. diff --git a/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager2.ts b/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager2.ts index b6c1944af3a..32b0932927d 100644 --- a/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager2.ts +++ b/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager2.ts @@ -20,7 +20,7 @@ import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/ur import { IFileWorkingCopy, IFileWorkingCopyModel, IFileWorkingCopyModelFactory, IFileWorkingCopyResolveOptions } from 'vs/workbench/services/workingCopy/common/fileWorkingCopy'; import { FileWorkingCopyManager, IFileWorkingCopyManager } from 'vs/workbench/services/workingCopy/common/fileWorkingCopyManager'; import { IUntitledFileWorkingCopy, IUntitledFileWorkingCopyModel, IUntitledFileWorkingCopyModelFactory, UntitledFileWorkingCopy } from 'vs/workbench/services/workingCopy/common/untitledFileWorkingCopy'; -import { IExistingUntitledFileWorkingCopyOptions, INewUntitledFileWorkingCopyOptions, INewUntitledFileWorkingCopyWithAssociatedResourceOptions, IUntitledFileWorkingCopyManager, UntitledFileWorkingCopyManager } from 'vs/workbench/services/workingCopy/common/untitledFileWorkingCopyManager'; +import { INewOrExistingUntitledFileWorkingCopyOptions, INewUntitledFileWorkingCopyOptions, INewUntitledFileWorkingCopyWithAssociatedResourceOptions, IUntitledFileWorkingCopyManager, UntitledFileWorkingCopyManager } from 'vs/workbench/services/workingCopy/common/untitledFileWorkingCopyManager'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { isValidBasename } from 'vs/base/common/extpath'; @@ -36,18 +36,6 @@ export interface IFileWorkingCopyManager2; - /** - * Resolves an untitled file working copy from the provided options. - */ - resolve(options?: INewUntitledFileWorkingCopyOptions): Promise>; - resolve(options?: INewUntitledFileWorkingCopyWithAssociatedResourceOptions): Promise>; - - /** - * Resolves an untitled file working copy from the provided options - * unless an existing working copy already exists with that resource. - */ - resolve(options?: IExistingUntitledFileWorkingCopyOptions): Promise>; - /** * Allows to resolve a file working copy. If the manager already knows * about a file working copy with the same `URI`, it will return that @@ -63,10 +51,34 @@ export interface IFileWorkingCopyManager2>; + /** + * Create a new untitled file working copy with optional initial contents. + * + * Note: Callers must `dispose` the working copy when no longer needed. + */ + resolve(options?: INewUntitledFileWorkingCopyOptions): Promise>; + + /** + * Create a new untitled file working copy with optional initial contents + * and associated resource. The associated resource will be used when + * saving and will not require to ask the user for a file path. + * + * Note: Callers must `dispose` the working copy when no longer needed. + */ + resolve(options?: INewUntitledFileWorkingCopyWithAssociatedResourceOptions): Promise>; + + /** + * Creates a new untitled file working copy with optional initial contents + * with the provided resource or return an existing untitled file working + * copy otherwise. + * + * Note: Callers must `dispose` the working copy when no longer needed. + */ + resolve(options?: INewOrExistingUntitledFileWorkingCopyOptions): Promise>; + /** * Implements "Save As" for file based working copies. The API is `URI` based * because it works even without resolved file working copies. If a file working @@ -81,6 +93,8 @@ export interface IFileWorkingCopyManager2>; resolve(options?: INewUntitledFileWorkingCopyWithAssociatedResourceOptions): Promise>; - resolve(options?: IExistingUntitledFileWorkingCopyOptions): Promise>; + resolve(options?: INewOrExistingUntitledFileWorkingCopyOptions): Promise>; resolve(resource: URI, options?: IFileWorkingCopyResolveOptions): Promise>; - resolve(arg1?: URI | INewUntitledFileWorkingCopyOptions | INewUntitledFileWorkingCopyWithAssociatedResourceOptions | IExistingUntitledFileWorkingCopyOptions, arg2?: IFileWorkingCopyResolveOptions): Promise | IFileWorkingCopy> { + resolve(arg1?: URI | INewUntitledFileWorkingCopyOptions | INewUntitledFileWorkingCopyWithAssociatedResourceOptions | INewOrExistingUntitledFileWorkingCopyOptions, arg2?: IFileWorkingCopyResolveOptions): Promise | IFileWorkingCopy> { if (URI.isUri(arg1)) { return this.files.resolve(arg1, arg2); } diff --git a/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopyManager.ts b/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopyManager.ts index 09562661f3d..8b326b5dadd 100644 --- a/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopyManager.ts +++ b/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopyManager.ts @@ -35,16 +35,29 @@ export interface IUntitledFileWorkingCopyManager>; /** - * Resolves an untitled file working copy from the provided options. + * Create a new untitled file working copy with optional initial contents. + * + * Note: Callers must `dispose` the working copy when no longer needed. */ resolve(options?: INewUntitledFileWorkingCopyOptions): Promise>; + + /** + * Create a new untitled file working copy with optional initial contents + * and associated resource. The associated resource will be used when + * saving and will not require to ask the user for a file path. + * + * Note: Callers must `dispose` the working copy when no longer needed. + */ resolve(options?: INewUntitledFileWorkingCopyWithAssociatedResourceOptions): Promise>; /** - * Resolves an untitled file working copy from the provided options - * unless an existing working copy already exists with that resource. + * Creates a new untitled file working copy with optional initial contents + * with the provided resource or return an existing untitled file working + * copy otherwise. + * + * Note: Callers must `dispose` the working copy when no longer needed. */ - resolve(options?: IExistingUntitledFileWorkingCopyOptions): Promise>; + resolve(options?: INewOrExistingUntitledFileWorkingCopyOptions): Promise>; } export interface INewUntitledFileWorkingCopyOptions { @@ -55,7 +68,7 @@ export interface INewUntitledFileWorkingCopyOptions { * Note: An untitled file working copy with initial * value is dirty right from the beginning. */ - initialValue?: VSBufferReadableStream; + contents?: VSBufferReadableStream; } export interface INewUntitledFileWorkingCopyWithAssociatedResourceOptions extends INewUntitledFileWorkingCopyOptions { @@ -71,7 +84,7 @@ export interface INewUntitledFileWorkingCopyWithAssociatedResourceOptions extend associatedResource: { authority?: string; path?: string; query?: string; fragment?: string; } } -export interface IExistingUntitledFileWorkingCopyOptions extends INewUntitledFileWorkingCopyOptions { +export interface INewOrExistingUntitledFileWorkingCopyOptions extends INewUntitledFileWorkingCopyOptions { /** * A resource to identify the untitled file working copy @@ -82,7 +95,7 @@ export interface IExistingUntitledFileWorkingCopyOptions extends INewUntitledFil untitledResource: URI; } -type IInternalUntitledFileWorkingCopyOptions = INewUntitledFileWorkingCopyOptions & INewUntitledFileWorkingCopyWithAssociatedResourceOptions & IExistingUntitledFileWorkingCopyOptions; +type IInternalUntitledFileWorkingCopyOptions = INewUntitledFileWorkingCopyOptions & INewUntitledFileWorkingCopyWithAssociatedResourceOptions & INewOrExistingUntitledFileWorkingCopyOptions; export class UntitledFileWorkingCopyManager extends BaseFileWorkingCopyManager> implements IUntitledFileWorkingCopyManager { @@ -115,7 +128,7 @@ export class UntitledFileWorkingCopyManager>; resolve(options?: INewUntitledFileWorkingCopyWithAssociatedResourceOptions): Promise>; - resolve(options?: IExistingUntitledFileWorkingCopyOptions): Promise>; + resolve(options?: INewOrExistingUntitledFileWorkingCopyOptions): Promise>; async resolve(options?: IInternalUntitledFileWorkingCopyOptions): Promise> { const workingCopy = this.doCreateOrGet(options); await workingCopy.resolve(); @@ -141,7 +154,7 @@ export class UntitledFileWorkingCopyManager; diff --git a/src/vs/workbench/services/workingCopy/test/browser/fileWorkingCopyManager2.test.ts b/src/vs/workbench/services/workingCopy/test/browser/fileWorkingCopyManager2.test.ts index ae6a5510c04..9ec0f1f82b2 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/fileWorkingCopyManager2.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/fileWorkingCopyManager2.test.ts @@ -65,7 +65,7 @@ suite('FileWorkingCopyManager2', () => { assert.strictEqual(accessor.workingCopyService.workingCopies.length, 0); await manager.resolve(URI.file('/test.html')); - await manager.resolve({ initialValue: bufferToStream(VSBuffer.fromString('Hello Untitled')) }); + await manager.resolve({ contents: bufferToStream(VSBuffer.fromString('Hello Untitled')) }); assert.strictEqual(accessor.workingCopyService.workingCopies.length, 2); assert.strictEqual(manager.files.workingCopies.length, 1); diff --git a/src/vs/workbench/services/workingCopy/test/browser/untitledFileWorkingCopyManager.test.ts b/src/vs/workbench/services/workingCopy/test/browser/untitledFileWorkingCopyManager.test.ts index 8a7b56c90d3..5acba50cecc 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/untitledFileWorkingCopyManager.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/untitledFileWorkingCopyManager.test.ts @@ -113,7 +113,7 @@ suite('UntitledFileWorkingCopyManager', () => { dirtyCounter++; }); - const workingCopy = await manager.untitled.resolve({ initialValue: bufferToStream(VSBuffer.fromString('Hello World')) }); + const workingCopy = await manager.untitled.resolve({ contents: bufferToStream(VSBuffer.fromString('Hello World')) }); assert.strictEqual(workingCopy.isDirty(), true); assert.strictEqual(dirtyCounter, 1); @@ -136,6 +136,20 @@ suite('UntitledFileWorkingCopyManager', () => { workingCopy3.dispose(); }); + test('resolve - untitled resource used for new working copy', async () => { + const invalidUntitledResource = URI.file('my/untitled.txt'); + const validUntitledResource = invalidUntitledResource.with({ scheme: Schemas.untitled }); + + const workingCopy1 = await manager.untitled.resolve({ untitledResource: invalidUntitledResource }); + assert.notStrictEqual(workingCopy1.resource.toString(), invalidUntitledResource.toString()); + + const workingCopy2 = await manager.untitled.resolve({ untitledResource: validUntitledResource }); + assert.strictEqual(workingCopy2.resource.toString(), validUntitledResource.toString()); + + workingCopy1.dispose(); + workingCopy2.dispose(); + }); + test('resolve - with associated resource', async () => { const workingCopy = await manager.untitled.resolve({ associatedResource: { path: '/some/associated.txt' } }); diff --git a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts index c49d8f6fade..7f134cebfa1 100644 --- a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts +++ b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Codicon } from 'vs/base/common/codicons'; import { Emitter } from 'vs/base/common/event'; import { splitName } from 'vs/base/common/labels'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -389,6 +390,7 @@ export class WorkspaceTrustRequestService extends Disposable implements IWorkspa const result = await this.dialogService.show(Severity.Info, localize('openLooseFileMesssage', "Are you sure you want to open these files?"), [localize('open', "Open"), localize('newWindow', "Open in New Window"), localize('cancel', "Cancel")], { detail: localize('openLooseFileDetails', "You are trying to open untrusted files into the current window which is trusted. How would you like to continue?"), cancelId: 2, + custom: { icon: Codicon.shield } }); switch (result.choice) { diff --git a/src/vs/workbench/test/browser/api/extHostWebview.test.ts b/src/vs/workbench/test/browser/api/extHostWebview.test.ts index 447a1009a0c..f966a1ca426 100644 --- a/src/vs/workbench/test/browser/api/extHostWebview.test.ts +++ b/src/vs/workbench/test/browser/api/extHostWebview.test.ts @@ -30,12 +30,7 @@ suite('ExtHostWebview', () => { test('Cannot register multiple serializers for the same view type', async () => { const viewType = 'view.type'; - const extHostWebviews = new ExtHostWebviews(rpcProtocol!, { - webviewCspSource: '', - webviewResourceRoot: '', - isExtensionDevelopmentDebug: false, - remote: { authority: undefined }, - }, undefined, new NullLogService(), NullApiDeprecationService); + const extHostWebviews = new ExtHostWebviews(rpcProtocol!, { remote: { authority: undefined } }, undefined, new NullLogService(), NullApiDeprecationService); const extHostWebviewPanels = new ExtHostWebviewPanels(rpcProtocol!, extHostWebviews, undefined); @@ -79,56 +74,68 @@ suite('ExtHostWebview', () => { assert.strictEqual(lastInvokedDeserializer, serializerB); }); - test('asWebviewUri for web endpoint', () => { - const extHostWebviews = new ExtHostWebviews(rpcProtocol!, { - webviewCspSource: '', - webviewResourceRoot: `https://{{uuid}}.webview.contoso.com/commit/{{resource}}`, - isExtensionDevelopmentDebug: false, - remote: { - authority: 'remote' - }, - }, undefined, new NullLogService(), NullApiDeprecationService); - - const extHostWebviewPanels = new ExtHostWebviewPanels(rpcProtocol!, extHostWebviews, undefined); - - const webview = extHostWebviewPanels.createWebviewPanel({} as any, 'type', 'title', 1, {}); - - function stripEndpointUuid(input: string) { - return input.replace(/^https:\/\/[^\.]+?\./, ''); - } + test('asWebviewUri for local file paths', () => { + const webview = createWebview(rpcProtocol, /* remoteAuthority */undefined); assert.strictEqual( stripEndpointUuid(webview.webview.asWebviewUri(URI.parse('file:///Users/codey/file.html')).toString()), - 'webview.contoso.com/commit/remote/file//Users/codey/file.html', + 'vscode-webview-test.com/vscode-resource/file//Users/codey/file.html', 'Unix basic' ); assert.strictEqual( stripEndpointUuid(webview.webview.asWebviewUri(URI.parse('file:///Users/codey/file.html#frag')).toString()), - 'webview.contoso.com/commit/remote/file//Users/codey/file.html#frag', + 'vscode-webview-test.com/vscode-resource/file//Users/codey/file.html#frag', 'Unix should preserve fragment' ); assert.strictEqual( stripEndpointUuid(webview.webview.asWebviewUri(URI.parse('file:///Users/codey/f%20ile.html')).toString()), - 'webview.contoso.com/commit/remote/file//Users/codey/f%20ile.html', + 'vscode-webview-test.com/vscode-resource/file//Users/codey/f%20ile.html', 'Unix with encoding' ); assert.strictEqual( stripEndpointUuid(webview.webview.asWebviewUri(URI.parse('file://localhost/Users/codey/file.html')).toString()), - 'webview.contoso.com/commit/remote/file/localhost/Users/codey/file.html', + 'vscode-webview-test.com/vscode-resource/file/localhost/Users/codey/file.html', 'Unix should preserve authority' ); assert.strictEqual( stripEndpointUuid(webview.webview.asWebviewUri(URI.parse('file:///c:/codey/file.txt')).toString()), - 'webview.contoso.com/commit/remote/file//c%3A/codey/file.txt', + 'vscode-webview-test.com/vscode-resource/file//c%3A/codey/file.txt', 'Windows C drive' ); }); + + test('asWebviewUri for remote file paths', () => { + const webview = createWebview(rpcProtocol, /* remoteAuthority */ 'remote'); + + assert.strictEqual( + stripEndpointUuid(webview.webview.asWebviewUri(URI.parse('file:///Users/codey/file.html')).toString()), + 'vscode-webview-test.com/vscode-resource/vscode-remote/remote/Users/codey/file.html', + 'Unix basic' + ); + }); }); +function createWebview(rpcProtocol: (IExtHostRpcService & IExtHostContext) | undefined, remoteAuthority: string | undefined) { + const extHostWebviews = new ExtHostWebviews(rpcProtocol!, { + remote: { + authority: remoteAuthority + }, + }, undefined, new NullLogService(), NullApiDeprecationService); + + const extHostWebviewPanels = new ExtHostWebviewPanels(rpcProtocol!, extHostWebviews, undefined); + + const webview = extHostWebviewPanels.createWebviewPanel({} as IExtensionDescription, 'type', 'title', 1, {}); + return webview; +} + +function stripEndpointUuid(input: string) { + return input.replace(/^https:\/\/[^\.]+?\./, ''); +} + function createNoopMainThreadWebviews() { return new class extends mock() { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 6ea06def96d..2414d2220b9 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -541,6 +541,7 @@ export class TestLayoutService implements IWorkbenchLayoutService { isStatusBarHidden(): boolean { return false; } isActivityBarHidden(): boolean { return false; } setActivityBarHidden(_hidden: boolean): void { } + setBannerHidden(_hidden: boolean): void { } isSideBarHidden(): boolean { return false; } async setEditorHidden(_hidden: boolean): Promise { } async setSideBarHidden(_hidden: boolean): Promise { } diff --git a/yarn.lock b/yarn.lock index 02c65997a24..8359b7fa542 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8692,9 +8692,9 @@ sparkles@^1.0.0: integrity sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw== spdlog@^0.13.0: - version "0.13.4" - resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.13.4.tgz#7393d436f077fca1d07500741e50cbf8928a838a" - integrity sha512-tdzk9ysc640emskx+pE/A2JdJ5IAr440ZIsNjRlD9aPK6U6IQ94VUGpl7u0NHamAB8O1H7RxLgtHyXT32V+RaA== + version "0.13.5" + resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.13.5.tgz#a31027dcccbe032e9a53579f42cb45428af08bad" + integrity sha512-D1xA5tRXw7eZOoFBCAnOxCxLN3JpHVDjpPJG/xjJ0nFZvtfOUTAzK66MVxJCDht/ZFwjLcBAltvzjfz4JTuSEw== dependencies: bindings "^1.5.0" mkdirp "^0.5.5"