-
Notifications
You must be signed in to change notification settings - Fork 30k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow extensions to contribute custom icons for webview panels #49657
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
151 changes: 151 additions & 0 deletions
151
src/vs/workbench/parts/webview/electron-browser/webviewExtensionPoint.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
|
||
import { join } from 'path'; | ||
import * as dom from 'vs/base/browser/dom'; | ||
import { IJSONSchema } from 'vs/base/common/jsonSchema'; | ||
import { localize } from 'vs/nls'; | ||
import { ExtensionMessageCollector, ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; | ||
|
||
namespace schema { | ||
|
||
export interface IUserFriendlyWebviewDescriptor { | ||
viewType: string; | ||
icon?: { | ||
light: string; | ||
dark: string; | ||
}; | ||
} | ||
|
||
export function isValidViewDescriptors(viewDescriptors: IUserFriendlyWebviewDescriptor[], collector: ExtensionMessageCollector): boolean { | ||
if (!Array.isArray(viewDescriptors)) { | ||
collector.error(localize('requirearray', "views must be an array")); | ||
return false; | ||
} | ||
|
||
for (let descriptor of viewDescriptors) { | ||
if (typeof descriptor.viewType !== 'string') { | ||
collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'viewType')); | ||
return false; | ||
} | ||
|
||
if (descriptor.icon) { | ||
if (typeof descriptor.icon.dark !== 'string') { | ||
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'icon.dark')); | ||
return false; | ||
} | ||
if (typeof descriptor.icon.light !== 'string') { | ||
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'icon.light')); | ||
return false; | ||
} | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
const webviewDescriptor: IJSONSchema = { | ||
type: 'object', | ||
properties: { | ||
viewType: { | ||
description: localize('vscode.extension.contributes.webview.viewType', 'The unique identifier of the view.'), | ||
type: 'string' | ||
}, | ||
icon: { | ||
type: 'object', | ||
properties: { | ||
light: { | ||
type: 'string' | ||
}, | ||
dark: { | ||
type: 'string' | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
|
||
export const webviewsContribution: IJSONSchema = { | ||
description: localize('vscode.extension.contributes.webviews', "Contributes webviews to the editor"), | ||
type: 'array', | ||
items: webviewDescriptor, | ||
default: [] | ||
}; | ||
} | ||
|
||
|
||
ExtensionsRegistry.registerExtensionPoint<schema.IUserFriendlyWebviewDescriptor[]>('webviews', [], schema.webviewsContribution) | ||
.setHandler((extensions) => { | ||
for (let extension of extensions) { | ||
const { value, collector } = extension; | ||
|
||
if (!schema.isValidViewDescriptors(value, collector)) { | ||
return; | ||
} | ||
|
||
const viewIds: string[] = []; | ||
const viewDescriptors: IWebviewDescriptor[] = value.map(item => { | ||
const viewDescriptor = <IWebviewDescriptor>{ | ||
viewType: item.viewType, | ||
icon: item.icon ? { | ||
light: join(extension.description.extensionFolderPath, item.icon.light), | ||
dark: join(extension.description.extensionFolderPath, item.icon.dark), | ||
} : undefined | ||
}; | ||
|
||
// validate | ||
if (viewIds.indexOf(viewDescriptor.viewType) !== -1) { | ||
collector.error(localize('duplicateView1', "Cannot register multiple webview with same viewtype `{0}`", viewDescriptor.viewType)); | ||
return null; | ||
} | ||
// if (registeredViews.some(v => v.id === viewDescriptor.id)) { | ||
// collector.error(localize('duplicateView2', "A view with id `{0}` is already registered in the location `{1}`", viewDescriptor.id, viewDescriptor.location.id)); | ||
// return null; | ||
// } | ||
|
||
viewIds.push(viewDescriptor.viewType); | ||
return viewDescriptor; | ||
}); | ||
|
||
WebviewsRegistry.registerViews(viewDescriptors); | ||
} | ||
}); | ||
|
||
export interface IWebviewDescriptor { | ||
viewType: string; | ||
icon?: { | ||
light: string; | ||
dark: string; | ||
}; | ||
} | ||
|
||
export const WebviewsRegistry = new class { | ||
readonly _webviews = new Map<string, IWebviewDescriptor>(); | ||
_styleElement: HTMLStyleElement; | ||
|
||
constructor() { | ||
this._styleElement = dom.createStyleSheet(); | ||
this._styleElement.className = 'webview-icons'; | ||
} | ||
|
||
public get(viewType: string): IWebviewDescriptor | undefined { | ||
return this._webviews.get(viewType); | ||
} | ||
|
||
public registerViews(views: IWebviewDescriptor[]) { | ||
const cssRules: string[] = []; | ||
for (const view of views) { | ||
this._webviews.set(view.viewType, view); | ||
if (view.icon) { | ||
cssRules.push(`.show-file-icons .${escapeCSS(view.viewType)}-name-file-icon::before { background-image: url(${view.icon.light}); }`); | ||
} | ||
} | ||
this._styleElement.innerHTML += cssRules.join('\n'); | ||
} | ||
}; | ||
|
||
function escapeCSS(str: string) { | ||
return window['CSS'].escape(str); | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I initially tried putting this new point under
views
, but now that we support custom view containers it does not seem like a great fit thereThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what you mean by 'custom view containers'?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The custom activity bar sections that we added last iteration. I see two concerns with reusing
views
for webview contributions:views
.webviews
. Pretty unlikely this will be a real problem so the first point is the main concernThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it has to be merged into the
views
extension point, theneditor
is the right location for web views instead ofwebviews
. IMO Web view and Tree view are types of views. I see that at present Tree views and Web views do not have common schema, for e.g. Tree views do not have icons where as web views do. We can have either schema by type or schema by location depends on how they emerge in future. Looking at present scenario, it looks to me that thelocation
drives the properties. I mean, views registered in activity bar containers do not need icons but icons are needed for views registered under editor. In future, if we want to be flexible in moving views across, then all views can define icons optionally. Not sure what isviewType
property in web views. May be this can be used to differentiate tree views vs web views?I think the second point you mentioned is a valid concern. May be we should think of restricting some default locations like
editor
orpanel
.Or other approach would be to use viewsContainers
extension point? Say, introduce a new location
editor` and create a new view container for web views. It means a view container inside editor can contain multiple views(web or tree).