Merge branch 'main' into notebook/dev

This commit is contained in:
Johannes Rieken 2021-06-09 09:31:45 +02:00
commit 1669dcee38
No known key found for this signature in database
GPG key ID: 96634B5AF12F8798
33 changed files with 1461 additions and 1062 deletions

View file

@ -989,6 +989,7 @@
"vscode-dts-cancellation": "warn",
"vscode-dts-use-thenable": "warn",
"vscode-dts-region-comments": "warn",
"vscode-dts-vscode-in-comments": "warn",
"vscode-dts-provider-naming": [
"warn",
{

View file

@ -7,19 +7,17 @@ on:
jobs:
main:
runs-on: ubuntu-latest
if: contains(github.event.issue.labels.*.name, '*english-please')
steps:
- name: Checkout Actions
if: contains(github.event.issue.labels.*.name, '*english-please')
uses: actions/checkout@v2
with:
repository: "microsoft/vscode-github-triage-actions"
ref: stable
path: ./actions
- name: Install Actions
if: contains(github.event.issue.labels.*.name, '*english-please')
run: npm install --production --prefix ./actions
- name: Run English Please
if: contains(github.event.issue.labels.*.name, '*english-please')
uses: ./actions/english-please
with:
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}

View file

@ -0,0 +1,45 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
module.exports = new class ApiVsCodeInComments {
constructor() {
this.meta = {
messages: {
comment: `Don't use the term 'vs code' in comments`
}
};
}
create(context) {
const sourceCode = context.getSourceCode();
return {
['Program']: (_node) => {
for (const comment of sourceCode.getAllComments()) {
if (comment.type !== 'Block') {
continue;
}
if (!comment.range) {
continue;
}
const startIndex = comment.range[0] + '/*'.length;
const re = /vs code/ig;
let match;
while ((match = re.exec(comment.value))) {
// Allow using 'VS Code' in quotes
if (comment.value[match.index - 1] === `'` && comment.value[match.index + match[0].length] === `'`) {
continue;
}
// Types for eslint seem incorrect
const start = sourceCode.getLocFromIndex(startIndex + match.index);
const end = sourceCode.getLocFromIndex(startIndex + match.index + match[0].length);
context.report({
messageId: 'comment',
loc: { start, end }
});
}
}
}
};
}
};

View file

@ -0,0 +1,53 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as eslint from 'eslint';
import type * as estree from 'estree';
export = new class ApiVsCodeInComments implements eslint.Rule.RuleModule {
readonly meta: eslint.Rule.RuleMetaData = {
messages: {
comment: `Don't use the term 'vs code' in comments`
}
};
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
const sourceCode = context.getSourceCode();
return {
['Program']: (_node: any) => {
for (const comment of sourceCode.getAllComments()) {
if (comment.type !== 'Block') {
continue;
}
if (!comment.range) {
continue;
}
const startIndex = comment.range[0] + '/*'.length;
const re = /vs code/ig;
let match: RegExpExecArray | null;
while ((match = re.exec(comment.value))) {
// Allow using 'VS Code' in quotes
if (comment.value[match.index - 1] === `'` && comment.value[match.index + match[0].length] === `'`) {
continue;
}
// Types for eslint seem incorrect
const start = sourceCode.getLocFromIndex(startIndex + match.index) as any as estree.Position;
const end = sourceCode.getLocFromIndex(startIndex + match.index + match[0].length) as any as estree.Position;
context.report({
messageId: 'comment',
loc: { start, end }
});
}
}
}
};
}
};

View file

@ -88,7 +88,7 @@ function update(options) {
for (let tp of translationPaths) {
localization.translations.push({ id: tp.id, path: `./translations/${tp.resourceName}` });
}
fs.writeFileSync(path.join(locExtFolder, 'package.json'), JSON.stringify(packageJSON, null, '\t'));
fs.writeFileSync(path.join(locExtFolder, 'package.json'), JSON.stringify(packageJSON, null, '\t') + '\n');
}
});
});

View file

@ -10,14 +10,164 @@ export function activate() {
html: true
});
const style = document.createElement('style');
style.classList.add('markdown-style');
style.textContent = `
.emptyMarkdownCell::before {
content: "${document.documentElement.style.getPropertyValue('--notebook-cell-markup-empty-content')}";
font-style: italic;
opacity: 0.6;
}
img {
max-width: 100%;
max-height: 100%;
}
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
a:focus,
input:focus,
select:focus,
textarea:focus {
outline: 1px solid -webkit-focus-ring-color;
outline-offset: -1px;
}
hr {
border: 0;
height: 2px;
border-bottom: 2px solid;
}
h1 {
font-size: 26px;
line-height: 31px;
margin: 0;
margin-bottom: 13px;
}
h2 {
font-size: 19px;
margin: 0;
margin-bottom: 10px;
}
h1,
h2,
h3 {
font-weight: normal;
}
div {
width: 100%;
}
/* Adjust margin of first item in markdown cell */
*:first-child {
margin-top: 0px;
}
/* h1 tags don't need top margin */
h1:first-child {
margin-top: 0;
}
/* Removes bottom margin when only one item exists in markdown cell */
*:only-child,
*:last-child {
margin-bottom: 0;
padding-bottom: 0;
}
/* makes all markdown cells consistent */
div {
min-height: var(--notebook-markdown-min-height);
}
table {
border-collapse: collapse;
border-spacing: 0;
}
table th,
table td {
border: 1px solid;
}
table > thead > tr > th {
text-align: left;
border-bottom: 1px solid;
}
table > thead > tr > th,
table > thead > tr > td,
table > tbody > tr > th,
table > tbody > tr > td {
padding: 5px 10px;
}
table > tbody > tr + tr > td {
border-top: 1px solid;
}
blockquote {
margin: 0 7px 0 5px;
padding: 0 16px 0 10px;
border-left-width: 5px;
border-left-style: solid;
}
code,
.code {
font-size: 1em;
line-height: 1.357em;
}
.code {
white-space: pre-wrap;
}
`;
document.head.append(style);
return {
renderOutputItem: (outputInfo: { text(): string }, element: HTMLElement) => {
const rendered = markdownIt.render(outputInfo.text());
element.innerHTML = rendered;
let previewNode: HTMLElement;
if (!element.shadowRoot) {
const previewRoot = element.attachShadow({ mode: 'open' });
// Insert styles into markdown preview shadow dom so that they are applied
for (const markdownStyleNode of document.getElementsByClassName('markdown-style')) {
element.insertAdjacentElement('beforebegin', markdownStyleNode.cloneNode(true) as Element);
// Insert styles into markdown preview shadow dom so that they are applied.
// First add default webview style
const defaultStyles = document.getElementById('_defaultStyles') as HTMLStyleElement;
previewRoot.appendChild(defaultStyles.cloneNode(true));
// And then contributed styles
for (const markdownStyleNode of document.getElementsByClassName('markdown-style')) {
previewRoot.appendChild(markdownStyleNode.cloneNode(true));
}
previewNode = document.createElement('div');
previewNode.id = 'preview';
previewRoot.appendChild(previewNode);
} else {
previewNode = element.shadowRoot.getElementById('preview')!;
}
const text = outputInfo.text();
if (text.trim().length === 0) {
previewNode.innerText = '';
previewNode.classList.add('emptyMarkdownCell');
} else {
previewNode.classList.remove('emptyMarkdownCell');
const rendered = markdownIt.render(text);
previewNode.innerHTML = rendered;
}
},
extendMarkdownIt: (f: (md: typeof markdownIt) => void) => {

View file

@ -703,7 +703,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
if (isWeb()) {
// On web, treat absolute paths as pointing to standard lib files
if (filepath.startsWith('/')) {
return vscode.Uri.joinPath(this.context.extensionUri, 'node_modules', 'typescript', 'lib', filepath.slice(1));
return vscode.Uri.joinPath(this.context.extensionUri, 'dist', 'browser', 'typescript', filepath.slice(1));
}
}

View file

@ -193,6 +193,24 @@ export class ActionRunner extends Disposable implements IActionRunner {
export class Separator extends Action {
/**
* Joins all non-empty lists of actions with separators.
*/
public static join(...actionLists: readonly IAction[][]) {
let out: IAction[] = [];
for (const list of actionLists) {
if (!list.length) {
// skip
} else if (out.length) {
out = [...out, new Separator(), ...list];
} else {
out = list;
}
}
return out;
}
static readonly ID = 'vs.actions.separator';
constructor(label?: string) {

View file

@ -30,7 +30,7 @@ export namespace Iterable {
return iterable[Symbol.iterator]().next().value;
}
export function some<T>(iterable: Iterable<T>, predicate: (t: T) => boolean): boolean {
export function some<T>(iterable: Iterable<T>, predicate: (t: T) => unknown): boolean {
for (const element of iterable) {
if (predicate(element)) {
return true;

View file

@ -19,7 +19,7 @@ import { ModesContentHoverWidget } from 'vs/editor/contrib/hover/modesContentHov
import { ModesGlyphHoverWidget } from 'vs/editor/contrib/hover/modesGlyphHover';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { editorHoverBackground, editorHoverBorder, editorHoverHighlight, textCodeBlockBackground, textLinkForeground, editorHoverStatusBarBackground, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry';
import { editorHoverBackground, editorHoverBorder, editorHoverHighlight, textCodeBlockBackground, textLinkForeground, editorHoverStatusBarBackground, editorHoverForeground, textLinkActiveForeground } from 'vs/platform/theme/common/colorRegistry';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
import { GotoDefinitionAtPositionEditorContribution } from 'vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition';
@ -327,6 +327,10 @@ registerThemingParticipant((theme, collector) => {
if (link) {
collector.addRule(`.monaco-editor .monaco-hover a { color: ${link}; }`);
}
const linkHover = theme.getColor(textLinkActiveForeground);
if (linkHover) {
collector.addRule(`.monaco-editor .monaco-hover a:hover { color: ${linkHover}; }`);
}
const hoverForeground = theme.getColor(editorHoverForeground);
if (hoverForeground) {
collector.addRule(`.monaco-editor .monaco-hover { color: ${hoverForeground}; }`);

136
src/vs/vscode.d.ts vendored
View file

@ -1989,7 +1989,7 @@ declare module 'vscode' {
* { language: 'typescript', scheme: 'file' }
*
* @example <caption>A language filter that applies to all package.json paths</caption>
* { language: 'json', scheme: 'untitled', pattern: '**/package.json' }
* { language: 'json', pattern: '**/package.json' }
*/
export interface DocumentFilter {
@ -2815,9 +2815,9 @@ declare module 'vscode' {
}
/**
* The inline values provider interface defines the contract between extensions and the VS Code debugger inline values feature.
* The inline values provider interface defines the contract between extensions and the editor's debugger inline values feature.
* In this contract the provider returns inline value information for a given document range
* and VS Code shows this information in the editor at the end of lines.
* and the editor shows this information in the editor at the end of lines.
*/
export interface InlineValuesProvider {
@ -2829,7 +2829,7 @@ declare module 'vscode' {
/**
* Provide "inline value" information for a given document and range.
* VS Code calls this method whenever debugging stops in the given document.
* The editor calls this method whenever debugging stops in the given document.
* The returned inline values information is rendered in the editor at the end of lines.
*
* @param document The document for which the inline values information is needed.
@ -3646,7 +3646,7 @@ declare module 'vscode' {
* ```
*
* @see {@link SemanticTokensBuilder} for a helper to encode tokens as integers.
* *NOTE*: When doing edits, it is possible that multiple edits occur until VS Code decides to invoke the semantic tokens provider.
* *NOTE*: When doing edits, it is possible that multiple edits occur until the editor decides to invoke the semantic tokens provider.
* *NOTE*: If the provider cannot temporarily compute semantic tokens, it can indicate this by throwing an error with the message 'Busy'.
*/
provideDocumentSemanticTokens(document: TextDocument, token: CancellationToken): ProviderResult<SemanticTokens>;
@ -5556,7 +5556,7 @@ declare module 'vscode' {
/**
* Role of the widget which defines how a screen reader interacts with it.
* The role should be set in special cases when for example a tree-like element behaves like a checkbox.
* If role is not specified VS Code will pick the appropriate role automatically.
* If role is not specified the editor will pick the appropriate role automatically.
* More about aria roles can be found here https://w3c.github.io/aria/#widget_roles
*/
role?: string;
@ -5648,7 +5648,7 @@ declare module 'vscode' {
* The command must be {@link commands.getCommands known}.
*
* Note that if this is a {@link Command `Command`} object, only the {@link Command.command `command`} and {@link Command.arguments `arguments`}
* are used by VS Code.
* are used by the editor.
*/
command: string | Command | undefined;
@ -5959,13 +5959,13 @@ declare module 'vscode' {
export enum ExtensionMode {
/**
* The extension is installed normally (for example, from the marketplace
* or VSIX) in VS Code.
* or VSIX) in the editor.
*/
Production = 1,
/**
* The extension is running from an `--extensionDevelopmentPath` provided
* when launching VS Code.
* when launching the editor.
*/
Development = 2,
@ -6549,7 +6549,7 @@ declare module 'vscode' {
constructor(commandLine: string, options?: ShellExecutionOptions);
/**
* Creates a shell execution with a command and arguments. For the real execution VS Code will
* Creates a shell execution with a command and arguments. For the real execution the editor will
* construct a command line from the command and the arguments. This is subject to interpretation
* especially when it comes to quoting. If full control over the command line is needed please
* use the constructor that creates a `ShellExecution` with the full command line.
@ -6865,7 +6865,7 @@ declare module 'vscode' {
export function fetchTasks(filter?: TaskFilter): Thenable<Task[]>;
/**
* Executes a task that is managed by VS Code. The returned
* Executes a task that is managed by the editor. The returned
* task execution can be used to terminate the task.
*
* @throws When running a ShellExecution or a ProcessExecution
@ -7281,7 +7281,7 @@ declare module 'vscode' {
* @param scheme The scheme of the filesystem, for example `file` or `git`.
*
* @return `true` if the file system supports writing, `false` if it does not
* support writing (i.e. it is readonly), and `undefined` if VS Code does not
* support writing (i.e. it is readonly), and `undefined` if the editor does not
* know about the filesystem.
*/
isWritableFileSystem(scheme: string): boolean | undefined;
@ -7362,7 +7362,7 @@ declare module 'vscode' {
* Webviews are sandboxed from normal extension process, so all communication with the webview must use
* message passing. To send a message from the extension to the webview, use {@link Webview.postMessage `postMessage`}.
* To send message from the webview back to an extension, use the `acquireVsCodeApi` function inside the webview
* to get a handle to VS Code's api and then call `.postMessage()`:
* to get a handle to the editor's api and then call `.postMessage()`:
*
* ```html
* <script>
@ -7384,7 +7384,7 @@ declare module 'vscode' {
/**
* Fired when the webview content posts a message.
*
* Webview content can post strings or json serializable objects back to a VS Code extension. They cannot
* Webview content can post strings or json serializable objects back to an extension. They cannot
* post `Blob`, `File`, `ImageData` and other DOM specific objects since the extension that receives the
* message does not run in a browser environment.
*/
@ -7452,7 +7452,7 @@ declare module 'vscode' {
*
* Normally the webview panel's html context is created when the panel becomes visible
* and destroyed when it is hidden. Extensions that have complex state
* or UI can set the `retainContextWhenHidden` to make VS Code keep the webview
* or UI can set the `retainContextWhenHidden` to make the editor keep the webview
* context around, even when the webview moves to a background tab. When a webview using
* `retainContextWhenHidden` becomes hidden, its scripts and other dynamic content are suspended.
* When the panel becomes visible again, the context is automatically restored
@ -7562,7 +7562,7 @@ declare module 'vscode' {
* There are two types of webview persistence:
*
* - Persistence within a session.
* - Persistence across sessions (across restarts of VS Code).
* - Persistence across sessions (across restarts of the editor).
*
* A `WebviewPanelSerializer` is only required for the second case: persisting a webview across sessions.
*
@ -7582,8 +7582,8 @@ declare module 'vscode' {
* setState({ value: oldState.value + 1 })
* ```
*
* A `WebviewPanelSerializer` extends this persistence across restarts of VS Code. When the editor is shutdown,
* VS Code will save off the state from `setState` of all webviews that have a serializer. When the
* A `WebviewPanelSerializer` extends this persistence across restarts of the editor. When the editor is shutdown,
* it will save off the state from `setState` of all webviews that have a serializer. When the
* webview first becomes visible after the restart, this state is passed to `deserializeWebviewPanel`.
* The extension can then restore the old `WebviewPanel` from this state.
*
@ -7678,7 +7678,7 @@ declare module 'vscode' {
/**
* Persisted state from the webview content.
*
* To save resources, VS Code normally deallocates webview documents (the iframe content) that are not visible.
* To save resources, the editor normally deallocates webview documents (the iframe content) that are not visible.
* For example, when the user collapse a view or switches to another top level activity in the sidebar, the
* `WebviewView` itself is kept alive but the webview's underlying document is deallocated. It is recreated when
* the view becomes visible again.
@ -7701,7 +7701,7 @@ declare module 'vscode' {
* setState({ value: oldState.value + 1 })
* ```
*
* VS Code ensures that the persisted state is saved correctly when a webview is hidden and across
* The editor ensures that the persisted state is saved correctly when a webview is hidden and across
* editor restarts.
*/
readonly state: T | undefined;
@ -7731,7 +7731,7 @@ declare module 'vscode' {
* Provider for text based custom editors.
*
* Text based custom editors use a {@link TextDocument `TextDocument`} as their data model. This considerably simplifies
* implementing a custom editor as it allows VS Code to handle many common operations such as
* implementing a custom editor as it allows the editor to handle many common operations such as
* undo and backup. The provider is responsible for synchronizing text changes between the webview and the `TextDocument`.
*/
export interface CustomTextEditorProvider {
@ -7762,7 +7762,7 @@ declare module 'vscode' {
* Represents a custom document used by a {@link CustomEditorProvider `CustomEditorProvider`}.
*
* Custom documents are only used within a given `CustomEditorProvider`. The lifecycle of a `CustomDocument` is
* managed by VS Code. When no more references remain to a `CustomDocument`, it is disposed of.
* managed by the editor. When no more references remain to a `CustomDocument`, it is disposed of.
*/
interface CustomDocument {
/**
@ -7773,14 +7773,14 @@ declare module 'vscode' {
/**
* Dispose of the custom document.
*
* This is invoked by VS Code when there are no more references to a given `CustomDocument` (for example when
* This is invoked by the editor when there are no more references to a given `CustomDocument` (for example when
* all editors associated with the document have been closed.)
*/
dispose(): void;
}
/**
* Event triggered by extensions to signal to VS Code that an edit has occurred on an {@link CustomDocument `CustomDocument`}.
* Event triggered by extensions to signal to the editor that an edit has occurred on an {@link CustomDocument `CustomDocument`}.
*
* @see {@link CustomEditorProvider.onDidChangeCustomDocument `CustomEditorProvider.onDidChangeCustomDocument`}.
*/
@ -7794,18 +7794,18 @@ declare module 'vscode' {
/**
* Undo the edit operation.
*
* This is invoked by VS Code when the user undoes this edit. To implement `undo`, your
* This is invoked by the editor when the user undoes this edit. To implement `undo`, your
* extension should restore the document and editor to the state they were in just before this
* edit was added to VS Code's internal edit stack by `onDidChangeCustomDocument`.
* edit was added to the editor's internal edit stack by `onDidChangeCustomDocument`.
*/
undo(): Thenable<void> | void;
/**
* Redo the edit operation.
*
* This is invoked by VS Code when the user redoes this edit. To implement `redo`, your
* This is invoked by the editor when the user redoes this edit. To implement `redo`, your
* extension should restore the document and editor to the state they were in just after this
* edit was added to VS Code's internal edit stack by `onDidChangeCustomDocument`.
* edit was added to the editor's internal edit stack by `onDidChangeCustomDocument`.
*/
redo(): Thenable<void> | void;
@ -7818,7 +7818,7 @@ declare module 'vscode' {
}
/**
* Event triggered by extensions to signal to VS Code that the content of a {@link CustomDocument `CustomDocument`}
* Event triggered by extensions to signal to the editor that the content of a {@link CustomDocument `CustomDocument`}
* has changed.
*
* @see {@link CustomEditorProvider.onDidChangeCustomDocument `CustomEditorProvider.onDidChangeCustomDocument`}.
@ -7844,7 +7844,7 @@ declare module 'vscode' {
/**
* Delete the current backup.
*
* This is called by VS Code when it is clear the current backup is no longer needed, such as when a new backup
* This is called by the editor when it is clear the current backup is no longer needed, such as when a new backup
* is made or when the file is saved.
*/
delete(): void;
@ -7955,14 +7955,14 @@ declare module 'vscode' {
* anything from changing some text, to cropping an image, to reordering a list. Your extension is free to
* define what an edit is and what data is stored on each edit.
*
* Firing `onDidChange` causes VS Code to mark the editors as being dirty. This is cleared when the user either
* Firing `onDidChange` causes the editors to be marked as being dirty. This is cleared when the user either
* saves or reverts the file.
*
* Editors that support undo/redo must fire a `CustomDocumentEditEvent` whenever an edit happens. This allows
* users to undo and redo the edit using VS Code's standard VS Code keyboard shortcuts. VS Code will also mark
* users to undo and redo the edit using the editor's standard keyboard shortcuts. The editor will also mark
* the editor as no longer being dirty if the user undoes all edits to the last saved state.
*
* Editors that support editing but cannot use VS Code's standard undo/redo mechanism must fire a `CustomDocumentContentChangeEvent`.
* Editors that support editing but cannot use the editor's standard undo/redo mechanism must fire a `CustomDocumentContentChangeEvent`.
* The only way for a user to clear the dirty state of an editor that does not support undo/redo is to either
* `save` or `revert` the file.
*
@ -7973,7 +7973,7 @@ declare module 'vscode' {
/**
* Save a custom document.
*
* This method is invoked by VS Code when the user saves a custom editor. This can happen when the user
* This method is invoked by the editor when the user saves a custom editor. This can happen when the user
* triggers save while the custom editor is active, by commands such as `save all`, or by auto save if enabled.
*
* To implement `save`, the implementer must persist the custom editor. This usually means writing the
@ -7990,7 +7990,7 @@ declare module 'vscode' {
/**
* Save a custom document to a different location.
*
* This method is invoked by VS Code when the user triggers 'save as' on a custom editor. The implementer must
* This method is invoked by the editor when the user triggers 'save as' on a custom editor. The implementer must
* persist the custom editor to `destination`.
*
* When the user accepts save as, the current editor is be replaced by an non-dirty editor for the newly saved file.
@ -8006,8 +8006,8 @@ declare module 'vscode' {
/**
* Revert a custom document to its last saved state.
*
* This method is invoked by VS Code when the user triggers `File: Revert File` in a custom editor. (Note that
* this is only used using VS Code's `File: Revert File` command and not on a `git revert` of the file).
* This method is invoked by the editor when the user triggers `File: Revert File` in a custom editor. (Note that
* this is only used using the editor's `File: Revert File` command and not on a `git revert` of the file).
*
* To implement `revert`, the implementer must make sure all editor instances (webviews) for `document`
* are displaying the document in the same state is saved in. This usually means reloading the file from the
@ -8025,7 +8025,7 @@ declare module 'vscode' {
*
* Backups are used for hot exit and to prevent data loss. Your `backup` method should persist the resource in
* its current state, i.e. with the edits applied. Most commonly this means saving the resource to disk in
* the `ExtensionContext.storagePath`. When VS Code reloads and your custom editor is opened for a resource,
* the `ExtensionContext.storagePath`. When the editor reloads and your custom editor is opened for a resource,
* your extension should first check to see if any backups exist for the resource. If there is a backup, your
* extension should load the file contents from there instead of from the resource in the workspace.
*
@ -8039,7 +8039,7 @@ declare module 'vscode' {
* @param cancellation Token that signals the current backup since a new backup is coming in. It is up to your
* extension to decided how to respond to cancellation. If for example your extension is backing up a large file
* in an operation that takes time to complete, your extension may decide to finish the ongoing backup rather
* than cancelling it to ensure that VS Code has some valid backup.
* than cancelling it to ensure that the editor has some valid backup.
*/
backupCustomDocument(document: T, context: CustomDocumentBackupContext, cancellation: CancellationToken): Thenable<CustomDocumentBackup>;
}
@ -8193,7 +8193,7 @@ declare module 'vscode' {
*
* If the extension is running remotely, this function automatically establishes a port forwarding tunnel
* from the local machine to `target` on the remote and returns a local uri to the tunnel. The lifetime of
* the port forwarding tunnel is managed by VS Code and the tunnel can be closed by the user.
* the port forwarding tunnel is managed by the editor and the tunnel can be closed by the user.
*
* *Note* that uris passed through `openExternal` are automatically resolved and you should not call `asExternalUri` on them.
*
@ -9357,7 +9357,7 @@ declare module 'vscode' {
cwd?: string | Uri;
/**
* Object with environment variables that will be added to the VS Code process.
* Object with environment variables that will be added to the editor process.
*/
env?: { [key: string]: string | null | undefined };
@ -10306,15 +10306,15 @@ declare module 'vscode' {
/**
* Namespace for dealing with the current workspace. A workspace is the collection of one
* or more folders that are opened in a VS Code window (instance).
* or more folders that are opened in an editor window (instance).
*
* It is also possible to open VS Code without a workspace. For example, when you open a
* new VS Code window by selecting a file from your platform's File menu, you will not be
* inside a workspace. In this mode, some of VS Code's capabilities are reduced but you can
* It is also possible to open an editor without a workspace. For example, when you open a
* new editor window by selecting a file from your platform's File menu, you will not be
* inside a workspace. In this mode, some of the editor's capabilities are reduced but you can
* still open text files and edit them.
*
* Refer to https://code.visualstudio.com/docs/editor/workspaces for more information on
* the concept of workspaces in VS Code.
* the concept of workspaces.
*
* The workspace offers support for {@link workspace.createFileSystemWatcher listening} to fs
* events and for {@link workspace.findFiles finding} files. Both perform well and run _outside_
@ -10331,22 +10331,22 @@ declare module 'vscode' {
export const fs: FileSystem;
/**
* The workspace folder that is open in VS Code. `undefined` when no workspace
* The workspace folder that is open in the editor. `undefined` when no workspace
* has been opened.
*
* Refer to https://code.visualstudio.com/docs/editor/workspaces for more information
* on workspaces in VS Code.
* on workspaces.
*
* @deprecated Use {@link workspace.workspaceFolders `workspaceFolders`} instead.
*/
export const rootPath: string | undefined;
/**
* List of workspace folders that are open in VS Code. `undefined` when no workspace
* List of workspace folders that are open in the editor. `undefined` when no workspace
* has been opened.
*
* Refer to https://code.visualstudio.com/docs/editor/workspaces for more information
* on workspaces in VS Code.
* on workspaces.
*
* *Note* that the first entry corresponds to the value of `rootPath`.
*/
@ -10357,7 +10357,7 @@ declare module 'vscode' {
* has been opened.
*
* Refer to https://code.visualstudio.com/docs/editor/workspaces for more information on
* the concept of workspaces in VS Code.
* the concept of workspaces.
*/
export const name: string | undefined;
@ -10386,7 +10386,7 @@ declare module 'vscode' {
* ```
*
* Refer to https://code.visualstudio.com/docs/editor/workspaces for more information on
* the concept of workspaces in VS Code.
* the concept of workspaces.
*
* **Note:** it is not advised to use `workspace.workspaceFile` to write
* configuration data into the file. You can use `workspace.getConfiguration().update()`
@ -11079,7 +11079,7 @@ declare module 'vscode' {
/**
* Register a provider that locates evaluatable expressions in text documents.
* VS Code will evaluate the expression in the active debug session and will show the result in the debug hover.
* The editor will evaluate the expression in the active debug session and will show the result in the debug hover.
*
* If multiple providers are registered for a language an arbitrary provider will be used.
*
@ -11091,7 +11091,7 @@ declare module 'vscode' {
/**
* Register a provider that returns data for the debugger's 'inline value' feature.
* Whenever the generic VS Code debugger has stopped in a source file, providers registered for the language of the file
* Whenever the generic debugger has stopped in a source file, providers registered for the language of the file
* are called to return textual data that will be shown in the editor at the end of lines.
*
* Multiple providers can be registered for a language. In that case providers are asked in
@ -12074,7 +12074,7 @@ declare module 'vscode' {
* The command must be {@link commands.getCommands known}.
*
* Note that if this is a {@link Command `Command`} object, only the {@link Command.command `command`} and {@link Command.arguments `arguments`}
* are used by VS Code.
* are used by the editor.
*/
command?: string | Command;
@ -12496,10 +12496,10 @@ declare module 'vscode' {
customRequest(command: string, args?: any): Thenable<any>;
/**
* Maps a VS Code breakpoint to the corresponding Debug Adapter Protocol (DAP) breakpoint that is managed by the debug adapter of the debug session.
* If no DAP breakpoint exists (either because the VS Code breakpoint was not yet registered or because the debug adapter is not interested in the breakpoint), the value `undefined` is returned.
* Maps a breakpoint in the editor to the corresponding Debug Adapter Protocol (DAP) breakpoint that is managed by the debug adapter of the debug session.
* If no DAP breakpoint exists (either because the editor breakpoint was not yet registered or because the debug adapter is not interested in the breakpoint), the value `undefined` is returned.
*
* @param breakpoint A VS Code {@link Breakpoint}.
* @param breakpoint A {@link Breakpoint} in the editor.
* @return A promise that resolves to the Debug Adapter Protocol breakpoint or `undefined`.
*/
getDebugProtocolBreakpoint(breakpoint: Breakpoint): Thenable<DebugProtocolBreakpoint | undefined>;
@ -12588,7 +12588,7 @@ declare module 'vscode' {
/**
* The command or path of the debug adapter executable.
* A command must be either an absolute path of an executable or the name of an command to be looked up via the PATH environment variable.
* The special value 'node' will be mapped to VS Code's built-in Node.js runtime.
* The special value 'node' will be mapped to the editor's built-in Node.js runtime.
*/
readonly command: string;
@ -12659,12 +12659,12 @@ declare module 'vscode' {
}
/**
* A debug adapter that implements the Debug Adapter Protocol can be registered with VS Code if it implements the DebugAdapter interface.
* A debug adapter that implements the Debug Adapter Protocol can be registered with the editor if it implements the DebugAdapter interface.
*/
export interface DebugAdapter extends Disposable {
/**
* An event which fires after the debug adapter has sent a Debug Adapter Protocol message to VS Code.
* An event which fires after the debug adapter has sent a Debug Adapter Protocol message to the editor.
* Messages can be requests, responses, or events.
*/
readonly onDidSendMessage: Event<DebugProtocolMessage>;
@ -12713,7 +12713,7 @@ declare module 'vscode' {
}
/**
* A Debug Adapter Tracker is a means to track the communication between VS Code and a Debug Adapter.
* A Debug Adapter Tracker is a means to track the communication between the editor and a Debug Adapter.
*/
export interface DebugAdapterTracker {
/**
@ -12721,11 +12721,11 @@ declare module 'vscode' {
*/
onWillStartSession?(): void;
/**
* The debug adapter is about to receive a Debug Adapter Protocol message from VS Code.
* The debug adapter is about to receive a Debug Adapter Protocol message from the editor.
*/
onWillReceiveMessage?(message: any): void;
/**
* The debug adapter has sent a Debug Adapter Protocol message to VS Code.
* The debug adapter has sent a Debug Adapter Protocol message to the editor.
*/
onDidSendMessage?(message: any): void;
/**
@ -12745,7 +12745,7 @@ declare module 'vscode' {
export interface DebugAdapterTrackerFactory {
/**
* The method 'createDebugAdapterTracker' is called at the start of a debug session in order
* to return a "tracker" object that provides read-access to the communication between VS Code and a debug adapter.
* to return a "tracker" object that provides read-access to the communication between the editor and a debug adapter.
*
* @param session The {@link DebugSession debug session} for which the debug adapter tracker will be used.
* @return A {@link DebugAdapterTracker debug adapter tracker} or undefined.
@ -13036,7 +13036,7 @@ declare module 'vscode' {
/**
* Converts a "Source" descriptor object received via the Debug Adapter Protocol into a Uri that can be used to load its contents.
* If the source descriptor is based on a path, a file Uri is returned.
* If the source descriptor uses a reference number, a specific debug Uri (scheme 'debug') is constructed that requires a corresponding VS Code ContentProvider and a running debug session
* If the source descriptor uses a reference number, a specific debug Uri (scheme 'debug') is constructed that requires a corresponding ContentProvider and a running debug session
*
* If the "Source" descriptor has insufficient information for creating the Uri, an error is thrown.
*
@ -13585,7 +13585,7 @@ declare module 'vscode' {
* quickpick to select which account they would like to use.
*
* Currently, there are only two authentication providers that are contributed from built in extensions
* to VS Code that implement GitHub and Microsoft authentication: their providerId's are 'github' and 'microsoft'.
* to the editor that implement GitHub and Microsoft authentication: their providerId's are 'github' and 'microsoft'.
* @param providerId The id of the provider to use
* @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication provider
* @param options The {@link GetSessionOptions} to use
@ -13600,7 +13600,7 @@ declare module 'vscode' {
* quickpick to select which account they would like to use.
*
* Currently, there are only two authentication providers that are contributed from built in extensions
* to VS Code that implement GitHub and Microsoft authentication: their providerId's are 'github' and 'microsoft'.
* to the editor that implement GitHub and Microsoft authentication: their providerId's are 'github' and 'microsoft'.
* @param providerId The id of the provider to use
* @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication provider
* @param options The {@link GetSessionOptions} to use

View file

@ -9,7 +9,7 @@
* distribution and CANNOT be used in published extensions.
*
* To test these API in local environment:
* - Use Insiders release of VS Code.
* - Use Insiders release of 'VS Code'.
* - Add `"enableProposedApi": true` to your package.json.
* - Copy this file to your project.
*/
@ -147,8 +147,8 @@ declare module 'vscode' {
/**
* Resolve the authority part of the current opened `vscode-remote://` URI.
*
* This method will be invoked once during the startup of VS Code and again each time
* VS Code detects a disconnection.
* This method will be invoked once during the startup of the editor and again each time
* the editor detects a disconnection.
*
* @param authority The authority part of the current opened `vscode-remote://` URI.
* @param context A context indicating if this is the first call or a subsequent call.
@ -438,11 +438,11 @@ declare module 'vscode' {
/**
* Additional information regarding the state of the completed search.
*
* Messages with "Information" tyle support links in markdown syntax:
* Messages with "Information" style support links in markdown syntax:
* - Click to [run a command](command:workbench.action.OpenQuickPick)
* - Click to [open a website](https://aka.ms)
*
* Commands may optionally return { triggerSearch: true } to signal to VS Code that the original search should run be agian.
* Commands may optionally return { triggerSearch: true } to signal to the editor that the original search should run be again.
*/
message?: TextSearchCompleteMessage | TextSearchCompleteMessage[];
}
@ -553,7 +553,7 @@ declare module 'vscode' {
/**
* A FileSearchProvider provides search results for files in the given folder that match a query string. It can be invoked by quickopen or other extensions.
*
* A FileSearchProvider is the more powerful of two ways to implement file search in VS Code. Use a FileSearchProvider if you wish to search within a folder for
* A FileSearchProvider is the more powerful of two ways to implement file search in the editor. Use a FileSearchProvider if you wish to search within a folder for
* all files that match the user's query.
*
* The FileSearchProvider will be invoked on every keypress in quickopen. When `workspace.findFiles` is called, it will be invoked with an empty query string,
@ -987,7 +987,7 @@ declare module 'vscode' {
* Handle when the underlying resource for a custom editor is renamed.
*
* This allows the webview for the editor be preserved throughout the rename. If this method is not implemented,
* VS Code will destory the previous custom editor and create a replacement one.
* the editor will destroy the previous custom editor and create a replacement one.
*
* @param newDocument New text document to use for the custom editor.
* @param existingWebviewPanel Webview panel for the custom editor.
@ -1352,7 +1352,7 @@ declare module 'vscode' {
/**
* Delete the current backup.
*
* This is called by VS Code when it is clear the current backup is no longer needed, such as when a new backup
* This is called by the editor when it is clear the current backup is no longer needed, such as when a new backup
* is made or when the file is saved.
*/
delete(): void;
@ -1884,7 +1884,7 @@ declare module 'vscode' {
* disambiguate multiple sets of results in a test run. It is useful if
* tests are run across multiple platforms, for example.
* @param persist Whether the results created by the run should be
* persisted in VS Code. This may be false if the results are coming from
* persisted in the editor. This may be false if the results are coming from
* a file already saved externally, such as a coverage information file.
*/
export function createTestRun<T>(request: TestRunRequest<T>, name?: string, persist?: boolean): TestRun<T>;
@ -1903,7 +1903,7 @@ declare module 'vscode' {
export function createTestItem<T = void, TChildren = any>(options: TestItemOptions): TestItem<T, TChildren>;
/**
* List of test results stored by VS Code, sorted in descnding
* List of test results stored by the editor, sorted in descending
* order by their `completedAt` time.
* @stability experimental
*/
@ -1943,7 +1943,7 @@ declare module 'vscode' {
readonly onDidDiscoverInitialTests: Event<void>;
/**
* Dispose of the observer, allowing VS Code to eventually tell test
* Dispose of the observer, allowing the editor to eventually tell test
* providers that they no longer need to update tests.
*/
dispose(): void;
@ -2030,7 +2030,7 @@ declare module 'vscode' {
tests: TestItem<T>[];
/**
* An array of tests the user has marked as excluded in VS Code. May be
* An array of tests the user has marked as excluded in the editor. May be
* omitted if no exclusions were requested. Test controllers should not run
* excluded tests or any children of excluded tests.
*/
@ -2334,7 +2334,7 @@ declare module 'vscode' {
}
/**
* TestResults can be provided to VS Code in {@link test.publishTestResult},
* TestResults can be provided to the editor in {@link test.publishTestResult},
* or read from it in {@link test.testResults}.
*
* The results contain a 'snapshot' of the tests at the point when the test
@ -2437,7 +2437,7 @@ declare module 'vscode' {
* if an opener should be selected automatically or if the user should be prompted to
* select an opener.
*
* VS Code will try to use the best available opener, as sorted by `ExternalUriOpenerPriority`.
* The editor will try to use the best available opener, as sorted by `ExternalUriOpenerPriority`.
* If there are multiple potential "best" openers for a URI, then the user will be prompted
* to select an opener.
*/
@ -2452,21 +2452,21 @@ declare module 'vscode' {
/**
* The opener can open the uri but will not cause a prompt on its own
* since VS Code always contributes a built-in `Default` opener.
* since the editor always contributes a built-in `Default` opener.
*/
Option = 1,
/**
* The opener can open the uri.
*
* VS Code's built-in opener has `Default` priority. This means that any additional `Default`
* The editor's built-in opener has `Default` priority. This means that any additional `Default`
* openers will cause the user to be prompted to select from a list of all potential openers.
*/
Default = 2,
/**
* The opener can open the uri and should be automatically selected over any
* default openers, include the built-in one from VS Code.
* default openers, include the built-in one from the editor.
*
* A preferred opener will be automatically selected if no other preferred openers
* are available. If multiple preferred openers are available, then the user
@ -2479,7 +2479,7 @@ declare module 'vscode' {
* Handles opening uris to external resources, such as http(s) links.
*
* Extensions can implement an `ExternalUriOpener` to open `http` links to a webserver
* inside of VS Code instead of having the link be opened by the web browser.
* inside of the editor instead of having the link be opened by the web browser.
*
* Currently openers may only be registered for `http` and `https` uris.
*/
@ -2570,11 +2570,11 @@ declare module 'vscode' {
* Allows using openers contributed by extensions through `registerExternalUriOpener`
* when opening the resource.
*
* If `true`, VS Code will check if any contributed openers can handle the
* If `true`, the editor will check if any contributed openers can handle the
* uri, and fallback to the default opener behavior.
*
* If it is string, this specifies the id of the `ExternalUriOpener`
* that should be used if it is available. Use `'default'` to force VS Code's
* that should be used if it is available. Use `'default'` to force the editor's
* standard external opener to be used.
*/
readonly allowContributedOpeners?: boolean | string;
@ -2603,7 +2603,7 @@ declare module 'vscode' {
*
* If the extension is running remotely, this function automatically establishes a port forwarding tunnel
* from the local machine to `target` on the remote and returns a local uri to the tunnel. The lifetime of
* the port forwarding tunnel is managed by VS Code and the tunnel can be closed by the user.
* the port forwarding tunnel is managed by the editor and the tunnel can be closed by the user.
*
* *Note* that uris passed through `openExternal` are automatically resolved and you should not call `asExternalUri` on them.
*
@ -2640,7 +2640,7 @@ declare module 'vscode' {
* #### Any other scheme
*
* Any other scheme will be handled as if the provided URI is a workspace URI. In that case, the method will return
* a URI which, when handled, will make VS Code open the workspace.
* a URI which, when handled, will make the editor open the workspace.
*
* @return A uri that can be used on the client machine.
*/
@ -2732,7 +2732,7 @@ declare module 'vscode' {
/**
* If your extension listens on ports, consider registering a PortAttributesProvider to provide information
* about the ports. For example, a debug extension may know about debug ports in it's debuggee. By providing
* this information with a PortAttributesProvider the extension can tell VS Code that these ports should be
* this information with a PortAttributesProvider the extension can tell the editor that these ports should be
* ignored, since they don't need to be user facing.
*
* @param portSelector If registerPortAttributesProvider is called after you start your process then you may already

View file

@ -1385,7 +1385,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
}
}
await this._webview!.initializeMarkdown(requests.map(request => ({
await this._webview!.initializeMarkup(requests.map(request => ({
mime: 'text/markdown',
cellId: request[0].id,
cellHandle: request[0].handle,
content: request[0].getText(),
@ -1393,8 +1394,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
visible: false,
})));
} else {
const initRequests = viewModel.viewCells.filter(cell => cell.cellKind === CellKind.Markup).slice(0, 5).map(cell => ({ cellId: cell.id, cellHandle: cell.handle, content: cell.getText(), offset: -10000, visible: false }));
await this._webview!.initializeMarkdown(initRequests);
const initRequests = viewModel.viewCells.filter(cell => cell.cellKind === CellKind.Markup).slice(0, 5).map(cell => ({
cellId: cell.id, cellHandle: cell.handle, content: cell.getText(), offset: -10000, visible: false, mime: 'text/markdown',
}));
await this._webview!.initializeMarkup(initRequests);
// no cached view state so we are rendering the first viewport
// after above async call, we already get init height for markdown cells, we can update their offset
@ -2261,6 +2264,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
const cellTop = this._list.getAbsoluteTopOfElement(cell);
await this._webview.showMarkdownPreview({
mime: 'text/markdown',
cellHandle: cell.handle,
cellId: cell.id,
content: cell.getText(),

View file

@ -26,7 +26,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { asWebviewUri } from 'vs/workbench/api/common/shared/webview';
import { CellEditState, ICellOutputViewModel, ICommonCellInfo, ICommonNotebookEditor, IDisplayOutputLayoutUpdateRequest, IDisplayOutputViewModel, IGenericCellViewModel, IInsetRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { PreloadOptions, preloadsScriptStr, RendererMetadata } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads';
import { preloadsScriptStr, RendererMetadata } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads';
import { transformWebviewThemeVars } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewThemeMapping';
import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel';
import { INotebookKernel, INotebookRendererInfo, RendererMessagingSpec } from 'vs/workbench/contrib/notebook/common/notebookCommon';
@ -34,368 +34,7 @@ import { IScopedRendererMessaging } from 'vs/workbench/contrib/notebook/common/n
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { IWebviewService, WebviewContentPurpose, WebviewElement } from 'vs/workbench/contrib/webview/browser/webview';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
interface BaseToWebviewMessage {
readonly __vscode_notebook_message: true;
}
export interface WebviewIntialized extends BaseToWebviewMessage {
type: 'initialized';
}
export interface DimensionUpdate {
id: string;
init?: boolean;
height: number;
isOutput?: boolean;
}
export interface IDimensionMessage extends BaseToWebviewMessage {
type: 'dimension';
updates: readonly DimensionUpdate[];
}
export interface IMouseEnterMessage extends BaseToWebviewMessage {
type: 'mouseenter';
id: string;
}
export interface IMouseLeaveMessage extends BaseToWebviewMessage {
type: 'mouseleave';
id: string;
}
export interface IOutputFocusMessage extends BaseToWebviewMessage {
type: 'outputFocus';
id: string;
}
export interface IOutputBlurMessage extends BaseToWebviewMessage {
type: 'outputBlur';
id: string;
}
export interface IWheelMessage extends BaseToWebviewMessage {
type: 'did-scroll-wheel';
payload: any;
}
export interface IScrollAckMessage extends BaseToWebviewMessage {
type: 'scroll-ack';
data: { top: number };
version: number;
}
export interface IBlurOutputMessage extends BaseToWebviewMessage {
type: 'focus-editor';
id: string;
focusNext?: boolean;
}
export interface IClickedDataUrlMessage extends BaseToWebviewMessage {
type: 'clicked-data-url';
data: string | ArrayBuffer | null;
downloadName?: string;
}
export interface IClickMarkdownPreviewMessage extends BaseToWebviewMessage {
readonly type: 'clickMarkdownPreview';
readonly cellId: string;
readonly ctrlKey: boolean
readonly altKey: boolean;
readonly metaKey: boolean;
readonly shiftKey: boolean;
}
export interface IContextMenuMarkdownPreviewMessage extends BaseToWebviewMessage {
readonly type: 'contextMenuMarkdownPreview';
readonly cellId: string;
readonly clientX: number;
readonly clientY: number;
}
export interface IMouseEnterMarkdownPreviewMessage extends BaseToWebviewMessage {
type: 'mouseEnterMarkdownPreview';
cellId: string;
}
export interface IMouseLeaveMarkdownPreviewMessage extends BaseToWebviewMessage {
type: 'mouseLeaveMarkdownPreview';
cellId: string;
}
export interface IToggleMarkdownPreviewMessage extends BaseToWebviewMessage {
type: 'toggleMarkdownPreview';
cellId: string;
}
export interface ICellDragStartMessage extends BaseToWebviewMessage {
type: 'cell-drag-start';
readonly cellId: string;
readonly dragOffsetY: number;
}
export interface ICellDragMessage extends BaseToWebviewMessage {
type: 'cell-drag';
readonly cellId: string;
readonly dragOffsetY: number;
}
export interface ICellDropMessage extends BaseToWebviewMessage {
readonly type: 'cell-drop';
readonly cellId: string;
readonly ctrlKey: boolean
readonly altKey: boolean;
readonly dragOffsetY: number;
}
export interface ICellDragEndMessage extends BaseToWebviewMessage {
readonly type: 'cell-drag-end';
readonly cellId: string;
}
export interface IInitializedMarkdownPreviewMessage extends BaseToWebviewMessage {
readonly type: 'initializedMarkdownPreview';
}
export interface ITelemetryFoundRenderedMarkdownMath extends BaseToWebviewMessage {
readonly type: 'telemetryFoundRenderedMarkdownMath';
}
export interface ITelemetryFoundUnrenderedMarkdownMath extends BaseToWebviewMessage {
readonly type: 'telemetryFoundUnrenderedMarkdownMath';
readonly latexDirective: string;
}
export interface IClearMessage {
type: 'clear';
}
export interface IOutputRequestMetadata {
/**
* Additional attributes of a cell metadata.
*/
custom?: { [key: string]: unknown };
}
export interface IOutputRequestDto {
/**
* { mime_type: value }
*/
data: { [key: string]: unknown; }
metadata?: IOutputRequestMetadata;
outputId: string;
}
export interface ICreationRequestMessage {
type: 'html';
content:
| { type: RenderOutputType.Html; htmlContent: string }
| { type: RenderOutputType.Extension; outputId: string; valueBytes: Uint8Array, metadata: unknown; metadata2: unknown, mimeType: string };
cellId: string;
outputId: string;
cellTop: number;
outputOffset: number;
left: number;
requiredPreloads: ReadonlyArray<IControllerPreload>;
readonly initiallyHidden?: boolean;
rendererId?: string | undefined;
}
export interface IContentWidgetTopRequest {
outputId: string;
cellTop: number;
outputOffset: number;
forceDisplay: boolean;
}
export interface IViewScrollTopRequestMessage {
type: 'view-scroll';
widgets: IContentWidgetTopRequest[];
markdownPreviews: { id: string; top: number }[];
}
export interface IScrollRequestMessage {
type: 'scroll';
id: string;
top: number;
widgetTop?: number;
version: number;
}
export interface IClearOutputRequestMessage {
type: 'clearOutput';
cellId: string;
outputId: string;
cellUri: string;
rendererId: string | undefined;
}
export interface IHideOutputMessage {
type: 'hideOutput';
outputId: string;
cellId: string;
}
export interface IShowOutputMessage {
type: 'showOutput';
cellId: string;
outputId: string;
cellTop: number;
outputOffset: number;
}
export interface IFocusOutputMessage {
type: 'focus-output';
cellId: string;
}
export interface IAckOutputHeightMessage {
type: 'ack-dimension',
cellId: string;
outputId: string;
height: number;
}
export interface IControllerPreload {
originalUri: string;
uri: string;
}
export interface IUpdateControllerPreloadsMessage {
type: 'preload';
resources: IControllerPreload[];
}
export interface IUpdateDecorationsMessage {
type: 'decorations';
cellId: string;
addedClassNames: string[];
removedClassNames: string[];
}
export interface ICustomKernelMessage extends BaseToWebviewMessage {
type: 'customKernelMessage';
message: unknown;
}
export interface ICustomRendererMessage extends BaseToWebviewMessage {
type: 'customRendererMessage';
rendererId: string;
message: unknown;
}
export interface ICreateMarkdownMessage {
type: 'createMarkdownPreview',
cell: IMarkdownCellInitialization;
}
export interface IDeleteMarkdownMessage {
type: 'deleteMarkdownPreview',
ids: readonly string[];
}
export interface IHideMarkdownMessage {
type: 'hideMarkdownPreviews';
ids: readonly string[];
}
export interface IUnhideMarkdownMessage {
type: 'unhideMarkdownPreviews';
ids: readonly string[];
}
export interface IShowMarkdownMessage {
type: 'showMarkdownPreview',
id: string;
handle: number;
content: string | undefined;
top: number;
}
export interface IUpdateSelectedMarkdownPreviews {
readonly type: 'updateSelectedMarkdownPreviews',
readonly selectedCellIds: readonly string[]
}
export interface IMarkdownCellInitialization {
cellId: string;
cellHandle: number;
content: string;
offset: number;
visible: boolean;
}
export interface IInitializeMarkdownMessage {
type: 'initializeMarkdownPreview';
cells: ReadonlyArray<IMarkdownCellInitialization>;
}
export interface INotebookStylesMessage {
type: 'notebookStyles';
styles: {
[key: string]: string;
};
}
export interface INotebookOptionsMessage {
type: 'notebookOptions';
options: PreloadOptions;
}
export type FromWebviewMessage =
| WebviewIntialized
| IDimensionMessage
| IMouseEnterMessage
| IMouseLeaveMessage
| IOutputFocusMessage
| IOutputBlurMessage
| IWheelMessage
| IScrollAckMessage
| IBlurOutputMessage
| ICustomKernelMessage
| ICustomRendererMessage
| IClickedDataUrlMessage
| IClickMarkdownPreviewMessage
| IContextMenuMarkdownPreviewMessage
| IMouseEnterMarkdownPreviewMessage
| IMouseLeaveMarkdownPreviewMessage
| IToggleMarkdownPreviewMessage
| ICellDragStartMessage
| ICellDragMessage
| ICellDropMessage
| ICellDragEndMessage
| IInitializedMarkdownPreviewMessage
| ITelemetryFoundRenderedMarkdownMath
| ITelemetryFoundUnrenderedMarkdownMath
;
export type ToWebviewMessage =
| IClearMessage
| IFocusOutputMessage
| IAckOutputHeightMessage
| ICreationRequestMessage
| IViewScrollTopRequestMessage
| IScrollRequestMessage
| IClearOutputRequestMessage
| IHideOutputMessage
| IShowOutputMessage
| IUpdateControllerPreloadsMessage
| IUpdateDecorationsMessage
| ICustomKernelMessage
| ICustomRendererMessage
| ICreateMarkdownMessage
| IDeleteMarkdownMessage
| IShowMarkdownMessage
| IHideMarkdownMessage
| IUnhideMarkdownMessage
| IUpdateSelectedMarkdownPreviews
| IInitializeMarkdownMessage
| INotebookStylesMessage
| INotebookOptionsMessage;
export type AnyMessage = FromWebviewMessage | ToWebviewMessage;
import { ICreationRequestMessage, IMarkupCellInitialization, FromWebviewMessage, IClickedDataUrlMessage, IContentWidgetTopRequest, IControllerPreload, ToWebviewMessage } from './webviewMessages';
export interface ICachedInset<K extends ICommonCellInfo> {
outputId: string;
@ -424,7 +63,7 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
element: HTMLElement;
webview: WebviewElement | undefined = undefined;
insetMapping: Map<IDisplayOutputViewModel, ICachedInset<T>> = new Map();
readonly markdownPreviewMapping = new Map<string, IMarkdownCellInitialization>();
readonly markdownPreviewMapping = new Map<string, IMarkupCellInitialization>();
hiddenInsetMapping: Set<IDisplayOutputViewModel> = new Set();
reversedInsetMapping: Map<string, IDisplayOutputViewModel> = new Map();
localResourceRootsCache: URI[] | undefined = undefined;
@ -525,7 +164,8 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
'notebook-markdown-left-margin': `${this.options.markdownLeftMargin}px`,
'notebook-output-node-left-padding': `${this.options.outputNodeLeftPadding}px`,
'notebook-markdown-min-height': `${this.options.previewNodePadding * 2}px`,
'notebook-cell-output-font-size': `${this.options.fontSize}px`
'notebook-cell-output-font-size': `${this.options.fontSize}px`,
'notebook-cell-markup-empty-content': nls.localize('notebook.emptyMarkdownPlaceholder', "Empty markdown cell, double click or press enter to edit."),
};
}
@ -536,133 +176,6 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
<head>
<meta charset="UTF-8">
<base href="${baseUrl}/"/>
<!--
Markdown previews are rendered using a shadow dom and are not effected by normal css.
Insert this style node into all preview shadow doms for styling.
-->
<template id="preview-styles">
<style>
img {
max-width: 100%;
max-height: 100%;
}
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
a:focus,
input:focus,
select:focus,
textarea:focus {
outline: 1px solid -webkit-focus-ring-color;
outline-offset: -1px;
}
hr {
border: 0;
height: 2px;
border-bottom: 2px solid;
}
h1 {
font-size: 26px;
line-height: 31px;
margin: 0;
margin-bottom: 13px;
}
h2 {
font-size: 19px;
margin: 0;
margin-bottom: 10px;
}
h1,
h2,
h3 {
font-weight: normal;
}
div {
width: 100%;
}
/* Adjust margin of first item in markdown cell */
*:first-child {
margin-top: 0px;
}
/* h1 tags don't need top margin */
h1:first-child {
margin-top: 0;
}
/* Removes bottom margin when only one item exists in markdown cell */
*:only-child,
*:last-child {
margin-bottom: 0;
padding-bottom: 0;
}
/* makes all markdown cells consistent */
div {
min-height: var(--notebook-markdown-min-height);
}
table {
border-collapse: collapse;
border-spacing: 0;
}
table th,
table td {
border: 1px solid;
}
table > thead > tr > th {
text-align: left;
border-bottom: 1px solid;
}
table > thead > tr > th,
table > thead > tr > td,
table > tbody > tr > th,
table > tbody > tr > td {
padding: 5px 10px;
}
table > tbody > tr + tr > td {
border-top: 1px solid;
}
blockquote {
margin: 0 7px 0 5px;
padding: 0 16px 0 10px;
border-left-width: 5px;
border-left-style: solid;
}
code,
.code {
font-size: 1em;
line-height: 1.357em;
}
.code {
white-space: pre-wrap;
}
dragging {
background-color: var(--theme-background);
}
</style>
</template>
<style>
#container .cell_container {
width: 100%;
@ -706,12 +219,6 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
cursor: grab;
}
#container > div.preview.emptyMarkdownCell::before {
content: "${nls.localize('notebook.emptyMarkdownPlaceholder', "Empty markdown cell, double click or press enter to edit.")}";
font-style: italic;
opacity: 0.6;
}
#container > div.preview.selected {
background: var(--theme-notebook-cell-selected-background);
}
@ -1048,7 +555,7 @@ var requirejs = (function() {
this.rendererMessaging?.postMessage(data.rendererId, data.message);
break;
}
case 'clickMarkdownPreview':
case 'clickMarkupCell':
{
const cell = this.notebookEditor.getCellById(data.cellId);
if (cell) {
@ -1062,7 +569,7 @@ var requirejs = (function() {
}
break;
}
case 'contextMenuMarkdownPreview':
case 'contextMenuMarkupCell':
{
const cell = this.notebookEditor.getCellById(data.cellId);
if (cell) {
@ -1087,7 +594,7 @@ var requirejs = (function() {
}
break;
}
case 'toggleMarkdownPreview':
case 'toggleMarkupPreview':
{
const cell = this.notebookEditor.getCellById(data.cellId);
if (cell) {
@ -1096,7 +603,7 @@ var requirejs = (function() {
}
break;
}
case 'mouseEnterMarkdownPreview':
case 'mouseEnterMarkupCell':
{
const cell = this.notebookEditor.getCellById(data.cellId);
if (cell instanceof MarkdownCellViewModel) {
@ -1104,7 +611,7 @@ var requirejs = (function() {
}
break;
}
case 'mouseLeaveMarkdownPreview':
case 'mouseLeaveMarkupCell':
{
const cell = this.notebookEditor.getCellById(data.cellId);
if (cell instanceof MarkdownCellViewModel) {
@ -1245,7 +752,7 @@ var requirejs = (function() {
const mdCells = [...this.markdownPreviewMapping.values()];
this.markdownPreviewMapping.clear();
this.initializeMarkdown(mdCells);
this.initializeMarkup(mdCells);
this._updateStyles();
this._updateOptions();
}
@ -1323,7 +830,7 @@ var requirejs = (function() {
});
}
private async createMarkdownPreview(initialization: IMarkdownCellInitialization) {
private async createMarkdownPreview(initialization: IMarkupCellInitialization) {
if (this._disposed) {
return;
}
@ -1335,12 +842,12 @@ var requirejs = (function() {
this.markdownPreviewMapping.set(initialization.cellId, initialization);
this._sendMessageToWebview({
type: 'createMarkdownPreview',
type: 'createMarkupCell',
cell: initialization
});
}
async showMarkdownPreview(initialization: IMarkdownCellInitialization) {
async showMarkdownPreview(initialization: IMarkupCellInitialization) {
if (this._disposed) {
return;
}
@ -1353,7 +860,7 @@ var requirejs = (function() {
const sameContent = initialization.content === entry.content;
if (!sameContent || !entry.visible) {
this._sendMessageToWebview({
type: 'showMarkdownPreview',
type: 'showMarkupCell',
id: initialization.cellId,
handle: initialization.cellHandle,
// If the content has not changed, we still want to make sure the
@ -1386,7 +893,7 @@ var requirejs = (function() {
if (cellsToHide.length) {
this._sendMessageToWebview({
type: 'hideMarkdownPreviews',
type: 'hideMarkupCells',
ids: cellsToHide
});
}
@ -1411,7 +918,7 @@ var requirejs = (function() {
}
this._sendMessageToWebview({
type: 'unhideMarkdownPreviews',
type: 'unhideMarkupCells',
ids: toUnhide,
});
}
@ -1430,7 +937,7 @@ var requirejs = (function() {
if (cellIds.length) {
this._sendMessageToWebview({
type: 'deleteMarkdownPreview',
type: 'deleteMarkupCell',
ids: cellIds
});
}
@ -1442,12 +949,12 @@ var requirejs = (function() {
}
this._sendMessageToWebview({
type: 'updateSelectedMarkdownPreviews',
type: 'updateSelectedMarkupCells',
selectedCellIds: selectedCellsIds.filter(id => this.markdownPreviewMapping.has(id)),
});
}
async initializeMarkdown(cells: ReadonlyArray<IMarkdownCellInitialization>) {
async initializeMarkup(cells: readonly IMarkupCellInitialization[]) {
if (this._disposed) {
return;
}
@ -1455,7 +962,7 @@ var requirejs = (function() {
// TODO: use proper handler
const p = new Promise<void>(resolve => {
this.webview?.onMessage(e => {
if (e.message.type === 'initializedMarkdownPreview') {
if (e.message.type === 'initializedMarkup') {
resolve();
}
});
@ -1466,7 +973,7 @@ var requirejs = (function() {
}
this._sendMessageToWebview({
type: 'initializeMarkdownPreview',
type: 'initializeMarkup',
cells,
});

View file

@ -0,0 +1,366 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type { RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import type { PreloadOptions } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads';
interface BaseToWebviewMessage {
readonly __vscode_notebook_message: true;
}
export interface WebviewIntialized extends BaseToWebviewMessage {
readonly type: 'initialized';
}
export interface DimensionUpdate {
readonly id: string;
readonly init?: boolean;
readonly height: number;
readonly isOutput?: boolean;
}
export interface IDimensionMessage extends BaseToWebviewMessage {
readonly type: 'dimension';
readonly updates: readonly DimensionUpdate[];
}
export interface IMouseEnterMessage extends BaseToWebviewMessage {
readonly type: 'mouseenter';
readonly id: string;
}
export interface IMouseLeaveMessage extends BaseToWebviewMessage {
readonly type: 'mouseleave';
readonly id: string;
}
export interface IOutputFocusMessage extends BaseToWebviewMessage {
readonly type: 'outputFocus';
readonly id: string;
}
export interface IOutputBlurMessage extends BaseToWebviewMessage {
readonly type: 'outputBlur';
readonly id: string;
}
export interface IWheelMessage extends BaseToWebviewMessage {
readonly type: 'did-scroll-wheel';
readonly payload: any;
}
export interface IScrollAckMessage extends BaseToWebviewMessage {
readonly type: 'scroll-ack';
readonly data: { top: number; };
readonly version: number;
}
export interface IBlurOutputMessage extends BaseToWebviewMessage {
readonly type: 'focus-editor';
readonly id: string;
readonly focusNext?: boolean;
}
export interface IClickedDataUrlMessage extends BaseToWebviewMessage {
readonly type: 'clicked-data-url';
readonly data: string | ArrayBuffer | null;
readonly downloadName?: string;
}
export interface IClickMarkupCellMessage extends BaseToWebviewMessage {
readonly type: 'clickMarkupCell';
readonly cellId: string;
readonly ctrlKey: boolean;
readonly altKey: boolean;
readonly metaKey: boolean;
readonly shiftKey: boolean;
}
export interface IContextMenuMarkupCellMessage extends BaseToWebviewMessage {
readonly type: 'contextMenuMarkupCell';
readonly cellId: string;
readonly clientX: number;
readonly clientY: number;
}
export interface IMouseEnterMarkupCellMessage extends BaseToWebviewMessage {
readonly type: 'mouseEnterMarkupCell';
readonly cellId: string;
}
export interface IMouseLeaveMarkupCellMessage extends BaseToWebviewMessage {
readonly type: 'mouseLeaveMarkupCell';
readonly cellId: string;
}
export interface IToggleMarkupPreviewMessage extends BaseToWebviewMessage {
readonly type: 'toggleMarkupPreview';
readonly cellId: string;
}
export interface ICellDragStartMessage extends BaseToWebviewMessage {
readonly type: 'cell-drag-start';
readonly cellId: string;
readonly dragOffsetY: number;
}
export interface ICellDragMessage extends BaseToWebviewMessage {
readonly type: 'cell-drag';
readonly cellId: string;
readonly dragOffsetY: number;
}
export interface ICellDropMessage extends BaseToWebviewMessage {
readonly type: 'cell-drop';
readonly cellId: string;
readonly ctrlKey: boolean;
readonly altKey: boolean;
readonly dragOffsetY: number;
}
export interface ICellDragEndMessage extends BaseToWebviewMessage {
readonly type: 'cell-drag-end';
readonly cellId: string;
}
export interface IInitializedMarkupMessage extends BaseToWebviewMessage {
readonly type: 'initializedMarkup';
}
export interface ITelemetryFoundRenderedMarkdownMath extends BaseToWebviewMessage {
readonly type: 'telemetryFoundRenderedMarkdownMath';
}
export interface ITelemetryFoundUnrenderedMarkdownMath extends BaseToWebviewMessage {
readonly type: 'telemetryFoundUnrenderedMarkdownMath';
readonly latexDirective: string;
}
export interface IClearMessage {
readonly type: 'clear';
}
export interface IOutputRequestMetadata {
/**
* Additional attributes of a cell metadata.
*/
readonly custom?: { [key: string]: unknown; };
}
export interface IOutputRequestDto {
/**
* { mime_type: value }
*/
readonly data: { [key: string]: unknown; };
readonly metadata?: IOutputRequestMetadata;
readonly outputId: string;
}
export interface ICreationRequestMessage {
readonly type: 'html';
readonly content: { type: RenderOutputType.Html; htmlContent: string; } |
{ type: RenderOutputType.Extension; outputId: string; valueBytes: Uint8Array; metadata: unknown; metadata2: unknown; mimeType: string; };
readonly cellId: string;
readonly outputId: string;
cellTop: number;
outputOffset: number;
readonly left: number;
readonly requiredPreloads: ReadonlyArray<IControllerPreload>;
readonly initiallyHidden?: boolean;
readonly rendererId?: string | undefined;
}
export interface IContentWidgetTopRequest {
readonly outputId: string;
readonly cellTop: number;
readonly outputOffset: number;
readonly forceDisplay: boolean;
}
export interface IViewScrollTopRequestMessage {
readonly type: 'view-scroll';
readonly widgets: IContentWidgetTopRequest[];
readonly markdownPreviews: { id: string; top: number; }[];
}
export interface IScrollRequestMessage {
readonly type: 'scroll';
readonly id: string;
readonly top: number;
readonly widgetTop?: number;
readonly version: number;
}
export interface IClearOutputRequestMessage {
readonly type: 'clearOutput';
readonly cellId: string;
readonly outputId: string;
readonly cellUri: string;
readonly rendererId: string | undefined;
}
export interface IHideOutputMessage {
readonly type: 'hideOutput';
readonly outputId: string;
readonly cellId: string;
}
export interface IShowOutputMessage {
readonly type: 'showOutput';
readonly cellId: string;
readonly outputId: string;
readonly cellTop: number;
readonly outputOffset: number;
}
export interface IFocusOutputMessage {
readonly type: 'focus-output';
readonly cellId: string;
}
export interface IAckOutputHeightMessage {
readonly type: 'ack-dimension';
readonly cellId: string;
readonly outputId: string;
readonly height: number;
}
export interface IControllerPreload {
readonly originalUri: string;
readonly uri: string;
}
export interface IUpdateControllerPreloadsMessage {
readonly type: 'preload';
readonly resources: IControllerPreload[];
}
export interface IUpdateDecorationsMessage {
readonly type: 'decorations';
readonly cellId: string;
readonly addedClassNames: string[];
readonly removedClassNames: string[];
}
export interface ICustomKernelMessage extends BaseToWebviewMessage {
readonly type: 'customKernelMessage';
readonly message: unknown;
}
export interface ICustomRendererMessage extends BaseToWebviewMessage {
readonly type: 'customRendererMessage';
readonly rendererId: string;
readonly message: unknown;
}
export interface ICreateMarkupCellMessage {
readonly type: 'createMarkupCell';
readonly cell: IMarkupCellInitialization;
}
export interface IDeleteMarkupCellMessage {
readonly type: 'deleteMarkupCell';
readonly ids: readonly string[];
}
export interface IHideMarkupCellMessage {
readonly type: 'hideMarkupCells';
readonly ids: readonly string[];
}
export interface IUnhideMarkupCellMessage {
readonly type: 'unhideMarkupCells';
readonly ids: readonly string[];
}
export interface IShowMarkupCellMessage {
readonly type: 'showMarkupCell';
readonly id: string;
readonly handle: number;
readonly content: string | undefined;
readonly top: number;
}
export interface IUpdateSelectedMarkupCellsMessage {
readonly type: 'updateSelectedMarkupCells';
readonly selectedCellIds: readonly string[];
}
export interface IMarkupCellInitialization {
mime: string;
cellId: string;
cellHandle: number;
content: string;
offset: number;
visible: boolean;
}
export interface IInitializeMarkupCells {
readonly type: 'initializeMarkup';
readonly cells: ReadonlyArray<IMarkupCellInitialization>;
}
export interface INotebookStylesMessage {
readonly type: 'notebookStyles';
readonly styles: {
[key: string]: string;
};
}
export interface INotebookOptionsMessage {
readonly type: 'notebookOptions';
readonly options: PreloadOptions;
}
export type FromWebviewMessage = WebviewIntialized |
IDimensionMessage |
IMouseEnterMessage |
IMouseLeaveMessage |
IOutputFocusMessage |
IOutputBlurMessage |
IWheelMessage |
IScrollAckMessage |
IBlurOutputMessage |
ICustomKernelMessage |
ICustomRendererMessage |
IClickedDataUrlMessage |
IClickMarkupCellMessage |
IContextMenuMarkupCellMessage |
IMouseEnterMarkupCellMessage |
IMouseLeaveMarkupCellMessage |
IToggleMarkupPreviewMessage |
ICellDragStartMessage |
ICellDragMessage |
ICellDropMessage |
ICellDragEndMessage |
IInitializedMarkupMessage |
ITelemetryFoundRenderedMarkdownMath |
ITelemetryFoundUnrenderedMarkdownMath;
export type ToWebviewMessage = IClearMessage |
IFocusOutputMessage |
IAckOutputHeightMessage |
ICreationRequestMessage |
IViewScrollTopRequestMessage |
IScrollRequestMessage |
IClearOutputRequestMessage |
IHideOutputMessage |
IShowOutputMessage |
IUpdateControllerPreloadsMessage |
IUpdateDecorationsMessage |
ICustomKernelMessage |
ICustomRendererMessage |
ICreateMarkupCellMessage |
IDeleteMarkupCellMessage |
IShowMarkupCellMessage |
IHideMarkupCellMessage |
IUnhideMarkupCellMessage |
IUpdateSelectedMarkupCellsMessage |
IInitializeMarkupCells |
INotebookStylesMessage |
INotebookOptionsMessage;
export type AnyMessage = FromWebviewMessage | ToWebviewMessage;

View file

@ -6,7 +6,7 @@
import type { Event } from 'vs/base/common/event';
import type { IDisposable } from 'vs/base/common/lifecycle';
import { RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import type { FromWebviewMessage, IBlurOutputMessage, ICellDropMessage, ICellDragMessage, ICellDragStartMessage, IClickedDataUrlMessage, IDimensionMessage, IClickMarkdownPreviewMessage, IMouseEnterMarkdownPreviewMessage, IMouseEnterMessage, IMouseLeaveMarkdownPreviewMessage, IMouseLeaveMessage, IToggleMarkdownPreviewMessage, IWheelMessage, ToWebviewMessage, ICellDragEndMessage, IOutputFocusMessage, IOutputBlurMessage, DimensionUpdate, IContextMenuMarkdownPreviewMessage, ITelemetryFoundRenderedMarkdownMath, ITelemetryFoundUnrenderedMarkdownMath, IMarkdownCellInitialization } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView';
import type * as webviewMessages from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages';
// !! IMPORTANT !! everything must be in-line within the webviewPreloads
// function. Imports are not allowed. This is stringified and injected into
@ -71,7 +71,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re
};
const handleDataUrl = async (data: string | ArrayBuffer | null, downloadName: string) => {
postNotebookMessage<IClickedDataUrlMessage>('clicked-data-url', {
postNotebookMessage<webviewMessages.IClickedDataUrlMessage>('clicked-data-url', {
data,
downloadName
});
@ -201,7 +201,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re
});
const dimensionUpdater = new class {
private readonly pending = new Map<string, DimensionUpdate>();
private readonly pending = new Map<string, webviewMessages.DimensionUpdate>();
update(id: string, height: number, options: { init?: boolean; isOutput?: boolean }) {
if (!this.pending.size) {
@ -221,7 +221,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re
return;
}
postNotebookMessage<IDimensionMessage>('dimension', {
postNotebookMessage<webviewMessages.IDimensionMessage>('dimension', {
updates: Array.from(this.pending.values())
});
this.pending.clear();
@ -300,7 +300,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re
if (event.defaultPrevented || scrollWillGoToParent(event)) {
return;
}
postNotebookMessage<IWheelMessage>('did-scroll-wheel', {
postNotebookMessage<webviewMessages.IWheelMessage>('did-scroll-wheel', {
payload: {
deltaMode: event.deltaMode,
deltaX: event.deltaX,
@ -324,7 +324,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re
const element = document.createElement('div');
element.tabIndex = 0;
element.addEventListener('focus', () => {
postNotebookMessage<IBlurOutputMessage>('focus-editor', {
postNotebookMessage<webviewMessages.IBlurOutputMessage>('focus-editor', {
id: outputId,
focusNext
});
@ -335,12 +335,12 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re
function addMouseoverListeners(element: HTMLElement, outputId: string): void {
element.addEventListener('mouseenter', () => {
postNotebookMessage<IMouseEnterMessage>('mouseenter', {
postNotebookMessage<webviewMessages.IMouseEnterMessage>('mouseenter', {
id: outputId,
});
});
element.addEventListener('mouseleave', () => {
postNotebookMessage<IMouseLeaveMessage>('mouseleave', {
postNotebookMessage<webviewMessages.IMouseLeaveMessage>('mouseleave', {
id: outputId,
});
});
@ -376,7 +376,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re
this._loosingFocus = false;
if (!this._hasFocus) {
this._hasFocus = true;
postNotebookMessage<IOutputFocusMessage>('outputFocus', {
postNotebookMessage<webviewMessages.IOutputFocusMessage>('outputFocus', {
id: this._outputId,
});
}
@ -389,7 +389,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re
if (this._loosingFocus) {
this._loosingFocus = false;
this._hasFocus = false;
postNotebookMessage<IOutputBlurMessage>('outputBlur', {
postNotebookMessage<webviewMessages.IOutputBlurMessage>('outputBlur', {
id: this._outputId,
});
}
@ -507,79 +507,45 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re
window.addEventListener('wheel', handleWheel);
window.addEventListener('message', async rawEvent => {
const event = rawEvent as ({ data: ToWebviewMessage; });
const event = rawEvent as ({ data: webviewMessages.ToWebviewMessage; });
switch (event.data.type) {
case 'initializeMarkdownPreview':
{
await ensureMarkdownPreviewCells(event.data.cells);
dimensionUpdater.updateImmediately();
postNotebookMessage('initializedMarkdownPreview', {});
}
case 'initializeMarkup':
await notebookDocument.ensureMarkupCells(event.data.cells);
dimensionUpdater.updateImmediately();
postNotebookMessage('initializedMarkup', {});
break;
case 'createMarkdownPreview':
ensureMarkdownPreviewCells([event.data.cell]);
break;
case 'showMarkdownPreview':
{
const data = event.data;
const cellContainer = document.getElementById(data.id);
if (cellContainer) {
cellContainer.style.visibility = 'visible';
cellContainer.style.top = `${data.top}px`;
updateMarkdownPreview(cellContainer, data.id, data.content);
}
}
case 'createMarkupCell':
notebookDocument.ensureMarkupCells([event.data.cell]);
break;
case 'hideMarkdownPreviews':
{
for (const id of event.data.ids) {
const cellContainer = document.getElementById(id);
if (cellContainer) {
cellContainer.style.visibility = 'hidden';
}
}
}
break;
case 'unhideMarkdownPreviews':
{
for (const id of event.data.ids) {
const cellContainer = document.getElementById(id);
if (cellContainer) {
cellContainer.style.visibility = 'visible';
updateMarkdownPreview(cellContainer, id, undefined);
}
}
}
break;
case 'deleteMarkdownPreview':
{
for (const id of event.data.ids) {
const cellContainer = document.getElementById(id);
cellContainer?.remove();
}
}
break;
case 'updateSelectedMarkdownPreviews':
{
const selectedCellIds = new Set<string>(event.data.selectedCellIds);
for (const oldSelected of document.querySelectorAll('.preview.selected')) {
const id = oldSelected.id;
if (!selectedCellIds.has(id)) {
oldSelected.classList.remove('selected');
}
}
case 'showMarkupCell':
notebookDocument.showMarkupCell(event.data.id, event.data.top, event.data.content);
break;
for (const newSelected of selectedCellIds) {
const previewContainer = document.getElementById(newSelected);
if (previewContainer) {
previewContainer.classList.add('selected');
}
}
case 'hideMarkupCells':
for (const id of event.data.ids) {
notebookDocument.hideMarkupCell(id);
}
break;
case 'unhideMarkupCells':
for (const id of event.data.ids) {
notebookDocument.unhideMarkupCell(id);
}
break;
case 'deleteMarkupCell':
for (const id of event.data.ids) {
notebookDocument.deleteMarkupCell(id);
}
break;
case 'updateSelectedMarkupCells':
notebookDocument.updateSelectedCells(event.data.selectedCellIds);
break;
case 'html': {
const data = event.data;
outputs.enqueue(event.data.outputId, async (state) => {
@ -829,10 +795,9 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re
// Update markdown previews
for (const markdownContainer of document.querySelectorAll('.preview')) {
setMarkdownContainerDraggable(markdownContainer, currentOptions.dragAndDropEnabled);
setMarkupContainerDraggable(markdownContainer, currentOptions.dragAndDropEnabled);
}
break;
}
});
@ -1021,47 +986,254 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re
this._renderers.get(rendererId)?.api?.disposeOutputItem?.(outputId);
}
public async renderCustom(rendererId: string, info: IOutputItem, element: HTMLElement) {
const api = await this.load(rendererId);
if (!api) {
throw new Error(`renderer ${rendererId} did not return an API`);
}
public async render(info: IOutputItem, element: HTMLElement) {
const renderers = Array.from(this._renderers.values())
.filter(renderer => renderer.data.mimeTypes.includes(info.mime) && !renderer.data.extends);
api.renderOutputItem(info, element);
}
public async renderMarkdown(id: string, element: HTMLElement, content: string): Promise<void> {
const markdownRenderers = Array.from(this._renderers.values())
.filter(renderer => renderer.data.mimeTypes.includes('text/markdown') && !renderer.data.extends);
if (!markdownRenderers.length) {
if (!renderers.length) {
throw new Error('Could not find renderer');
}
await Promise.all(markdownRenderers.map(x => x.load()));
await Promise.all(renderers.map(x => x.load()));
markdownRenderers[0].api?.renderOutputItem({
id,
element,
mime: 'text/markdown',
metadata: undefined,
metadata2: undefined,
outputId: undefined,
text() { return content; },
json() { return undefined; },
bytes() { return this.data(); },
data() { return new TextEncoder().encode(content); },
blob() { return new Blob([this.data()], { type: this.mime }); },
}, element);
renderers[0].api?.renderOutputItem(info, element);
}
}();
let hasPostedRenderedMathTelemetry = false;
const unsupportedKatexTermsRegex = /(\\(?:abovewithdelims|array|Arrowvert|arrowvert|atopwithdelims|bbox|bracevert|buildrel|cancelto|cases|class|cssId|ddddot|dddot|DeclareMathOperator|definecolor|displaylines|enclose|eqalign|eqalignno|eqref|hfil|hfill|idotsint|iiiint|label|leftarrowtail|leftroot|leqalignno|lower|mathtip|matrix|mbox|mit|mmlToken|moveleft|moveright|mspace|newenvironment|Newextarrow|notag|oldstyle|overparen|overwithdelims|pmatrix|raise|ref|renewenvironment|require|root|Rule|scr|shoveleft|shoveright|sideset|skew|Space|strut|style|texttip|Tiny|toggle|underparen|unicode|uproot)\b)/gi;
const notebookDocument = new class {
private readonly _markupCells = new Map<string, MarkupCell>();
private async createMarkupCell(init: webviewMessages.IMarkupCellInitialization, top: number): Promise<MarkupCell> {
const existing = this._markupCells.get(init.cellId);
if (existing) {
console.error(`Trying to create markup that already exists: ${init.cellId}`);
return existing;
}
const markdownCell = new MarkupCell(init.cellId, init.mime, init.content, top);
this._markupCells.set(init.cellId, markdownCell);
await markdownCell.ready;
return markdownCell;
}
public async ensureMarkupCells(update: readonly webviewMessages.IMarkupCellInitialization[]): Promise<void> {
await Promise.all(update.map(async info => {
let cell = this._markupCells.get(info.cellId);
if (cell) {
await cell.updateContentAndRender(info.content);
} else {
cell = await this.createMarkupCell(info, info.offset);
}
cell.element.style.visibility = info.visible ? 'visible' : 'hidden';
}));
}
public deleteMarkupCell(id: string) {
const cell = this.getExpectedMarkupCell(id);
if (cell) {
cell.element.remove();
this._markupCells.delete(id);
}
}
public async updateMarkupContent(id: string, newContent: string): Promise<void> {
const cell = this.getExpectedMarkupCell(id);
await cell?.updateContentAndRender(newContent);
}
public showMarkupCell(id: string, top: number, newContent: string | undefined): void {
const cell = this.getExpectedMarkupCell(id);
cell?.show(id, top, newContent);
}
public hideMarkupCell(id: string): void {
const cell = this.getExpectedMarkupCell(id);
cell?.hide();
}
public unhideMarkupCell(id: string): void {
const cell = this.getExpectedMarkupCell(id);
cell?.unhide();
}
private getExpectedMarkupCell(id: string): MarkupCell | undefined {
const cell = this._markupCells.get(id);
if (!cell) {
console.log(`Could not find markup cell '${id}'`);
return undefined;
}
return cell;
}
public updateSelectedCells(selectedCellIds: readonly string[]) {
const selectedCellSet = new Set<string>(selectedCellIds);
for (const cell of this._markupCells.values()) {
cell.setSelected(selectedCellSet.has(cell.id));
}
}
}();
class MarkupCell implements IOutputItem {
public readonly ready: Promise<void>;
/// Internal field that holds markdown text
private _content: string;
constructor(id: string, mime: string, content: string, top: number) {
this.id = id;
this.mime = mime;
this._content = content;
let resolveReady: () => void;
this.ready = new Promise<void>(r => resolveReady = r);
const root = document.getElementById('container')!;
this.element = document.createElement('div');
this.element.id = this.id;
this.element.classList.add('preview');
this.element.style.position = 'absolute';
this.element.style.top = top + 'px';
root.appendChild(this.element);
this.addEventListeners();
this.updateContentAndRender(this._content).then(() => {
resizeObserver.observe(this.element, this.id, false);
resolveReady();
});
}
//#region IOutputItem
public readonly id: string;
public readonly mime;
public readonly element: HTMLElement;
// deprecated fields
public readonly metadata = undefined;
public readonly metadata2 = undefined;
public readonly outputId?: string | undefined;
text() { return this._content; }
json() { return undefined; }
bytes() { return this.data(); }
data() { return new TextEncoder().encode(this._content); }
blob() { return new Blob([this.data()], { type: this.mime }); }
//#endregion
private addEventListeners() {
this.element.addEventListener('dblclick', () => {
postNotebookMessage<webviewMessages.IToggleMarkupPreviewMessage>('toggleMarkupPreview', { cellId: this.id });
});
this.element.addEventListener('click', e => {
postNotebookMessage<webviewMessages.IClickMarkupCellMessage>('clickMarkupCell', {
cellId: this.id,
altKey: e.altKey,
ctrlKey: e.ctrlKey,
metaKey: e.metaKey,
shiftKey: e.shiftKey,
});
});
this.element.addEventListener('contextmenu', e => {
postNotebookMessage<webviewMessages.IContextMenuMarkupCellMessage>('contextMenuMarkupCell', {
cellId: this.id,
clientX: e.clientX,
clientY: e.clientY,
});
});
this.element.addEventListener('mouseenter', () => {
postNotebookMessage<webviewMessages.IMouseEnterMarkupCellMessage>('mouseEnterMarkupCell', { cellId: this.id });
});
this.element.addEventListener('mouseleave', () => {
postNotebookMessage<webviewMessages.IMouseLeaveMarkupCellMessage>('mouseLeaveMarkupCell', { cellId: this.id });
});
setMarkupContainerDraggable(this.element, currentOptions.dragAndDropEnabled);
this.element.addEventListener('dragstart', e => {
markdownPreviewDragManager.startDrag(e, this.id);
});
this.element.addEventListener('drag', e => {
markdownPreviewDragManager.updateDrag(e, this.id);
});
this.element.addEventListener('dragend', e => {
markdownPreviewDragManager.endDrag(e, this.id);
});
}
public async updateContentAndRender(newContent: string): Promise<void> {
this._content = newContent;
await renderers.render(this, this.element);
if (!hasPostedRenderedMathTelemetry) {
const hasRenderedMath = this.element.querySelector('.katex');
if (hasRenderedMath) {
hasPostedRenderedMathTelemetry = true;
postNotebookMessage<webviewMessages.ITelemetryFoundRenderedMarkdownMath>('telemetryFoundRenderedMarkdownMath', {});
}
}
const matches = this.element.innerText.match(unsupportedKatexTermsRegex);
if (matches) {
postNotebookMessage<webviewMessages.ITelemetryFoundUnrenderedMarkdownMath>('telemetryFoundUnrenderedMarkdownMath', {
latexDirective: matches[0],
});
}
dimensionUpdater.update(this.id, this.element.clientHeight, {
isOutput: false
});
}
public show(id: string, top: number, newContent: string | undefined): void {
this.element.style.visibility = 'visible';
this.element.style.top = `${top}px`;
if (typeof newContent === 'string') {
this.updateContentAndRender(newContent);
} else {
this.updateMarkupDimensions();
}
}
public hide() {
this.element.style.visibility = 'hidden';
}
public unhide() {
this.element.style.visibility = 'visible';
this.updateMarkupDimensions();
}
private async updateMarkupDimensions() {
dimensionUpdater.update(this.id, this.element.clientHeight, {
isOutput: false
});
}
public setSelected(selected: boolean) {
this.element.classList.toggle('selected', selected);
}
}
vscode.postMessage({
__vscode_notebook_message: true,
type: 'initialized'
});
function setMarkdownContainerDraggable(element: Element, isDraggable: boolean) {
function setMarkupContainerDraggable(element: Element, isDraggable: boolean) {
if (isDraggable) {
element.classList.add('draggable');
element.setAttribute('draggable', 'true');
@ -1071,102 +1243,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re
}
}
async function createMarkdownPreview(cellId: string, content: string, top: number): Promise<HTMLElement> {
const container = document.getElementById('container')!;
const cellContainer = document.createElement('div');
const existing = document.getElementById(cellId);
if (existing) {
console.error(`Trying to create markdown preview that already exists: ${cellId}`);
return existing;
}
cellContainer.id = cellId;
cellContainer.classList.add('preview');
cellContainer.style.position = 'absolute';
cellContainer.style.top = top + 'px';
container.appendChild(cellContainer);
cellContainer.addEventListener('dblclick', () => {
postNotebookMessage<IToggleMarkdownPreviewMessage>('toggleMarkdownPreview', { cellId });
});
cellContainer.addEventListener('click', e => {
postNotebookMessage<IClickMarkdownPreviewMessage>('clickMarkdownPreview', {
cellId,
altKey: e.altKey,
ctrlKey: e.ctrlKey,
metaKey: e.metaKey,
shiftKey: e.shiftKey,
});
});
cellContainer.addEventListener('contextmenu', e => {
postNotebookMessage<IContextMenuMarkdownPreviewMessage>('contextMenuMarkdownPreview', {
cellId,
clientX: e.clientX,
clientY: e.clientY,
});
});
cellContainer.addEventListener('mouseenter', () => {
postNotebookMessage<IMouseEnterMarkdownPreviewMessage>('mouseEnterMarkdownPreview', { cellId });
});
cellContainer.addEventListener('mouseleave', () => {
postNotebookMessage<IMouseLeaveMarkdownPreviewMessage>('mouseLeaveMarkdownPreview', { cellId });
});
setMarkdownContainerDraggable(cellContainer, currentOptions.dragAndDropEnabled);
cellContainer.addEventListener('dragstart', e => {
markdownPreviewDragManager.startDrag(e, cellId);
});
cellContainer.addEventListener('drag', e => {
markdownPreviewDragManager.updateDrag(e, cellId);
});
cellContainer.addEventListener('dragend', e => {
markdownPreviewDragManager.endDrag(e, cellId);
});
const previewRoot = cellContainer.attachShadow({ mode: 'open' });
// Add default webview style
const defaultStyles = document.getElementById('_defaultStyles') as HTMLStyleElement;
previewRoot.appendChild(defaultStyles.cloneNode(true));
// Add default preview style
const previewStyles = document.getElementById('preview-styles') as HTMLTemplateElement;
previewRoot.appendChild(previewStyles.content.cloneNode(true));
const previewNode = document.createElement('div');
previewNode.id = 'preview';
previewRoot.appendChild(previewNode);
await updateMarkdownPreview(cellContainer, cellId, content);
resizeObserver.observe(cellContainer, cellId, false);
return cellContainer;
}
async function ensureMarkdownPreviewCells(update: readonly IMarkdownCellInitialization[]): Promise<void> {
await Promise.all(update.map(async cell => {
let container = document.getElementById(cell.cellId);
if (container) {
await updateMarkdownPreview(container, cell.cellId, cell.content);
} else {
container = await createMarkdownPreview(cell.cellId, cell.content, cell.offset);
}
container.style.visibility = cell.visible ? 'visible' : 'hidden';
}));
}
function postNotebookMessage<T extends FromWebviewMessage>(
function postNotebookMessage<T extends webviewMessages.FromWebviewMessage>(
type: T['type'],
properties: Omit<T, '__vscode_notebook_message' | 'type'>
) {
@ -1177,46 +1254,6 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re
});
}
let hasPostedRenderedMathTelemetry = false;
const unsupportedKatexTermsRegex = /(\\(?:abovewithdelims|array|Arrowvert|arrowvert|atopwithdelims|bbox|bracevert|buildrel|cancelto|cases|class|cssId|ddddot|dddot|DeclareMathOperator|definecolor|displaylines|enclose|eqalign|eqalignno|eqref|hfil|hfill|idotsint|iiiint|label|leftarrowtail|leftroot|leqalignno|lower|mathtip|matrix|mbox|mit|mmlToken|moveleft|moveright|mspace|newenvironment|Newextarrow|notag|oldstyle|overparen|overwithdelims|pmatrix|raise|ref|renewenvironment|require|root|Rule|scr|shoveleft|shoveright|sideset|skew|Space|strut|style|texttip|Tiny|toggle|underparen|unicode|uproot)\b)/gi;
async function updateMarkdownPreview(previewContainerNode: HTMLElement, cellId: string, content: string | undefined) {
const previewRoot = previewContainerNode.shadowRoot;
const previewNode = previewRoot?.getElementById('preview');
if (!previewNode) {
return;
}
if (typeof content === 'string') {
if (content.trim().length === 0) {
previewContainerNode.classList.add('emptyMarkdownCell');
previewNode.innerText = '';
} else {
previewContainerNode.classList.remove('emptyMarkdownCell');
await renderers.renderMarkdown(cellId, previewNode, content);
if (!hasPostedRenderedMathTelemetry) {
const hasRenderedMath = previewNode.querySelector('.katex');
if (hasRenderedMath) {
hasPostedRenderedMathTelemetry = true;
postNotebookMessage<ITelemetryFoundRenderedMarkdownMath>('telemetryFoundRenderedMarkdownMath', {});
}
}
const matches = previewNode.innerText.match(unsupportedKatexTermsRegex);
if (matches) {
postNotebookMessage<ITelemetryFoundUnrenderedMarkdownMath>('telemetryFoundUnrenderedMarkdownMath', {
latexDirective: matches[0],
});
}
}
}
dimensionUpdater.update(cellId, previewContainerNode.clientHeight, {
isOutput: false
});
}
const markdownPreviewDragManager = new class MarkdownPreviewDragManager {
private currentDrag: { cellId: string, clientY: number } | undefined;
@ -1236,7 +1273,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re
}
this.currentDrag = undefined;
postNotebookMessage<ICellDropMessage>('cell-drop', {
postNotebookMessage<webviewMessages.ICellDropMessage>('cell-drop', {
cellId: drag.cellId,
ctrlKey: e.ctrlKey,
altKey: e.altKey,
@ -1258,7 +1295,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re
(e.target as HTMLElement).classList.add('dragging');
postNotebookMessage<ICellDragStartMessage>('cell-drag-start', {
postNotebookMessage<webviewMessages.ICellDragStartMessage>('cell-drag-start', {
cellId: cellId,
dragOffsetY: e.clientY,
});
@ -1270,7 +1307,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re
return;
}
postNotebookMessage<ICellDragMessage>('cell-drag', {
postNotebookMessage<webviewMessages.ICellDragMessage>('cell-drag', {
cellId: cellId,
dragOffsetY: this.currentDrag.clientY,
});
@ -1289,7 +1326,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re
endDrag(e: DragEvent, cellId: string) {
this.currentDrag = undefined;
(e.target as HTMLElement).classList.remove('dragging');
postNotebookMessage<ICellDragEndMessage>('cell-drag-end', {
postNotebookMessage<webviewMessages.ICellDragEndMessage>('cell-drag-end', {
cellId: cellId
});
}

View file

@ -19,19 +19,24 @@ import { BaseCellViewModel } from './baseCellViewModel';
export class CodeCellViewModel extends BaseCellViewModel implements ICellViewModel {
readonly cellKind = CellKind.Code;
protected readonly _onDidChangeOutputs = new Emitter<NotebookCellOutputsSplice[]>();
protected readonly _onDidChangeOutputs = this._register(new Emitter<NotebookCellOutputsSplice[]>());
readonly onDidChangeOutputs = this._onDidChangeOutputs.event;
private readonly _onDidRemoveOutputs = new Emitter<readonly ICellOutputViewModel[]>();
private readonly _onDidRemoveOutputs = this._register(new Emitter<readonly ICellOutputViewModel[]>());
readonly onDidRemoveOutputs = this._onDidRemoveOutputs.event;
private readonly _onDidHideInput = new Emitter<void>();
private readonly _onDidHideInput = this._register(new Emitter<void>());
readonly onDidHideInput = this._onDidHideInput.event;
private readonly _onDidHideOutputs = new Emitter<readonly ICellOutputViewModel[]>();
private readonly _onDidHideOutputs = this._register(new Emitter<readonly ICellOutputViewModel[]>());
readonly onDidHideOutputs = this._onDidHideOutputs.event;
private _outputCollection: number[] = [];
private _outputsTop: PrefixSumComputer | null = null;
protected readonly _onDidChangeLayout = new Emitter<CodeCellLayoutChangeEvent>();
protected readonly _onDidChangeLayout = this._register(new Emitter<CodeCellLayoutChangeEvent>());
readonly onDidChangeLayout = this._onDidChangeLayout.event;
private _editorHeight = 0;

View file

@ -17,6 +17,8 @@ import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/mode
import { CellKind, INotebookSearchOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { dirname } from 'vs/base/common/resources';
export class MarkdownCellViewModel extends BaseCellViewModel implements ICellViewModel {
readonly cellKind = CellKind.Markup;
@ -97,17 +99,22 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie
private readonly _onDidHideInput = new Emitter<void>();
readonly onDidHideInput = this._onDidHideInput.event;
private readonly _mdRenderer: MarkdownRenderer;
constructor(
viewType: string,
model: NotebookCellTextModel,
initialNotebookLayoutInfo: NotebookLayoutInfo | null,
readonly foldingDelegate: EditorFoldingStateDelegate,
readonly viewContext: ViewContext,
private readonly _mdRenderer: MarkdownRenderer,
@IConfigurationService configurationService: IConfigurationService,
@ITextModelService textModelService: ITextModelService,
@IInstantiationService instantiationService: IInstantiationService,
) {
super(viewType, model, UUID.generateUuid(), viewContext, configurationService, textModelService);
this._mdRenderer = this._register(instantiationService.createInstance(MarkdownRenderer, { baseUrl: dirname(model.uri) }));
const { bottomToolbarGap } = this.viewContext.notebookOptions.computeBottomToolbarDimensions(this.viewType);
this._layoutInfo = {

View file

@ -8,10 +8,8 @@ import { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { clamp } from 'vs/base/common/numbers';
import { dirname } from 'vs/base/common/resources';
import * as strings from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer';
import { IBulkEditService, ResourceEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
import { IPosition, Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
@ -1188,7 +1186,6 @@ export function createCellViewModel(instantiationService: IInstantiationService,
if (cell.cellKind === CellKind.Code) {
return instantiationService.createInstance(CodeCellViewModel, notebookViewModel.viewType, cell, notebookViewModel.layoutInfo, notebookViewModel.viewContext);
} else {
const mdRenderer = instantiationService.createInstance(MarkdownRenderer, { baseUrl: dirname(notebookViewModel.uri) });
return instantiationService.createInstance(MarkdownCellViewModel, notebookViewModel.viewType, cell, notebookViewModel.layoutInfo, notebookViewModel, notebookViewModel.viewContext, mdRenderer);
return instantiationService.createInstance(MarkdownCellViewModel, notebookViewModel.viewType, cell, notebookViewModel.layoutInfo, notebookViewModel, notebookViewModel.viewContext);
}
}

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { renderStringAsPlaintext } from 'vs/base/browser/markdownRenderer';
import { Action, IAction, Separator } from 'vs/base/common/actions';
import { Action, IAction, Separator, SubmenuAction } from 'vs/base/common/actions';
import { Event } from 'vs/base/common/event';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { Disposable, dispose, IDisposable, IReference, MutableDisposable } from 'vs/base/common/lifecycle';
@ -19,6 +19,7 @@ import { IModelDeltaDecoration, OverviewRulerLane, TrackedRangeStickiness } from
import { overviewRulerError, overviewRulerInfo, overviewRulerWarning } from 'vs/editor/common/view/editorColorRegistry';
import { localize } from 'vs/nls';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IThemeService, themeColorFromId, ThemeIcon } from 'vs/platform/theme/common/themeService';
@ -28,7 +29,10 @@ import { BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution } from
import { testingRunAllIcon, testingRunIcon, testingStatesToIcons } from 'vs/workbench/contrib/testing/browser/icons';
import { TestingOutputPeekController } from 'vs/workbench/contrib/testing/browser/testingOutputPeek';
import { testMessageSeverityColors } from 'vs/workbench/contrib/testing/browser/theme';
import { IncrementalTestCollectionItem, IRichLocation, ITestMessage, TestDiffOpType, TestResultItem } from 'vs/workbench/contrib/testing/common/testCollection';
import { DefaultGutterClickAction, getTestingConfiguration, TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration';
import { labelForTestInState } from 'vs/workbench/contrib/testing/common/constants';
import { IncrementalTestCollectionItem, InternalTestItem, IRichLocation, ITestMessage, TestDiffOpType, TestResultItem } from 'vs/workbench/contrib/testing/common/testCollection';
import { maxPriority } from 'vs/workbench/contrib/testing/common/testingStates';
import { buildTestUri, TestUriType } from 'vs/workbench/contrib/testing/common/testingUri';
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
import { IMainThreadTestCollection, ITestService } from 'vs/workbench/contrib/testing/common/testService';
@ -173,8 +177,15 @@ export class TestingDecorations extends Disposable implements IEditorContributio
for (const test of ref.object.all) {
const stateLookup = this.results.getStateById(test.item.extId);
if (test.item.range) {
newDecorations.push(this.instantiationService.createInstance(
RunTestDecoration, test, ref.object, test.item.range, this.editor, stateLookup?.[1]));
const line = test.item.range.startLineNumber;
const resultItem = stateLookup?.[1];
const existing = newDecorations.findIndex(d => d instanceof RunTestDecoration && d.line === line);
if (existing !== -1) {
newDecorations[existing] = (newDecorations[existing] as RunTestDecoration).merge(test, ref.object, resultItem);
} else {
newDecorations.push(this.instantiationService.createInstance(
RunSingleTestDecoration, test, ref.object, this.editor, stateLookup?.[1]));
}
}
if (!stateLookup) {
@ -249,116 +260,179 @@ const firstLineRange = (originalRange: IRange) => ({
endColumn: 1,
});
class RunTestDecoration extends Disposable implements ITestDecoration {
/**
* @inheritdoc
*/
id = '';
/**
* @inheritdoc
*/
public readonly editorDecoration: IModelDeltaDecoration;
private line: number;
constructor(
private readonly test: IncrementalTestCollectionItem,
private readonly collection: IMainThreadTestCollection,
range: IRange,
private readonly editor: ICodeEditor,
stateItem: TestResultItem | undefined,
@ITestService private readonly testService: ITestService,
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@ICommandService private readonly commandService: ICommandService,
) {
super();
this.line = range.startLineNumber;
const icon = stateItem?.computedState !== undefined && stateItem.computedState !== TestResultState.Unset
? testingStatesToIcons.get(stateItem.computedState)!
: test.children.size > 0 ? testingRunAllIcon : testingRunIcon;
const hoverMessage = new MarkdownString('', true).appendText(localize('failedHoverMessage', '{0} has failed. ', test.item.label));
if (stateItem?.tasks.some(s => s.messages.length > 0)) {
const args = encodeURIComponent(JSON.stringify([test.item.extId]));
hoverMessage.appendMarkdown(`[${localize('failedPeekAction', 'Peek Error')}](command:vscode.peekTestError?${args})`);
}
let glyphMarginClassName = ThemeIcon.asClassName(icon) + ' testing-run-glyph';
if (stateItem?.retired) {
glyphMarginClassName += ' retired';
}
this.editorDecoration = {
range: firstLineRange(range),
options: {
description: 'run-test-decoration',
isWholeLine: true,
hoverMessage,
glyphMarginClassName,
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
glyphMarginHoverMessage: new MarkdownString().appendText(localize('testing.clickToRun', 'Click to run tests, right click for more options')),
}
};
const createRunTestDecoration = (tests: readonly IncrementalTestCollectionItem[], states: readonly (TestResultItem | undefined)[]): IModelDeltaDecoration => {
const range = tests[0]?.item.range;
if (!range) {
throw new Error('Test decorations can only be created for tests with a range');
}
/**
* @inheritdoc
*/
let computedState = TestResultState.Unset;
let hoverMessageParts: string[] = [];
let testIdWithMessages: string | undefined;
let retired = false;
for (let i = 0; i < tests.length; i++) {
const test = tests[i];
const resultItem = states[i];
const state = resultItem?.computedState ?? TestResultState.Unset;
hoverMessageParts.push(labelForTestInState(test.item.label, state));
computedState = maxPriority(computedState, state);
retired = retired || !!resultItem?.retired;
if (!testIdWithMessages && resultItem?.tasks.some(t => t.messages.length)) {
testIdWithMessages = test.item.extId;
}
}
const hasMultipleTests = tests.length > 1 || tests[0].children.size > 0;
const icon = computedState === TestResultState.Unset
? (hasMultipleTests ? testingRunAllIcon : testingRunIcon)
: testingStatesToIcons.get(computedState)!;
const hoverMessage = new MarkdownString('', true).appendText(hoverMessageParts.join(', ') + '.');
if (testIdWithMessages) {
const args = encodeURIComponent(JSON.stringify([testIdWithMessages]));
hoverMessage.appendMarkdown(`[${localize('peekTestOutout', 'Peek Test Output')}](command:vscode.peekTestError?${args})`);
}
let glyphMarginClassName = ThemeIcon.asClassName(icon) + ' testing-run-glyph';
if (retired) {
glyphMarginClassName += ' retired';
}
return {
range: firstLineRange(range),
options: {
description: 'run-test-decoration',
isWholeLine: true,
hoverMessage,
glyphMarginClassName,
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
}
};
};
abstract class RunTestDecoration extends Disposable {
/** @inheritdoc */
public id = '';
public get line() {
return this.editorDecoration.range.startLineNumber;
}
constructor(
public editorDecoration: IModelDeltaDecoration,
protected readonly editor: ICodeEditor,
@ITestService protected readonly testService: ITestService,
@IContextMenuService protected readonly contextMenuService: IContextMenuService,
@ICommandService protected readonly commandService: ICommandService,
@IConfigurationService protected readonly configurationService: IConfigurationService,
) {
super();
editorDecoration.options.glyphMarginHoverMessage = new MarkdownString().appendText(this.getGutterLabel());
}
/** @inheritdoc */
public click(e: IEditorMouseEvent): boolean {
if (e.target.position?.lineNumber !== this.line || e.target.type !== MouseTargetType.GUTTER_GLYPH_MARGIN) {
return false;
}
if (e.event.rightButton) {
const actions = this.getContextMenu();
this.contextMenuService.showContextMenu({
getAnchor: () => ({ x: e.event.posx, y: e.event.posy }),
getActions: () => actions,
onHide: () => dispose(actions),
});
} else {
// todo: customize click behavior
this.testService.runTests({
tests: [{ testId: this.test.item.extId, src: this.test.src }],
debug: false,
});
this.showContextMenu(e);
return true;
}
switch (getTestingConfiguration(this.configurationService, TestingConfigKeys.DefaultGutterClickAction)) {
case DefaultGutterClickAction.ContextMenu:
this.showContextMenu(e);
break;
case DefaultGutterClickAction.Debug:
this.defaultDebug();
break;
case DefaultGutterClickAction.Run:
default:
this.defaultRun();
break;
}
return true;
}
public override dispose() {
// no-op
/**
* Adds the test to this decoration.
*/
public abstract merge(other: IncrementalTestCollectionItem, collection: IMainThreadTestCollection, resultItem: TestResultItem | undefined): RunTestDecoration;
/**
* Called when the decoration is clicked on.
*/
protected abstract getContextMenuActions(e: IEditorMouseEvent): IAction[];
/**
* Default run action.
*/
protected abstract defaultRun(): void;
/**
* Default debug action.
*/
protected abstract defaultDebug(): void;
private showContextMenu(e: IEditorMouseEvent) {
let actions = this.getContextMenuActions(e);
const model = this.editor.getModel();
if (model) {
actions = Separator.join(
actions,
this.editor
.getContribution<IBreakpointEditorContribution>(BREAKPOINT_EDITOR_CONTRIBUTION_ID)
.getContextMenuActionsAtPosition(this.line, model)
);
}
this.contextMenuService.showContextMenu({
getAnchor: () => ({ x: e.event.posx, y: e.event.posy }),
getActions: () => actions,
onHide: () => dispose(actions),
});
}
private getContextMenu() {
const model = this.editor.getModel();
if (!model) {
return [];
private getGutterLabel() {
switch (getTestingConfiguration(this.configurationService, TestingConfigKeys.DefaultGutterClickAction)) {
case DefaultGutterClickAction.ContextMenu:
return localize('testing.gutterMsg.contextMenu', 'Click for test options');
case DefaultGutterClickAction.Debug:
return localize('testing.gutterMsg.debug', 'Click to debug tests, right click for more options');
case DefaultGutterClickAction.Run:
default:
return localize('testing.gutterMsg.run', 'Click to run tests, right click for more options');
}
}
/**
* Gets context menu actions relevant for a singel test.
*/
protected getTestContextMenuActions(collection: IMainThreadTestCollection, test: InternalTestItem) {
const testActions: IAction[] = [];
if (this.test.item.runnable) {
testActions.push(new Action('testing.run', localize('run test', 'Run Test'), undefined, undefined, () => this.testService.runTests({
if (test.item.runnable) {
testActions.push(new Action('testing.gutter.run', localize('run test', 'Run Test'), undefined, undefined, () => this.testService.runTests({
debug: false,
tests: [{ src: this.test.src, testId: this.test.item.extId }],
tests: [{ src: test.src, testId: test.item.extId }],
})));
}
if (this.test.item.debuggable) {
testActions.push(new Action('testing.debug', localize('debug test', 'Debug Test'), undefined, undefined, () => this.testService.runTests({
if (test.item.debuggable) {
testActions.push(new Action('testing.gutter.debug', localize('debug test', 'Debug Test'), undefined, undefined, () => this.testService.runTests({
debug: true,
tests: [{ src: this.test.src, testId: this.test.item.extId }],
tests: [{ src: test.src, testId: test.item.extId }],
})));
}
testActions.push(new Action('testing.reveal', localize('reveal test', 'Reveal in Test Explorer'), undefined, undefined, async () => {
const path = [this.test];
testActions.push(new Action('testing.gutter.reveal', localize('reveal test', 'Reveal in Test Explorer'), undefined, undefined, async () => {
const path = [test];
while (true) {
const parentId = path[0].parent;
const parent = parentId && this.collection.getNodeById(parentId);
const parent = parentId && collection.getNodeById(parentId);
if (!parent) {
break;
}
@ -369,11 +443,112 @@ class RunTestDecoration extends Disposable implements ITestDecoration {
await this.commandService.executeCommand('vscode.revealTestInExplorer', path.map(t => t.item.extId));
}));
const breakpointActions = this.editor
.getContribution<IBreakpointEditorContribution>(BREAKPOINT_EDITOR_CONTRIBUTION_ID)
.getContextMenuActionsAtPosition(this.line, model);
return testActions;
}
}
return breakpointActions.length ? [...testActions, new Separator(), ...breakpointActions] : testActions;
class MultiRunTestDecoration extends RunTestDecoration implements ITestDecoration {
constructor(
private readonly tests: {
test: IncrementalTestCollectionItem,
collection: IMainThreadTestCollection,
resultItem: TestResultItem | undefined,
}[],
editor: ICodeEditor,
@ITestService testService: ITestService,
@ICommandService commandService: ICommandService,
@IContextMenuService contextMenuService: IContextMenuService,
@IConfigurationService configurationService: IConfigurationService,
) {
super(createRunTestDecoration(tests.map(t => t.test), tests.map(t => t.resultItem)), editor, testService, contextMenuService, commandService, configurationService);
}
public override merge(test: IncrementalTestCollectionItem, collection: IMainThreadTestCollection, resultItem: TestResultItem | undefined): RunTestDecoration {
this.tests.push({ collection, test, resultItem });
this.editorDecoration = createRunTestDecoration(this.tests.map(t => t.test), this.tests.map(t => t.resultItem));
return this;
}
protected override getContextMenuActions() {
const allActions: IAction[] = [];
if (this.tests.some(({ test }) => test.item.runnable)) {
allActions.push(new Action('testing.gutter.runAll', localize('run all test', 'Run All Tests'), undefined, undefined, () => this.defaultRun()));
}
if (this.tests.some(({ test }) => test.item.debuggable)) {
allActions.push(new Action('testing.gutter.debugAll', localize('debug all test', 'Debug All Tests'), undefined, undefined, () => this.defaultDebug()));
}
const testSubmenus = this.tests.map(({ collection, test }) =>
new SubmenuAction(test.item.extId, test.item.label, this.getTestContextMenuActions(collection, test)));
return Separator.join(allActions, testSubmenus);
}
protected override defaultRun() {
return this.testService.runTests({
tests: this.tests
.filter(({ test }) => test.item.runnable)
.map(({ test }) => ({ testId: test.item.extId, src: test.src })),
debug: false,
});
}
protected override defaultDebug() {
return this.testService.runTests({
tests: this.tests
.filter(({ test }) => test.item.debuggable)
.map(({ test }) => ({ testId: test.item.extId, src: test.src })),
debug: true,
});
}
}
class RunSingleTestDecoration extends RunTestDecoration implements ITestDecoration {
constructor(
private readonly test: IncrementalTestCollectionItem,
private readonly collection: IMainThreadTestCollection,
editor: ICodeEditor,
private readonly resultItem: TestResultItem | undefined,
@ITestService testService: ITestService,
@ICommandService commandService: ICommandService,
@IContextMenuService contextMenuService: IContextMenuService,
@IConfigurationService configurationService: IConfigurationService,
) {
super(createRunTestDecoration([test], [resultItem]), editor, testService, contextMenuService, commandService, configurationService);
}
public override merge(test: IncrementalTestCollectionItem, collection: IMainThreadTestCollection, resultItem: TestResultItem | undefined): RunTestDecoration {
return new MultiRunTestDecoration([
{ collection: this.collection, test: this.test, resultItem: this.resultItem },
{ collection, test, resultItem },
], this.editor, this.testService, this.commandService, this.contextMenuService, this.configurationService);
}
protected override getContextMenuActions(e: IEditorMouseEvent) {
return this.getTestContextMenuActions(this.collection, this.test);
}
protected override defaultRun() {
if (!this.test.item.runnable) {
return;
}
return this.testService.runTests({
tests: [{ testId: this.test.item.extId, src: this.test.src }],
debug: false,
});
}
protected override defaultDebug() {
if (!this.test.item.debuggable) {
return;
}
return this.testService.runTests({
tests: [{ testId: this.test.item.extId, src: this.test.src }],
debug: true,
});
}
}

View file

@ -51,12 +51,12 @@ import { HierarchicalByNameProjection } from 'vs/workbench/contrib/testing/brows
import { IActionableTestTreeElement, isActionableTestTreeElement, ITestTreeProjection, TestExplorerTreeElement, TestItemTreeElement, TestTreeErrorMessage, TestTreeWorkspaceFolder } from 'vs/workbench/contrib/testing/browser/explorerProjections/index';
import { testingHiddenIcon, testingStatesToIcons } from 'vs/workbench/contrib/testing/browser/icons';
import { ITestExplorerFilterState, TestExplorerFilterState, TestingExplorerFilter } from 'vs/workbench/contrib/testing/browser/testingExplorerFilter';
import { ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener';
import { ITestingProgressUiService } from 'vs/workbench/contrib/testing/browser/testingProgressUiService';
import { getTestingConfiguration, TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration';
import { TestExplorerStateFilter, TestExplorerViewMode, TestExplorerViewSorting, Testing, testStateNames } from 'vs/workbench/contrib/testing/common/constants';
import { labelForTestInState, TestExplorerStateFilter, TestExplorerViewMode, TestExplorerViewSorting, Testing, testStateNames } from 'vs/workbench/contrib/testing/common/constants';
import { TestIdPath, TestItemExpandState } from 'vs/workbench/contrib/testing/common/testCollection';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
import { ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener';
import { cmpPriority, isFailedState, isStateWithResult } from 'vs/workbench/contrib/testing/common/testingStates';
import { getPathForTestInResult, TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult';
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
@ -830,10 +830,7 @@ class TestExplorerActionRunner extends ActionRunner {
}
const getLabelForTestTreeElement = (element: IActionableTestTreeElement) => {
let label = localize({
key: 'testing.treeElementLabel',
comment: ['label then the unit tests state, for example "Addition Tests (Running)"'],
}, '{0} ({1})', element.label, testStateNames[element.state]);
let label = labelForTestInState(element.label, element.state);
if (element instanceof TestItemTreeElement) {
if (element.duration !== undefined) {

View file

@ -64,7 +64,7 @@ import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingC
import { ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener';
import { isFailedState } from 'vs/workbench/contrib/testing/common/testingStates';
import { buildTestUri, ParsedTestUri, parseTestUri, TestUriType } from 'vs/workbench/contrib/testing/common/testingUri';
import { getPathForTestInResult, ITestResult, maxCountPriority, TestResultItemChange, TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult';
import { getPathForTestInResult, ITestResult, maxCountPriority, resultItemParents, TestResultItemChange, TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult';
import { ITestResultService, ResultChangeEvent } from 'vs/workbench/contrib/testing/common/testResultService';
import { getAllTestsInHierarchy, ITestService } from 'vs/workbench/contrib/testing/common/testService';
import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
@ -196,9 +196,11 @@ export class TestingPeekOpener extends Disposable implements ITestingPeekOpener
// don't show the peek if the user asked to only auto-open peeks for visible tests,
// and this test is not in any of the editors' models.
const testUri = evt.item.item.uri?.toString();
if (cfg === AutoOpenPeekViewWhen.FailureVisible && (!testUri || !editors.some(e => e.getModel()?.uri.toString() === testUri))) {
return;
if (cfg === AutoOpenPeekViewWhen.FailureVisible) {
const editorUris = new Set(editors.map(e => e.getModel()?.uri.toString()));
if (!Iterable.some(resultItemParents(evt.result, evt.item), i => i.item.uri && editorUris.has(i.item.uri.toString()))) {
return;
}
}
const controllers = editors.map(TestingOutputPeekController.get);
@ -912,20 +914,12 @@ export class TestCaseElement implements ITreeElement {
private readonly results: ITestResult,
public readonly test: TestResultItem,
) {
for (const parent of this.parents()) {
this.description = this.description
? parent.item.label + flatTestItemDelimiter + this.description
: parent.item.label;
}
}
private *parents() {
for (
let parent = this.test.parent && this.results.getStateById(this.test.parent);
parent;
parent = parent.parent && this.results.getStateById(parent.parent)
) {
yield parent;
for (const parent of resultItemParents(results, test)) {
if (parent !== test) {
this.description = this.description
? parent.item.label + flatTestItemDelimiter + this.description
: parent.item.label;
}
}
}
}

View file

@ -13,6 +13,7 @@ export const enum TestingConfigKeys {
AutoOpenPeekView = 'testing.automaticallyOpenPeekView',
AutoOpenPeekViewDuringAutoRun = 'testing.automaticallyOpenPeekViewDuringAutoRun',
FollowRunningTest = 'testing.followRunningTest',
DefaultGutterClickAction = 'testing.defaultGutterClickAction',
}
export const enum AutoOpenPeekViewWhen {
@ -25,6 +26,12 @@ export const enum AutoRunMode {
OnlyPreviouslyRun = 'rerun',
}
export const enum DefaultGutterClickAction {
Run = 'run',
Debug = 'debug',
ContextMenu = 'contextMenu',
}
export const testingConfiguation: IConfigurationNode = {
id: 'testing',
order: 21,
@ -71,6 +78,20 @@ export const testingConfiguation: IConfigurationNode = {
type: 'boolean',
default: true,
},
[TestingConfigKeys.DefaultGutterClickAction]: {
description: localize('testing.defaultGutterClickAction', 'Controls the action to take when left-clicking on a test decoration in the gutter.'),
enum: [
DefaultGutterClickAction.Run,
DefaultGutterClickAction.Debug,
DefaultGutterClickAction.ContextMenu,
],
enumDescriptions: [
localize('testing.defaultGutterClickAction.run', 'Run the test.'),
localize('testing.defaultGutterClickAction.debug', 'Debug the test.'),
localize('testing.defaultGutterClickAction.contextMenu', 'Open the context menu for more options.'),
],
default: DefaultGutterClickAction.Run,
},
}
};
@ -80,6 +101,7 @@ export interface ITestingConfiguration {
[TestingConfigKeys.AutoOpenPeekView]: AutoOpenPeekViewWhen;
[TestingConfigKeys.AutoOpenPeekViewDuringAutoRun]: boolean;
[TestingConfigKeys.FollowRunningTest]: boolean;
[TestingConfigKeys.DefaultGutterClickAction]: DefaultGutterClickAction;
}
export const getTestingConfiguration = <K extends TestingConfigKeys>(config: IConfigurationService, key: K) => config.getValue<ITestingConfiguration[K]>(key);

View file

@ -38,5 +38,10 @@ export const testStateNames: { [K in TestResultState]: string } = {
[TestResultState.Queued]: localize('testState.queued', 'Queued'),
[TestResultState.Running]: localize('testState.running', 'Running'),
[TestResultState.Skipped]: localize('testState.skipped', 'Skipped'),
[TestResultState.Unset]: localize('testState.unset', 'Unset'),
[TestResultState.Unset]: localize('testState.unset', 'Not yet run'),
};
export const labelForTestInState = (label: string, state: TestResultState) => localize({
key: 'testing.treeElementLabel',
comment: ['label then the unit tests state, for example "Addition Tests (Running)"'],
}, '{0} ({1})', label, testStateNames[state]);

View file

@ -69,19 +69,21 @@ export interface ITestResult {
toJSON(): ISerializedTestResults | undefined;
}
export const getPathForTestInResult = (test: TestResultItem, results: ITestResult): TestIdPath => {
const path = [test];
while (true) {
const parentId = path[0].parent;
const parent = parentId && results.getStateById(parentId);
if (!parent) {
break;
}
export const resultItemParents = function* (results: ITestResult, item: TestResultItem) {
let i: TestResultItem | undefined = item;
while (i) {
yield i;
i = i.parent ? results.getStateById(i.parent) : undefined;
}
};
path.unshift(parent);
export const getPathForTestInResult = (test: TestResultItem, results: ITestResult): TestIdPath => {
const path: TestIdPath = [];
for (const node of resultItemParents(results, test)) {
path.unshift(node.item.extId);
}
return path.map(t => t.item.extId);
return path;
};
/**

View file

@ -69,10 +69,10 @@ export interface IMainThreadTestCollection extends AbstractIncrementalTestCollec
* Iterates through the item and its parents to the root.
*/
export const getCollectionItemParents = function* (collection: IMainThreadTestCollection, item: InternalTestItem) {
let p: InternalTestItem | undefined = item;
while (p) {
yield p;
p = p.parent ? collection.getNodeById(p.parent) : undefined;
let i: InternalTestItem | undefined = item;
while (i) {
yield i;
i = i.parent ? collection.getNodeById(i.parent) : undefined;
}
};

View file

@ -10,7 +10,7 @@ import { Lazy } from 'vs/base/common/lazy';
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
import { NullLogService } from 'vs/platform/log/common/log';
import { ITestTaskState, TestResultItem } from 'vs/workbench/contrib/testing/common/testCollection';
import { HydratedTestResult, LiveOutputController, LiveTestResult, makeEmptyCounts, TestResultItemChange, TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult';
import { getPathForTestInResult, HydratedTestResult, LiveOutputController, LiveTestResult, makeEmptyCounts, resultItemParents, TestResultItemChange, TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult';
import { TestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
import { InMemoryResultStorage, ITestResultStorage } from 'vs/workbench/contrib/testing/common/testResultStorage';
import { Convert, ReExportedTestRunState as TestRunState, TestItemImpl, TestResultState, testStubs, testStubsChain } from 'vs/workbench/contrib/testing/common/testStubs';
@ -291,4 +291,24 @@ suite('Workbench - Test Results Service', () => {
assert.deepStrictEqual(results.results, [r, hydrated1, hydrated2]);
});
});
test('resultItemParents', () => {
assert.deepStrictEqual([...resultItemParents(r, r.getStateById('id-aa')!)], [
r.getStateById('id-aa'),
r.getStateById('id-a'),
r.getStateById('id-root'),
]);
assert.deepStrictEqual([...resultItemParents(r, r.getStateById('id-root')!)], [
r.getStateById('id-root'),
]);
});
test('getPathForTestInResult', () => {
assert.deepStrictEqual([...getPathForTestInResult(r.getStateById('id-aa')!, r)], [
'id-root',
'id-a',
'id-aa',
]);
});
});

View file

@ -5,7 +5,7 @@
import 'vs/css!./gettingStarted';
import { localize } from 'vs/nls';
import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IEditorInputSerializer, IEditorOpenContext } from 'vs/workbench/common/editor';
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { assertIsDefined } from 'vs/base/common/types';
@ -17,7 +17,7 @@ import { IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platfor
import { welcomePageBackground, welcomePageProgressBackground, welcomePageProgressForeground, welcomePageTileBackground, welcomePageTileHoverBackground, welcomePageTileShadow } from 'vs/workbench/contrib/welcome/page/browser/welcomePageColors';
import { activeContrastBorder, buttonBackground, buttonForeground, buttonHoverBackground, contrastBorder, descriptionForeground, focusBorder, foreground, textLinkActiveForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ITelemetryService, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry';
import { firstSessionDateStorageKey, ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { gettingStartedCheckedCodicon, gettingStartedUncheckedCodicon } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedIcons';
import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener';
@ -27,7 +27,6 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag
import { CancellationToken } from 'vs/base/common/cancellation';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService';
import { IRecentFolder, IRecentlyOpened, IRecentWorkspace, isRecentFolder, isRecentWorkspace, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { onUnexpectedError } from 'vs/base/common/errors';
@ -103,7 +102,6 @@ export class GettingStartedPage extends EditorPane {
private container: HTMLElement;
private contextService: IContextKeyService;
private tasExperimentService?: ITASExperimentService;
private previousSelection?: string;
private recentlyOpened: Promise<IRecentlyOpened>;
private selectedStepElement?: HTMLDivElement;
@ -144,7 +142,6 @@ export class GettingStartedPage extends EditorPane {
@IHostService private readonly hostService: IHostService,
@IWebviewService private readonly webviewService: IWebviewService,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@optional(ITASExperimentService) tasExperimentService: ITASExperimentService,
) {
super(GettingStartedPage.ID, telemetryService, themeService, storageService);
@ -158,9 +155,6 @@ export class GettingStartedPage extends EditorPane {
this.stepMediaComponent = $('.getting-started-media');
this.stepMediaComponent.id = generateUuid();
this.tasExperimentService = tasExperimentService;
this.contextService = this._register(contextService.createScoped(this.container));
inGettingStartedContext.bindTo(this.contextService).set(true);
@ -797,28 +791,19 @@ export class GettingStartedPage extends EditorPane {
const someStepsComplete = this.gettingStartedCategories.some(categry => categry.content.type === 'steps' && categry.content.stepsComplete);
if (!someStepsComplete && !this.hasScrolledToFirstCategory) {
const fistContentBehaviour =
!this.storageService.get(lastSessionDateStorageKey, StorageScope.GLOBAL) // isNewUser ?
? 'openToFirstCategory'
: await Promise.race([
this.tasExperimentService?.getTreatment<'index' | 'openToFirstCategory'>('GettingStartedFirstContent'),
new Promise<'index'>(resolve => setTimeout(() => resolve('index'), 1000)),
]);
const firstSessionDateString = this.storageService.get(firstSessionDateStorageKey, StorageScope.GLOBAL) || new Date().toUTCString();
const daysSinceFirstSession = ((+new Date()) - (+new Date(firstSessionDateString))) / 1000 / 60 / 60 / 24;
const fistContentBehaviour = daysSinceFirstSession < 1 ? 'openToFirstCategory' : 'index';
if (this.gettingStartedCategories.some(category => category.content.type === 'steps' && category.content.stepsComplete)) {
this.setSlide('categories');
return;
} else {
if (fistContentBehaviour === 'openToFirstCategory') {
const first = this.gettingStartedCategories.find(category => category.content.type === 'steps');
this.hasScrolledToFirstCategory = true;
if (first) {
this.currentCategory = first;
this.editorInput.selectedCategory = this.currentCategory?.id;
this.buildCategorySlide(this.editorInput.selectedCategory);
this.setSlide('details');
return;
}
if (fistContentBehaviour === 'openToFirstCategory') {
const first = this.gettingStartedCategories.find(category => category.content.type === 'steps');
this.hasScrolledToFirstCategory = true;
if (first) {
this.currentCategory = first;
this.editorInput.selectedCategory = this.currentCategory?.id;
this.buildCategorySlide(this.editorInput.selectedCategory);
this.setSlide('details');
return;
}
}
}

View file

@ -7,6 +7,8 @@ import { localize } from 'vs/nls';
import { IStartEntry, IWalkthrough } from 'vs/platform/extensions/common/extensions';
import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
const titleTranslated = localize('title', "Title");
export const walkthroughsExtensionPoint = ExtensionsRegistry.registerExtensionPoint<IWalkthrough[]>({
extensionPoint: 'walkthroughs',
jsonSchema: {
@ -64,10 +66,10 @@ export const walkthroughsExtensionPoint = ExtensionsRegistry.registerExtensionPo
},
description: {
type: 'string',
description: localize('walkthroughs.steps.description', "Description of step. Supports ``preformatted``, __italic__, and **bold** text. Use markdown-style links for commands or external links: [Title](command:myext.command), [Title](command:toSide:myext.command), or [Title](https://aka.ms). Links on their own line will be rendered as buttons.")
description: localize('walkthroughs.steps.description.interpolated', "Description of step. Supports ``preformatted``, __italic__, and **bold** text. Use markdown-style links for commands or external links: {0}, {1}, or {2}. Links on their own line will be rendered as buttons.", `[${titleTranslated}](command:myext.command)`, `[${titleTranslated}](command:toSide:myext.command)`, `[${titleTranslated}](https://aka.ms)`)
},
button: {
deprecationMessage: localize('walkthroughs.steps.button.deprecated', "Deprecated. Use markdown links in the description instead, i.e. [Title](command:myext.command), [Title](command:toSide:myext.command), or [Title](https://aka.ms), "),
deprecationMessage: localize('walkthroughs.steps.button.deprecated.interpolated', "Deprecated. Use markdown links in the description instead, i.e. {0}, {1}, or {2}", `[${titleTranslated}](command:myext.command)`, `[${titleTranslated}](command:toSide:myext.command)`, `[${titleTranslated}](https://aka.ms)`),
},
media: {
type: 'object',

View file

@ -118,6 +118,8 @@ export const startEntries: GettingStartedStartEntryContent = [
},
];
const Button = (title: string, href: string) => `[${title}](${href})`;
export const walkthroughs: GettingStartedWalkthroughContent = [
{
id: 'Setup',
@ -131,14 +133,17 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
{
id: 'pickColorTheme',
title: localize('gettingStarted.pickColor.title', "Choose the look you want"),
description: localize('gettingStarted.pickColor.description', "The right color palette helps you focus on your code, is easy on your eyes, and is simply more fun to use.\n[Browse Color Themes](command:workbench.action.selectTheme)"),
completionEvents: ['onSettingChanged:workbench.colorTheme'],
description: localize('gettingStarted.pickColor.description.interpolated', "The right color palette helps you focus on your code, is easy on your eyes, and is simply more fun to use.\n{0}", Button(localize('titleID', "Browse Color Themes"), 'command:workbench.action.selectTheme')),
completionEvents: [
'onSettingChanged:workbench.colorTheme',
'onCommand:workbench.action.selectTheme'
],
media: { type: 'markdown', path: 'example_markdown_media', }
},
{
id: 'findLanguageExtensions',
title: localize('gettingStarted.findLanguageExts.title', "Rich support for all your languages"),
description: localize('gettingStarted.findLanguageExts.description', "Code smarter with syntax highlighting, code completion, linting and debugging. While many languages are built-in, many more can be added as extensions.\n[Browse Language Extensions](command:workbench.extensions.action.showLanguageExtensions)"),
description: localize('gettingStarted.findLanguageExts.description.interpolated', "Code smarter with syntax highlighting, code completion, linting and debugging. While many languages are built-in, many more can be added as extensions.\n{0}", Button(localize('browseLangExts', "Browse Language Extensions"), 'command:workbench.extensions.action.showLanguageExtensions')),
media: {
type: 'image', altText: 'Language extensions', path: {
dark: 'dark/languageExtensions.png',
@ -150,7 +155,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
{
id: 'commandPaletteTask',
title: localize('gettingStarted.commandPalette.title', "One shortcut to access everything"),
description: localize('gettingStarted.commandPalette.description', "Commands Palette is the keyboard way to accomplish any task in VS Code. **Practice** by looking up your frequently used commands to save time and keep in the flow.\n[Open Command Palette](command:workbench.action.showCommands)\n__Try searching for 'view toggle'.__"),
description: localize('gettingStarted.commandPalette.description.interpolated', "Commands Palette is the keyboard way to accomplish any task in VS Code. **Practice** by looking up your frequently used commands to save time and keep in the flow.\n{0}\n__Try searching for 'view toggle'.__", Button(localize('commandPalette', "Open Command Palette"), 'command:workbench.action.showCommands')),
media: {
type: 'image', altText: 'Command Palette overlay for searching and executing commands.', path: {
dark: 'dark/commandPalette.png',
@ -162,7 +167,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
{
id: 'workspaceTrust',
title: localize('gettingStarted.workspaceTrust.title', "Safely browse and edit code"),
description: localize('gettingStarted.workspaceTrust.description', "[Workspace Trust](https://github.com/microsoft/vscode-docs/blob/workspaceTrust/docs/editor/workspace-trust.md) lets you decide whether your project folders should **allow or restrict** automatic code execution __(required for extensions, debugging, etc)__.\nOpening a file/folder will prompt to grant trust. You can always [enable trust](command:toSide:workbench.action.manageTrustedDomain) later."),
description: localize('gettingStarted.workspaceTrust.description.interpolated', "{0} lets you decide whether your project folders should **allow or restrict** automatic code execution __(required for extensions, debugging, etc)__.\nOpening a file/folder will prompt to grant trust. You can always {1} later.", Button(localize('workspaceTrust', "Workspace Trust"), 'https://github.com/microsoft/vscode-docs/blob/workspaceTrust/docs/editor/workspace-trust.md'), Button(localize('enableTrust', "enable trust"), 'command:toSide:workbench.action.manageTrustedDomain')),
when: '!isWorkspaceTrusted && workspaceFolderCount == 0',
media: {
type: 'image', altText: 'Workspace Trust editor in Restricted mode and a primary button for switching to Trusted mode.', path: {
@ -175,7 +180,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
{
id: 'pickAFolderTask-Mac',
title: localize('gettingStarted.setup.OpenFolder.title', "Open up your code"),
description: localize('gettingStarted.setup.OpenFolder.description', "You're all set to start coding. Open a project folder to get your files into VS Code.\n[Pick a Folder](command:workbench.action.files.openFileFolder)"),
description: localize('gettingStarted.setup.OpenFolder.description.interpolated', "You're all set to start coding. Open a project folder to get your files into VS Code.\n{0}", Button(localize('pickFolder', "Pick a Folder"), 'command:workbench.action.files.openFileFolder')),
when: 'isMac && workspaceFolderCount == 0',
media: {
type: 'image', altText: 'Explorer view showing buttons for opening folder and cloning repository.', path: {
@ -188,7 +193,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
{
id: 'pickAFolderTask-Other',
title: localize('gettingStarted.setup.OpenFolder.title', "Open up your code"),
description: localize('gettingStarted.setup.OpenFolder.description2', "You're all set to start coding. Open a project folder to get your files into VS Code.\n[Pick a Folder](command:workbench.action.files.openFolder)"),
description: localize('gettingStarted.setup.OpenFolder.description.interpolated', "You're all set to start coding. Open a project folder to get your files into VS Code.\n{0}", Button(localize('pickFolder', "Pick a Folder"), 'command:workbench.action.files.openFolder')),
when: '!isMac && workspaceFolderCount == 0',
media: {
type: 'image', altText: 'Explorer view showing buttons for opening folder and cloning repository.', path: {
@ -201,7 +206,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
{
id: 'quickOpen',
title: localize('gettingStarted.quickOpen.title', "Quickly navigate between your files"),
description: localize('gettingStarted.quickOpen.description', "Navigate between files in an instant with one keystroke. Tip: Open multiple files by pressing the right arrow key.\n[Quick Open a File](command:toSide:workbench.action.quickOpen)"),
description: localize('gettingStarted.quickOpen.description.interpolated', "Navigate between files in an instant with one keystroke. Tip: Open multiple files by pressing the right arrow key.\n{0}", Button(localize('quickOpen', "Quick Open a File"), 'command:toSide:workbench.action.quickOpen')),
when: 'workspaceFolderCount != 0',
media: {
type: 'image', altText: 'Go to file in quick search.', path: {
@ -227,7 +232,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
{
id: 'playground',
title: localize('gettingStarted.playground.title', "Redefine your editing skills"),
description: localize('gettingStarted.playground.description', "Want to code faster and smarter? Practice powerful code editing features in the interactive playground.\n[Open Interactive Playground](command:toSide:workbench.action.showInteractivePlayground)"),
description: localize('gettingStarted.playground.description.interpolated', "Want to code faster and smarter? Practice powerful code editing features in the interactive playground.\n{0}", Button(localize('openInteractivePlayground', "Open Interactive Playground"), 'command:toSide:workbench.action.showInteractivePlayground')),
media: {
type: 'image', altText: 'Interactive Playground.', path: {
dark: 'dark/playground.png',
@ -239,7 +244,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
{
id: 'terminal',
title: localize('gettingStarted.terminal.title', "Convenient built-in terminal"),
description: localize('gettingStarted.terminal.description', "Quickly run shell commands and monitor build output, right next to your code.\n[Show Terminal Panel](command:workbench.action.terminal.toggleTerminal)"),
description: localize('gettingStarted.terminal.description.interpolated', "Quickly run shell commands and monitor build output, right next to your code.\n{0}", Button(localize('showTerminal', "Show Terminal Panel"), 'command:workbench.action.terminal.toggleTerminal')),
when: 'remoteName != codespaces && !terminalIsOpen',
media: {
type: 'image', altText: 'Integrated terminal running a few npm commands', path: {
@ -252,7 +257,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
{
id: 'extensions',
title: localize('gettingStarted.extensions.title', "Limitless extensibility"),
description: localize('gettingStarted.extensions.description', "Extensions are VS Code's power-ups. They range from handy productivity hacks, expanding out-of-the-box features, to adding completely new capabilities.\n[Browse Recommended Extensions](command:workbench.extensions.action.showRecommendedExtensions)"),
description: localize('gettingStarted.extensions.description.interpolated', "Extensions are VS Code's power-ups. They range from handy productivity hacks, expanding out-of-the-box features, to adding completely new capabilities.\n{0}", Button(localize('browseRecommended', "Browse Recommended Extensions"), 'command:workbench.extensions.action.showRecommendedExtensions')),
media: {
type: 'image', altText: 'VS Code extension marketplace with featured language extensions', path: {
dark: 'dark/extensions.png',
@ -264,7 +269,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
{
id: 'settings',
title: localize('gettingStarted.settings.title', "Tune your settings"),
description: localize('gettingStarted.settings.description', "Tweak every aspect of VS Code and your extensions to your liking. Commonly used settings are listed first to get you started.\n[Tweak my Settings](command:toSide:workbench.action.openSettings)"),
description: localize('gettingStarted.settings.description.interpolated', "Tweak every aspect of VS Code and your extensions to your liking. Commonly used settings are listed first to get you started.\n{0}", Button(localize('tweakSettings', "Tweak my Settings"), 'command:toSide:workbench.action.openSettings')),
media: {
type: 'image', altText: 'VS Code Settings', path: {
dark: 'dark/settings.png',
@ -276,7 +281,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
{
id: 'settingsSync',
title: localize('gettingStarted.settingsSync.title', "Sync your stuff across devices"),
description: localize('gettingStarted.settingsSync.description', "Never lose the perfect VS Code setup! Settings Sync will back up and share settings, keybindings & extensions across several installations.\n[Enable Settings Sync](command:workbench.userDataSync.actions.turnOn)"),
description: localize('gettingStarted.settingsSync.description.interpolated', "Never lose the perfect VS Code setup! Settings Sync will back up and share settings, keybindings & extensions across several installations.\n{0}", Button(localize('enableSync', "Enable Settings Sync"), 'command:workbench.userDataSync.actions.turnOn')),
when: 'syncStatus != uninitialized',
completionEvents: ['onEvent:sync-enabled'],
media: {
@ -290,7 +295,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
{
id: 'videoTutorial',
title: localize('gettingStarted.videoTutorial.title', "Lean back and learn"),
description: localize('gettingStarted.videoTutorial.description', "Watch the first in a series of short & practical video tutorials for VS Code's key features.\n[Watch Tutorial](https://aka.ms/vscode-getting-started-video)"),
description: localize('gettingStarted.videoTutorial.description.interpolated', "Watch the first in a series of short & practical video tutorials for VS Code's key features.\n{0}", Button(localize('watch', "Watch Tutorial"), 'https://aka.ms/vscode-getting-started-video')),
media: { type: 'image', altText: 'VS Code Settings', path: 'tutorialVideo.png' },
}
]
@ -308,7 +313,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
{
id: 'splitview',
title: localize('gettingStarted.splitview.title', "Side by side editing"),
description: localize('gettingStarted.splitview.description', "Make the most of your screen estate by opening files side by side, vertically and horizontally.\n[Split Editor](command:workbench.action.splitEditor)"),
description: localize('gettingStarted.splitview.description.interpolated', "Make the most of your screen estate by opening files side by side, vertically and horizontally.\n{0}", Button(localize('splitEditor', "Split Editor"), 'command:workbench.action.splitEditor')),
media: {
type: 'image', altText: 'Multiple editors in split view.', path: {
dark: 'dark/splitview.png',
@ -320,7 +325,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
{
id: 'debugging',
title: localize('gettingStarted.debug.title', "Watch your code in action"),
description: localize('gettingStarted.debug.description', "Accelerate your edit, build, test, and debug loop by setting up a launch configuration.\n[Run your Project](command:workbench.action.debug.selectandstart)"),
description: localize('gettingStarted.debug.description.interpolated', "Accelerate your edit, build, test, and debug loop by setting up a launch configuration.\n{0}", Button(localize('runProject', "Run your Project"), 'command:workbench.action.debug.selectandstart')),
when: 'workspaceFolderCount != 0',
media: {
type: 'image', altText: 'Run and debug view.', path: {
@ -333,7 +338,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
{
id: 'scmClone',
title: localize('gettingStarted.scm.title', "Track your code with Git"),
description: localize('gettingStarted.scmClone.description', "Set up the built-in version control for your project to track your changes and collaborate with others.\n[Clone Repository](command:git.clone)"),
description: localize('gettingStarted.scmClone.description.interpolated', "Set up the built-in version control for your project to track your changes and collaborate with others.\n{0}", Button(localize('cloneRepo', "Clone Repository"), 'command:git.clone')),
when: 'config.git.enabled && !git.missing && workspaceFolderCount == 0',
media: {
type: 'image', altText: 'Source Control view.', path: {
@ -346,7 +351,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
{
id: 'scmSetup',
title: localize('gettingStarted.scm.title', "Track your code with Git"),
description: localize('gettingStarted.scmSetup.description', "Set up the built-in version control for your project to track your changes and collaborate with others.\n[Initialize Git Repository](command:git.init)"),
description: localize('gettingStarted.scmSetup.description.interpolated', "Set up the built-in version control for your project to track your changes and collaborate with others.\n{0}", Button(localize('initRepo', "Initialize Git Repository"), 'command:git.init')),
when: 'config.git.enabled && !git.missing && workspaceFolderCount != 0 && gitOpenRepositoryCount == 0',
media: {
type: 'image', altText: 'Source Control view.', path: {
@ -359,7 +364,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
{
id: 'scm',
title: localize('gettingStarted.scm.title', "Track your code with Git"),
description: localize('gettingStarted.scm.description', "No more looking up Git commands! Git and GitHub workflows are seamlessly integrated.\n[Open Source Control](command:workbench.view.scm)"),
description: localize('gettingStarted.scm.description.interpolated', "No more looking up Git commands! Git and GitHub workflows are seamlessly integrated.\n{0}", Button(localize('openSCM', "Open Source Control"), 'command:workbench.view.scm')),
when: 'config.git.enabled && !git.missing && workspaceFolderCount != 0 && gitOpenRepositoryCount != 0 && activeViewlet != \'workbench.view.scm\'',
media: {
type: 'image', altText: 'Source Control view.', path: {
@ -373,7 +378,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
id: 'tasks',
title: localize('gettingStarted.tasks.title', "Automate your project tasks"),
when: 'workspaceFolderCount != 0',
description: localize('gettingStarted.tasks.description', "Create tasks for your common workflows and enjoy the integrated experience of running scripts and automatically checking results.\n[Run Auto-detected Tasks](command:workbench.action.tasks.runTask)"),
description: localize('gettingStarted.tasks.description.interpolated', "Create tasks for your common workflows and enjoy the integrated experience of running scripts and automatically checking results.\n{0}", Button(localize('runTasks', "Run Auto-detected Tasks"), 'command:workbench.action.tasks.runTask')),
media: {
type: 'image', altText: 'Task runner.', path: {
dark: 'dark/tasks.png',
@ -385,7 +390,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
{
id: 'shortcuts',
title: localize('gettingStarted.shortcuts.title', "Customize your shortcuts"),
description: localize('gettingStarted.shortcuts.description', "Once you have discovered your favorite commands, create custom keyboard shortcuts for instant access.\n[Keyboard Shortcuts](command:toSide:workbench.action.openGlobalKeybindings)"),
description: localize('gettingStarted.shortcuts.description.interpolated', "Once you have discovered your favorite commands, create custom keyboard shortcuts for instant access.\n{0}", Button(localize('keyboardShortcuts', "Keyboard Shortcuts"), 'command:toSide:workbench.action.openGlobalKeybindings')),
media: {
type: 'image', altText: 'Interactive shortcuts.', path: {
dark: 'dark/shortcuts.png',

View file

@ -25,7 +25,7 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
'enumDescriptions': [
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.none' }, "Start without an editor."),
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePage' }, "Open the legacy Welcome page."),
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.readme' }, "Open the README when opening a folder that contains one, fallback to 'welcomePage' otherwise."),
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.readme' }, "Open the README when opening a folder that contains one, fallback to 'welcomePage' otherwise. Note: This is only observed as a global ccnfiguration, it will be ignored if set in a workspace or folder configuration."),
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.newUntitledFile' }, "Open a new untitled file (only applies when opening an empty window)."),
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePageInEmptyWorkbench' }, "Open the legacy Welcome page when opening an empty workbench."),
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.gettingStarted' }, "Open the new Welcome Page with content to aid in getting started with VS Code and extensions."),

View file

@ -637,7 +637,7 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
},
[WORKSPACE_TRUST_EMPTY_WINDOW]: {
type: 'boolean',
default: false,
default: true,
included: !isWeb,
markdownDescription: localize('workspace.trust.emptyWindow.description', "Controls whether or not the empty window is trusted by default within VS Code. When used with `#{0}#`, you can enable the full functionality of VS Code without prompting in an empty window.", WORKSPACE_TRUST_UNTRUSTED_FILES),
scope: ConfigurationScope.APPLICATION

View file

@ -823,13 +823,13 @@ export class WorkspaceTrustEditor extends EditorPane {
[
localize('untrustedTasks', "Tasks are disabled"),
localize('untrustedDebugging', "Debugging is disabled"),
localize('untrustedExtensions', "[{0} extensions](command:{1}) are disabled or have limited functionality", numExtensions, LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID)
localize('untrustedExtensions', "[{0} extensions]({1}) are disabled or have limited functionality", numExtensions, `command:${LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID}`)
] :
[
localize('untrustedTasks', "Tasks are disabled"),
localize('untrustedDebugging', "Debugging is disabled"),
numSettings ? localize('untrustedSettings', "[{0} workspace settings](command:{1}) are not applied", numSettings, 'settings.filterUntrusted') : localize('no untrustedSettings', "Workspace settings requiring trust are not applied"),
localize('untrustedExtensions', "[{0} extensions](command:{1}) are disabled or have limited functionality", numExtensions, LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID)
numSettings ? localize('untrustedSettings', "[{0} workspace settings]({1}) are not applied", numSettings, 'command:settings.filterUntrusted') : localize('no untrustedSettings', "Workspace settings requiring trust are not applied"),
localize('untrustedExtensions', "[{0} extensions]({1}) are disabled or have limited functionality", numExtensions, `command:${LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID}`)
];
this.renderLimitationsListElement(untrustedContainer, untrustedContainerItems, xListIcon.classNamesArray);