Updated: April 24, 2020
The Custom Text Editor API has shipped with VS Code 1.44! Checkout the documentation and example extension to get started creating your custom text editors.
If you encounter any issues using CustomTextEditorProvider
, please open a new issue.
This issue now tracks custom editors for binary files, which we aim to finalize for VS Code 1.46
The custom editor API aims to allow extensions to create fully customizable read/write editors that are used in place of VS Code's standard text editor for specific resources. These editors will be based on webviews. We will support editors for both binary and text resources
A XAML custom editor, for example, could show a WYSIWYG style editor for your .xaml
files. Our end goal is to give extensions the most flexibility possible while keeping VS Code fast, lean, and consistent.
Many features that are potentially related to custom editors are out of scope of this current proposal, including:
These all would fall under separate feature requests. Many also be a better fit for an external helper library instead of VS Code core
This issue thread captures the evolution of the webview editor proposal. That means that many comments may now be out of date. Some of these have been minimized, others have not.
For the most up to date info, see:
Two main parts of this that I see:
Custom views for resources (interactive but not editable)
A good example would be an image or 3d model viewer. To test out this api, we could even look into moving our builtin image preview into an extension
Custom editors for resources (hooking the custom views up to VS Code's text editor lifecycle)
These would be custom views that can be edited and saved like normal VS Code text editors
Some quick sketches
export enum WebviewEditorState {
Default = 1,
Dirty = 2,
}
export interface WebviewEditor extends WebviewPanel {
state: WebviewEditorState;
readonly onWillSave: Event<{ waitUntil: (thenable: Thenable<any>) => void }>
}
export interface WebviewEditorProvider {
/**
* Fills out a `WebviewEditor` for a given resource.
*
* The provider should take ownership of passed in `editor`.
*/
resolveWebviewEditor(
resource: Uri,
editor: WebviewEditor
): Thenable<void>;
}
export function registerWebviewEditorProvider(
viewType: string,
provider: WebviewEditorProvider,
): void;
{
"contributes": {
"webviewEditors": [
{
"viewType": "hexViewerExtension.hexEditor",
"displayName": "Hex Editor",
"languages": [
"hexdump"
]
}
]
}
}
User clicks on a hexdump file for the first time
If no custom view for a hexdump
file is registered.
If only a single extension is registered for a hexdump
file
registerWebviewEditorProvider
for hexViewerExtension.hexEditor
WebviewEditor
that the extension fills outIf multiple extensions are registered for a hexdump
file
User has a custom view open and wants to change to a different view
I like, I think this goes into the right direction. Other ideas/thoughts when looking at this
language
I would favour something like pattern
Is language the right abstraction? hexdump or png is not really a language but types of files and languages might just be a subset of that. So, instead or in addition to language I would favour something like pattern
Very good point. Think of files like WASM that can be represented _and modified_ as text in multiple formats (parens form, linear form) as well as block diagram as well as hexadecimal...
Or SVG, which could be viewed inside an image editor as well as XML editor.
I'm very hyped about this.
Is it possible i read in the docs for extension developers (about a year ago) that functionality like this was deliberately not supported for performance and stability reasons?
Not that i'm not hyped as well.. ;)
An API proposal and a draft implementation is in #77789. That one ties custom editors to webviews specifically.
An alternative approach that @jrieken and @bpasero brought up is more generic "open handler" extension point. Here's a quick draft of what this could look like:
{
"contributes": {
"openHandler": [
{
"id": "imageView.imageViewer",
"displayName": "Image Viewer",
"extensions": [
"png",
"jpg"
]
}
]
}
}
/* ignoring all the editable stuff about webview editors for the moment */
export interface WebviewEditor extends WebviewPanel { }
export function createWebviewEditor(resoruce: Uri, viewType: string, showOptions: ViewColumn | { viewColumn: ViewColumn, preserveFocus?: boolean }, options?: WebviewPanelOptions & WebviewOptions): WebviewEditor;
export function registerOpenHandler(
id: string,
handler: (resource: Uri, viewColumn: ViewColumn) => void,
): void;
export function activate() {
vscode.window.registerOpenHandler('imageView.imageViewer', (resource: vscode.Uri, viewColumn: ViewColumn) => {
vscode.window.createWebviewEditor(resource, 'imageView.imageView', viewColumn);
...
});
}
createWebviewEditor
? Just as one example of why we may not want to allow this, we likey only want to allow one editor for a given resource per view column/cc @Tyriar @kieferrm Since I know you were also interested in the discussion around the "open handler" proposal
Some thoughts:
After talking over the "open handler" proposal with @Tyriar and thinking about it more, I agree that it is likely too open ended in its current form. Does anyone has any specific use cases for the "open handler" proposal that could not be addressed by the previous custom editor proposed API or by existing VS Code apis?
As for commands such as undo/redo in webview, a couple of options:
Add events for very common commands such as save, undo, and redo:
readonly onWillSave: Event<{ waitUntil: (thenable: Thenable<any>) => void }>
readonly onWillUndo: Event<{ waitUntil: (thenable: Thenable<any>) => void }>
readonly onWillRedo: Event<{ waitUntil: (thenable: Thenable<any>) => void }>
....
Add a generic way to listen for commands:
onWillExecuteCommand(command: 'workbench.action.files.save' | 'undo' | 'redo' | ..., handler: (e) => Thenable<any>>
Both solutions would require that extensions explicitly state which commands they are interested in. This lets us avoid sending all commands to extensions, and would also let us enable/disable the relevant buttons in the menubar. (We may still want to consider save
a special case since it will be very common and may have special requirements)
Does anyone has any specific use cases for the "open handler" proposal that could not be addressed by the previous custom editor proposed API
I think a use-case would be files that open as text but need some prior processing. E.g clicking a foo.class
-file shouldn't open a web view editor but, after a decompile-step, should open a foo.generated.java
-file. However, we can treat that as a different problem and think about it as a default text content provider so certain file-types.
Let's continue with the existing proposal.
I think undo and redo should definitely be considered from the beginning in order to make more useful non-read only editors.
@Tyriar Can you explain why? We have added save-logic because editors render the dirty state (in their titles) but undo/redo isn't part of the editor UX. I agree that undo/redo is important but to me it's just commands that an extension should register.
Does anyone has any specific use cases for the "open handler" proposal that could not be addressed by the previous custom editor proposed API or by existing VS Code apis?
So are we saying that https://marketplace.visualstudio.com/items?itemName=slevesque.vscode-hexdump needs to rewrite the extension so that the standalone editor is embedded into a webview? So they would have to ship the standalone editor as part of their extension? This scenario is quite popular (see https://github.com/Microsoft/vscode/issues/2582, has even more upvotes than https://github.com/Microsoft/vscode/issues/12176).
Maybe I misunderstood and we are still thinking of supporting text editors to open but with the previous API.
Maybe I misunderstood and we are still thinking of supporting text editors to open but with the previous API.
Yeah, I think that's still a valid scenario, e.g. the webview editor should have an option to let the workbench know that it wants to show to the side of a text editor. For hexdump but also for markdown
To be clear, here is what I expect:
I agree that I would not put the burden of finding out if the editor is already opened onto the extension but on us.
Can you explain why? We have added save-logic because editors render the dirty state (in their titles) but undo/redo isn't part of the editor UX. I agree that undo/redo is important but to me it's just commands that an extension should register.
@bpasero I don't mind so much on how this is done, but if the user has a custom keybindings for the regular undo/redo commands it should work in the custom editor too.
Does anyone has any specific use cases for the "open handler" proposal that could not be addressed by the previous custom editor proposed API or by existing VS Code apis?
The original resolveWebviewEditor proposal is, at a broad level, probably enough for our needs. One thing it's missing compared to createWebviewEditor is starting a new document with no backing file. Is there a path to supporting this in the resolveWebviewEditor API?
Add a generic way to listen for commands:
YES! There are so many commands that we want to hook into. In addition to Save/Undo/Redo: Save As, Find/Replace; the LSP commands like Go To Definition/Find References, and the DAP commands like Debug Start/Continue/Step Into/Step Over/Stop.
First iteration of custom editors proposal (#77789) is merged in order to get testing and feedback in VS insiders.
Here's what things stand:
export interface WebviewEditor extends WebviewPanel { }
export interface WebviewEditorProvider {
/**
* Fills out a `WebviewEditor` for a given resource.
*
* The provider should take ownership of passed in `editor`.
*/
resolveWebviewEditor(
resource: Uri,
editor: WebviewEditor
): Thenable<void>;
}
namespace window {
export function registerWebviewEditorProvider(
viewType: string,
provider: WebviewEditorProvider,
): Disposable;
}
"contributes": {
"webviewEditors": [
{
"viewType": "imagePreview.previewEditor",
"displayName": "%webviewEditors.displayName%",
"discretion": "default",
"selector": [
{
"filenamePattern": "*.{jpg,png}"
}
]
}
]
}
discretion
ā Controls if the custom editor should be used by default or not when the extension is active (the default is true). selector
āĀ Let you specify both a filenamePattern
and an optional scheme
that the custom editor should be enabled forVS Code's image preview has been moved into an extension powered by webview editors
A new workbench.experimental.editorAssociations
user setting allows configuring which custom editor to use for a given resource. The schema of setting will likely be reworked as it is not too user friendly ATM
A new open with
command has been added to the explorer context menu. This lets you select which editor to open the target resource with
A new reopen with
command has been added. This commands lets you re-open the current editor using a custom editor
Confirm #77789 didn't break the world ;)
Ux improvements, bug fixes, polish
Save + dirty file tracking
Command execution (copy, paste, ...)
Try to understand what we need to do for the new file case. The current implementation can already be used with untitled
files (obviously without save or anything hooked up)
Fantastic, thank you! Looking forward to Save :)
@mjbvz any thoughts on how to support an extension such as https://marketplace.visualstudio.com/items?itemName=slevesque.vscode-hexdump can open a monaco text editor?
Save + dirty file tracking
We might need to talk. Today this is entirely controlled by checking for the resource scheme (e.g. supported are everything the file service can handle + untitled). Would we support hot-exit for custom editors? Auto save?
I'm trying this API and it's looking great so far.
One thing that doesn't seem to work is opening a file programatically. I wrote an extension to handle .ipynb
files and set it as the default. This works for when I click a file in the Explorer.
But when I called vscode.window.showTextDocument('file:///path/to/my.ipynb')
, I got an exception:
Error: Failed to show text document file:///path/to/my.ipynb, should show in editor #undefined
Would it be hard to make showTextDocument work with custom editors?
Would it be hard to make showTextDocument work with custom editors?
You are not showing a text document as in "returns a text editor" - therefore the API won't be supported. However, you should be able to use the vscode.open
-command
Thanks, that worked! Didn't see that one because I was just looking in vscode.d.ts.
np - it does bring up an interesting point and raises the question if we should have something like showWebviewEditor
and if we should allow to enumerate/discover web views (my feeling is that we shouldn't since web views are pretty specific to an owner, unlike text editors which are pretty generic)
@bpasero Still not sure about handling that case.
None of us really liked the idea of a generic Open handler
api (see https://github.com/microsoft/vscode/issues/77131#issuecomment-522187431 and @Tyriar's comment: https://github.com/microsoft/vscode/issues/77131#issuecomment-523157471)
But revisiting the open handler
proposal for a moment: what if extensions didn't need to know anything about editors and instead only remapped uris? This would leave the actual editor opening up to VS Code. Something like:
export function registerOpenHandler(
id: string,
handler: (resource: Uri) => Promise<Uri>,
): void;
For the custom text editor case (like the hexdump extension), an open handler would map the incoming uri (file://Users/mat/cat.png
) to a uri that a TextDocumentContentProvider
could handle ( hexdump://Users/mat/cat.png
)
Custom webview editors would do the same, i.e. file://Users/mat/cat.png
-> my-webview-scheme://Users/mat/cat.png
Some other aspects of this api direction:
We would have an openers
contributions that would define the contributed custom editors for a given file type. We may not even to expose a registerOpenHandler
function since the contribution could tell us what scheme to remap the uri to:
"contributions": {
"openers": [
{
"id": "myExt.hexDump",
"displayName": "Hex Dump",
"filenamePattern": "*.png",
"scheme": "file",
"targetScheme": "hexdump"
}
]
}
WebviewEditors would be contributed for a scheme instead of for a file pattern like the current proposal, much like TextDocumentContentProvider
or the old vscode.previewHtml
command ;)
Needs more thought but it seems like an interesting direction. Would be interested in hearing other's thoughts on the idea
For the custom text editor case (like the hexdump extension), an open handler would map the incoming uri (file://Users/mat/cat.png) to a uri that a TextDocumentContentProvider could handle ( hexdump://Users/mat/cat.png)
I like this idea š
I've been learning a lot while iterating on the custom editor proposal. I've also been thinking more about how custom editors need to fit into VS Code and have had to rethink some of my earlier assumptions about this.
Here I've attempted to capture what my current thoughts on the direction of custom editors, as well as noting the questions I still have and sketching out some potential approaches to addressing them.
First off, here are a few conclusions I've reached while working on the custom editor proposal:
Custom editors need to support non-disk resources
This includes:
Custom editors should support dirty resources
If I make a change in a text file and then open a custom editor on it, I expect the custom editor to reflect the dirty file (not the version on disk).
TextDocumentContentProvider
is not a custom editor
TextDocumentContentProvider
can only create readonly text buffers and cannot participate in save or any other editor actions.
A better analog for custom text editors is actually our file system provider api, as this both gives the resource a custom scheme and allows the extension to participate in save. However this API is a really heavyweight way to implement a custom text editor.
Extensions should not need to know where the resource data they are rendering comes from, nor should they need to know how the resource data itself will be used by VS Code.
The existing custom editor api works pretty well for readonly documents. However once we start talking about writable custom editors, the current proposal is vague on a number of important points:
On save, should the custom editor be responsible for writing data to disk?
Do we delegate file saving entirely to custom editors, or should save be more a notification to the custom editor that it should prepare its contents to be written to disk.
Does VS Code maintain editor history, or do custom editors maintain their own editor histories?
How does a custom editor know when its backing resource has changed?
How do we support custom editors for different versions of a file (the diff view case)?
When does a custom editor flush its contents back to VS Code?
For example: I edit a file using a custom editor and then switch to view the same file in VS Code's normal text editor without saving. In this case, I expect to see the modified content as text.
How does hot exit work?
What about save as?
Who provides Ui for all this?
I think most of these questions boil down to a debate of what exactly a custom editor should be and how it interacts with VS Code core. Should custom editors simply be views of a resource? or should they take over responsibility for that resource as well?
Let me quickly sketch out these two extremes to highlight this difference:
At one extreme, we could say that custom editors are simply views of resources. The custom editor takes in some resource state, renders itself, and can trigger events to tell VS Code that the resource has changed. This is a very React-style way of looking at the problem, so why not just express it in React terms:
<MyImageEditor
/* What we are rendering */
resource={'file:///Users/matb/cat.gif'}
/* Some abstraction so that the editor can read the file, since the file on disk may be different than VSCode's version */
resourceState={...}
/* Callback fired by the custom editor to signal the state of the editor has changed */
onContentChanged={...}
/* Some way to signal if editor is readonly or not? */
/>
Every time the resourceState
changes, the custom editor would be re-rendered.
If we take this approach, VS Code core would maintain the data for each resource (just like it does for text editors today). Core would be responsible for writing resources to disk on save, it would track editor history, and would handle reverts and other content operations, re-rendering the custom editor as needed as resource state changes.
At the other extreme, we could say that a custom editor both provides a view of a resource and is responsible for that resource. The API for this would be much closer to the existing proposal:
interface CustomEditor {
state: EditorState; // readonly, dirty, unchanged
/*
* Invoked when the user triggers save.
*
* Should write the file content to disk.
*/
save(): Thenable<void>;
/*
* Invoked when the user presses undo or redo.
*
* Should write the file content so that VS Code can read it
*/
undo(): Thenable<void>;
redo(): Thenable<void>;
...
}
But this proposal doesn't answer the questions I brought up. It also gives extensions a lot of room for interpretation, and we know that extensions will implement an api like this in different, incompatible ways.
My feeling is that considering custom editors to be simple views is the better approach (although perhaps not the simpler one for us to implement). It allows core to control resources so that edge cases are handled properly and so that the user experience is consistent no matter what type of custom editors user are using.
At an extremely high level, here's what this approach would mean for the various types of custom editors we have discussed:
Consider an image editor extension. Under this new proposal, this extension would need to:
Consider a hex dump extension (that is also writable). This extension would need to:
As @bpasero brought up, we may be able to leverage the filesystem provider api for this
Are there cases where custom editors as simple view do not work?
What would such an api look like in practice?
Can we implement it efficiently? I'm viewing a 100mb json file in a custom editor, we can't be sending around 100mb buffers all the time.
Other thoughts or concerns?
Is there an ETA on the API being available in a public build? Since the Python VS Code extension is using a webview based editor for Jupyter notebooks and need access to the File Save menu for our upcoming release in October. Thanks!
@mjbvz Door Number 1 sounds good to me. It's exactly what we'd need for editing notebooks. Although the write to disk problem isn't a big issue for us as the webview doesn't do the writing.
@jmew No. Proposed apis move out of proposed when they are ready. But it will certainly not be by October
Actually thinking more about your 'Door number 1' option, I don't think this is going to work for us unless we can also talk to the webview somehow.
We do a bunch of stuff on the extension side now (like starting the jupyter server) that we'd have to somehow do when the simple view was opened.
We also do conversions on the data using tools installed in the jupyter server, so we'd likely have to roundtrip the data anyway before showing it in the webview.
When the user makes a change in the image editor, flush that back to VS Code in some way (not to disk!)
...
Can we implement it efficiently? I'm viewing a 100mb json file in a custom editor, we can't be sending around 100mb buffers all the time.
I'm concerned about this part, in an image editor it would be too expensive to generate a copy of the actual file on every single change. Instead you would probably maintain a grid of pixels and a bunch of transforms that can be applied and undone. Ideally the webview editor would handle undo/redo in their own way and saves (generating/sending the file) would only be done when absolutely necessary.
I'll echo the above concerns about Door Number 1.
In addition to the performance concerns, I also have concerns about the UI state preservation (eg. an authenticated Jupyter connection for a notebook editor). If the content changes on disk, I don't think the extension should forcibly reload with all the UI state reset.
I do see value in between the extremes though. For example, I don't see any need for the extension to read or write the file directly to disk. That seems better suited to a module which can read and write from a variety of sources.
@mjbvz I think the fundamental discussion is about how much control to give to the custom editor, and who should be responsible for file state. I donāt think we need to go with one extreme or the other, and can have a reasonable middle ground which allows VSCode to guarantee consistent experience across custom-editor extensions.
I think VSCode should be responsible for reading/writing the raw file to disk. There could be a getContents(stream)
API which the custom editor has to implement, which VSCode can call:
More formally, here are some ideas for the questions you raised:
On save, should the custom editor be responsible for writing data to disk?
VSCode should be responsible for writing data to disk. Given above approach the custom editor can provide contents to an output stream with getContents
API. The custom editor should be notified when data is about to be or has been written to disk (e.g. a didSave or willSave notification)
Do we delegate file saving entirely to custom editors, or should save be more a notification to the custom editor that it should prepare its contents to be written to disk.
See above
Does VS Code maintain editor history, or do custom editors maintain their own editor histories?
Perhaps the custom editor provides VSCode with state/commands for undo/redo stack, or maintains its own history. For Jupyter notebooks it could have been an āinsert/delete cellā command (i.e. very context-specific to extension), and we probably donāt want VSCode to keep track of the underlying fileās contents on every keystroke.
How does a custom editor know when its backing resource has changed?
- Do we expect it to set up file watchers?
VSCode could provide this as a notification to the custom editor as well, notifying it that the underlying file has changed. Similar to LSP (textDocument/didChange)
- How do we handle conflicts?
Are you referring to source control merge conflicts?
In that case perhaps itās fine for VSCode to fallback to the text editor for merge conflicts, because SCM will insert merge markers into the file, so the regular text editor UI makes more sense for dealing with that.
How do we support custom editors for different versions of a file (the diff view case)?
Either by allowing the custom editor to provide text for the diff view (@alexandrudima's comment), or expect the custom editor to implement diff-view APIās as well.
When does a custom editor flush its contents back to VS Code?
For example: I edit a file using a custom editor and then switch to view the same file in VS Code's normal text editor without saving. In this case, I expect to see the modified content as text.
The custom editor can be pulled for its contents by VSCode as it sees fit. This gives you control of how frequently to fetch state. I think it should only be onSave/SaveAs, and some periodic fetch (for hot exit case).
How does hot exit work?
Call getContents
periodically to fetch state from custom editor, or during an onExit call explicitly, if VSCode has that for hot exits.
What about save as?
This should be similar to Save, where VSCode is responsible for the saving, but notifies the custom editor about the Save As event. Should just be metadata on the notification, I donāt think the custom editor needs to do anything special for Save As.
Who provides Ui for all this?
As much as possible it should be VSCode, because that provides consistency across custom editor extensions, which is better for VSCode.
Hi @mjbvz, is there anything blocking retainContextWhenHidden being implemented for custom editors?
I tried adding the option at https://github.com/microsoft/vscode/blob/313ede61cbad8f9dc748907b3384e059ddddb79a/src/vs/workbench/api/common/extHostWebview.ts#L291 but it just didn't seem to do anything. Do you have any idea why that didn't work?
This is important for editors with complex state so they don't get destroyed when the user switches to another tab.
Just did some testing and one thing I noticed that was it was overly complicated to get the actual file data, I think what I'm meant to do is do all the webview uri mapping stuff and then load it via fetch. While this is certainly convenient when you're just displaying an image, it's more cumbersome for editing a file where you would typically want to deal directly with a buffer. Considering the following:
resolveWebviewEditor(
resource: Uri,
editor: WebviewEditor,
data: Promise<ArrayBuffer>
): Thenable<void>;
// alternatively
export interface WebviewEditor extends WebviewPanel {
readonly onLoad: Event<ArrayBuffer>
}
This seems like it would be very useful for building a binary or image editor as the extension would not need to deal with the file system at all, provided the method of saving the buffer is similar.
Looking at the use cases for extension-provided editors it seems prudent to allow them to embed VSCode's editor functionality somehow to get access to the editing-related features provided by other extensions - be it a code notebook or the thing that I'm currently building, which is an inline HTML editor.
dcecb9eea6158f561ee703cbcace49b84048e6e3 documents the current thinking on the Webview Editor API (although none of the functionality around edit/save has actually been implemented yet.
This direction takes the recent feedback into account and is a compromise between the two extremes originally proposed: it leaves the specifics of how files are saved up to extensions but abstracts away most of the details about how editor actions interact with custom editors and how editor state is shown to the user. Here's a quick review of the new proposed design:
To start with, resolveWebviewEditor
now returns a WebviewEditorCapabilities
object
export interface WebviewEditorProvider {
resolveWebviewEditor(
input: {
readonly resource: Uri
},
webview: WebviewPanel,
): Thenable<WebviewEditorCapabilities>;
}
The WebviewEditorCapabilities
describes how the webview editor interacts with VS Code:
interface WebviewEditorCapabilities {
rename?(newResource: Uri): Thenable<void>;
readonly editingCapability?: WebviewEditorEditingCapability;
}
interface WebviewEditorEditingCapability {
save(resource: Uri): Thenable<void>;
hotExit(hotExitPath: Uri): Thenable<void>;
readonly onEdit: Event<any>;
applyEdits(edits: any[]): Thenable<void>;
undoEdits(edits: any[]): Thenable<void>;
}
VS Code would then wire up these WebviewEditorCapabilities
to the editor itself. For example, pressing cmd+s on a webview editor would invoke the save
function. This model should give extensions a lot of freedom to implement the logic for save and undo/redo, while still letting VS Code drive the core user experience.
We will be working on implementing the first parts of the API in November. Keep in mind that the capabilities interfaces may change significantly as we learn more about it
Hi @mjbvz! Thanks for the update, the API looks very promising! I'm one of the maintainers of kogito-tooling and we are currently shipping a DMN/BPMN editor based on VSCode WebViews.
We've been working with the existing API for a while, so I'd like to offer my two cents on this discussion. Basically, the two major challenges we have today are:
Since the first one is most likely going to be solved by this new API, I'll focus on the second one. Native text editors on VSCode have a bunch of features that seem secondary, but actually do improve the UX a lot, like "Confirm before closing a modified file", "Show a "modified" indicator when a file is changed", "Show a (deleted) label on the tab title when the file is externally renamed/deleted", etc..
So for the new API, here are a few questions:
WebviewEditorCapabilities#rename
WebviewEditorEditingCapability#save
WebviewEditorEditingCapability#hotExit
WebviewEditorEditingCapability#onEdit
WebviewEditorEditingCapability#applyEdits
and WebviewEditorEditingCapability#undoEdits
onEdit
?Hope that's insighful!
One more thing I'd like to ask is do you guys have an estimate of when this will become a stable API?
Really interested in this feature. GJ guys so far. Thank you.
@mjbvz Are there any plans on providing some API to optionally pass line/column information to the webview.
E.g. when users open a file from VS Code (in problems pane or debug stackframe), it opens a file and goes to a specific line number.
We'd like to have this functionality in the notebook editor for DS functionality in the Python extension.
@kieferrm @greazer /cc
& yes, thanks for the awesome work in this space.
@tiagobento
Extensions would not invoked rename; it is invoked by VS Code on the extension after the user renames a file.
Yes, save
lets you hook into saving of files however that is triggered
The intended UX is to match how normal editors work. If hot exit is enabled, there is no popup in this case
Yes. However it is VS Code who decides to mark the file dirty or not based using the edits it knows about. Extensions cannot directly mark the editor as dirty (without pushing an edit)
Edits will need to be a json serializable objects. We do not care about the actual value of this object. That's why any
is used for now
@DonJayamanne An initial line number can be passed through the URI using the fragment or query parameters
At the moment, there is no way to reopen the same document again at the different line number. That is something we'll consider supporting in the future. It may be pushed to a separate API proposal after the core custom editor logic is finalized
Here's where things stand on the proposal:
The api is unchanged for the most part since the last update, although I have commented out or removed the non-functional parts of it. You can find the latest version in vscode.proposed.d.ts
Basic save
and undo/redo are now functional
I've been using this simple extension to test the apis: https://github.com/mjbvz/vscode-experimental-webview-editor-extension
The next goal is hook up saveAs
this iteration, and possibly rename
as well.
Hot exit support will require more time
If you have a use case in mind for writable custom editors, we'd appreciate your feedback on the newly enabled parts of the api:
Open bug reports for stuff that doesn't work
Can you implement undo/redo/save using this api? How difficult is it?
Are there uses cases around undo/redo/save that would not work with the current api?
@mjbvz Is it possible to use the new editor for customized schema?
In PROBLEMS view, Java language server reports the workspace root folder as the diagnostics resource of the project level errors. When the user clicks it, actually there are no editors able to open it. To address the issue, we want to open a customized web view to show the build path info. But because it doesn't have a specific file, it seems we cannot use filenamePattern to enable the webviewEditors in the contributes of package.json. If the custom editors support schema match, we'd like to return a special uri scheme for project root errors.
@testforstephen Not at the moment. That use case may be a good fit for the Text Document Content provider API?
@mjbvz Thanks. We have a try on text document content provider, but the limitation is it only supports plain text document. It's suitable for code presentation such as decompiled class, but not good for project config presentation. That's why we want to try WebView for more rich presentation.
@testforstephen Yes give text documents a try; you can probably get pretty far with them. As author of the webview API, my advice is always to not use webviews unless you really need to
@mjbvz, After trying out the new API, I'd suggest giving an option to opt out of the undo/redo capability while still being able to set the dirty bit and save, because undo/redo is significantly more complicated for an extension to implement.
The complication I've got is as follows:
I'm building a notebook editor which includes Monaco editors in the webview, so I want to integrate Monaco's undo stack to the Custom Editor undo stack. Monaco can't provide an event to notify when an "undoable edit" occurs because it doesn't know the extent of the edit until after the fact. ie. if I type "hello", it gives me a change event for each of the five keys typed, but pressing Undo at this point will remove the entire word even though the last edit event was only to insert the character "o". It doesn't make sense to delay the events until after all the keys are typed because we want to set the document to dirty immediately, but it also doesn't make sense for me to send five separate onEdit
events to your API because I want a single Undo to revert all five characters.
Monaco has the concept of a "current open stack element" which sits at the top of the undo stack. Each edit is coalesced into this element until the client calls pushStackElement, at which point a new open stack element is pushed on top. Each stack element represents one undo step, but can comprise many edits. I think the Custom Editor API should follow a similar model.
Caveat: Even with this system, it wouldn't solve my problem because the Monaco API doesn't currently expose when it internally decides to push a stack element, so I'd also need it expose that as an event.
Another suggestion:
Complex webviews (like graphical editors) requires much communication with the extension code. maintaining it with post messages are hard, since you need to handle callbacks, etc'.
I created an RPC library to ease this communication https://github.com/SAP/vscode-webview-rpc-lib.
It could be great if it would be part of the VSCode APIs.
Funnily enough I was just thinking this morning about a custom view of .gitignore
files to make editing those easier. Especially when there are multiple .gitignore
files in a project, a GUI editor might be able to incorporate all of them into a tree view, allow drag/drop between branches, generate globs by selecting multiple files to match, all sorts of things.
@KevinWuWon What if we allowed saving of unchanged files too? (unchanged just being files that we don't have any edit stack for) I believe text editors already support saving unchanged files. I believe it is still best to keep the dirty indicator itself tied to the undo/redo/revert so that the UX is consistent with that of text editors
@mjbvz I believe that should be an API to control the dirty indicator.
Also, the save option (file > save + key binding) should be enabled in custom view and there will be another API l, something like workspace.onDidSave
maybe.
I'm facing with the exact issue in an extension I'm working on, also with Monaco.
@mjbvz The dirty bit is important not just for the visual indication that there are changes, but also for controlling whether the Save/Don't Save/Cancel dialog appears when the user closes the tab. Also for the "Close Unsaved" command and the like.
Without it, users are prone to losing unsaved work, which is bad.
ok. Let me throw my bits in the mix on this:
First of all this is a really great idea to simplify scaffolding of webviews and custom editors for different file types.
Question: if a custom web editor requires separate config for webview settings, connection strings, etc. would you expect it to manage that via exposed state/edit API?
I'd love to port Data Preview šø to this API when it's fully baked, but I think we could use additional 'config' files mapping for the resource and such web editor. State/edit API is good enough for saves and restores, but falls short for custom editor config settings per file view. Could be a good next step to try and standardize that as part of this effort too.
See https://github.com/RandomFractals/vscode-data-preview/tree/master/data/json for some examples of how I handle it for json data files preview. Could be a good test bed too ...
In my case, I can see data preview config changes handled with undo/redo stack and save.
Do you have read/update/save and undo/redo fully implemented yet for me to try it?
My expectations are vscode would handle file changes and stream data reads of local and remote text and binary formats, per line for text and per 1K or so for binary data. see https://nodejs.org/api/readline.html for example. Any plans of adding that to worksspace.fs api any time soon? My simple data files processing use case for that scenario, whenever that fs api is available: https://github.com/RandomFractals/vscode-data-preview/issues/167 Till then I think I'll stick with nodejs fs api.
Also, I have not implemented Undo/Redo yet, but if you want a practical use case that's beyond the cat drawing apps :), see https://github.com/RandomFractals/vscode-data-preview/issues/25
@mjbvz you might find my data preview Save As use cases interesting to. Would be nice if a web editor could register save as file types, and vscode then exposed that UI to the developer via a standard UX. For most graphics views those webviews typically require custom save as png commands and UI. You can also view Vega Viewer for that scenario: https://github.com/RandomFractals/vscode-vega-viewer
@RandomFractals
Not sure what you mean by separate config? Are you talking about VS Code settings? Can you share your use case
Yes undo and save are ready for testing in insiders.
There is no streaming built in. The current API leaves file watching and saving entirely up to extensions, so you are free to implement file syncing / save however you want including using streaming. Please file separate feature requests if the vscode fs apis don't offer what you need
Creating more advanced save as flows is outside the scope of this api. You are probably better implementing a custom Save as png
command for your custom editor if you need to get user input beyond the file name while saving
@KevinWuWon I think the concept of an open stack element for undo/redo may be interesting. But could you implement that using the current API by maintaining your own edit data?
If the user types hello
for example:
h
{ id: 1 }
onto the VS Code undo stack{ 1: [{ insert: 'h' }]}
e
{ 1: [{ insert: 'h' }, { insert: 'e' }]}
Continue this for letters through o
.
At this point, your extension decide to commit the edit (create an undo stop). When this happens, on the next text change, you push a new edit with a new id onto VS Code's stack
However, if an undo had occurred before you committed the edit, VS Code would tell you to undo { id: 1}
. You lookup the real data for the id and then undo that
Nice idea, I think that might actually work. Now I just need Monaco to report when undo stops are created.
@mjbvz so, based on your feedback to my data read/write and save as use cases, this web editor api in its v1 incarnation will be good only for very basic read/save/update files handling with alternative webviews.
I'll open separate tickets for the scenarios I brought up. If you try Data Preview extension, you should have a fairly clear picture of what my requirements are to migrate to this api and I don't think they are advanced in any way, but perhaps could be good for your rev. 2 of this web editor api.
This iteration, we're looking into making the view / model separation more clear for webview editors. The proposed fixes and background for it are fairly verbose, so I've temporarily forked off the discussion for it to #86802. Please post feedback/questions there if you are interested in editable webview editors
Once we settle on what we are going to attempt in #86802, I'll post the results back to this issue and close #86802
@mjbvz it's so weird how you always have 10 layers of misdirection in your github issues spinoffs & PR's reassignments. a bit hard to follow your flow, but ok!
Based on the outcome of #86802, we've updated the proposed api to the following:
/**
* Defines the editing functionality of a webview editor. This allows the webview editor to hook into standard
* editor events such as `undo` or `save`.
*
* @param EditType Type of edits. Edit objects must be json serializable.
*/
interface WebviewCustomEditorEditingDelegate<EditType> {
/**
* Save a resource.
*
* @param resource Resource being saved.
*
* @return Thenable signaling that the save has completed.
*/
save(resource: Uri): Thenable<void>;
/**
* Save an existing resource at a new path.
*
* @param resource Resource being saved.
* @param targetResource Location to save to.
*
* @return Thenable signaling that the save has completed.
*/
saveAs(resource: Uri, targetResource: Uri): Thenable<void>;
/**
* Event triggered by extensions to signal to VS Code that an edit has occurred.
*/
readonly onEdit: Event<{ readonly resource: Uri, readonly edit: EditType }>;
/**
* Apply a set of edits.
*
* Note that is not invoked when `onEdit` is called as `onEdit` implies also updating the view to reflect the edit.
*
* @param resource Resource being edited.
* @param edit Array of edits. Sorted from oldest to most recent.
*
* @return Thenable signaling that the change has completed.
*/
applyEdits(resource: Uri, edits: readonly EditType[]): Thenable<void>;
/**
* Undo a set of edits.
*
* This is triggered when a user undoes an edit or when revert is called on a file.
*
* @param resource Resource being edited.
* @param edit Array of edits. Sorted from most recent to oldest.
*
* @return Thenable signaling that the change has completed.
*/
undoEdits(resource: Uri, edits: readonly EditType[]): Thenable<void>;
}
export interface WebviewCustomEditorProvider {
/**
* Resolve a webview editor for a given resource.
*
* To resolve a webview editor, a provider must fill in its initial html content and hook up all
* the event listeners it is interested it. The provider should also take ownership of the passed in `WebviewPanel`.
*
* @param resource Resource being resolved.
* @param webview Webview being resolved. The provider should take ownership of this webview.
*
* @return Thenable indicating that the webview editor has been resolved.
*/
resolveWebviewEditor(
resource: Uri,
webview: WebviewPanel,
): Thenable<void>;
/**
* Controls the editing functionality of a webview editor. This allows the webview editor to hook into standard
* editor events such as `undo` or `save`.
*
* WebviewEditors that do not have `editingCapability` are considered to be readonly. Users can still interact
* with readonly editors, but these editors will not integrate with VS Code's standard editor functionality.
*/
readonly editingDelegate?: WebviewCustomEditorEditingDelegate<unknown>;
}
namespace window {
/**
* Register a new provider for webview editors of a given type.
*
* @param viewType Type of the webview editor provider.
* @param provider Resolves webview editors.
* @param options Content settings for a webview panels the provider is given.
*
* @return Disposable that unregisters the `WebviewCustomEditorProvider`.
*/
export function registerWebviewCustomEditorProvider(
viewType: string,
provider: WebviewCustomEditorProvider,
options?: WebviewPanelOptions,
): Disposable;
}
The main differences over the proposed API in VS Code 1.41:
The editingDelegate
(previously called the editingCapabilities
) is now specified on the provider instead of being returned by resolveWebviewEditor
This means is now a single editing delegate for all custom editors of a given type. With this setup, actions such as save
should only be fired once per resource (not once per editor)
To support having a single delegate object, all delegate methods now take a resource
that specifies what resource we are talking about
This api is now in vscode.proposed.d.ts
and the example custom editors have been updated to use it
We are still looking into how best support hotexit and how many hooks we should expose to extensions around this
@mjbvz Is there a way for the extension to tell VS Code to NOT load the custom editor.
I'm thinking of scenarios such as the git diff view. When user opens a file in source control history view, then we need to display the differences. At this point, we'd prefer for VS Code to display its own text base diff viewer.
Further to this, sometimes I've found the need to view the raw file content instead of using the custom editor. Out of curiousity, is there a provision for such a scenario. E.g. displaying an icon on the top right, that allows users to open the raw viewer. Similar to displaying the icon to open settings.json file instead of the settings viewer.
@mjbvz do you have an ETA on when this api will be released as part of vscode for general public?
I've seen February release mention in some other related issue thread.
Does it mean we'll see it as part of built-in vscode api only in March?
Any heads up on your timeline for the GA relase of this api would be much appreciated to help 3rd party extension devs plan accordingly.
Thanks!
@mjbvz had a question about 'blank' files. Right now we allow a user to create a 'blank' notebook. For a normal text document, this would be handled by openTextDocument with options specifying untitled.
Would this work by just passing the correct 'language' to openTextDocument?
@mjbvz & @rchiodo I have the same scenario in my extensions like create new Vega chart || kepler.gl map.
How do we handle that with this api?
I mean I can provide Untitled templates, but what's the vscode provided tooling for that new graph || map with this custom webview editor api ;) ...
@rchiodo You can open a custom editor for an untitled file using a uri with an untitled:
scheme. For a custom editor for *.abc files for example, you would open a file with the uri untitled:file.abc
.
Note that you need to open this file using the vscode.open
command and not openTextDocument
since custom editors are not backed by text documents
@mjbvz Hi! I'm testing the experimental extension you provided (mjbvz/vscode-experimental-webview-editor-extension) and I found something that I don't quite understand.
When closing a file that contains modifications, shouldn't a confirmation popup appear? Take a look at this GIF.
I'm using VSCode Insiders.
Version: 1.42.0-insider
Commit: 68615717819b3731705d92ec06b52edd47665291
Date: 2020-01-22T05:26:38.760Z
@tiagobento Prompts on exit are disabled when in development mode (try with a text file as well)
@mjbvz Is that the same case for the "File > Save all" menu? I see it disabled on VSCode Insiders Version: 1.42.0-insider
. If so, is there a place where I can find all the differences between VSCode Insiders VS. VScode VS. development mode?
@mjbvz is Undo/Redo supported yet? It kinda works with your ABC editor example, but it doesn't seem to actually call the undo/apply API in the editingDelegate. (I'm guessing the editor itself has a CTRL+Z handler and that's what sort of works).
yeah, this api is just so beta. why I gave up checking it. I'll wait for Py dev tools team to integrate before it's worth another look ...
@rchiodo It works using keyboard shortcuts (however not using the Edit -> Undo
menu item). Try the binary editor example (which doesn't have any undo/redo logic inside of it)
@mjbvz I'm attempting to implement Undo/Redo but hitting a problem.
We currently host monaco editors inside of our webview. They also handle ctrl+Z. This makes things a little more complicated in that the new API will also handle ctrl+Z.
At first I thought I could just ignore the subsequent undo from the API, but the problem is the monaco undo is not equivalent. It undoes a bunch of edits at once and I have no way to tell the undo stack in the new API that hey, you can actually concatenate all of these together because they're on the same line (so that they match what monaco thinks).
I _think_ I either need to prevent the monaco editors from allowing undo themselves, or I would need a way to tell the API to concatenate a bunch of edit changes together.
Thoughts?
I thought I add this comment, but couldn't find it hence adding (hopefully not a dup).
Please could you add support for navigation to a specific line.
E.g. displaying problems in problems window will open the custom editor and pass the line number information accordingly (possibly a callback in the custom editor), similarly debugging will open the editor with the right like number focused (yes the custom editor will need to handle the event and display the line according).
@DonJayamanne that's an interesting idea.
I could see that work with my data preview ext. when a user selects a row in a grid to scroll the open text data document to the line of the selected row data ...
Found another problem with undo/redo which I thought I had already mentioned (sorry @mjbvz if Kai mentions it before you see this).
Follow these steps (works with the cat editor):
@mjbvz & @rchiodo I know you guys are still working through your cat demos & it's cute, but I think this thread and the overall api could use more down to earth examaples, like I'd like to see how 1 would use this custom webview editor api for SVG preview || dot graph preview ...
Feel free to mark this as of topic as usual || just ignore it like you have done the other comments I posted on this thread. my 2 fair cents on this dev front :)
@RandomFractals look here.
https://github.com/microsoft/vscode-python/tree/ds/custom_editor. We've implemented our notebook editor using this API.
I use the cat demo to give feedback to @mjbvz when something doesn't work in my real implementation.
@rchiodo yeah, I've been watching your progress on that :) ...
@mjbvz Hi! I have two questions:
Webview
.@misode
@mjbvz why is not wiring default binary extensions in the wild like data preview ext. I procured for https://github.com/apache/arrow binary data display is not a part of your scope?
You seem to do a lot of talking & very little walking ;) ...
.@donJayamanne you can thumb it down all you like ... that is still the fact when you get down to earth in the data field in vscode ;) ...
@RandomFractals Binary files work fine. You need to be more specific if you have actual feedback
And look, I'm telling you this so that maybe you can learn something: we're looking for feedback on this API and although you certainly have left a lot of comments, I do not recall you ever providing any helpful feedback about it. In fact, I wasted a lot of time trying to support you on another thread and nothing came of it.
Worse, your tone is actually highly counterproductive. This area is very complex and passive aggressive little snipes like yours makes it not very fun for me to do the actual hard work this API requires. So really, your impact so far has been to slow down the delivery of this API.
You need to seriously recalibrate how you try to contribute to open source projects and how you interact with people in general because your current approach is not productive for anyone.
PS: As a start, make sure you next comment actually provides feedback on this API and is not some meaningless debate of what I've written here.
ok. I am considering leaving this thread. thanks for your lack of feedback on our works in the field & the feedback I actually did provide above too.
I look forward to you finally release this API for ext. devs to use for publishing in marketplace ...
P.S.: I don't think I need to re-calibrate anything. Perhaps you need to adjust your lenses & actually try the ext's trying to fit the api you are drafting. I could punch more holes in it, but I think I mentioned a few like procure an SVG ext, or dot graph || Arrow data demo display and exports to diff. fromats from those starting with png. The proposed API you started last year still can't do that per my reads & insiders runs of it.
So, please do better before you flame the only dev in vscode statosphere that actually got some fun webview editor exts for devs to play with.
Over & out! :)
Based on testing and feedback, we've decided to revise the custom editor API proposal for the VS Code 1.43 release. While this is a fairly dramatic change, many of the core concepts remain the same as before so hopefully should not be too difficult to update your existing prototypes to use the new API.
While our existing webview editor API proposal more or less works, creating an editable webview using it is actually fairly tricky! It is especially cumbersome from simple, text based files.
We'd also really like to support bi-directional live editing of text files. For example, if I open the same file in a webview editor and in VS Code's normal editor, edits on either side should be reflected in the other. While this can sort of be implemented using the existing API, it has some big limitations and challenging to get correct.
To address these problems, we've decided have two types of webview editors:
Text based webview editors. These editors used a TextDocument
as their data model, which considerably simplifies implementing an editable webview. In almost all cases, this should be what you use for text files
Custom webview editors. This is basically the existing proposed API. This gives extension hooks into all the VS Code events, such as save
, undo
, and so on. These should be used for binary files or in complex text editor cases (such as a single editor that represents multiple resources).
Both editor types now have an explicit model layer based on documents. Text editor use TextDocument
as their model, while custom editors use a WebviewEditorCustomDocument
. This replaces the delegate based approach previously used.
Here's the current API proposal.
The experimental editors have booth been updated to use this new api.
We are still actively working on:
We'd also appreciate your feedback on this new proposal. Specifically, we'd like to know:
Feedback:
I had implemented a custom webview editor against the previous version of this API and was able to update it and get it mostly working against this new version of the API, so that's good.
What is the expected way for an extension command to get the current custom webview editor? I had a couple commands using the *EditorProvider property activeEditor but that has been removed from the example, and vscode.window.activeTextEditor is always undefined for me, probably because these aren't text editors they're webview editors.
@jess-sheneberger Thanks for testing.
This is up to your extension to track using the webview panel's view state events. Our intent is that webviews can only be accessed by the extension that owns them so we don't provide global methods for getting information about webviews. This is how the existing webview api also works
Also note that the next insiders build has polished the API proposal, including renaming most of the types and contribution points:
"webviewEditors"
contribution point -> "customEditors"
contribution point webview
has been removed from most type names.CustomEditorProvider
are now given a document to resolve rather than having to create a documentregisterCustomEditorProvider
method instead of one for text providers and one for custom providersThe checked in api is what we plan to ship as a proposal with VS code 1.43
Are these changes in last night's insiders build? If not, when will they be available?
I'm running into errors that make me think maybe I haven't got the right vscode bits.
Yes, they are all in today's insiders. I just confirmed the example extensions again the b0be0672c210f1fa76109ce675365bb785657e84 insiders build
OK, I'm on that build too. I get the error "Unable to open 'test.cat': Cannot add property userData, object is not extensible" when I use the latest example extensions and try to open a .cat document.
I get the same error in my extension. Please advise.
Thatās a known itās thatās already been fixed in master. Youāll have to wait until the next insiders to be able to set user data on a custom document
I have been looking at the current custom editor proposal. The problem that I see is that I don't have a 1:1 mapping between
There are two main concerns I have with the API as proposed:
WebviewCustomDocument
is based around a URI. Imagine a resource editor for DLL files (in the PE format). Perhaps a tree view presents sections and metadata. The user then tries to double-click on one of the sub-sections; where the file in question has a clear URI, the construction of a URI that points to a particular section or sub-section within a PE file seems to me to be something that hasn't been done, and would be prone to cross-platform problems.resolveCustomEditor
passes in a WebviewPanel
. It seems to me that this should instead present an opportunity to new
a WebviewPanel
to allow the extension to leverage MDI. Perhaps I'm misunderstanding; but the way my extension works (it's based with React) is that it points to sub-section of a file. File parts can be rewritten without affecting the balance of the application. This might work where the editor / document is on a per-file basis; but I provide a UI for sub-file, and I would like that.I'd be happier with a simpler API to start, with the following capabilities:
@robpaveza
If you are using a tree view type to show files that can then be edited in the custom editor, you likely should use your own uris scheme: my-fancy-dll-extension://dll-name/path/to/subresource
This is how existing webview apis also work. We do this so that VS Code can easily hook up everything it needs on the webviewpanel before it gives it to extension (plus we don't want people to new up a WebviewPanel
unless it is returned back to VS Code in the resolve
method)
The trouble with the URI scheme of my-fancy-dll-extension://dll-name/path/to/subresource
is the dll-name
part. If you have two DLLs of the same name in different paths in your workspace, you can't just use dll-name
, you need to use full-path-to-dll
. That can work fine, but then if you're on a Unixy system, you can't just do my-fancy-dll-extension://full/path/to/dll/path/to/subresource
; you need to figure out how to differentiate between the virtual part.
dll-ext://c:\projects\workspace\my.dll:cdata/0
dll-ext://home/rob/projects/workspace/my.dll:cdata/0
Becomes more complex to detect Windows and skip the first :
but I guess it's possible.
@mjbvz Hi! I believe the link on https://github.com/microsoft/vscode/issues/77131#issuecomment-589894806 is pointing to an outdated version of the proposed API. The API is now different on vscode
master branch and the vscode-experimental-webview-editor-extension
repo as well.
@mjbvz I can confirm that the new API does fit out needs. In case you are interested, here [1] are the changes I did to adapt it our extension to the new version. I didn't test the new backup method, but all the others seem to work fine.
[1] https://github.com/tiagobento/kogito-tooling/commit/99a50b4acfde769ed1ee26f20d7e349cb80e3669
@mjbvz it's working good for us too. Had a question though. backup is working for saving, but VS code doesn't know when I load from backup vs the real file and therefore doesn't realize the file is dirty. Do we need some other event or indication that a file is dirty?
@mjbvz Hi! I'm working along with @tiagobento integrating the new Custom Webview Editor API in our editors.
I've been focused on integrating the Undo/Redo on our editor state control API. I managed to succesfully propagate each change that happens on our editor by generating an edit and firing the onDidChange
event on our CustomEditorEditingCapability
, however I found two issues when trying to execute the Undo/Redo from VSCode.
The first issue is that Undo/Redo menu actions under Edit menu are not working. Clicking them isn't notifying our CustomEditorEditingCapability
to execute the undoEdits
/applyEdits
methods. However the key shortcuts (Ctrl+Z / Ctrl+Y) seem to work, and our capability is able to execute the undoEdits
/applyEdits
methods.
Second one only happens when running VSCode on Linux. Looks like if two or more changes happen on the editor and I execute the Undo/Redo key shortcut the undo's/redo's are executed twice. For example if we make two changes on the editor that generate two separate edits (_edit1_ and _edit2_). If later I try to undo last change (_edit2_), our capability undoEdits
method is executed twice, first time for _edit2_ and then for _edit1_. As I said, the same error happens with Redo. We tried this on different computers (Fedora, Ubuntu, Mac & Windows) and it seems to be present only Linux ones. I attached some gifs showing the error on different systems.
_Wrong behaviour (Fedora): undoing two edits at same time_
_Correct behaviour (Mac): undoing only a single edit each time_
_Correct behaviour (Windows): undoing only a single edit each time_
@pefernan Thanks for testing. I extracted your comment to https://github.com/microsoft/vscode/issues/92608. We're also investigating hooking up the undo/redo menu items in https://github.com/microsoft/vscode/issues/90110
The goal for this iteration is to finalize the custom editor APIs. Editable custom binary editors still have a number of known issues, so we may make the call to keep those in proposed an extra iteration for stabilization. Both CustomTextEditor
and readonly CustomEditor
have far fewer remaining challenges.
As part of this finalization push, we're going to continue tweaking the API. While some of these changes may break existing extension, it should not be difficult to update them to the new API version.
Here's the main changes I've checked in:
Moving back to a delegate based model for editing
In the February release, resolveCustomDocument
returned an object that describes the editing capabilities of that document. We've since decided to return to the previous delegate model where the CustomEditorProvider
itself has a editingDelegate
that expresses these capabilities.
I prefer the delegate based approach as will hopefully be more consistent with any future API hooks we add for custom editors. It also avoids some the the confusion about how the capabilities and the CustomDocument
are linked.
Undo
, save
, and others can still live on your CustomDocument
object if you have the editingDelegate
delegate to the document.
Adding an explicit revert
method
There's now a revert
method. Previously revert
was implemented by calling undoEdits
or applyEdits
.
Revert signals that the extension should reload the resource from disk, so having an explicit method for this is helpful
Adding a label for edits
When your extension creates edits, it can now pass an optional label
. This will be used in the future to display information about the undo stack to users
Once the next insiders build comes out, please update your extension to use the latest API. Please report any issues you run into with this and let us know if we've overlooked any keep features (some API features can be added after the initial custom editor api is finalized)
Again, there may be further api tweaks this iteration, but we're going to be more deliberate about them as we move towards finalization
I've created a video walking through the current state of the API proposal
API Changes
resolve
methods of CustomEditor
and CustomTextEditor
.Cancellation will be used in cases such as when an extension takes too long top open a editor and the user closes it before it has been resolved.
Added an experimental moveCustomTextEditor
method to CustomTextEditor
.
This will not be finalized this iteration. It was added to make sure our API design could support such an API in the future
The goal of moveCustomTextEditor
is to allow a webview to be persisted across the rename of a resource (currently a rename causes the webview to be destroyed and then recreated). This will be useful for extensions that have expensive webviews
Ongoing
Naming. We're still trying to figure out good names (especially on the providers)
Hooking up custom editors to the backup restorer: https://github.com/microsoft/vscode/pull/92629 This is proving to be rather complicated.
Handling file renames properly (for example, if the file is dirty and then renamed.
One more set of API changes, mostly focused on improving quality of life for developers. This is the last set of changes I plan to make this iteration. If needed, we still may make a few changes based on our endgame testing and extension author feedback
API Changes
CustomDocument
is now a class instead of an interface. Extensions can add additional data to a custom document by subclassing CustomDocument
. This pattern should be more familiar than CustomDocument.userData
resolveCustomDocument
has been renamed openCustomDocument
. It now is given a uri and must return a custom document. Again, this pattern should be more familiar to developers.
Exposed edits on CustomDocument
. CustomDocument.appliedEdits
contains all edits to get the document's current state. CustomDocument.savedEdits
contains all edits to get the document's last save state.
The third argument of registerCustomEditorProvider
is now a options bag that contains a WebviewPanelOptions
instead of being a WebviewPanelOptions
directly
Sample extensions
Additionally, I've checked in initial custom editor extension samples: https://github.com/microsoft/vscode-extension-samples/tree/custom-editor-examples/custom-editor-sample
These samples are not yet complete but should already be much clearer (and more up to date) than the experimental-webview-editor-extension
samples I previously was using for development (while also increasing cat levels considerably)
After testing and discussion among the VS Code team, we've decided to keep the custom editors for binary files as proposed one more iteration while finalizing CustomTextEditor
for the current release. This means that in VS Code 1.44 extensions will be able to ship custom editors for text based files, be those file plaintext, SVG, markdown, xml, json, ...
The binary custom editor side of things (called CustomEditor
in the API proposal) is considerably more complicated than the text custom editor API so we're taking time to work though some more use cases. Our focus is specifically around edits and where the editor's source of truth lives since with custom editors there are three processes at play (the webview, the extension, and VS code)
The Custom Text Editor API has shipped with VS Code 1.44! Checkout the documentation and example extension to get started creating your custom text editors
For binary custom editors, we've revised the proposed API for editable documents. These changes look substantial however most of the core concepts are the same as before (such as when VS code calls your extension and what your extension is responsible for doing).
You can find the current api proposal here and it will ship in the next VS Code insiders build. Here's an summary of the changes:
Removed explicit edit objects
The API previously had edit data objects and let extensions access the full undo/redo stack of edits. These have been removed .
Edits are now tracked using an object that has an undo
and redo
method on it. To express that an edit has occurred for example, an extension now fires an event that looks like:
this._onDidEdit.fire({
label: 'Crop',
undo: () => { /* undo crop */ },
redo: () => { /* redo crop */ },
});
There was concern that edits were too opinionated, so we decide that it is better to leave tracking edits and edits stacks up to extensions.
CustomEditorEditingDelegate
-> EditableCustomDocument
The new EditableCustomDocument
type is now how you implement an editable custom editor. This replaces the CustomEditorEditingDelegate
we were using before. All methods from CustomEditorEditingDelegate
have moved to instances of EditableCustomDocument
This pattern should be more familiar to extension authors and hopefully provides a more clear guide on how you typically want to implement a custom editor.
Slimmer CustomDocument objects
CustomDocument
is now a very minimal interface instead of a class. Many of the properties that is previously shared with TextDocument
have been removed since they were not very helpful in practice
Better expression of backup livecylce
Backups now have a more well specified lifecycle. Your extension returns a CustomDocumentBackup
and VS Code will call .dispose()
on that backup when it is no longer needed (such as when the user saves). Previously extensions had to manage the lifecycle of backups themselves
Please try out the new API proposal and let us know if you have any concerns or feedback about it. We'll continue iterating on it this iteration so that it can ship with VS Code 1.45
Hi @mjbvz! Thanks for the updates. I think the API is looking really good. As you might know, I'm one of the maintainers of Kogito Tooling and we ship a custom editor for DMN and BPMN files as _binary_ editors.
We've recently moved to the proposed API, since we expect it to become stable soon. On VS Code 1.44.0, we're using the temporary vscode.window.registerCustomEditorProvider2
method to register our Webview editor provider.
Everything seems to work fine, with exception of the dirty detection mechanism and the undo/redo stack. Since we're counting on VS Code to call the (now deprecated) applyEdits
and undoEdits
methods, I can only assume that this is an issue that was introduced on 1.44.0. Also, the CustomDocumentEditEvent
seems to have no effect when fired.
We've done some tests and we are in fact correctly firing CustomDocumentEditEvent
when a change occurs on our editor, but VS Code doesn't seem to recognise that a change was made. This is important to us, since having no dirty detection mechanism can lead to loss of work.
I know this API is currently on proposed state and that it has already been deprecated, but we already did all the testing we could think of and it would be nice if some of you guys could confirm that this is in fact an issue on VS Code side. We would be happy to help testing it further when/if a fix is provided.
Just FYI, we will definitely adapt our codebase to new API as described here https://github.com/microsoft/vscode/issues/77131#issuecomment-611282903 and we're looking forward to shipping our extension with the stable API as soon as it is released.
Thank you in advance š
@tiagobento Thanks for testing!
The current custom editor API from vscode.proposed.d.ts
only works in that latest VS Code insiders builds (VS Code 1.45). Please make sure you are testing using that pairing
If you are still having issues using the new API in VS Code insiders, can you please share a branch of your extension that is using it so that I can take a look to see what may be going on?
@mjbvz Thx for the reply!
We haven't tested the API that's on master
yet. But the API that's on 1.44.0
has an issue with undo/redo and dirty state on VS Code 1.44.0.
It looks like VS Code is not calling the applyEdits
and undoEdits
methods and also that it's not detecting a dirty state after we fire CustomDocumentEditEvent
.. do you know if that's expected?
@tiagobento Since no extensions are can ship with proposed APIs, only the version from master with the latest insiders build is supported. Please update your code to use that
@tiagobento Can you point us to your branch so that we can understand how the API is working for you
@jrieken Sure! That's [1] where we register the webview provider. We're currently working on the 0.3.x
branch.
EDIT: This 0.3.x
branch is currently targeting VS Code 1.44.0. We're already working on implementing the API for 1.45.0 on master
.
Hi @mjbvz. I'm working with @tiagobento and @pefernan.
I had the chance to test the proposed API from master with the latest insiders build.
Everything seems to be working on our custom editors, except:
save
is being fired twice on our EditableCustomDocument
and VS code is showing the message "_Failed to save 'fileXXXX': Canceled_". However, if I try to save from the File
menu, then it works fine.Hope this helps.
Update April 16, 2020
The next code insiders makes a few minor tweaks to the api in order to support a wider range use cases. Here are the details
onDidEdit
has been renamed to onDidChange
Added CustomDocumentContentChangeEvent
which marks an editor as dirty
While reviewing some of the editor libraries that we expect extension developers to try using in their custom editors, we determined that custom editors generally fall into three different classes:
Readonly editors. These already work well today.
Editable custom editors that can't integrate into the VS Code's undo/redo stack.
The editor libraries being provide a way to edit files, but either don't have undo/redo functionality or their undo/redo model doesn't match what VS Code needs.
Full custom editors with a stack based undo/redo model. The are what we target today.
Our API supported class 1 and 3 pretty well, but class 2 editors were out of luck. Such editors would either have to claim they were readonly or try to abuse our undo/redo api proposal. Neither are good outcomes.
Asking all editors to match VS code's undo/redo model may be unrelasitic but we still need a way for an editor to all us that they are dirty. Being dirty triggers auto save, backup, and also causes VS code to prompt the user if they try to close a dirty file without saving it first. However we don't want to give extensions direct control over this dirty state because implementing it correctly is surprisingly complicated and the consequences of messing us dirty state tracking are pretty bad: such as lost user data and never being able to close an editor.
So to support this middle class of editors, we decided to add an CustomDocumentContentChangeEvent
that an editor can fire if they cannot use the full CustomDocumentEditEvent
with undo/redo. CustomDocumentContentChangeEvent
only marks an editor as dirty. The only way for an editor that uses CustomDocumentContentChangeEvent
to become non-dirty is for the user to save or revert the file. These editors also have no integration with VS Code's standard undo/redo commands.
Keep in mind that CustomDocumentContentChangeEvent
has been added to support those cases where undo/redo simply will not work. To provide your users with the best experience, you should use CustomDocumentEditEvent
and ensure your editors play nicely with VS Code's undo/redo.
Added an supportsMultipleEditorsPerResource
option (the default is false)
Another concern we discussed is that not all custom editors will be able to correctly support having multiple editors instances for the same resource. This is important with editable custom editors since all editor instances for a resource must be in the same state.
Given the complexity of properly synchronizing multiple instances of an editorāas well as the fact that much of this complexity may not be well understood or tested by extension authorsāwe've decided to only allow a single default instance of a custom editor per resource by default.
If you pass in supportsMultipleEditorsPerResource: true
to registerCustomEditorProvider2
however, VS Code will again allow users to split custom editors and open multiple copies of a custom editor for the same resources.
Again, if you want your users to have the best experience with a custom editor, make sure your editors can properly synchronize with each other and then set supportsMultipleEditorsPerResource: true
.
@caponetto Thanks for testing this. I just posted an update to #92608: those issues should be fixed now or are known limitations currently.
The duplicate save had the same root cause a the duplicate undo/redo, #82670. This should be fixed in the next VS Code insiders build
We're already working on implementing the API for 1.45.0 on master.
@tiagobento and @pefernan have you been able to make progress? As @mjbvz outlined above we have changed the API proposal in a few significant ways and we are looking for your feedback. Specifically
@jrieken Thanks for taking some time to look int our code base! We really appreciate that.
save-as, backup, revert
will most likely be implemented in the future. Our 0.3.x
branch is considered a Development Preview to us, so it's expected that some features are not ready yet. 0.3.x
will also be updated to the new API once VS Code 1.45.0 is released. We wanted users to have the opportunity to use our extension as quickly as possible, so we have this preliminary version in place. We'll share more news as this branch gets updated :)0.3.x
, we're using VS Code's 1.44.x
proposed API, so undo/redo is implemented using the applyEdits
and undoEdits
methods. Since VS Code calls these methods when undo
and redo
commands are fired, we then simply warn our editor to change its contents.@caponetto is working on implementing the 1.45.0 API on our master
and so far the results are good. These are the issues we were able to find:
revert
method is not called. I see from the documentation that it's probably expected? Could you help us understand that a little bit better? When we revert changes using the version control tab, the revert method is not called.
Yeah - revert isn't the SCM revert (and I now see how that is confusing) but it is File > Revert File. It's used when having unsaved changes and when you don't want to save such changes.
Since VS Code calls these methods when undo and redo commands are fired, we then simply warn our editor to change its contents.
Ok, but this is also where the biggest changes happened, e.g the "source of truth" used to be with us and is now with extensions. We aren't talking about concrete edits anymore but simply about undo/redo-functions which must implement undo and redo:
April 24, 2020
We've decided to keep the binary custom editor API proposed one more iteration. This lets us better align the custom editor API with the notebook API proposal, while also giving more time for you to provide feedback on it.
The currently version of the API checked into vscode.proposal.d.ts
is what we plan to ship with VS Code 1.45. Here's an overview of recent changes:
Moved edit, save, revert, backup from documents back to the provider
In order to align with the notebook API proposal, we've moved the edit, save, revert, and backup function out of EditableCustomDocument
and back onto the custom editor provider:
export interface CustomEditorProvider<T extends CustomDocument = CustomDocument> extends CustomReadonlyEditorProvider<T> {
readonly onDidChangeCustomDocument: Event<CustomDocumentEditEvent> | Event<CustomDocumentContentChangeEvent>;
saveCustomDocument(document: CustomDocument, cancellation: CancellationToken): Thenable<void>;
saveCustomDocumentAs(document: CustomDocument, destination: Uri, cancellation: CancellationToken): Thenable<void>;
revertCustomDocument(document: CustomDocument, cancellation: CancellationToken): Thenable<void>;
backupCustomDocument(document: CustomDocument, context: CustomDocumentBackupContext, cancellation: CancellationToken): Thenable<CustomDocumentBackup>;
}
We've gone back and forth a few times about where these methods should live, but ultimately it is important to align with the notebook api here. Extension authors can also easily have the provider call into methods on their custom document.
CustomReadonlyEditorProvider
The CustomReadonlyEditorProvider
interface is now the base for a custom editors provider. It has the same two methods the previous proposal did:
export interface CustomReadonlyEditorProvider<T extends CustomDocument = CustomDocument> {
openCustomDocument(uri: Uri, openContext: CustomDocumentOpenContext, token: CancellationToken): Thenable<T> | T;
resolveCustomEditor(document: T, webviewPanel: WebviewPanel, token: CancellationToken): Thenable<void> | void;
}
If your binary custom editors are editable, you would instead implement CustomEditorProvider
which defines all the required editing hooks.
CustomDocumentBackupContext.destination
The CustomDocumentBackupContext.destination
now suggests a specific file name to use for a backup. You extension does not have to write to this file, it is simply a suggestion.
Again, the current API in master is what plan to ship in VS Code 1.45. Provided no major issues are discovered next month, this is api is also what we plan to finalize.
If you have an extension in mind that would use binary custom editors, please give the new API a try and let us know if it works for you. We're specifically interested in understanding if you could implement all the required functions and if you ran into any confusion while doing so.
Hi @mjbvz @jrieken,
I've been catching up with the daily updates, thanks for them btw.
While working on our stuff, I've been noticing some issues on VSCode.
Probably they're known issues but I'm sharing here in case they've been missed somehow.
I'm able to reproduce them on both our code and this code.
Linux and Windows
Linux-only
Windows-only
Please let me know if you need any further information.
@mjbvz one thing to note is that auto-save for text editors is not editor bound, so it will continue after an editor closes. Maybe for custom editors this is different and you need to wire this in properly.
Regarding the backup feature.
I played a little bit more with the sample code but the restore part doesn't seem to be working.
I can see that the backup file is being created/deleted when appropriated,
but the backupId
received on the openCustomDocument
method is always undefined
.
I understood that if I close the VSCode with unsaved changes,
then these changes will be restored once I open VSCode again.
Here's a gif where I do these steps.
Am I missing something here?
Thanks in advance.
@caponetto See #96484
If you are interested, I wrote some thoughts down I had when I implemented the drawio editor for png files.
I'd like my custom text editor to have a different UI when a document is open normally versus in the source control diff view. (There are features that are unneeded in a diff.) However, given the arguments to CustomTextEditorProvider.resolveCustomTextEditor
, I don't see what logic I can write to tell that I'm in a diff view. In fact, for my use case, I'd prefer that Code use a regular text editor for diffs rather than creating my custom text editor. Maybe there's a way to specify that?
@rdeline See https://github.com/microsoft/vscode/issues/97683
Hi all. Iām looking for some advice to address several of my concerns implementing a custom text editor.
Iām working on a WYSIWYG markdown editor -- you can play with the technical prototype here: https://editor-v2-arch.netlify.app. Iāve been working on making the extension-version for VSCode, but Iām honestly pretty confused. Iāve followed the abc
(textarea) and catScratch
demos for reference.
What Iām most confused about is undo and cursor selection.
So when my editor is an extension, the undo stack actually appears to live _outside_ of my editor, right? The reason this confuses me is because I _already_ implemented all of the undo functionality myself, but now I need to let VSCode handle that for me? Am I simply not allowed to intercept undo/redo events myself and handle them? Is there a way I can change the relationship so my editor is in charge of handling undo/redo? Having this work the other way around is seriously confusing me.
I understand that I can emit edits on every event, and message-pass those edits to VSCode, but when a user hits undo, from what I can tell, the way my editor is made _aware_ of this is by VSCode message-passing me the entire document as a string (naive implementation) or VSCode passing individual edits I can respond to. If I respond to individual edits, this seems fine (I would need to rearchitect some things to make this work) but this leads me to my second point: cursor selection.
When a user presses undo, they expect the cursor to revert to where it was. So assuming my editor responds to edits (as mentioned in the previous paragraph), does this mean I need to _compute_ the cursor? Right now, my undo history stack tracks the cursor positions, so thereās no need for me to compute anything during an undo, redo event. But this _seems_ like something else I would need to architect in order to get even the most basic editor behaviors working.
All things considered, making my editor VSCode-ready is proving to be way more difficult than I would have imagined. I hope Iāve got some things wrong here and thereās a much simpler way for me to port my editor.
@codex-zaydek You are on the wrong issue, this is about custom _binary_ editors, please create a new one.
@mjbvz I was wondering how I would create an untitled editor with the new API? I believe we used to do this with a uri of "untitled:Untitled-1.ipynb"
, but this gives an error now:
Adding a '/' to the front of the file name seems to allow the creation of the file, but it's not treated as untitled (it's just treated as a real file then and closing doesn't ask to save as)
```
Ah it seems there's already a discussion of this here:
https://github.com/microsoft/vscode/issues/93441
@rchiodo yes #93441 tracks a better solution. Until then, you should be able to create untitled files by using an untitled
uri that points to a specific file path:
untitled:/absolute/path/to/file/in/workspace.png
The binary custom editor API has been merged into vscode.d.ts
.
If you were testing this proposed API previously, please make sure to update your code to call registerCustomEditorProvider
instead of registerCustomEditorProvider2
. Other than that, there should be no changes to the API
Is there a way to change the title of a custom editor?
Right now my extension uses a webview witha custom title when editing a certain file, but I need the save/close features of the custom editor API.
@DemonOne No, custom editors currently always use the 'resource' as their title (they try to match what we do for normal text editors)
The binary custom editor API will ship with VS Code 1.46 š Thanks to everyone who helped test this API and provided feedback along the way
Follow up resources:
Going forward, please open any bugs/feature-requests/questions as new issues.
Most helpful comment
@mjbvz Is there a way for the extension to tell VS Code to NOT load the custom editor.
I'm thinking of scenarios such as the git diff view. When user opens a file in source control history view, then we need to display the differences. At this point, we'd prefer for VS Code to display its own text base diff viewer.
Further to this, sometimes I've found the need to view the raw file content instead of using the custom editor. Out of curiousity, is there a provision for such a scenario. E.g. displaying an icon on the top right, that allows users to open the raw viewer. Similar to displaying the icon to open settings.json file instead of the settings viewer.