Merge branch 'main' into alex/ghost-text

This commit is contained in:
Henning Dieterichs 2021-05-31 09:06:57 +02:00
commit 94f47f44c9
No known key found for this signature in database
GPG key ID: 771381EFFDB9EC06
72 changed files with 1497 additions and 717 deletions

View file

@ -1,14 +1,14 @@
# Code - OSS Development Container
This repository includes configuration for a development container for working with Code - OSS in an isolated local container or using [GitHub Codespaces](https://github.com/features/codespaces).
This repository includes configuration for a development container for working with Code - OSS in a local container or using [GitHub Codespaces](https://github.com/features/codespaces).
> **Tip:** The default VNC password is `vscode`. The VNC server runs on port `5901` with a web client at `6080`. For better performance, we recommend using a [VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/). Applications like the macOS Screen Sharing app will not perform as well.
> **Tip:** The default VNC password is `vscode`. The VNC server runs on port `5901` and a web client is available on port `6080`.
## Quick start - local
1. Install Docker Desktop or Docker for Linux on your local machine. (See [docs](https://aka.ms/vscode-remote/containers/getting-started) for additional details.)
2. **Important**: Docker needs at least **4 Cores and 6 GB of RAM (8 GB recommended)** to run full build. If you on macOS, or using the old Hyper-V engine for Windows, update these values for Docker Desktop by right-clicking on the Docker status bar item, going to **Preferences/Settings > Resources > Advanced**.
2. **Important**: Docker needs at least **4 Cores and 6 GB of RAM (8 GB recommended)** to run a full build. If you are on macOS, or are using the old Hyper-V engine for Windows, update these values for Docker Desktop by right-clicking on the Docker status bar item and going to **Preferences/Settings > Resources > Advanced**.
> **Note:** The [Resource Monitor](https://marketplace.visualstudio.com/items?itemName=mutantdino.resourcemonitor) extension is included in the container so you can keep an eye on CPU/Memory in the status bar.
@ -16,53 +16,56 @@ This repository includes configuration for a development container for working w
![Image of Remote - Containers extension](https://microsoft.github.io/vscode-remote-release/images/remote-containers-extn.png)
> Note that the Remote - Containers extension requires the Visual Studio Code distribution of Code - OSS. See the [FAQ](https://aka.ms/vscode-remote/faq/license) for details.
> **Note:** The Remote - Containers extension requires the Visual Studio Code distribution of Code - OSS. See the [FAQ](https://aka.ms/vscode-remote/faq/license) for details.
4. Press <kbd>Ctrl/Cmd</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd> and select **Remote-Containers: Clone Repository in Container Volume...**.
4. Press <kbd>Ctrl/Cmd</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd> or <kbd>F1</kbd> and select **Remote-Containers: Clone Repository in Container Volume...**.
> **Tip:** While you can use your local source tree instead, operations like `yarn install` can be slow on macOS or using the Hyper-V engine on Windows. We recommend the "clone repository in container" approach instead since it uses "named volume" rather than the local filesystem.
> **Tip:** While you can use your local source tree instead, operations like `yarn install` can be slow on macOS or when using the Hyper-V engine on Windows. We recommend the "clone repository in container" approach instead since it uses "named volume" rather than the local filesystem.
5. Type `https://github.com/microsoft/vscode` (or a branch or PR URL) in the input box and press <kbd>Enter</kbd>.
6. After the container is running, open a web browser and go to [http://localhost:6080](http://localhost:6080) or use a [VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/) to connect to `localhost:5901` and enter `vscode` as the password.
6. After the container is running, open a web browser and go to [http://localhost:6080](http://localhost:6080), or use a [VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/) to connect to `localhost:5901` and enter `vscode` as the password.
Anything you start in VS Code or the integrated terminal will appear here.
Anything you start in VS Code, or the integrated terminal, will appear here.
Next: **[Try it out!](#try-it)**
## Quick start - GitHub Codespaces
> **IMPORTANT:** You need to use a "Standard" sized codespace or larger (4-core, 8GB) since VS Code needs 6GB of RAM to compile. This is now the default for GitHub Codespaces, but do not downgrade to "Basic" unless you do not intend to compile.
1. From the [microsoft/vscode GitHub repository](https://github.com/microsoft/vscode), click on the **Code** dropdown, select **Open with Codespaces**, and then click on **New codespace**. If prompted, select the **Standard** machine size (which is also the default).
1. From the [microsoft/vscode GitHub repository](https://github.com/microsoft/vscode), click on the **Code** dropdown, select **Open with Codespaces**, and the **New codespace**
> **Note:** You will not see these options within GitHub if you are not in the Codespaces beta.
> Note that you will not see these options if you are not in the beta yet.
2. After the codespace is up and running in your browser, press <kbd>Ctrl/Cmd</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd> or <kbd>F1</kbd> and select **Ports: Focus on Ports View**.
2. After the codespace is up and running in your browser, press <kbd>F1</kbd> and select **Ports: Focus on Ports View**.
3. You should see **VNC web client (6080)** under in the list of ports. Select the line and click on the globe icon to open it in a browser tab.
3. You should see port `6080` under **Forwarded Ports**. Select the line and click on the globe icon to open it in a browser tab.
> If you do not see port `6080`, press <kbd>F1</kbd>, select **Forward a Port** and enter port `6080`.
> **Tip:** If you do not see the port, <kbd>Ctrl/Cmd</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd> or <kbd>F1</kbd>, select **Forward a Port** and enter port `6080`.
4. In the new tab, you should see noVNC. Click **Connect** and enter `vscode` as the password.
Anything you start in VS Code or the integrated terminal will appear here.
Anything you start in VS Code, or the integrated terminal, will appear here.
Next: **[Try it out!](#try-it)**
### Using VS Code with GitHub Codespaces
You will likely see better performance when accessing the codespace you created from VS Code since you can use a[VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/). Here's how to do it.
You may see improved VNC responsiveness when accessing a codespace from VS Code client since you can use a [VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/). Here's how to do it.
1. [Create a codespace](#quick-start---github-codespaces) if you have not already.
1. Install [Visual Studio Code Stable](https://code.visualstudio.com/) or [Insiders](https://code.visualstudio.com/insiders/) and the the [GitHub Codespaces extension](https://marketplace.visualstudio.com/items?itemName=GitHub.codespaces).
2. Set up [VS Code for use with GitHub Codespaces](https://docs.github.com/github/developing-online-with-codespaces/using-codespaces-in-visual-studio-code)
> **Note:** The GitHub Codespaces extension requires the Visual Studio Code distribution of Code - OSS.
3. After the VS Code is up and running, press <kbd>F1</kbd>, choose **Codespaces: Connect to Codespace**, and select the codespace you created.
2. After the VS Code is up and running, press <kbd>Ctrl/Cmd</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd> or <kbd>F1</kbd>, choose **Codespaces: Create New Codespace**, and use the following settings:
- `microsoft/vscode` for the repository.
- Select any branch (e.g. **main**) - you select a different one later.
- Choose **Standard** (4-core, 8GB) as the size.
4. After you've connected to the codespace, use a [VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/) to connect to `localhost:5901` and enter `vscode` as the password.
4. After you have connected to the codespace, you can use a [VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/) to connect to `localhost:5901` and enter `vscode` as the password.
5. Anything you start in VS Code or the integrated terminal will appear here.
> **Tip:** You may also need change your VNC client's **Picture Quaility** setting to **High** to get a full color desktop.
5. Anything you start in VS Code, or the integrated terminal, will appear here.
Next: **[Try it out!](#try-it)**
@ -70,20 +73,18 @@ Next: **[Try it out!](#try-it)**
This container uses the [Fluxbox](http://fluxbox.org/) window manager to keep things lean. **Right-click on the desktop** to see menu options. It works with GNOME and GTK applications, so other tools can be installed if needed.
Note you can also set the resolution from the command line by typing `set-resolution`.
> **Note:** You can also set the resolution from the command line by typing `set-resolution`.
To start working with Code - OSS, follow these steps:
1. In your local VS Code, open a terminal (<kbd>Ctrl/Cmd</kbd> + <kbd>Shift</kbd> + <kbd>\`</kbd>) and type the following commands:
1. In your local VS Code client, open a terminal (<kbd>Ctrl/Cmd</kbd> + <kbd>Shift</kbd> + <kbd>\`</kbd>) and type the following commands:
```bash
yarn install
bash scripts/code.sh
```
Note that a previous run of `yarn install` will already be cached, so this step should simply pick up any recent differences.
2. After the build is complete, open a web browser or a [VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/) to the desktop environnement as described in the quick start and enter `vscode` as the password.
2. After the build is complete, open a web browser or a [VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/) to connect to the desktop environment as described in the quick start and enter `vscode` as the password.
3. You should now see Code - OSS!
@ -91,7 +92,7 @@ Next, let's try debugging.
1. Shut down Code - OSS by clicking the box in the upper right corner of the Code - OSS window through your browser or VNC viewer.
2. Go to your local VS Code client, and use Run / Debug view to launch the **VS Code** configuration. (Typically the default, so you can likely just press <kbd>F5</kbd>).
2. Go to your local VS Code client, and use the **Run / Debug** view to launch the **VS Code** configuration. (Typically the default, so you can likely just press <kbd>F5</kbd>).
> **Note:** If launching times out, you can increase the value of `timeout` in the "VS Code", "Attach Main Process", "Attach Extension Host", and "Attach to Shared Process" configurations in [launch.json](../.vscode/launch.json). However, running `scripts/code.sh` first will set up Electron which will usually solve timeout issues.

View file

@ -3,20 +3,26 @@
// Image contents: https://github.com/microsoft/vscode-dev-containers/blob/master/repository-containers/images/github.com/microsoft/vscode/.devcontainer/base.Dockerfile
"image": "mcr.microsoft.com/vscode/devcontainers/repos/microsoft/vscode:branch-main",
"workspaceMount": "source=${localWorkspaceFolder},target=/home/node/workspace/vscode,type=bind,consistency=cached",
"workspaceFolder": "/home/node/workspace/vscode",
"overrideCommand": false,
"runArgs": [ "--init", "--security-opt", "seccomp=unconfined"],
"settings": {
"terminal.integrated.shell.linux": "/bin/bash",
"resmon.show.battery": false,
"resmon.show.cpufreq": false
},
// noVNC, VNC, debug ports
"forwardPorts": [6080, 5901, 9222],
// noVNC, VNC
"forwardPorts": [6080, 5901],
"portsAttributes": {
"6080": {
"label": "VNC web client (noVNC)",
"onAutoForward": "silent"
},
"5901": {
"label": "VNC TCP port",
"onAutoForward": "silent"
}
},
"extensions": [
"dbaeumer.vscode-eslint",

View file

@ -7,6 +7,7 @@
"labels": {
"L10N": {"assign": []},
"VIM": {"assign": []},
"accessibility": { "assign": ["isidorn"]},
"api": {"assign": ["jrieken"]},
"api-finalization": {"assign": []},
"api-proposal": {"assign": ["jrieken"]},

196
.vscode/searches/api-todos.code-search vendored Normal file
View file

@ -0,0 +1,196 @@
# Query: todo@API
# Flags: WordMatch OpenEditors
# Including: */vscode.proposed.d.ts
# ContextLines: 1
56 results - 1 file
src/vs/vscode.proposed.d.ts:
1098 */
1099: // todo@API should this be called `notebookType` or `notebookKind`
1100 readonly viewType: string;
1159
1160: // todo@API jsdoc
1161 export class NotebookCellMetadata {
1165 */
1166: // todo@API decouple from metadata? extract as dedicated field or inside an options object and leave metadata purely for extensions?
1167 readonly inputCollapsed?: boolean;
1171 */
1172: // todo@API decouple from metadata? extract as dedicated field or inside an options object and leave metadata purely for extensions?
1173 readonly outputCollapsed?: boolean;
1215 */
1216: // todo@API use duration instead of start/end?
1217 readonly startTime?: number;
1224
1225: // todo@API jsdoc
1226: // todo@API remove this and use simple {}?
1227 export class NotebookDocumentMetadata {
1290 */
1291: // todo@API document which mime types are supported out of the box and
1292 // which are considered secure
1365
1366: //todo@API remove in favour of NotebookCellOutput#metadata
1367 metadata?: { [key: string]: any };
1383 */
1384: //todo@API - add sugar function to add more outputs
1385 export class NotebookCellOutput {
1407
1408: //todo@API have this OR NotebookCellOutputItem#metadata but not both? Preference for this.
1409 metadata?: { [key: string]: any };
1471 * @param languageId The language identifier of the source value.
1472: * @param outputs //TODO@API remove from ctor?
1473: * @param metadata //TODO@API remove from ctor?
1474: * @param executionSummary //TODO@API remove from ctor?
1475 */
1558 */
1559: // todo@API ...NotebookDocument... or just ...Notebook... just like...Cell... above
1560 transientDocumentMetadata?: { [K in keyof NotebookDocumentMetadata]?: boolean };
1617
1618: // todo@api jsdoc
1619: // todo@api Inline unless we can come up with more (future) properties
1620 export interface NotebookCellExecuteStartContext {
1627
1628: // todo@api jsdoc
1629: // todo@api Inline unless we can come up with more (future) properties
1630 export interface NotebookCellExecuteEndContext {
1634 */
1635: // todo@api undefined === false, so by default cells execution fails?
1636 success?: boolean;
1672
1673: // todo@API inline context object?
1674 start(context?: NotebookCellExecuteStartContext): void;
1675
1676: // todo@API inline context object?
1677 end(result?: NotebookCellExecuteEndContext): void;
1751 */
1752: // todo@api rename to notebookType?
1753 readonly viewType: string;
1790 */
1791: // todo@API rename to supportsExecutionOrder, usesExecutionOrder
1792 hasExecutionOrder?: boolean;
1849
1850: // todo@API allow add, not remove
1851 readonly rendererScripts: NotebookRendererScript[];
1870
1871: //todo@API validate this works
1872 asWebviewUri(localResource: Uri): Uri;
1899 */
1900: // todo@api remove? use cell.notebook instead?
1901 readonly document: NotebookDocument;
1910 */
1911: // todo@API rename to state?
1912 readonly executionState: NotebookCellExecutionState;
1930
1931: // todo@api jsdoc
1932 export class NotebookCellStatusBarItem {
1942
1943: // todo@api jsdoc
1944 export interface NotebookCellStatusBarItemProvider {
1952 */
1953: // todo@API rename to ...NotebookCell...
1954 provideCellStatusBarItems(cell: NotebookCell, token: CancellationToken): ProviderResult<NotebookCellStatusBarItem[]>;
1965 */
1966: // todo@api rename to notebooks?
1967: // todo@api what should be in this namespace? should notebookDocuments and friends be in the workspace namespace?
1968 export namespace notebook {
2035 * @param handler
2036: * @param rendererScripts todo@API should renderer scripts come later?
2037 */
2042 */
2043: // todo@API this is an event that is fired for a property that cells don't have and that makes me wonder
2044 // how a correct consumer work, e.g the consumer could have been late and missed an event?
2046
2047: // todo@api jsdoc
2048 export function registerNotebookCellStatusBarItemProvider(notebookType: string, provider: NotebookCellStatusBarItemProvider): Disposable;
2118 readonly start: number;
2119: // todo@API end? Use NotebookCellRange instead?
2120 readonly deletedCount: number;
2121: // todo@API removedCells, deletedCells?
2122 readonly deletedItems: NotebookCell[];
2123: // todo@API addedCells, insertedCells, newCells?
2124 readonly items: NotebookCell[];
2183
2184: // todo@API add onDidChangeNotebookCellOutputs
2185 export const onDidChangeCellOutputs: Event<NotebookCellOutputsChangeEvent>;
2186
2187: // todo@API add onDidChangeNotebookCellMetadata
2188 export const onDidChangeCellMetadata: Event<NotebookCellMetadataChangeEvent>;
2206
2207: // todo@API add NotebookEdit-type which handles all these cases?
2208 // export class NotebookEdit {
2223 export interface WorkspaceEdit {
2224: // todo@API add NotebookEdit-type which handles all these cases?
2225 replaceNotebookMetadata(uri: Uri, value: NotebookDocumentMetadata): void;
2285 */
2286: // todo@API really needed? we didn't find a user here
2287 export function createConcatTextDocument(notebook: NotebookDocument, selector?: DocumentSelector): NotebookConcatTextDocument;
2339
2340: // todo@API use openNotebookDOCUMENT to align with openCustomDocument etc?
2341: // todo@API rename to NotebookDocumentContentProvider
2342 export interface NotebookContentProvider {
2352
2353: // todo@API use NotebookData instead
2354 saveNotebook(document: NotebookDocument, token: CancellationToken): Thenable<void>;
2355
2356: // todo@API use NotebookData instead
2357 saveNotebookAs(targetResource: Uri, document: NotebookDocument, token: CancellationToken): Thenable<void>;
2358
2359: // todo@API use NotebookData instead
2360 backupNotebook(document: NotebookDocument, context: NotebookDocumentBackupContext, token: CancellationToken): Thenable<NotebookDocumentBackup>;
2364
2365: // TODO@api use NotebookDocumentFilter instead of just notebookType:string?
2366: // TODO@API options duplicates the more powerful variant on NotebookContentProvider
2367 export function registerNotebookContentProvider(notebookType: string, provider: NotebookContentProvider, options?: NotebookDocumentContentOptions): Disposable;
2647
2648: // todo@API Split between Inlay- and OverlayHints (InlayHint are for a position, OverlayHints for a non-empty range)
2649: // todo@API add "mini-markdown" for links and styles
2650 // (done) remove description
2699
2700: // todo@API make range first argument
2701 constructor(text: string, position: Position, kind?: InlayHintKind);
3564
3565: // TODO@API must be a class
3566 export interface OpenEditorInfo {
3574
3575: // todo@API proper event type
3576 export const onDidChangeOpenEditors: Event<void>;

View file

@ -57,10 +57,10 @@ VS Code includes a set of built-in extensions located in the [extensions](extens
## Development Container
This repository includes a Visual Studio Code Remote - Containers / Codespaces development container.
This repository includes a Visual Studio Code Remote - Containers / GitHub Codespaces development container.
- For [Remote - Containers](https://aka.ms/vscode-remote/download/containers), use the **Remote-Containers: Open Repository in Container...** command which creates a Docker volume for better disk I/O on macOS and Windows.
- For Codespaces, install the [Visual Studio Codespaces](https://aka.ms/vscs-ext-vscode) extension in VS Code, and use the **Codespaces: Create New Codespace** command.
- For [Remote - Containers](https://aka.ms/vscode-remote/download/containers), use the **Remote-Containers: Clone Repository in Container Volume...** command which creates a Docker volume for better disk I/O on macOS and Windows.
- For Codespaces, install the [Github Codespaces](https://marketplace.visualstudio.com/items?itemName=GitHub.codespacese) extension in VS Code, and use the **Codespaces: Create New Codespace** command.
Docker / the Codespace should have at least **4 Cores and 6 GB of RAM (8 GB recommended)** to run full build. See the [development container README](.devcontainer/README.md) for more information.

View file

@ -52,7 +52,7 @@
"p-limit": "^3.1.0",
"plist": "^3.0.1",
"source-map": "0.6.1",
"typescript": "^4.3.0-dev.20210503",
"typescript": "^4.4.0-dev.20210528",
"vsce": "1.48.0",
"vscode-universal": "deepak1556/universal#61454d96223b774c53cda10f72c2098c0ce02d58"
},

View file

@ -1879,10 +1879,10 @@ typescript@^4.1.3:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7"
integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==
typescript@^4.3.0-dev.20210503:
version "4.3.0-dev.20210503"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.0-dev.20210503.tgz#0a3eb480676effd4975beb5a2f097530ed53550a"
integrity sha512-Gj3TQve5PLCZoPBy96Yp6Y3+jNLmms0i3ynhxEJAKgax7Fxui29/uG/DClbBtKz1peNhzXwikXVFFAV1BB/3mw==
typescript@^4.4.0-dev.20210528:
version "4.4.0-dev.20210528"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.0-dev.20210528.tgz#42453bc42e9d9df8ad0741c207c24d56407c0347"
integrity sha512-ACV+mYKC+PhWUXIDUL6qmFClIdrKc20KRxDePt8bniCgkKQD4XRYKl7m02paxJM3nTMRdlfjs0ncaslA5BA1GA==
uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.5"

View file

@ -566,7 +566,8 @@ export class AzureActiveDirectoryService {
});
const proxyEndpoints: { [providerId: string]: string } | undefined = await vscode.commands.executeCommand('workbench.getCodeExchangeProxyEndpoints');
const endpoint = proxyEndpoints && proxyEndpoints['microsoft'] || `${loginEndpointUrl}${tenant}/oauth2/v2.0/token`;
const endpointUrl = proxyEndpoints?.microsoft || loginEndpointUrl;
const endpoint = `${endpointUrl}${tenant}/oauth2/v2.0/token`;
const result = await fetch(endpoint, {
method: 'POST',
@ -602,7 +603,10 @@ export class AzureActiveDirectoryService {
let result: Response;
try {
result = await fetch(`https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`, {
const proxyEndpoints: { [providerId: string]: string } | undefined = await vscode.commands.executeCommand('workbench.getCodeExchangeProxyEndpoints');
const endpointUrl = proxyEndpoints?.microsoft || loginEndpointUrl;
const endpoint = `${endpointUrl}${tenant}/oauth2/v2.0/token`;
result = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',

View file

@ -10,7 +10,8 @@
"selection.background": "#008000",
"editor.selectionBackground": "#FFFFFF",
"statusBarItem.remoteBackground": "#00000000",
"ports.iconRunningProcessForeground": "#FFFFFF"
"ports.iconRunningProcessForeground": "#FFFFFF",
"editorWhitespace.foreground": "#7c7c7c"
},
"tokenColors": [
{

View file

@ -475,10 +475,11 @@ suite('Notebook API tests', function () {
const secondCell = vscode.window.activeNotebookEditor!.document.cellAt(1);
assert.strictEqual(secondCell!.outputs.length, 1);
assert.deepStrictEqual(secondCell!.outputs[0].metadata, { testOutputMetadata: true });
assert.strictEqual(secondCell!.outputs[0].outputs.length, 1);
assert.strictEqual(secondCell!.outputs[0].outputs[0].mime, 'text/plain');
assert.strictEqual(new TextDecoder().decode(secondCell!.outputs[0].outputs[0].data), 'Hello World');
assert.deepStrictEqual(secondCell!.outputs[0].outputs[0].metadata, { testOutputItemMetadata: true });
assert.strictEqual((<any>secondCell!.outputs[0]).outputs.length, 1); //todo@jrieken will FAIL once the backwards compatibility is gone
assert.strictEqual(secondCell!.outputs[0].items.length, 1);
assert.strictEqual(secondCell!.outputs[0].items[0].mime, 'text/plain');
assert.strictEqual(new TextDecoder().decode(secondCell!.outputs[0].items[0].data), 'Hello World');
assert.deepStrictEqual(secondCell!.outputs[0].items[0].metadata, { testOutputItemMetadata: true });
assert.strictEqual(secondCell!.executionSummary?.executionOrder, 5);
assert.strictEqual(secondCell!.executionSummary?.success, true);
@ -746,9 +747,9 @@ suite('Notebook API tests', function () {
await vscode.commands.executeCommand('notebook.cell.execute');
await event;
assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked
assert.strictEqual(cell.outputs[0].outputs.length, 1);
assert.strictEqual(cell.outputs[0].outputs[0].mime, 'text/plain');
assert.deepStrictEqual(new TextDecoder().decode(cell.outputs[0].outputs[0].data), 'my output');
assert.strictEqual(cell.outputs[0].items.length, 1);
assert.strictEqual(cell.outputs[0].items[0].mime, 'text/plain');
assert.deepStrictEqual(new TextDecoder().decode(cell.outputs[0].items[0].data), 'my output');
});
await withEvent<vscode.NotebookCellOutputsChangeEvent>(vscode.notebook.onDidChangeCellOutputs, async (event) => {
@ -756,9 +757,9 @@ suite('Notebook API tests', function () {
await vscode.commands.executeCommand('notebook.cell.execute');
await event;
assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked
assert.strictEqual(cell.outputs[0].outputs.length, 1);
assert.strictEqual(cell.outputs[0].outputs[0].mime, 'text/plain');
assert.deepStrictEqual(new TextDecoder().decode(cell.outputs[0].outputs[0].data), 'my second output');
assert.strictEqual(cell.outputs[0].items.length, 1);
assert.strictEqual(cell.outputs[0].items[0].mime, 'text/plain');
assert.deepStrictEqual(new TextDecoder().decode(cell.outputs[0].items[0].data), 'my second output');
});
});
@ -796,9 +797,9 @@ suite('Notebook API tests', function () {
await vscode.commands.executeCommand('notebook.cell.cancelExecution');
await event;
assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked
assert.strictEqual(cell.outputs[0].outputs.length, 1);
assert.strictEqual(cell.outputs[0].outputs[0].mime, 'text/plain');
assert.deepStrictEqual(new TextDecoder().decode(cell.outputs[0].outputs[0].data), 'Canceled');
assert.strictEqual(cell.outputs[0].items.length, 1);
assert.strictEqual(cell.outputs[0].items[0].mime, 'text/plain');
assert.deepStrictEqual(new TextDecoder().decode(cell.outputs[0].items[0].data), 'Canceled');
});
cancelableKernel.controller.dispose();
@ -838,9 +839,9 @@ suite('Notebook API tests', function () {
await vscode.commands.executeCommand('notebook.cell.cancelExecution');
await event;
assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked
assert.strictEqual(cell.outputs[0].outputs.length, 1);
assert.strictEqual(cell.outputs[0].outputs[0].mime, 'text/plain');
assert.deepStrictEqual(new TextDecoder().decode(cell.outputs[0].outputs[0].data), 'Interrupted');
assert.strictEqual(cell.outputs[0].items.length, 1);
assert.strictEqual(cell.outputs[0].items[0].mime, 'text/plain');
assert.deepStrictEqual(new TextDecoder().decode(cell.outputs[0].items[0].data), 'Interrupted');
});
interruptableKernel.controller.dispose();
@ -1184,7 +1185,7 @@ suite('Notebook API tests', function () {
vscode.NotebookCellOutputItem.text('Some output', 'text/plain', undefined)
])]);
assert.strictEqual(cell.notebook.cellAt(0).outputs.length, 1);
assert.deepStrictEqual(new TextDecoder().decode(cell.notebook.cellAt(0).outputs[0].outputs[0].data), 'Some output');
assert.deepStrictEqual(new TextDecoder().decode(cell.notebook.cellAt(0).outputs[0].items[0].data), 'Some output');
task.end({});
called = true;
}

View file

@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.57.0",
"distro": "2f889e0f6ee745795ab935602189cac9672a478b",
"distro": "7eccefdc46cee4d9a6b8f55bbc2e14045b19d382",
"author": {
"name": "Microsoft Corporation"
},
@ -189,7 +189,7 @@
"style-loader": "^1.0.0",
"ts-loader": "^6.2.1",
"tsec": "0.1.4",
"typescript": "^4.3.0-dev.20210503",
"typescript": "^4.4.0-dev.20210528",
"typescript-formatter": "7.1.0",
"underscore": "^1.12.1",
"vinyl": "^2.0.0",

View file

@ -37,3 +37,10 @@
line-height: 16px;
margin-left: -4px;
}
.monaco-dropdown-with-primary > .dropdown-action-container > .monaco-dropdown > .dropdown-label > .action-label {
display: block;
background-size: 16px;
background-position: center center;
background-repeat: no-repeat;
}

View file

@ -554,3 +554,37 @@ export function mapFind<T, R>(array: Iterable<T>, mapFn: (value: T) => R | undef
return undefined;
}
/**
* Like Math.min with a delegate, and returns the winning index
*/
export function minIndex<T>(array: readonly T[], fn: (value: T) => number): number {
let minValue = Number.MAX_SAFE_INTEGER;
let minIdx = 0;
array.forEach((value, i) => {
const thisValue = fn(value);
if (thisValue < minValue) {
minValue = thisValue;
minIdx = i;
}
});
return minIdx;
}
/**
* Like Math.max with a delegate, and returns the winning index
*/
export function maxIndex<T>(array: readonly T[], fn: (value: T) => number): number {
let minValue = Number.MIN_SAFE_INTEGER;
let maxIdx = 0;
array.forEach((value, i) => {
const thisValue = fn(value);
if (thisValue > minValue) {
minValue = thisValue;
maxIdx = i;
}
});
return maxIdx;
}

View file

@ -295,4 +295,20 @@ suite('Arrays', () => {
remove();
assert.strictEqual(array.length, 0);
});
test('minIndex', () => {
const array = ['a', 'b', 'c'];
assert.strictEqual(arrays.minIndex(array, value => array.indexOf(value)), 0);
assert.strictEqual(arrays.minIndex(array, value => -array.indexOf(value)), 2);
assert.strictEqual(arrays.minIndex(array, _value => 0), 0);
assert.strictEqual(arrays.minIndex(array, value => value === 'b' ? 0 : 5), 1);
});
test('maxIndex', () => {
const array = ['a', 'b', 'c'];
assert.strictEqual(arrays.maxIndex(array, value => array.indexOf(value)), 2);
assert.strictEqual(arrays.maxIndex(array, value => -array.indexOf(value)), 0);
assert.strictEqual(arrays.maxIndex(array, _value => 0), 0);
assert.strictEqual(arrays.maxIndex(array, value => value === 'b' ? 5 : 0), 1);
});
});

View file

@ -13,7 +13,7 @@ import { GhostTextController, ShowNextInlineCompletionAction, ShowPreviousInline
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IViewZoneData } from 'vs/editor/browser/controller/mouseTarget';
import { ITextContentData, IViewZoneData } from 'vs/editor/browser/controller/mouseTarget';
export class InlineCompletionsHover implements IHoverPart {
constructor(
@ -51,16 +51,19 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan
return new HoverForeignElementAnchor(1000, this, Range.fromPositions(viewZoneData.positionBefore || viewZoneData.position, viewZoneData.positionBefore || viewZoneData.position));
}
}
if (mouseEvent.target.type === MouseTargetType.CONTENT_EMPTY) {
if (mouseEvent.target.type === MouseTargetType.CONTENT_EMPTY && mouseEvent.target.range) {
// handle the case where the mouse is over the empty portion of a line following ghost text
if (mouseEvent.target.range && controller.shouldShowHoverAt(mouseEvent.target.range)) {
if (controller.shouldShowHoverAt(mouseEvent.target.range)) {
return new HoverForeignElementAnchor(1000, this, mouseEvent.target.range);
}
}
if (mouseEvent.target.type === MouseTargetType.CONTENT_TEXT && mouseEvent.target.range && mouseEvent.target.detail) {
// handle the case where the mouse is directly over ghost text
const mightBeForeignElement = (<ITextContentData>mouseEvent.target.detail).mightBeForeignElement;
if (mightBeForeignElement && controller.shouldShowHoverAt(mouseEvent.target.range)) {
return new HoverForeignElementAnchor(1000, this, mouseEvent.target.range);
}
}
// let mightBeForeignElement = false;
// if (mouseEvent.target.type === MouseTargetType.CONTENT_TEXT && mouseEvent.target.detail) {
// mightBeForeignElement = (<ITextContentData>mouseEvent.target.detail).mightBeForeignElement;
// }
return null;
}

View file

@ -4,11 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import { IContextMenuProvider } from 'vs/base/browser/contextmenu';
import * as DOM from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { ActionViewItem, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
import { IAction } from 'vs/base/common/actions';
import * as DOM from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { Event } from 'vs/base/common/event';
import { KeyCode } from 'vs/base/common/keyCodes';
import { MenuItemAction } from 'vs/platform/actions/common/actions';
import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
@ -21,6 +22,10 @@ export class DropdownWithPrimaryActionViewItem extends BaseActionViewItem {
private _container: HTMLElement | null = null;
private _dropdownContainer: HTMLElement | null = null;
get onDidChangeDropdownVisibility(): Event<boolean> {
return this._dropdown.onDidChangeVisibility;
}
constructor(
primaryAction: MenuItemAction,
dropdownAction: IAction,
@ -33,10 +38,17 @@ export class DropdownWithPrimaryActionViewItem extends BaseActionViewItem {
super(null, primaryAction);
this._primaryAction = new MenuEntryActionViewItem(primaryAction, _keybindingService, _notificationService);
this._dropdown = new DropdownMenuActionViewItem(dropdownAction, dropdownMenuActions, this._contextMenuProvider, {
menuAsChild: true
menuAsChild: true,
classNames: ['codicon', 'codicon-chevron-down']
});
}
override setActionContext(newContext: unknown): void {
super.setActionContext(newContext);
this._primaryAction.setActionContext(newContext);
this._dropdown.setActionContext(newContext);
}
override render(container: HTMLElement): void {
this._container = container;
super.render(this._container);

View file

@ -131,7 +131,6 @@ export interface IWalkthrough {
readonly title: string;
readonly description: string;
readonly steps: IWalkthroughStep[];
readonly primary?: boolean;
readonly when?: string;
}

View file

@ -32,29 +32,32 @@ export interface WorkspaceTrustRequestOptions {
readonly message?: string;
}
export type WorkspaceTrustChangeEvent = Event<boolean>;
export const IWorkspaceTrustManagementService = createDecorator<IWorkspaceTrustManagementService>('workspaceTrustManagementService');
export interface IWorkspaceTrustManagementService {
readonly _serviceBrand: undefined;
onDidChangeTrust: WorkspaceTrustChangeEvent;
onDidChangeTrust: Event<boolean>;
onDidChangeTrustedFolders: Event<void>;
onDidInitiateWorkspaceTrustRequestOnStartup: Event<void>;
get acceptsOutOfWorkspaceFiles(): boolean;
set acceptsOutOfWorkspaceFiles(value: boolean);
addWorkspaceTrustTransitionParticipant(participant: IWorkspaceTrustTransitionParticipant): IDisposable;
initializeWorkspaceTrust(): Promise<void>;
readonly workspaceTrustInitialized: Promise<void>;
acceptsOutOfWorkspaceFiles: boolean;
isWorkpaceTrusted(): boolean;
canSetParentFolderTrust(): boolean;
setParentFolderTrust(trusted: boolean): Promise<void>;
canSetWorkspaceTrust(): Promise<boolean>;
recalculateWorkspaceTrust(): Promise<void>;
canSetWorkspaceTrust(): boolean;
setWorkspaceTrust(trusted: boolean): Promise<void>;
getUriTrustInfo(folder: URI): Promise<IWorkspaceTrustUriInfo>;
setUrisTrust(folders: URI[], trusted: boolean): Promise<void>;
getTrustedFolders(): URI[];
setTrustedFolders(folders: URI[]): Promise<void>;
getUriTrustInfo(uri: URI): Promise<IWorkspaceTrustUriInfo>;
setUrisTrust(uri: URI[], trusted: boolean): Promise<void>;
getTrustedUris(): URI[];
setTrustedUris(uris: URI[]): Promise<void>;
addWorkspaceTrustTransitionParticipant(participant: IWorkspaceTrustTransitionParticipant): IDisposable;
}
export const enum WorkspaceTrustUriResponse {

View file

@ -1090,11 +1090,12 @@ declare module 'vscode' {
* saved on disk and therefore the `scheme` must be checked before trying to access the underlying file or siblings on disk.
*
* @see {@link FileSystemProvider}
* @see {@link TextDocumentContentProvider}
*/
readonly uri: Uri;
// todo@API should we really expose this?
/**
* The type of notebook.
*/
// todo@API should this be called `notebookType` or `notebookKind`
readonly viewType: string;
@ -1148,24 +1149,27 @@ declare module 'vscode' {
getCells(range?: NotebookRange): NotebookCell[];
/**
* Save the document. The saving will be handled by the corresponding content provider
* Save the document. The saving will be handled by the corresponding {@link NotebookSerializer serializer}.
*
* @return A promise that will resolve to true when the document
* has been saved. If the file was not dirty or the save failed,
* will return false.
* has been saved. Will return false if the file was not dirty or when save failed.
*/
save(): Thenable<boolean>;
}
// todo@API jsdoc
export class NotebookCellMetadata {
/**
* Whether a code cell's editor is collapsed
*/
// todo@API decouple from metadata? extract as dedicated field or inside an options object and leave metadata purely for extensions?
readonly inputCollapsed?: boolean;
/**
* Whether a code cell's outputs are collapsed
*/
// todo@API decouple from metadata? extract as dedicated field or inside an options object and leave metadata purely for extensions?
readonly outputCollapsed?: boolean;
/**
@ -1191,13 +1195,35 @@ declare module 'vscode' {
with(change: { inputCollapsed?: boolean | null, outputCollapsed?: boolean | null, [key: string]: any }): NotebookCellMetadata;
}
/**
* The summary of a notebook cell execution.
*/
export interface NotebookCellExecutionSummary {
/**
* The order in which the execution happened.
*/
readonly executionOrder?: number;
/**
* If the exclusive finished successfully.
*/
readonly success?: boolean;
/**
* The unix timestamp at which execution started.
*/
// todo@API use duration instead of start/end?
readonly startTime?: number;
/**
* The unix timestamp at which execution ended.
*/
readonly endTime?: number;
}
// todo@API jsdoc
// todo@API remove this and use simple {}?
export class NotebookDocumentMetadata {
/**
* Additional attributes of the document metadata.
@ -1220,7 +1246,7 @@ declare module 'vscode' {
}
/**
* A notebook range represents on ordered pair of two cell indicies.
* A notebook range represents an ordered pair of two cell indicies.
* It is guaranteed that start is less than or equal to end.
*/
export class NotebookRange {
@ -1337,7 +1363,7 @@ declare module 'vscode' {
*/
data: Uint8Array;
//todo@API
//todo@API remove in favour of NotebookCellOutput#metadata
metadata?: { [key: string]: any };
/**
@ -1377,14 +1403,26 @@ declare module 'vscode' {
* ])
* ```
*/
//todo@API rename to items
outputs: NotebookCellOutputItem[];
items: NotebookCellOutputItem[];
//todo@API
//todo@API have this OR NotebookCellOutputItem#metadata but not both? Preference for this.
metadata?: { [key: string]: any };
/**
* Create new notebook output.
*
* @param outputs Notebook output items.
* @param metadata Optional metadata.
*/
constructor(outputs: NotebookCellOutputItem[], metadata?: { [key: string]: any });
/**
* Create new notebook output.
*
* @param outputs Notebook output items.
* @param id Identifier of this output.
* @param metadata Optional metadata.
*/
constructor(outputs: NotebookCellOutputItem[], id: string, metadata?: { [key: string]: any });
}
@ -1431,9 +1469,9 @@ declare module 'vscode' {
* @param kind The kind.
* @param value The source value.
* @param languageId The language identifier of the source value.
* @param outputs //TODO@API remove ctor?
* @param metadata //TODO@API remove ctor?
* @param executionSummary //TODO@API remove ctor?
* @param outputs //TODO@API remove from ctor?
* @param metadata //TODO@API remove from ctor?
* @param executionSummary //TODO@API remove from ctor?
*/
constructor(kind: NotebookCellKind, value: string, languageId: string, outputs?: NotebookCellOutput[], metadata?: NotebookCellMetadata, executionSummary?: NotebookCellExecutionSummary);
}
@ -1495,6 +1533,12 @@ declare module 'vscode' {
serializeNotebook(data: NotebookData, token: CancellationToken): Uint8Array | Thenable<Uint8Array>;
}
/**
* Notebook content options define what parts of a notebook are persisted. Note
*
* For instance, a notebook serializer can opt-out of saving outputs and in that case the editor doesn't mark a
* notebooks as {@link NotebookDocument.isDirty dirty} when its output has changed.
*/
export interface NotebookDocumentContentOptions {
/**
* Controls if outputs change will trigger notebook document content change and if it will be used in the diff editor
@ -1512,9 +1556,13 @@ declare module 'vscode' {
* Controls if a document metadata property change will trigger notebook document content change and if it will be used in the diff editor
* Default to false. If the content provider doesn't persisit a metadata property in the file document, it should be set to true.
*/
// todo@API ...NotebookDocument... or just ...Notebook... just like...Cell... above
transientDocumentMetadata?: { [K in keyof NotebookDocumentMetadata]?: boolean };
}
/**
* A callback that is invoked by the editor whenever cell execution has been triggered.
*/
export interface NotebookExecuteHandler {
/**
* @param cells The notebook cells to execute.
@ -1524,12 +1572,28 @@ declare module 'vscode' {
(this: NotebookController, cells: NotebookCell[], notebook: NotebookDocument, controller: NotebookController): void | Thenable<void>
}
/**
* Notebook controller affinity for notebook documents.
*
* @see {@link NotebookController.updateNotebookAffinity}
*/
export enum NotebookControllerAffinity {
/**
* Default affinity.
*/
Default = 1,
/**
* A controller is preferred for a notebook.
*/
Preferred = 2
}
/**
* Represents a script that is loaded into the notebook renderer before rendering output. This allows
* to provide and share functionality for notebook markup and notebook output renderers.
*/
export class NotebookRendererScript {
/**
@ -1551,6 +1615,8 @@ declare module 'vscode' {
constructor(uri: Uri, provides?: string | string[]);
}
// todo@api jsdoc
// todo@api Inline unless we can come up with more (future) properties
export interface NotebookCellExecuteStartContext {
/**
* The time that execution began, in milliseconds in the Unix epoch. Used to drive the clock
@ -1559,11 +1625,14 @@ declare module 'vscode' {
startTime?: number;
}
// todo@api jsdoc
// todo@api Inline unless we can come up with more (future) properties
export interface NotebookCellExecuteEndContext {
/**
* If true, a green check is shown on the cell status bar.
* If false, a red X is shown.
*/
// todo@api undefined === false, so by default cells execution fails?
success?: boolean;
/**
@ -1572,18 +1641,13 @@ declare module 'vscode' {
endTime?: number;
}
// todo@API jsdoc slightly outdated: kernel, notebook.createNotebookCellExecution
/**
* A NotebookCellExecution is how the kernel modifies a notebook cell as it is executing. When
* {@link notebook.createNotebookCellExecution `createNotebookCellExecution`} is called, the cell
* enters the Pending state. When `start()` is called on the execution task, it enters the Executing state. When
* `end()` is called, it enters the Idle state. While in the Executing state, cell outputs can be
* modified with the methods on the run task.
* A NotebookCellExecution is how {@link NotebookController notebook controller} modify a notebook cell as
* it is executing.
*
* All outputs methods operate on this NotebookCellExecution's cell by default. They optionally take
* a cellIndex parameter that allows them to modify the outputs of other cells. `appendOutputItems` and
* `replaceOutputItems` operate on the output with the given ID, which can be an output on any cell. They
* all resolve once the output edit has been applied.
* When a cell execution object is created, the cell enters the {@link NotebookCellExecutionState.Pending `Pending`} state.
* When {@link NotebookCellExecution.start `start(...)`} is called on the execution task, it enters the {@link NotebookCellExecutionState.Executing `Executing`} state. When
* {@link NotebookCellExecution.end `end(...)`} is called, it enters the {@link NotebookCellExecutionState.Idle `Idle`} state.
*/
export interface NotebookCellExecution {
@ -1601,9 +1665,6 @@ declare module 'vscode' {
*/
readonly token: CancellationToken;
//todo@API remove? use cell.notebook instead?
readonly document: NotebookDocument;
/**
* Set and unset the order of this cell execution.
*/
@ -1611,18 +1672,70 @@ declare module 'vscode' {
// todo@API inline context object?
start(context?: NotebookCellExecuteStartContext): void;
// todo@API inline context object?
end(result?: NotebookCellExecuteEndContext): void;
// todo@API use object instead of index? FF
clearOutput(cellIndex?: number): Thenable<void>;
appendOutput(out: NotebookCellOutput | NotebookCellOutput[], cellIndex?: number): Thenable<void>;
replaceOutput(out: NotebookCellOutput | NotebookCellOutput[], cellIndex?: number): Thenable<void>;
// todo@API use object instead of index?
appendOutputItems(items: NotebookCellOutputItem | NotebookCellOutputItem[], outputId: string): Thenable<void>;
replaceOutputItems(items: NotebookCellOutputItem | NotebookCellOutputItem[], outputId: string): Thenable<void>;
/**
* Clears the output of the cell that is executing or of another cell that is affected by this execution.
*
* @param cell Cell for which output is cleared. Defaults to the {@link NotebookCellExecution.cell cell} of
* this execution.
* @return A thenable that resolves when the operation finished.
*/
clearOutput(cell?: NotebookCell): Thenable<void>;
/**
* Replace the output of the cell that is executing or of another cell that is affected by this execution.
*
* @param out Output that replaces the current output.
* @param cell Cell for which output is cleared. Defaults to the {@link NotebookCellExecution.cell cell} of
* this execution.
* @return A thenable that resolves when the operation finished.
*/
replaceOutput(out: NotebookCellOutput | NotebookCellOutput[], cell?: NotebookCell): Thenable<void>;
/**
* Append to the output of the cell that is executing or to another cell that is affected by this execution.
*
* @param out Output that is appended to the current output.
* @param cell Cell for which output is cleared. Defaults to the {@link NotebookCellExecution.cell cell} of
* this execution.
* @return A thenable that resolves when the operation finished.
*/
appendOutput(out: NotebookCellOutput | NotebookCellOutput[], cell?: NotebookCell): Thenable<void>;
/**
* Replace all output items of existing cell output.
*
* @param items Output items that replace the items of existing output.
* @param output Output object or the identifier of one.
* @return A thenable that resolves when the operation finished.
*/
replaceOutputItems(items: NotebookCellOutputItem | NotebookCellOutputItem[], output: NotebookCellOutput | string): Thenable<void>;
/**
* Append output items to existing cell output.
*
* @param items Output items that are append to existing output.
* @param output Output object or the identifier of one.
* @return A thenable that resolves when the operation finished.
*/
appendOutputItems(items: NotebookCellOutputItem | NotebookCellOutputItem[], output: NotebookCellOutput | string): Thenable<void>;
}
/**
* A notebook controller represents an entity that can execute notebook cells. This is often referred to as a kernel.
*
* There can be multiple controllers and the editor will let users choose which controller to use for a certain notebook. The
* {@link NotebookController.viewType `viewType`}-property defines for what kind of notebooks a controller is for and
* the {@link NotebookController.updateNotebookAffinity `updateNotebookAffinity`}-function allows controllers to set a preference
* for specific notebooks.
*
* When a cell is being run the editor will invoke the {@link NotebookController.executeHandler `executeHandler`} and a controller
* is expected to create and finalize a {@link NotebookCellExecution notebook cell execution}. However, controllers are also free
* to create executions by themselves.
*/
export interface NotebookController {
/**
@ -1636,6 +1749,7 @@ declare module 'vscode' {
/**
* The notebook view type this controller is for.
*/
// todo@api rename to notebookType?
readonly viewType: string;
/**
@ -1689,8 +1803,8 @@ declare module 'vscode' {
* By default cell execution is canceled via {@link NotebookCellExecution.token tokens}. Cancellation
* tokens require that a controller can keep track of its execution so that it can cancel a specific execution at a later
* point. Not all scenarios allow for that, eg. REPL-style controllers often work by interrupting whatever is currently
* running. For those cases the {@link NotebookInterruptHandler interrupt handler} exists - it can be thought of as the
* equivalent of `SIGINT` or `Control+C` in terminals.
* running. For those cases the interrupt handler exists - it can be thought of as the equivalent of `SIGINT`
* or `Control+C` in terminals.
*
* _Note_ that supporting {@link NotebookCellExecution.token cancellation tokens} is preferred and that interrupt handlers should
* only be used when tokens cannot be supported.
@ -1711,7 +1825,7 @@ declare module 'vscode' {
/**
* A controller can set affinities for specific notebook documents. This allows a controller
* to be more important for some notebooks.
* to be presented more prominent for some notebooks.
*
* @param notebook The notebook for which a priority is set.
* @param affinity A controller affinity
@ -1721,8 +1835,11 @@ declare module 'vscode' {
/**
* Create a cell execution task.
*
* _Note_ that there can only be one execution per cell at a time and that an error is thrown if
* a cell execution is created while another is still active.
*
* This should be used in response to the {@link NotebookController.executeHandler execution handler}
* being calleed or when cell execution has been started else, e.g when a cell was already
* being called or when cell execution has been started else, e.g when a cell was already
* executing or when cell execution was triggered from another source.
*
* @param cell The notebook cell for which to create the execution.
@ -1734,7 +1851,8 @@ declare module 'vscode' {
readonly rendererScripts: NotebookRendererScript[];
/**
* An event that fires when a renderer (see `preloads`) has send a message to the controller.
* An event that fires when a {@link NotebookController.rendererScripts renderer script} has send a message to
* the controller.
*/
readonly onDidReceiveMessage: Event<{ editor: NotebookEditor, message: any }>;
@ -1754,18 +1872,43 @@ declare module 'vscode' {
asWebviewUri(localResource: Uri): Uri;
}
/**
* The execution state of a notebook cell.
*/
export enum NotebookCellExecutionState {
/**
* The cell is idle.
*/
Idle = 1,
/**
* Execution for the cell is pending.
*/
Pending = 2,
/**
* The cell is currently executing.
*/
Executing = 3,
}
/**
* An event describing a cell execution state change.
*/
export interface NotebookCellExecutionStateChangeEvent {
/**
* The {@link NotebookDocument notebook document} for which the cell execution state has changed.
*/
// todo@api remove? use cell.notebook instead?
readonly document: NotebookDocument;
/**
* The {@link NotebookCell cell} for which the execution state has changed.
*/
readonly cell: NotebookCell;
/**
* The new execution state of the cell.
*/
// todo@API rename to state?
readonly executionState: NotebookCellExecutionState;
}
@ -1785,6 +1928,7 @@ declare module 'vscode' {
Right = 2
}
// todo@api jsdoc
export class NotebookCellStatusBarItem {
text: string;
alignment: NotebookCellStatusBarAlignment;
@ -1796,6 +1940,7 @@ declare module 'vscode' {
constructor(text: string, alignment: NotebookCellStatusBarAlignment, command?: string | Command, tooltip?: string, priority?: number, accessibilityInformation?: AccessibilityInformation);
}
// todo@api jsdoc
export interface NotebookCellStatusBarItemProvider {
/**
* Implement and fire this event to signal that statusbar items have changed. The provide method will be called again.
@ -1805,9 +1950,21 @@ declare module 'vscode' {
/**
* The provider will be called when the cell scrolls into view, when its content, outputs, language, or metadata change, and when it changes execution state.
*/
// todo@API rename to ...NotebookCell...
provideCellStatusBarItems(cell: NotebookCell, token: CancellationToken): ProviderResult<NotebookCellStatusBarItem[]>;
}
/**
* Namespace for notebooks.
*
* The notebooks functionality is composed of three loosly coupled components:
*
* 1. {@link NotebookSerializer} enable the editor to open and save notebooks
* 2. {@link NotebookController} own the execution of notebooks, e.g they create output from code cells.
* 3. NotebookRenderer present notebook output in the editor. They run in a separate context.
*/
// todo@api rename to notebooks?
// todo@api what should be in this namespace? should notebookDocuments and friends be in the workspace namespace?
export namespace notebook {
/**
@ -1876,14 +2033,18 @@ declare module 'vscode' {
* @param viewType A notebook view type for which this controller is for.
* @param label The label of the controller
* @param handler
* @param rendererScripts
* @param rendererScripts todo@API should renderer scripts come later?
*/
export function createNotebookController(id: string, viewType: string, label: string, handler?: NotebookExecuteHandler, rendererScripts?: NotebookRendererScript[]): NotebookController;
// todo@API what is this used for?
// todo@API qualify cell, ...NotebookCell...
/**
* An {@link Event} which fires when the execution state of a cell has changed.
*/
// todo@API this is an event that is fired for a property that cells don't have and that makes me wonder
// how a correct consumer work, e.g the consumer could have been late and missed an event?
export const onDidChangeNotebookCellExecutionState: Event<NotebookCellExecutionStateChangeEvent>;
// todo@api jsdoc
export function registerNotebookCellStatusBarItemProvider(notebookType: string, provider: NotebookCellStatusBarItemProvider): Disposable;
}

View file

@ -16,7 +16,7 @@ import { isEqual, isEqualOrParent, toLocalResource } from 'vs/base/common/resour
import { URI, UriComponents } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { FileOperation, FileSystemProviderCapabilities, IFileService } from 'vs/platform/files/common/files';
import { FileOperation, IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label';
import { IUndoRedoService, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo';
@ -445,16 +445,12 @@ class MainThreadCustomEditorModel extends ResourceWorkingCopy implements ICustom
private readonly _onDidChangeContent: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChangeContent: Event<void> = this._onDidChangeContent.event;
readonly onDidChangeEditable = Event.None;
readonly onDidChangeReadonly = Event.None;
//#endregion
public isEditable(): boolean {
return this._editable;
}
public isOnReadonlyFileSystem(): boolean {
return this.fileService.hasCapability(this.editorResource, FileSystemProviderCapabilities.Readonly);
public isReadonly(): boolean {
return !this._editable;
}
public get viewType() {

View file

@ -44,7 +44,7 @@ export class ExtHostCell {
};
}
private _outputs: extHostTypes.NotebookCellOutput[];
private _outputs: vscode.NotebookCellOutput[];
private _metadata: extHostTypes.NotebookCellMetadata;
private _previousResult: vscode.NotebookCellExecutionSummary | undefined;
@ -102,9 +102,9 @@ export class ExtHostCell {
const output = this._outputs.find(op => op.id === outputId);
if (output) {
if (!append) {
output.outputs.length = 0;
output.items.length = 0;
}
output.outputs.push(...newItems);
output.items.push(...newItems);
}
}

View file

@ -381,38 +381,53 @@ class NotebookCellExecutionTask extends Disposable {
this.applyEdits([edit]);
}
private cellIndexToHandle(cellIndex: number | undefined): number {
if (typeof cellIndex !== 'number') {
return this._cell.handle;
private cellIndexToHandle(cellOrCellIndex: vscode.NotebookCell | number | undefined): number {
let cell: ExtHostCell | undefined = this._cell;
if (typeof cellOrCellIndex === 'number') {
// todo@jrieken remove support for number shortly
cell = this._document.getCellFromIndex(cellOrCellIndex);
} else if (cellOrCellIndex) {
cell = this._document.getCellFromApiCell(cellOrCellIndex);
}
const cell = this._document.getCellFromIndex(cellIndex);
if (!cell) {
throw new Error('INVALID cell index');
throw new Error('INVALID cell');
}
return cell.handle;
}
private validateAndConvertOutputs(items: vscode.NotebookCellOutput[]): IOutputDto[] {
return items.map(output => {
const newOutput = NotebookCellOutput.ensureUniqueMimeTypes(output.outputs, true);
if (newOutput === output.outputs) {
const newOutput = NotebookCellOutput.ensureUniqueMimeTypes(output.items, true);
if (newOutput === output.items) {
return extHostTypeConverters.NotebookCellOutput.from(output);
}
return extHostTypeConverters.NotebookCellOutput.from({
outputs: newOutput,
items: newOutput,
id: output.id,
metadata: output.metadata
});
});
}
private async updateOutputs(outputs: vscode.NotebookCellOutput | vscode.NotebookCellOutput[], cell: vscode.NotebookCell | number | undefined, append: boolean): Promise<void> {
const handle = this.cellIndexToHandle(cell);
const outputDtos = this.validateAndConvertOutputs(asArray(outputs));
return this.applyEditSoon({ editType: CellEditType.Output, handle, append, outputs: outputDtos });
}
private async updateOutputItems(items: vscode.NotebookCellOutputItem | vscode.NotebookCellOutputItem[], outputOrOutputId: vscode.NotebookCellOutput | string, append: boolean): Promise<void> {
if (NotebookCellOutput.isNotebookCellOutput(outputOrOutputId)) {
outputOrOutputId = outputOrOutputId.id;
}
items = NotebookCellOutput.ensureUniqueMimeTypes(asArray(items), true);
return this.applyEditSoon({ editType: CellEditType.OutputItems, items: items.map(extHostTypeConverters.NotebookCellOutputItem.from), outputId: outputOrOutputId, append });
}
asApiObject(): vscode.NotebookCellExecution {
const that = this;
return Object.freeze(<vscode.NotebookCellExecution>{
const result: vscode.NotebookCellExecution = {
get token() { return that._tokenSource.token; },
get document() { return that._document.apiNotebook; },
get cell() { return that._cell.apiCell; },
get executionOrder() { return that._executionOrder; },
set executionOrder(v: number | undefined) {
that._executionOrder = v;
@ -450,37 +465,32 @@ class NotebookCellExecutionTask extends Disposable {
});
},
clearOutput(cellIndex?: number): Thenable<void> {
clearOutput(cell?: vscode.NotebookCell | number): Thenable<void> {
that.verifyStateForOutput();
return this.replaceOutput([], cellIndex);
return that.updateOutputs([], cell, false);
},
async appendOutput(outputs: vscode.NotebookCellOutput | vscode.NotebookCellOutput[], cellIndex?: number): Promise<void> {
async appendOutput(outputs: vscode.NotebookCellOutput | vscode.NotebookCellOutput[], cell?: vscode.NotebookCell | number): Promise<void> {
that.verifyStateForOutput();
const handle = that.cellIndexToHandle(cellIndex);
const outputDtos = that.validateAndConvertOutputs(asArray(outputs));
return that.applyEditSoon({ editType: CellEditType.Output, handle, append: true, outputs: outputDtos });
return that.updateOutputs(outputs, cell, true);
},
async replaceOutput(outputs: vscode.NotebookCellOutput | vscode.NotebookCellOutput[], cellIndex?: number): Promise<void> {
async replaceOutput(outputs: vscode.NotebookCellOutput | vscode.NotebookCellOutput[], cell?: vscode.NotebookCell | number): Promise<void> {
that.verifyStateForOutput();
const handle = that.cellIndexToHandle(cellIndex);
const outputDtos = that.validateAndConvertOutputs(asArray(outputs));
return that.applyEditSoon({ editType: CellEditType.Output, handle, outputs: outputDtos });
return that.updateOutputs(outputs, cell, false);
},
async appendOutputItems(items: vscode.NotebookCellOutputItem | vscode.NotebookCellOutputItem[], outputId: string): Promise<void> {
async appendOutputItems(items: vscode.NotebookCellOutputItem | vscode.NotebookCellOutputItem[], output: vscode.NotebookCellOutput | string): Promise<void> {
that.verifyStateForOutput();
items = NotebookCellOutput.ensureUniqueMimeTypes(asArray(items), true);
return that.applyEditSoon({ editType: CellEditType.OutputItems, append: true, items: items.map(extHostTypeConverters.NotebookCellOutputItem.from), outputId });
that.updateOutputItems(items, output, true);
},
async replaceOutputItems(items: vscode.NotebookCellOutputItem | vscode.NotebookCellOutputItem[], outputId: string): Promise<void> {
async replaceOutputItems(items: vscode.NotebookCellOutputItem | vscode.NotebookCellOutputItem[], output: vscode.NotebookCellOutput | string): Promise<void> {
that.verifyStateForOutput();
items = NotebookCellOutput.ensureUniqueMimeTypes(asArray(items), true);
return that.applyEditSoon({ editType: CellEditType.OutputItems, items: items.map(extHostTypeConverters.NotebookCellOutputItem.from), outputId });
that.updateOutputItems(items, output, false);
}
});
};
return Object.freeze(result);
}
}

View file

@ -1547,10 +1547,10 @@ export namespace NotebookCellOutputItem {
}
export namespace NotebookCellOutput {
export function from(output: types.NotebookCellOutput): notebooks.IOutputDto {
export function from(output: vscode.NotebookCellOutput): notebooks.IOutputDto {
return {
outputId: output.id,
outputs: output.outputs.map(NotebookCellOutputItem.from),
outputs: output.items.map(NotebookCellOutputItem.from),
metadata: output.metadata
};
}

View file

@ -9,7 +9,7 @@ import { IRelativePattern } from 'vs/base/common/glob';
import { isMarkdownString, MarkdownString as BaseMarkdownString } from 'vs/base/common/htmlContent';
import { ReadonlyMapView, ResourceMap } from 'vs/base/common/map';
import { normalizeMimeType } from 'vs/base/common/mime';
import { isStringArray } from 'vs/base/common/types';
import { isArray, isStringArray } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files';
@ -3075,11 +3075,11 @@ export class NotebookCellData {
kind: NotebookCellKind;
value: string;
languageId: string;
outputs?: NotebookCellOutput[];
outputs?: vscode.NotebookCellOutput[];
metadata?: NotebookCellMetadata;
executionSummary?: vscode.NotebookCellExecutionSummary;
constructor(kind: NotebookCellKind, value: string, languageId: string, outputs?: NotebookCellOutput[], metadata?: NotebookCellMetadata, executionSummary?: vscode.NotebookCellExecutionSummary) {
constructor(kind: NotebookCellKind, value: string, languageId: string, outputs?: vscode.NotebookCellOutput[], metadata?: NotebookCellMetadata, executionSummary?: vscode.NotebookCellExecutionSummary) {
this.kind = kind;
this.value = value;
this.languageId = languageId;
@ -3164,6 +3164,16 @@ export class NotebookCellOutputItem {
export class NotebookCellOutput {
static isNotebookCellOutput(candidate: any): candidate is vscode.NotebookCellOutput {
if (candidate instanceof NotebookCellOutput) {
return true;
}
if (!candidate || typeof candidate !== 'object') {
return false;
}
return typeof (<NotebookCellOutput>candidate).id === 'string' && isArray((<NotebookCellOutput>candidate).items);
}
static ensureUniqueMimeTypes(items: NotebookCellOutputItem[], warn: boolean = false): NotebookCellOutputItem[] {
const seen = new Set<string>();
const removeIdx = new Set<number>();
@ -3187,15 +3197,18 @@ export class NotebookCellOutput {
}
id: string;
outputs: NotebookCellOutputItem[];
items: NotebookCellOutputItem[];
metadata?: Record<string, any>;
get outputs() { return this.items; }
set outputs(value) { this.items = value; }
constructor(
outputs: NotebookCellOutputItem[],
items: NotebookCellOutputItem[],
idOrMetadata?: string | Record<string, any>,
metadata?: Record<string, any>
) {
this.outputs = NotebookCellOutput.ensureUniqueMimeTypes(outputs, true);
this.items = NotebookCellOutput.ensureUniqueMimeTypes(items, true);
if (typeof idOrMetadata === 'string') {
this.id = idOrMetadata;
this.metadata = metadata;

View file

@ -60,7 +60,7 @@ import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/ur
import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService';
import { BrowserWindow } from 'vs/workbench/browser/window';
import { ITimerService } from 'vs/workbench/services/timer/browser/timerService';
import { RemoteWorkspaceTrustManagementService, WorkspaceTrustManagementService } from 'vs/workbench/services/workspaces/common/workspaceTrust';
import { WorkspaceTrustManagementService } from 'vs/workbench/services/workspaces/common/workspaceTrust';
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
import { HTMLFileSystemProvider } from 'vs/platform/files/browser/htmlFileSystemProvider';
import { IOpenerService } from 'vs/platform/opener/common/opener';
@ -210,10 +210,7 @@ class BrowserMain extends Disposable {
]);
// Workspace Trust Service
const workspaceTrustManagementService = !environmentService.remoteAuthority ?
new WorkspaceTrustManagementService(configurationService, storageService, uriIdentityService, environmentService, configurationService) :
new RemoteWorkspaceTrustManagementService(configurationService, storageService, uriIdentityService, environmentService, configurationService, remoteAuthorityResolverService);
await workspaceTrustManagementService.initializeWorkspaceTrust();
const workspaceTrustManagementService = new WorkspaceTrustManagementService(configurationService, storageService, uriIdentityService, environmentService, configurationService, remoteAuthorityResolverService);
serviceCollection.set(IWorkspaceTrustManagementService, workspaceTrustManagementService);
// Update workspace trust so that configuration is updated accordingly

View file

@ -16,9 +16,6 @@ import { dirname, isEqual } from 'vs/base/common/resources';
*/
export abstract class AbstractResourceEditorInput extends EditorInput implements IEditorInputWithPreferredResource {
private _preferredResource: URI;
get preferredResource(): URI { return this._preferredResource; }
override get capabilities(): EditorInputCapabilities {
let capabilities = EditorInputCapabilities.None;
@ -33,6 +30,9 @@ export abstract class AbstractResourceEditorInput extends EditorInput implements
return capabilities;
}
private _preferredResource: URI;
get preferredResource(): URI { return this._preferredResource; }
constructor(
readonly resource: URI,
preferredResource: URI | undefined,
@ -48,7 +48,7 @@ export abstract class AbstractResourceEditorInput extends EditorInput implements
private registerListeners(): void {
// Clear label memoizer on certain events that have impact
// Clear our labels on certain label related events
this._register(this.labelService.onDidChangeFormatters(e => this.onLabelEvent(e.scheme)));
this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(e => this.onLabelEvent(e.scheme)));
this._register(this.fileService.onDidChangeFileSystemProviderCapabilities(e => this.onLabelEvent(e.scheme)));

View file

@ -105,7 +105,7 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi
override getTelemetryDescriptor(): { [key: string]: unknown } {
const descriptor = this.primary.getTelemetryDescriptor();
return Object.assign(descriptor, super.getTelemetryDescriptor());
return { ...descriptor, ...super.getTelemetryDescriptor() };
}
override isDirty(): boolean {
@ -129,7 +129,7 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi
}
override matches(otherInput: unknown): boolean {
if (otherInput === this) {
if (super.matches(otherInput)) {
return true;
}

View file

@ -165,6 +165,17 @@ export class TextResourceEditorInput extends AbstractTextResourceEditorInput imp
}
override async resolve(): Promise<ITextEditorModel> {
// Unset preferred contents and mode after resolving
// once to prevent these properties to stick. We still
// want the user to change the language mode in the editor
// and want to show updated contents (if any) in future
// `resolve` calls.
const preferredContents = this.preferredContents;
const preferredMode = this.preferredMode;
this.preferredContents = undefined;
this.preferredMode = undefined;
if (!this.modelReference) {
this.modelReference = this.textModelResolverService.createModelReference(this.resource);
}
@ -183,23 +194,15 @@ export class TextResourceEditorInput extends AbstractTextResourceEditorInput imp
this.cachedModel = model;
// Set contents and mode if preferred
if (typeof this.preferredContents === 'string' || typeof this.preferredMode === 'string') {
model.updateTextEditorModel(typeof this.preferredContents === 'string' ? createTextBufferFactory(this.preferredContents) : undefined, this.preferredMode);
if (typeof preferredContents === 'string' || typeof preferredMode === 'string') {
model.updateTextEditorModel(typeof preferredContents === 'string' ? createTextBufferFactory(preferredContents) : undefined, preferredMode);
}
// Unset preferred contents and mode after having applied
// them once to prevent these properties to stick. We still
// want the user to change the language mode in the editor
// and want to show updated contents (if any) in future
// `resolve` calls.
this.preferredContents = undefined;
this.preferredMode = undefined;
return model;
}
override matches(otherInput: unknown): boolean {
if (otherInput === this) {
if (super.matches(otherInput)) {
return true;
}

View file

@ -4,16 +4,16 @@
*--------------------------------------------------------------------------------------------*/
import { VSBuffer } from 'vs/base/common/buffer';
import { memoize } from 'vs/base/common/decorators';
import { IReference } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { basename } from 'vs/base/common/path';
import { isEqual } from 'vs/base/common/resources';
import { dirname, isEqual } from 'vs/base/common/resources';
import { assertIsDefined } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
import { FileSystemProviderCapabilities, IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
@ -79,6 +79,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
@IFileDialogService private readonly fileDialogService: IFileDialogService,
@IEditorService private readonly editorService: IEditorService,
@IUndoRedoService private readonly undoRedoService: IUndoRedoService,
@IFileService private readonly fileService: IFileService
) {
super(id, viewType, '', webview, webviewWorkbenchService);
this._editorResource = resource;
@ -86,6 +87,36 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
this._defaultDirtyState = options.startsDirty;
this._backupId = options.backupId;
this._untitledDocumentData = options.untitledDocumentData;
this.registerListeners();
}
private registerListeners(): void {
// Clear our labels on certain label related events
this._register(this.labelService.onDidChangeFormatters(e => this.onLabelEvent(e.scheme)));
this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(e => this.onLabelEvent(e.scheme)));
this._register(this.fileService.onDidChangeFileSystemProviderCapabilities(e => this.onLabelEvent(e.scheme)));
}
private onLabelEvent(scheme: string): void {
if (scheme === this.resource.scheme) {
this.updateLabel();
}
}
private updateLabel(): void {
// Clear any cached labels from before
this._shortDescription = undefined;
this._mediumDescription = undefined;
this._longDescription = undefined;
this._shortTitle = undefined;
this._mediumTitle = undefined;
this._longTitle = undefined;
// Trigger recompute of label
this._onDidChangeLabel.fire();
}
public override get typeId(): string {
@ -99,8 +130,14 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
capabilities |= EditorInputCapabilities.Singleton;
}
if (this._modelRef && !this._modelRef.object.isEditable()) {
capabilities |= EditorInputCapabilities.Readonly;
if (this._modelRef) {
if (this._modelRef.object.isReadonly()) {
capabilities |= EditorInputCapabilities.Readonly;
}
} else {
if (this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly)) {
capabilities |= EditorInputCapabilities.Readonly;
}
}
if (this.resource.scheme === Schemas.untitled) {
@ -115,56 +152,101 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
return this.decorateLabel(name);
}
override matches(other: IEditorInput): boolean {
override getDescription(verbosity = Verbosity.MEDIUM): string | undefined {
switch (verbosity) {
case Verbosity.SHORT:
return this.shortDescription;
case Verbosity.LONG:
return this.longDescription;
case Verbosity.MEDIUM:
default:
return this.mediumDescription;
}
}
private _shortDescription: string | undefined = undefined;
private get shortDescription(): string {
if (typeof this._shortDescription !== 'string') {
this._shortDescription = this.labelService.getUriBasenameLabel(dirname(this.resource));
}
return this._shortDescription;
}
private _mediumDescription: string | undefined = undefined;
private get mediumDescription(): string {
if (typeof this._mediumDescription !== 'string') {
this._mediumDescription = this.labelService.getUriLabel(dirname(this.resource), { relative: true });
}
return this._mediumDescription;
}
private _longDescription: string | undefined = undefined;
private get longDescription(): string {
if (typeof this._longDescription !== 'string') {
this._longDescription = this.labelService.getUriLabel(dirname(this.resource));
}
return this._longDescription;
}
private _shortTitle: string | undefined = undefined;
private get shortTitle(): string {
if (typeof this._shortTitle !== 'string') {
this._shortTitle = this.getName();
}
return this._shortTitle;
}
private _mediumTitle: string | undefined = undefined;
private get mediumTitle(): string {
if (typeof this._mediumTitle !== 'string') {
this._mediumTitle = this.labelService.getUriLabel(this.resource, { relative: true });
}
return this._mediumTitle;
}
private _longTitle: string | undefined = undefined;
private get longTitle(): string {
if (typeof this._longTitle !== 'string') {
this._longTitle = this.labelService.getUriLabel(this.resource);
}
return this._longTitle;
}
override getTitle(verbosity?: Verbosity): string {
switch (verbosity) {
case Verbosity.SHORT:
return this.decorateLabel(this.shortTitle);
case Verbosity.LONG:
return this.decorateLabel(this.longTitle);
default:
case Verbosity.MEDIUM:
return this.decorateLabel(this.mediumTitle);
}
}
private decorateLabel(label: string): string {
const readonly = this.hasCapability(EditorInputCapabilities.Readonly);
const orphaned = !!this._modelRef?.object.isOrphaned();
return decorateFileEditorLabel(label, { orphaned, readonly });
}
public override matches(other: IEditorInput): boolean {
return this === other || (other instanceof CustomEditorInput
&& this.viewType === other.viewType
&& isEqual(this.resource, other.resource));
}
override copy(): IEditorInput {
public override copy(): IEditorInput {
return CustomEditorInput.create(this.instantiationService, this.resource, this.viewType, this.group, this.webview.options);
}
@memoize
private get shortTitle(): string {
return this.getName();
}
@memoize
private get mediumTitle(): string {
return this.labelService.getUriLabel(this.resource, { relative: true });
}
@memoize
private get longTitle(): string {
return this.labelService.getUriLabel(this.resource);
}
public override getTitle(verbosity?: Verbosity): string {
switch (verbosity) {
case Verbosity.SHORT:
return this.decorateLabel(this.shortTitle);
default:
case Verbosity.MEDIUM:
return this.decorateLabel(this.mediumTitle);
case Verbosity.LONG:
return this.decorateLabel(this.longTitle);
}
}
private decorateLabel(label: string): string {
const orphaned = !!this._modelRef?.object.isOrphaned();
const readonly = this._modelRef
? this._modelRef.object.isEditable() && this._modelRef.object.isOnReadonlyFileSystem()
: false;
return decorateFileEditorLabel(label, {
orphaned,
readonly
});
}
public override isDirty(): boolean {
if (!this._modelRef) {
return !!this._defaultDirtyState;
@ -226,7 +308,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
this._modelRef = this._register(assertIsDefined(await this.customEditorService.models.tryRetain(this.resource, this.viewType)));
this._register(this._modelRef.object.onDidChangeDirty(() => this._onDidChangeDirty.fire()));
this._register(this._modelRef.object.onDidChangeOrphaned(() => this._onDidChangeLabel.fire()));
this._register(this._modelRef.object.onDidChangeEditable(() => this._onDidChangeCapabilities.fire()));
this._register(this._modelRef.object.onDidChangeReadonly(() => this._onDidChangeCapabilities.fire()));
// If we're loading untitled file data we should ensure it's dirty
if (this._untitledDocumentData) {
this._defaultDirtyState = true;
@ -239,7 +321,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
return null;
}
override rename(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined {
public override rename(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined {
// See if we can keep using the same custom editor provider
const editorInfo = this.customEditorService.getCustomEditor(this.viewType);
if (editorInfo?.matches(newResource)) {
@ -293,18 +375,18 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
return other;
}
get backupId(): string | undefined {
public get backupId(): string | undefined {
if (this._modelRef) {
return this._modelRef.object.backupId;
}
return this._backupId;
}
get untitledDocumentData(): VSBuffer | undefined {
public get untitledDocumentData(): VSBuffer | undefined {
return this._untitledDocumentData;
}
override asResourceEditorInput(groupId: GroupIdentifier): IResourceEditorInput {
public override asResourceEditorInput(groupId: GroupIdentifier): IResourceEditorInput {
return {
resource: this.resource,
options: {

View file

@ -57,9 +57,8 @@ export interface ICustomEditorModel extends IDisposable {
readonly resource: URI;
readonly backupId: string | undefined;
isEditable(): boolean;
readonly onDidChangeEditable: Event<void>;
isOnReadonlyFileSystem(): boolean;
isReadonly(): boolean;
readonly onDidChangeReadonly: Event<void>;
isOrphaned(): boolean;
readonly onDidChangeOrphaned: Event<void>;

View file

@ -8,7 +8,6 @@ import { Disposable, IReference } from 'vs/base/common/lifecycle';
import { isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService';
import { FileSystemProviderCapabilities, IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor';
import { ICustomEditorModel } from 'vs/workbench/contrib/customEditor/common/customEditor';
@ -33,15 +32,14 @@ export class CustomTextEditorModel extends Disposable implements ICustomEditorMo
private readonly _onDidChangeOrphaned = this._register(new Emitter<void>());
public readonly onDidChangeOrphaned = this._onDidChangeOrphaned.event;
private readonly _onDidChangeEditable = this._register(new Emitter<void>());
public readonly onDidChangeEditable = this._onDidChangeEditable.event;
private readonly _onDidChangeReadonly = this._register(new Emitter<void>());
public readonly onDidChangeReadonly = this._onDidChangeReadonly.event;
constructor(
public readonly viewType: string,
private readonly _resource: URI,
private readonly _model: IReference<IResolvedTextEditorModel>,
@ITextFileService private readonly textFileService: ITextFileService,
@IFileService private readonly _fileService: IFileService,
@ITextFileService private readonly textFileService: ITextFileService
) {
super();
@ -50,7 +48,7 @@ export class CustomTextEditorModel extends Disposable implements ICustomEditorMo
this._textFileModel = this.textFileService.files.get(_resource);
if (this._textFileModel) {
this._register(this._textFileModel.onDidChangeOrphaned(() => this._onDidChangeOrphaned.fire()));
this._register(this._textFileModel.onDidChangeReadonly(() => this._onDidChangeEditable.fire()));
this._register(this._textFileModel.onDidChangeReadonly(() => this._onDidChangeReadonly.fire()));
}
this._register(this.textFileService.files.onDidChangeDirty(e => {
@ -65,12 +63,8 @@ export class CustomTextEditorModel extends Disposable implements ICustomEditorMo
return this._resource;
}
public isEditable(): boolean {
return !this._model.object.isReadonly();
}
public isOnReadonlyFileSystem(): boolean {
return this._fileService.hasCapability(this._resource, FileSystemProviderCapabilities.Readonly);
public isReadonly(): boolean {
return this._model.object.isReadonly();
}
public get backupId() {

View file

@ -308,22 +308,23 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements
private async doResolveAsText(): Promise<ITextFileEditorModel | BinaryEditorModel> {
try {
// Unset preferred contents after having applied it once
// to prevent this property to stick. We still want future
// `resolve` calls to fetch the contents from disk.
const preferredContents = this.preferredContents;
this.preferredContents = undefined;
// Resolve resource via text file service and only allow
// to open binary files if we are instructed so
await this.textFileService.files.resolve(this.resource, {
mode: this.preferredMode,
encoding: this.preferredEncoding,
contents: typeof this.preferredContents === 'string' ? createTextBufferFactory(this.preferredContents) : undefined,
contents: typeof preferredContents === 'string' ? createTextBufferFactory(preferredContents) : undefined,
reload: { async: true }, // trigger a reload of the model if it exists already but do not wait to show the model
allowBinary: this.forceOpenAs === ForceOpenAs.Text,
reason: TextFileResolveReason.EDITOR
});
// Unset preferred contents after having applied it once
// to prevent this property to stick. We still want future
// `resolve` calls to fetch the contents from disk.
this.preferredContents = undefined;
// This is a bit ugly, because we first resolve the model and then resolve a model reference. the reason being that binary
// or very large files do not resolve to a text file model but should be opened as binary files without text. First calling into
// resolve() ensures we are not creating model references for these kind of resources.
@ -402,7 +403,7 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements
}
override matches(otherInput: unknown): boolean {
if (otherInput === this) {
if (super.matches(otherInput)) {
return true;
}

View file

@ -460,7 +460,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
id: CUT_FILE_ID,
title: nls.localize('cut', "Cut")
},
when: ExplorerRootContext.toNegated()
when: ContextKeyExpr.and(ExplorerRootContext.toNegated(), ExplorerResourceNotReadonlyContext)
});
MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {

View file

@ -831,6 +831,10 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
if (!containsDragType(originalEvent, DataTransfers.FILES, CodeDataTransfers.FILES, DataTransfers.RESOURCES)) {
return false;
}
if (isWeb) {
// Drag and drop from vscode to web is not supported #115535
return false;
}
}
// Other-Tree DND

View file

@ -157,7 +157,7 @@ function removeEmbeddedSVGs(documentContent: string): string {
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7', 'h8', 'br', 'b', 'i', 'strong', 'em', 'a', 'pre', 'code', 'img', 'tt',
'div', 'ins', 'del', 'sup', 'sub', 'p', 'ol', 'ul', 'table', 'thead', 'tbody', 'tfoot', 'blockquote', 'dl', 'dt',
'dd', 'kbd', 'q', 'samp', 'var', 'hr', 'ruby', 'rt', 'rp', 'li', 'tr', 'td', 'th', 's', 'strike', 'summary', 'details',
'caption', 'figure', 'figcaption', 'abbr', 'bdo', 'cite', 'dfn', 'mark', 'small', 'span', 'time', 'wbr', 'checkbox', 'checklist'
'caption', 'figure', 'figcaption', 'abbr', 'bdo', 'cite', 'dfn', 'mark', 'small', 'span', 'time', 'wbr', 'checkbox', 'checklist', 'vertically-centered'
],
allowedAttributes: {
'*': [

View file

@ -34,7 +34,7 @@ import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } f
import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { Iterable } from 'vs/base/common/iterator';
import { flatten } from 'vs/base/common/arrays';
import { flatten, maxIndex, minIndex } from 'vs/base/common/arrays';
import { Codicon } from 'vs/base/common/codicons';
// Kernel Command
@ -62,6 +62,8 @@ const DELETE_CELL_COMMAND_ID = 'notebook.cell.delete';
const CANCEL_CELL_COMMAND_ID = 'notebook.cell.cancelExecution';
const EXECUTE_CELL_SELECT_BELOW = 'notebook.cell.executeAndSelectBelow';
const EXECUTE_CELL_INSERT_BELOW = 'notebook.cell.executeAndInsertBelow';
const EXECUTE_CELL_AND_BELOW = 'notebook.cell.executeCellAndBelow';
const EXECUTE_CELLS_ABOVE = 'notebook.cell.executeCellsAbove';
const CLEAR_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.clearOutputs';
const CENTER_ACTIVE_CELL = 'notebook.centerActiveCell';
@ -410,6 +412,74 @@ function parseMultiCellExecutionArgs(accessor: ServicesAccessor, ...args: any[])
return context;
}
registerAction2(class ExecuteAboveCells extends NotebookMultiCellAction<INotebookActionContext> {
constructor() {
super({
id: EXECUTE_CELLS_ABOVE,
precondition: executeCellCondition,
title: localize('notebookActions.executeAbove', "Execute Above Cells"),
menu: {
id: MenuId.NotebookCellExecute,
when: executeCellCondition
},
icon: icons.executeAboveIcon
});
}
parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookActionContext | undefined {
return parseMultiCellExecutionArgs(accessor, ...args);
}
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> {
let endCellIdx: number | undefined = undefined;
if (context.ui && context.cell) {
endCellIdx = context.notebookEditor.viewModel.getCellIndex(context.cell);
} else if (context.selectedCells) {
endCellIdx = maxIndex(context.selectedCells, cell => context.notebookEditor.viewModel.getCellIndex(cell));
}
if (typeof endCellIdx === 'number') {
const range = { start: 0, end: endCellIdx };
const cells = context.notebookEditor.viewModel.getCells(range);
context.notebookEditor.executeNotebookCells(cells);
}
}
});
registerAction2(class ExecuteCellAndBelow extends NotebookMultiCellAction<INotebookActionContext> {
constructor() {
super({
id: EXECUTE_CELL_AND_BELOW,
precondition: executeCellCondition,
title: localize('notebookActions.executeBelow', "Execute Cell and Below"),
menu: {
id: MenuId.NotebookCellExecute,
when: executeCellCondition,
},
icon: icons.executeBelowIcon
});
}
parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookActionContext | undefined {
return parseMultiCellExecutionArgs(accessor, ...args);
}
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> {
let startCellIdx: number | undefined = undefined;
if (context.ui && context.cell) {
startCellIdx = context.notebookEditor.viewModel.getCellIndex(context.cell);
} else if (context.selectedCells) {
startCellIdx = minIndex(context.selectedCells, cell => context.notebookEditor.viewModel.getCellIndex(cell));
}
if (typeof startCellIdx === 'number') {
const range = { start: startCellIdx, end: context.notebookEditor.viewModel.viewCells.length };
const cells = context.notebookEditor.viewModel.getCells(range);
context.notebookEditor.executeNotebookCells(cells);
}
}
});
registerAction2(class ExecuteCell extends NotebookMultiCellAction<INotebookActionContext> {
constructor() {
super({

View file

@ -498,7 +498,7 @@
height: initial;
}
.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .run-button-container .monaco-toolbar .codicon {
.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .run-button-container .monaco-toolbar .action-item:not(.monaco-dropdown-with-primary) .codicon {
padding: 6px;
}
@ -508,6 +508,7 @@
.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover .run-button-container .monaco-toolbar,
.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused .run-button-container .monaco-toolbar,
.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-run-toolbar-dropdown-active .run-button-container .monaco-toolbar,
.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.cell-output-hover .run-button-container .monaco-toolbar {
visibility: visible;
}

View file

@ -30,7 +30,7 @@ import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEd
import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { NotebookService } from 'vs/workbench/contrib/notebook/browser/notebookServiceImpl';
import { CellKind, CellToolbarLocKey, CellToolbarVisibility, CellUri, DisplayOrderKey, UndoRedoPerCell, ExperimentalUseMarkdownRenderer, getCellUndoRedoComparisonKey, IResolvedNotebookEditorModel, NotebookDocumentBackupData, NotebookTextDiffEditorPreview, NotebookWorkingCopyTypeIdentifier, ShowCellStatusBarKey, CompactView, FocusIndicator, InsertToolbarPosition, GlobalToolbar, ConsolidatedOutputButton, ShowFoldingControls, DragAndDropEnabled, ShowCellStatusBarAfterExecuteKey, NotebookCellEditorOptionsCustomizations } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellKind, CellToolbarLocKey, CellToolbarVisibility, CellUri, DisplayOrderKey, UndoRedoPerCell, ExperimentalUseMarkdownRenderer, getCellUndoRedoComparisonKey, IResolvedNotebookEditorModel, NotebookDocumentBackupData, NotebookTextDiffEditorPreview, NotebookWorkingCopyTypeIdentifier, ShowCellStatusBarKey, CompactView, FocusIndicator, InsertToolbarPosition, GlobalToolbar, ConsolidatedOutputButton, ShowFoldingControls, DragAndDropEnabled, ShowCellStatusBarAfterExecuteKey, NotebookCellEditorOptionsCustomizations, ConsolidatedRunButton } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
@ -694,6 +694,12 @@ configurationRegistry.registerConfiguration({
default: true,
tags: ['notebookLayout']
},
[ConsolidatedRunButton]: {
description: nls.localize('notebook.consolidatedRunButton.description', "Control whether extra actions are shown in a dropdown next to the run button."),
type: 'boolean',
default: false,
tags: ['notebookLayout']
},
[NotebookCellEditorOptionsCustomizations]: editorOptionsCustomizationSchema
}
});

View file

@ -16,6 +16,7 @@ import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebo
import { IReference } from 'vs/base/common/lifecycle';
import { INotebookDiffEditorModel, IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { Schemas } from 'vs/base/common/network';
import { FileSystemProviderCapabilities, IFileService } from 'vs/platform/files/common/files';
interface NotebookEditorInputOptions {
startDirty?: boolean;
@ -70,7 +71,8 @@ export class NotebookDiffEditorInput extends EditorInput {
public readonly options: NotebookEditorInputOptions,
@INotebookService private readonly _notebookService: INotebookService,
@INotebookEditorModelResolverService private readonly _notebookModelResolverService: INotebookEditorModelResolverService,
@IFileDialogService private readonly _fileDialogService: IFileDialogService
@IFileDialogService private readonly _fileDialogService: IFileDialogService,
@IFileService private readonly _fileService: IFileService
) {
super();
this._defaultDirtyState = !!options.startDirty;
@ -87,6 +89,16 @@ export class NotebookDiffEditorInput extends EditorInput {
capabilities |= EditorInputCapabilities.Untitled;
}
if (this._modifiedTextModel) {
if (this._modifiedTextModel.object.isReadonly()) {
capabilities |= EditorInputCapabilities.Readonly;
}
} else {
if (this._fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly)) {
capabilities |= EditorInputCapabilities.Readonly;
}
}
return capabilities;
}
@ -214,7 +226,7 @@ ${patterns}
}
override matches(otherInput: unknown): boolean {
if (this === otherInput) {
if (super.matches(otherInput)) {
return true;
}
if (otherInput instanceof NotebookDiffEditorInput) {

View file

@ -11,6 +11,8 @@ export const configureKernelIcon = registerIcon('notebook-kernel-configure', Cod
export const selectKernelIcon = registerIcon('notebook-kernel-select', Codicon.serverEnvironment, localize('selectKernelIcon', 'Configure icon to select a kernel in notebook editors.'));
export const executeIcon = registerIcon('notebook-execute', Codicon.play, localize('executeIcon', 'Icon to execute in notebook editors.'));
export const executeAboveIcon = registerIcon('notebook-execute-above', Codicon.runAbove, localize('executeAboveIcon', 'Icon to execute above cells in notebook editors.'));
export const executeBelowIcon = registerIcon('notebook-execute-below', Codicon.runBelow, localize('executeBelowIcon', 'Icon to execute below cells in notebook editors.'));
export const stopIcon = registerIcon('notebook-stop', Codicon.primitiveSquare, localize('stopIcon', 'Icon to stop an execution in notebook editors.'));
export const deleteCellIcon = registerIcon('notebook-delete-cell', Codicon.trash, localize('deleteCellIcon', 'Icon to delete a cell in notebook editors.'));
export const executeAllIcon = registerIcon('notebook-execute-all', Codicon.runAll, localize('executeAllIcon', 'Icon to execute all cells in notebook editors.'));

View file

@ -192,7 +192,7 @@ export interface ICreationRequestMessage {
type: 'html';
content:
| { type: RenderOutputType.Html; htmlContent: string }
| { type: RenderOutputType.Extension; outputId: string; valueBytes: Uint8Array, metadata: unknown; mimeType: string };
| { type: RenderOutputType.Extension; outputId: string; valueBytes: Uint8Array, metadata: unknown; metadata2: unknown, mimeType: string };
cellId: string;
outputId: string;
cellTop: number;
@ -833,6 +833,16 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
}
async createWebview(): Promise<void> {
const baseUrl = this.asWebviewUri(dirname(this.documentUri), undefined);
// Python hasn't moved to use a preload to load require support yet.
// For all other notebooks, we no longer want to include our loader.
if (!this.documentUri.path.toLowerCase().endsWith('.ipynb')) {
const htmlContent = this.generateContent('', baseUrl.toString());
this._initialize(htmlContent);
return;
}
let coreDependencies = '';
let resolveFunc: () => void;
@ -840,7 +850,6 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
resolveFunc = resolve;
});
const baseUrl = this.asWebviewUri(dirname(this.documentUri), undefined);
if (!isWeb) {
const loaderUri = FileAccess.asFileUri('vs/loader.js', require);
@ -889,7 +898,7 @@ var requirejs = (function() {
await this._initalized;
}
private async _initialize(content: string) {
private _initialize(content: string) {
if (!document.body.contains(this.element)) {
throw new Error('Element is already detached from the DOM tree');
}
@ -1515,6 +1524,7 @@ var requirejs = (function() {
mimeType: content.mimeType,
valueBytes: new Uint8Array(outputDto?.valueBytes ?? []),
metadata: outputDto?.metadata,
metadata2: output.metadata
},
};
} else {

View file

@ -3,16 +3,16 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as Codicons from 'vs/base/common/codicons';
import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser';
import * as DOM from 'vs/base/browser/dom';
import * as aria from 'vs/base/browser/ui/aria/aria';
import { domEvent } from 'vs/base/browser/event';
import * as aria from 'vs/base/browser/ui/aria/aria';
import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
import { IAction } from 'vs/base/common/actions';
import { Action, IAction } from 'vs/base/common/actions';
import * as Codicons from 'vs/base/common/codicons';
import { Color } from 'vs/base/common/color';
import { combinedDisposable, Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import * as platform from 'vs/base/common/platform';
@ -26,6 +26,7 @@ import { ITextModel } from 'vs/editor/common/model';
import * as modes from 'vs/editor/common/modes';
import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer';
import { localize } from 'vs/nls';
import { DropdownWithPrimaryActionViewItem } from 'vs/platform/actions/browser/dropdownWithPrimaryActionViewItem';
import { createActionViewItem, createAndFillInActionBarActions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IMenu, MenuItemAction } from 'vs/platform/actions/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@ -35,10 +36,15 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { syncing } from 'vs/platform/theme/common/iconRegistry';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { DeleteCellAction, INotebookActionContext, INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions';
import { BaseCellRenderTemplate, CellEditState, CodeCellLayoutInfo, CodeCellRenderTemplate, EXPAND_CELL_INPUT_COMMAND_ID, ICellViewModel, INotebookEditor, isCodeCellRenderTemplate, MarkdownCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { errorStateIcon, successStateIcon, unfoldIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons';
import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellActionView';
import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys';
import { CellDragAndDropController, DRAGGING_CLASS } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellDnd';
import { CellEditorOptions } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellEditorOptions';
import { CellMenus } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellMenus';
import { CellEditorStatusBar } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets';
import { CodeCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/codeCell';
@ -46,12 +52,7 @@ import { StatefulMarkdownCell } from 'vs/workbench/contrib/notebook/browser/view
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel';
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { CellEditType, CellKind, NotebookCellMetadata, NotebookCellExecutionState, NotebookCellInternalMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellActionView';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { errorStateIcon, successStateIcon, unfoldIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons';
import { syncing } from 'vs/platform/theme/common/iconRegistry';
import { CellEditorOptions } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellEditorOptions';
import { CellEditType, CellKind, NotebookCellExecutionState, NotebookCellInternalMetadata, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions';
const $ = DOM.$;
@ -94,11 +95,11 @@ abstract class AbstractCellRenderer {
protected readonly notebookEditor: INotebookEditor,
protected readonly contextMenuService: IContextMenuService,
configurationService: IConfigurationService,
private readonly keybindingService: IKeybindingService,
private readonly notificationService: INotificationService,
protected readonly keybindingService: IKeybindingService,
protected readonly notificationService: INotificationService,
protected readonly contextKeyServiceProvider: (container: HTMLElement) => IContextKeyService,
language: string,
protected dndController: CellDragAndDropController | undefined,
protected dndController: CellDragAndDropController | undefined
) {
this.editorOptions = new CellEditorOptions(notebookEditor.notebookOptions, configurationService, language);
this.cellMenus = this.instantiationService.createInstance(CellMenus);
@ -700,7 +701,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende
const cellContainer = DOM.append(container, $('.cell.code'));
const runButtonContainer = DOM.append(cellContainer, $('.run-button-container'));
const runToolbar = disposables.add(this.setupRunToolbar(runButtonContainer, contextKeyService, disposables));
const runToolbar = this.setupRunToolbar(runButtonContainer, container, contextKeyService, disposables);
const executionOrderLabel = DOM.append(cellContainer, $('div.execution-count-label'));
const editorPart = DOM.append(cellContainer, $('.cell-editor-part'));
@ -831,18 +832,59 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende
return combinedDisposable(dragHandleListener, collapsedPartListener);
}
private setupRunToolbar(runButtonContainer: HTMLElement, contextKeyService: IContextKeyService, disposables: DisposableStore): ToolBar {
const runToolbar = this.createToolbar(runButtonContainer);
const runMenu = this.cellMenus.getCellExecuteMenu(contextKeyService);
const update = () => {
const actions = this.getCellToolbarActions(runMenu);
runToolbar.setActions(actions.primary, actions.secondary);
};
disposables.add(runMenu);
disposables.add(runMenu.onDidChange(() => {
update();
private createRunCellToolbar(container: HTMLElement, cellContainer: HTMLElement, contextKeyService: IContextKeyService, disposables: DisposableStore): ToolBar {
const actionViewItemDisposables = disposables.add(new DisposableStore());
const dropdownAction = disposables.add(new Action('notebook.moreRunActions', localize('notebook.moreRunActionsLabel', "More..."), 'codicon-chevron-down', true));
const toolbar = disposables.add(new ToolBar(container, this.contextMenuService, {
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),
actionViewItemProvider: _action => {
actionViewItemDisposables.clear();
const actions = this.getCellToolbarActions(this.cellMenus.getCellExecuteMenu(contextKeyService));
const primary = actions.primary[0];
if (!(primary instanceof MenuItemAction)) {
return undefined;
}
if (!actions.secondary.length) {
return undefined;
}
if (!this.notebookEditor.notebookOptions.getLayoutConfiguration().consolidatedRunButton) {
return undefined;
}
const item = new DropdownWithPrimaryActionViewItem(
primary,
dropdownAction,
actions.secondary,
'notebook-cell-run-toolbar',
this.contextMenuService,
this.keybindingService,
this.notificationService);
actionViewItemDisposables.add(item.onDidChangeDropdownVisibility(visible => {
cellContainer.classList.toggle('cell-run-toolbar-dropdown-active', visible);
}));
return item;
},
renderDropdownAsChildElement: true
}));
update();
return toolbar;
}
private setupRunToolbar(runButtonContainer: HTMLElement, cellContainer: HTMLElement, contextKeyService: IContextKeyService, disposables: DisposableStore): ToolBar {
const menu = this.cellMenus.getCellExecuteMenu(contextKeyService);
const runToolbar = this.createRunCellToolbar(runButtonContainer, cellContainer, contextKeyService, disposables);
const updateActions = () => {
const actions = this.getCellToolbarActions(this.cellMenus.getCellExecuteMenu(contextKeyService));
runToolbar.setActions(actions.primary);
};
updateActions();
disposables.add(menu.onDidChange(updateActions));
disposables.add(this.notebookEditor.notebookOptions.onDidChangeOptions(updateActions));
return runToolbar;
}

View file

@ -464,6 +464,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re
mime: string;
metadata: unknown;
metadata2: unknown;
text(): string;
json(): any;
@ -643,6 +644,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re
element: outputNode,
mime: content.mimeType,
metadata: content.metadata,
metadata2: content.metadata2,
data() {
return content.valueBytes;
},
@ -1036,6 +1038,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re
element,
mime: 'text/markdown',
metadata: undefined,
metadata2: undefined,
outputId: undefined,
text() { return content; },
json() { return undefined; },

View file

@ -180,12 +180,12 @@ export interface IOutputItemDto {
export interface IOutputDto {
outputs: IOutputItemDto[];
outputId: string;
metadata?: Record<string, unknown>;
metadata?: Record<string, any>;
}
export interface ICellOutput {
outputs: IOutputItemDto[];
// metadata?: NotebookCellOutsputMetadata;
metadata?: Record<string, any>;
outputId: string;
onDidChangeData: Event<void>;
replaceData(items: IOutputItemDto[]): void;
@ -921,6 +921,7 @@ export const ConsolidatedOutputButton = 'notebook.consolidatedOutputButton';
export const ShowFoldingControls = 'notebook.showFoldingControls';
export const DragAndDropEnabled = 'notebook.dragAndDropEnabled';
export const NotebookCellEditorOptionsCustomizations = 'notebook.editorOptionsCustomizations';
export const ConsolidatedRunButton = 'notebook.consolidatedRunButton';
export const enum CellStatusbarAlignment {
Left = 1,

View file

@ -210,7 +210,7 @@ export class NotebookEditorInput extends AbstractResourceEditorInput {
}
override matches(otherInput: unknown): boolean {
if (this === otherInput) {
if (super.matches(otherInput)) {
return true;
}
if (otherInput instanceof NotebookEditorInput) {

View file

@ -6,7 +6,7 @@
import { Emitter } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { CellToolbarLocKey, CellToolbarVisibility, CompactView, ConsolidatedOutputButton, DragAndDropEnabled, ExperimentalInsertToolbarAlignment, FocusIndicator, GlobalToolbar, InsertToolbarPosition, NotebookCellEditorOptionsCustomizations, ShowCellStatusBarAfterExecuteKey, ShowCellStatusBarKey, ShowFoldingControls } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellToolbarLocKey, CellToolbarVisibility, CompactView, ConsolidatedOutputButton, ConsolidatedRunButton, DragAndDropEnabled, ExperimentalInsertToolbarAlignment, FocusIndicator, GlobalToolbar, InsertToolbarPosition, NotebookCellEditorOptionsCustomizations, ShowCellStatusBarAfterExecuteKey, ShowCellStatusBarKey, ShowFoldingControls } from 'vs/workbench/contrib/notebook/common/notebookCommon';
const SCROLLABLE_ELEMENT_PADDING_TOP = 18;
@ -54,6 +54,7 @@ export interface NotebookLayoutConfiguration {
insertToolbarAlignment: 'left' | 'center';
globalToolbar: boolean;
consolidatedOutputButton: boolean;
consolidatedRunButton: boolean;
showFoldingControls: 'always' | 'mouseover';
dragAndDropEnabled: boolean;
fontSize: number;
@ -74,6 +75,7 @@ interface NotebookOptionsChangeEvent {
globalToolbar?: boolean;
showFoldingControls?: boolean;
consolidatedOutputButton?: boolean;
consolidatedRunButton?: boolean;
dragAndDropEnabled?: boolean;
fontSize?: boolean;
editorOptionsCustomizations?: boolean;
@ -110,6 +112,7 @@ export class NotebookOptions {
const showCellStatusBarAfterExecute = this.configurationService.getValue<boolean>(ShowCellStatusBarAfterExecuteKey);
const globalToolbar = this.configurationService.getValue<boolean | undefined>(GlobalToolbar) ?? false;
const consolidatedOutputButton = this.configurationService.getValue<boolean | undefined>(ConsolidatedOutputButton) ?? true;
const consolidatedRunButton = this.configurationService.getValue<boolean | undefined>(ConsolidatedRunButton) ?? false;
const dragAndDropEnabled = this.configurationService.getValue<boolean | undefined>(DragAndDropEnabled) ?? true;
const cellToolbarLocation = this.configurationService.getValue<string | { [key: string]: string }>(CellToolbarLocKey) ?? { 'default': 'right' };
const cellToolbarInteraction = this.configurationService.getValue<string>(CellToolbarVisibility);
@ -142,6 +145,7 @@ export class NotebookOptions {
showCellStatusBarAfterExecute,
globalToolbar,
consolidatedOutputButton,
consolidatedRunButton,
dragAndDropEnabled,
cellToolbarLocation,
cellToolbarInteraction,
@ -177,6 +181,7 @@ export class NotebookOptions {
const insertToolbarAlignment = e.affectsConfiguration(ExperimentalInsertToolbarAlignment);
const globalToolbar = e.affectsConfiguration(GlobalToolbar);
const consolidatedOutputButton = e.affectsConfiguration(ConsolidatedOutputButton);
const consolidatedRunButton = e.affectsConfiguration(ConsolidatedRunButton);
const showFoldingControls = e.affectsConfiguration(ShowFoldingControls);
const dragAndDropEnabled = e.affectsConfiguration(DragAndDropEnabled);
const fontSize = e.affectsConfiguration('editor.fontSize');
@ -193,6 +198,7 @@ export class NotebookOptions {
&& !insertToolbarAlignment
&& !globalToolbar
&& !consolidatedOutputButton
&& !consolidatedRunButton
&& !showFoldingControls
&& !dragAndDropEnabled
&& !fontSize
@ -246,6 +252,10 @@ export class NotebookOptions {
configuration.consolidatedOutputButton = this.configurationService.getValue<boolean>(ConsolidatedOutputButton) ?? true;
}
if (consolidatedRunButton) {
configuration.consolidatedRunButton = this.configurationService.getValue<boolean>(ConsolidatedRunButton) ?? true;
}
if (showFoldingControls) {
configuration.showFoldingControls = this._computeShowFoldingControlsOption();
}
@ -277,6 +287,7 @@ export class NotebookOptions {
globalToolbar,
showFoldingControls,
consolidatedOutputButton,
consolidatedRunButton,
dragAndDropEnabled,
fontSize,
editorOptionsCustomizations

View file

@ -214,7 +214,7 @@ export class SearchEditorInput extends EditorInput {
}
override matches(other: unknown) {
if (this === other) { return true; }
if (super.matches(other)) { return true; }
if (other instanceof SearchEditorInput) {
return !!(other.modelUri.fragment && other.modelUri.fragment === this.modelUri.fragment) || !!(other.backingUri && isEqual(other.backingUri, this.backingUri));

View file

@ -77,7 +77,7 @@ export class ReleaseNotesManager {
this._webviewWorkbenchService.revealWebview(this._currentReleaseNotes, activeEditorPane ? activeEditorPane.group : this._editorGroupService.activeGroup, false);
} else {
this._currentReleaseNotes = this._webviewWorkbenchService.createWebview(
'vs_code_release_notes',
generateUuid(),
'releaseNotes',
title,
{ group: ACTIVE_GROUP, preserveFocus: false },

View file

@ -83,6 +83,8 @@ namespace WebviewState {
export abstract class BaseWebview<T extends HTMLElement> extends Disposable {
protected readonly _expectedServiceWorkerVersion = 2; // Keep this in sync with the version in service-worker.js
private _element: T | undefined;
protected get element(): T | undefined { return this._element; }
@ -250,7 +252,7 @@ export abstract class BaseWebview<T extends HTMLElement> extends Disposable {
this.loadResource(entry.id, uri, entry.ifNoneMatch);
} catch (e) {
this._send('did-load-resource', {
id,
id: entry.id,
status: 404,
path: entry.path,
});

View file

@ -25,6 +25,7 @@ const isSafari = navigator.vendor && navigator.vendor.indexOf('Apple') > -1 &&
const searchParams = new URL(location.toString()).searchParams;
const ID = searchParams.get('id');
const expectedWorkerVersion = parseInt(searchParams.get('swVersion'));
/**
* Use polling to track focus of main webview and iframes within the webview
@ -210,16 +211,16 @@ const workerReady = new Promise(async (resolve, reject) => {
return reject(new Error('Service Workers are not enabled in browser. Webviews will not work.'));
}
const expectedWorkerVersion = 2;
const swPath = `service-worker.js${self.location.search}`;
navigator.serviceWorker.register(`service-worker.js${self.location.search}`).then(
navigator.serviceWorker.register(swPath).then(
async registration => {
await navigator.serviceWorker.ready;
/**
* @param {MessageEvent} event
*/
const versionHandler = (event) => {
const versionHandler = async (event) => {
if (event.data.channel !== 'version') {
return;
}
@ -228,10 +229,16 @@ const workerReady = new Promise(async (resolve, reject) => {
if (event.data.version === expectedWorkerVersion) {
return resolve();
} else {
// If we have the wrong version, try once to unregister and re-register
return registration.update()
console.log(`Found unexpected service worker version. Found: ${event.data.version}. Expected: ${expectedWorkerVersion}`);
console.log(`Attempting to reload service worker`);
// If we have the wrong version, try once (and only once) to unregister and re-register
// Note that `.update` doesn't seem to work desktop electron at the moment so we use
// `unregister` and `register` here.
return registration.unregister()
.then(() => navigator.serviceWorker.register(swPath))
.then(() => navigator.serviceWorker.ready)
.finally(resolve);
.finally(() => { resolve(); });
}
};
navigator.serviceWorker.addEventListener('message', versionHandler);

View file

@ -100,6 +100,7 @@ export class IFrameWebview extends BaseWebview<HTMLIFrameElement> implements Web
protected initElement(extension: WebviewExtensionDescription | undefined, options: WebviewOptions, extraParams?: { [key: string]: string }) {
const params: { [key: string]: string } = {
id: this.id,
swVersion: String(this._expectedServiceWorkerVersion),
extensionId: extension?.id.value ?? '', // The extensionId and purpose in the URL are used for filtering in js-debug:
...extraParams,
'vscode-resource-base-authority': this.webviewRootResourceAuthority,

View file

@ -159,7 +159,7 @@ export class ElectronWebviewBasedWebview extends BaseWebview<WebviewTag> impleme
// and not the `vscode-file` URI because preload scripts are loaded
// via node.js from the main side and only allow `file:` protocol
this.element!.preload = FileAccess.asFileUri('./pre/electron-index.js', require).toString(true);
this.element!.src = `${Schemas.vscodeWebview}://${this.id}/electron-browser-index.html?platform=electron&id=${this.id}&vscode-resource-base-authority=${encodeURIComponent(this.webviewRootResourceAuthority)}`;
this.element!.src = `${Schemas.vscodeWebview}://${this.id}/electron-browser-index.html?platform=electron&id=${this.id}&vscode-resource-base-authority=${encodeURIComponent(this.webviewRootResourceAuthority)}&swVersion=${this._expectedServiceWorkerVersion}`;
}
protected createElement(options: WebviewOptions) {

View file

@ -70,11 +70,7 @@ export class ElectronIframeWebview extends IFrameWebview {
}
protected override get webviewContentEndpoint(): string {
const endpoint = `${Schemas.vscodeWebview}://${this.id}`;
if (endpoint[endpoint.length - 1] === '/') {
return endpoint.slice(0, endpoint.length - 1);
}
return endpoint;
return `${Schemas.vscodeWebview}://${this.id}`;
}
protected override async doPostMessage(channel: string, data?: any): Promise<void> {

View file

@ -233,15 +233,26 @@ Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({
...workbenchConfigurationNodeBase,
properties: {
'workbench.welcomePage.walkthroughs.openOnInstall': {
scope: ConfigurationScope.APPLICATION,
type: 'boolean',
default: true,
description: localize('workbench.welcomePage.walkthroughs.openOnInstall', "When enabled, an extension's walkthrough will open upon install the extension. Walkthroughs are the items contributed the the 'Getting Started' section of the welcome page")
}
}
});
if (product.quality !== 'stable') {
configurationRegistry.registerConfiguration({
...workbenchConfigurationNodeBase,
properties: {
'workbench.welcomePage.experimental.extensionContributions': {
'workbench.welcomePage.experimental.startEntryContributions': {
scope: ConfigurationScope.APPLICATION,
type: 'boolean',
default: false,
description: localize('workbench.welcomePage.experimental.extensionContributions', "When enabled, allow extensions to contribute items to the \"Getting Started\" and \"Start\" sections of the welcome page. Experimental, subject to breakage as api changes.")
description: localize('workbench.welcomePage.experimental.startEntryContributions', "When enabled, allow extensions to contribute items to the \"Start\" section of the welcome page. Experimental, subject to breakage as api changes.")
}
}
});

View file

@ -41,7 +41,7 @@ import { GettingStartedInput } from 'vs/workbench/contrib/welcome/gettingStarted
import { GroupDirection, GroupsOrder, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { Emitter, Event } from 'vs/base/common/event';
import { LinkedText } from 'vs/base/common/linkedText';
import { ILink, LinkedText } from 'vs/base/common/linkedText';
import { Button } from 'vs/base/browser/ui/button/button';
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
import { Link } from 'vs/platform/opener/browser/link';
@ -117,6 +117,8 @@ export class GettingStartedPage extends EditorPane {
private stepsContent!: HTMLElement;
private stepMediaComponent!: HTMLElement;
private layoutMarkdown: (() => void) | undefined;
private webviewID = generateUuid();
constructor(
@ -469,6 +471,17 @@ export class GettingStartedPage extends EditorPane {
mediaElement.setAttribute('alt', media.altText);
this.updateMediaSourceForColorMode(mediaElement, media.path);
this.stepDisposables.add(addDisposableListener(mediaElement, 'click', () => {
const hrefs = flatten(stepToExpand.description.map(lt => lt.nodes.filter((node): node is ILink => typeof node !== 'string').map(node => node.href)));
if (hrefs.length === 1) {
const href = hrefs[0];
if (href.startsWith('http')) {
this.telemetryService.publicLog2<GettingStartedActionEvent, GettingStartedActionClassification>('gettingStarted.ActionExecuted', { command: 'runStepAction', argument: href });
this.openerService.open(href);
}
}
}));
this.stepDisposables.add(this.themeService.onDidColorThemeChange(() => this.updateMediaSourceForColorMode(mediaElement, media.path)));
} else if (stepToExpand.media.type === 'markdown') {
@ -488,7 +501,11 @@ export class GettingStartedPage extends EditorPane {
const postTrueKeysMessage = () => {
const enabledContextKeys = serializedContextKeyExprs?.filter(expr => this.contextService.contextMatchesRules(ContextKeyExpr.deserialize(expr)));
if (enabledContextKeys) { webview.postMessage({ enabledContextKeys }); }
if (enabledContextKeys) {
webview.postMessage({
enabledContextKeys
});
}
};
let isDisposed = false;
@ -517,6 +534,10 @@ export class GettingStartedPage extends EditorPane {
if (e.affectsSome(watchingKeys)) { postTrueKeysMessage(); }
}));
this.layoutMarkdown = () => { webview.postMessage({ layout: true }); };
this.stepDisposables.add({ dispose: () => this.layoutMarkdown = undefined });
this.layoutMarkdown();
postTrueKeysMessage();
webview.onMessage(e => {
@ -575,7 +596,8 @@ export class GettingStartedPage extends EditorPane {
private updateMediaSourceForColorMode(element: HTMLImageElement, sources: { hc: URI, dark: URI, light: URI }) {
const themeType = this.themeService.getColorTheme().type;
element.srcset = sources[themeType].toString(true).replace(/ /g, '%20') + ' 1.5x';
const src = sources[themeType].toString(true).replace(/ /g, '%20');
element.srcset = src.toLowerCase().endsWith('.svg') ? src : (src + ' 1.5x');
}
private async renderMarkdown(path: URI, base: URI): Promise<string> {
@ -618,6 +640,7 @@ export class GettingStartedPage extends EditorPane {
display: flex;
flex-direction: column;
align-items: center;
margin: 5px;
cursor: pointer;
}
checkbox.checked > img {
@ -644,9 +667,14 @@ export class GettingStartedPage extends EditorPane {
});
window.addEventListener('message', event => {
document.querySelectorAll('.checked').forEach(element => element.classList.remove('checked'))
for (const key of event.data.enabledContextKeys) {
document.querySelectorAll('[checked-on="' + key + '"]').forEach(element => element.classList.add('checked'))
document.querySelectorAll('vertically-centered').forEach(element => {
element.style.marginTop = Math.max((document.body.scrollHeight - element.scrollHeight) * 2/5, 10) + 'px';
})
if (event.data.enabledContextKeys) {
document.querySelectorAll('.checked').forEach(element => element.classList.remove('checked'))
for (const key of event.data.enabledContextKeys) {
document.querySelectorAll('[checked-on="' + key + '"]').forEach(element => element.classList.add('checked'))
}
}
});
</script>
@ -928,6 +956,8 @@ export class GettingStartedPage extends EditorPane {
this.gettingStartedList?.layout(size);
this.recentlyOpenedList?.layout(size);
this.layoutMarkdown?.();
this.container.classList[size.height <= 600 ? 'add' : 'remove']('height-constrained');
this.container.classList[size.width <= 400 ? 'add' : 'remove']('width-constrained');
this.container.classList[size.width <= 800 ? 'add' : 'remove']('width-semi-constrained');

View file

@ -11,7 +11,7 @@ export const walkthroughsExtensionPoint = ExtensionsRegistry.registerExtensionPo
extensionPoint: 'walkthroughs',
jsonSchema: {
doNotSuggest: true,
description: localize('walkthroughs', "Contribute collections of steps to help users with your extension. Experimental, available in VS Code Insiders only."),
description: localize('walkthroughs', "Contribute walkthroughs to help users getting started with your extension."),
type: 'array',
items: {
type: 'object',
@ -31,8 +31,7 @@ export const walkthroughsExtensionPoint = ExtensionsRegistry.registerExtensionPo
description: localize('walkthroughs.description', "Description of walkthrough.")
},
primary: {
type: 'boolean',
description: localize('walkthroughs.primary', "if this is a `primary` walkthrough, hinting if it should be opened on install of the extension. The first `primary` walkthough with a `when` condition matching the current context may be opened by core on install of the extension.")
deprecationMessage: localize('walkthroughs.primary.deprecated', "Deprecated. The first walkthrough with a satisfied when condition will be opened on install.")
},
when: {
type: 'string',

View file

@ -366,45 +366,37 @@ export class GettingStartedService extends Disposable implements IGettingStarted
}
};
let sectionToOpen: string | undefined;
if (!(extension.contributes?.walkthroughs?.length)) {
return;
}
if (this.productService.quality === 'stable') {
console.warn('Extension', extension.identifier.value, 'contributes welcome page content but this is a Stable build and extension contributions are only available in Insiders. The contributed content will be disregarded.');
return;
}
if (!this.configurationService.getValue<boolean>('workbench.welcomePage.experimental.extensionContributions')) {
console.warn('Extension', extension.identifier.value, 'contributes welcome page content but the welcome page extension contribution feature flag has not been set. Set `workbench.welcomePage.experimental.extensionContributions` to begin using this experimental feature.');
return;
}
extension.contributes.startEntries?.forEach(entry => {
const entryID = extension.identifier.value + '#startEntry#' + idForStartEntry(entry);
this.registerStartEntry({
content: {
type: 'startEntry',
command: entry.command,
},
description: entry.description,
title: entry.title,
id: entryID,
order: 0,
when: ContextKeyExpr.deserialize(entry.when) ?? ContextKeyExpr.true(),
icon: {
type: 'image',
path: extension.icon
? FileAccess.asBrowserUri(joinPath(extension.extensionLocation, extension.icon)).toString(true)
: DefaultIconPath
}
if (this.configurationService.getValue<boolean>('workbench.welcomePage.experimental.startEntryContributions') && this.productService.quality !== 'stable') {
extension.contributes.startEntries?.forEach(entry => {
const entryID = extension.identifier.value + '#startEntry#' + idForStartEntry(entry);
this.registerStartEntry({
content: {
type: 'startEntry',
command: entry.command,
},
description: entry.description,
title: entry.title,
id: entryID,
order: 0,
when: ContextKeyExpr.deserialize(entry.when) ?? ContextKeyExpr.true(),
icon: {
type: 'image',
path: extension.icon
? FileAccess.asBrowserUri(joinPath(extension.extensionLocation, extension.icon)).toString(true)
: DefaultIconPath
}
});
});
});
}
await Promise.all(extension.contributes?.walkthroughs?.map(async walkthrough => {
let sectionToOpen: string | undefined;
let sectionToOpenIndex = Math.max();
await Promise.all(extension.contributes?.walkthroughs?.map(async (walkthrough, index) => {
const categoryID = extension.identifier.value + '#' + walkthrough.id;
const override = await Promise.race([
@ -414,11 +406,13 @@ export class GettingStartedService extends Disposable implements IGettingStarted
if (
this.sessionInstalledExtensions.has(extension.identifier.value)
&& walkthrough.primary
&& this.contextService.contextMatchesRules(ContextKeyExpr.deserialize(override ?? walkthrough.when) ?? ContextKeyExpr.true())
) {
this.sessionInstalledExtensions.delete(extension.identifier.value);
sectionToOpen = categoryID;
if (index < sectionToOpenIndex) {
sectionToOpen = categoryID;
sectionToOpenIndex = index;
}
}
const walkthoughDescriptior = {
@ -433,7 +427,7 @@ export class GettingStartedService extends Disposable implements IGettingStarted
? FileAccess.asBrowserUri(joinPath(extension.extensionLocation, extension.icon)).toString(true)
: DefaultIconPath
},
when: ContextKeyExpr.deserialize(walkthrough.when) ?? ContextKeyExpr.true(),
when: ContextKeyExpr.deserialize(override ?? walkthrough.when) ?? ContextKeyExpr.true(),
} as const;
const steps = (walkthrough.steps ?? (walkthrough as any).tasks).map((step, index) => {
@ -498,7 +492,7 @@ export class GettingStartedService extends Disposable implements IGettingStarted
this.triggerInstalledExtensionsRegistered();
if (sectionToOpen && this.configurationService.getValue<string>('workbench.welcomePage.experimental.extensionContributions') !== 'hide') {
if (sectionToOpen && this.configurationService.getValue<string>('workbench.welcomePage.walkthroughs.openOnInstall')) {
this.commandService.executeCommand('workbench.action.openWalkthrough', sectionToOpen);
}
}

View file

@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/workbench/contrib/welcome/gettingStarted/common/media/example_markdown_media';
import { localize } from 'vs/nls';
import { Codicon } from 'vs/base/common/codicons';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
@ -182,6 +183,7 @@ 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'],
media: { type: 'markdown', path: 'example_markdown_media', }
},
{
@ -209,18 +211,17 @@ 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)"),
when: 'syncStatus != uninitialized',
completionEvents: ['onEvent:sync-enabled'],
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."),
when: '!isWorkspaceTrusted && workspaceFolderCount == 0',
media: {
type: 'image', altText: 'The "Turn on Sync" entry in the settings gear menu.', path: {
dark: 'dark/settingsSync.png',
light: 'light/settingsSync.png',
hc: 'hc/settingsSync.png',
type: 'image', altText: 'Workspace Trust editor in Restricted mode and a primary button for switching to Trusted mode.', path: {
dark: 'dark/workspaceTrust.svg',
light: 'light/workspaceTrust.svg',
hc: 'dark/workspaceTrust.svg',
},
}
},
},
{
id: 'pickAFolderTask-Mac',
@ -250,7 +251,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
},
{
id: 'quickOpen',
title: localize('gettingStarted.quickOpen.title', "Quickly navigate between your file"),
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)"),
when: 'workspaceFolderCount != 0',
media: {
@ -323,6 +324,20 @@ 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)"),
when: 'syncStatus != uninitialized',
completionEvents: ['onEvent:sync-enabled'],
media: {
type: 'image', altText: 'The "Turn on Sync" entry in the settings gear menu.', path: {
dark: 'dark/settingsSync.png',
light: 'light/settingsSync.png',
hc: 'hc/settingsSync.png',
},
}
},
{
id: 'videoTutorial',
title: localize('gettingStarted.videoTutorial.title', "Lean back and learn"),

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 30 KiB

View file

@ -7,41 +7,23 @@ import { escape } from 'vs/base/common/strings';
import { localize } from 'vs/nls';
export default () => `
<vertically-centered>
<checklist>
<checkbox on-checked="setTheme:Default Light+" checked-on="config.workbench.colorTheme == 'Default Light+'">
<img width="200" src="./light.png"/>
<img width="150" src="./light.png"/>
${escape(localize('light', "Light"))}
</checkbox>
<checkbox on-checked="setTheme:Default Dark+" checked-on="config.workbench.colorTheme == 'Default Dark+'">
<img width="200" src="./dark.png"/>
<img width="150" src="./dark.png"/>
${escape(localize('dark', "Dark"))}
</checkbox>
<checkbox on-checked="setTheme:Quiet Light" checked-on="config.workbench.colorTheme == 'Quiet Light'">
<img width="200" src="./quiet-light.png"/>
Quiet Light
</checkbox>
<checkbox on-checked="setTheme:Monokai" checked-on="config.workbench.colorTheme == 'Monokai'">
<img width="200" src="./monokai.png"/>
Monokai
</checkbox>
<checkbox on-checked="command:workbench.action.selectTheme" checked-on="false">
<img width="200" src="./more.png"/>
See More...
<checkbox on-checked="setTheme:Default High Contrast" checked-on="config.workbench.colorTheme == 'Default High Contrast'">
<img width="150" src="./monokai.png"/>
${escape(localize('HighContrast', "High Contrast"))}
</checkbox>
</checklist>
\`\`\`js
const btn = document.getElementById('btn')
let count = 0
function render() {
btn.innerText = \`Count: \${count}\`
}
btn.addEventListener('click', () => {
// Count from 1 to 10.
if (count < 10) {
count += 1
render()
}
})
\`\`\`
<checkbox on-checked="command:workbench.action.selectTheme" checked-on="false">
${escape(localize('seeMore', "See More Themes..."))}
</checkbox>
</vertically-centered>
`;

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 30 KiB

View file

@ -6,15 +6,36 @@
import { localize } from 'vs/nls';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { Registry } from 'vs/platform/registry/common/platform';
import { WelcomePageContribution, WelcomePageAction, WelcomeInputSerializer, DEFAULT_STARTUP_EDITOR_CONFIG } from 'vs/workbench/contrib/welcome/page/browser/welcomePage';
import { WelcomePageContribution, WelcomePageAction, WelcomeInputSerializer } from 'vs/workbench/contrib/welcome/page/browser/welcomePage';
import { IWorkbenchActionRegistry, Extensions as ActionExtensions, CATEGORIES } from 'vs/workbench/common/actions';
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { IEditorInputFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration';
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
.registerConfiguration(DEFAULT_STARTUP_EDITOR_CONFIG);
.registerConfiguration({
...workbenchConfigurationNodeBase,
'properties': {
'workbench.startupEditor': {
'scope': ConfigurationScope.RESOURCE,
'type': 'string',
'enum': ['none', 'welcomePage', 'readme', 'newUntitledFile', 'welcomePageInEmptyWorkbench', 'gettingStarted', 'gettingStartedInEmptyWorkbench'],
'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.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."),
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.gettingStartedInEmptyWorkbench' }, "When opening an empty workbench, open the new Welcome Page with content to aid in getting started with VS Code and extensions.")
],
'default': 'welcomePage',
'description': localize('workbench.startupEditor', "Controls which editor is shown at startup, if none are restored from the previous session.")
},
}
});
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
.registerWorkbenchContribution(WelcomePageContribution, LifecyclePhase.Restored);

View file

@ -10,7 +10,7 @@ import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/c
import * as arrays from 'vs/base/common/arrays';
import { WalkThroughInput } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { onUnexpectedError, isPromiseCanceledError } from 'vs/base/common/errors';
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
@ -48,63 +48,15 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { GettingStartedInput, gettingStartedInputTypeId } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedInput';
import { welcomeButtonBackground, welcomeButtonHoverBackground, welcomePageBackground } from 'vs/workbench/contrib/welcome/page/browser/welcomePageColors';
import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationNode, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
export const DEFAULT_STARTUP_EDITOR_CONFIG: IConfigurationNode = {
...workbenchConfigurationNodeBase,
'properties': {
'workbench.startupEditor': {
'scope': ConfigurationScope.RESOURCE,
'type': 'string',
'enum': ['none', 'welcomePage', 'readme', 'newUntitledFile', 'welcomePageInEmptyWorkbench', 'gettingStarted'],
'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 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.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 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 Getting Started page.")]
],
'default': 'welcomePage',
'description': localize('workbench.startupEditor', "Controls which editor is shown at startup, if none are restored from the previous session.")
},
}
};
export const EXPERIMENTAL_GETTING_STARTED_STARTUP_EDITOR_CONFIG: IConfigurationNode = {
...workbenchConfigurationNodeBase,
'properties': {
'workbench.startupEditor': {
'scope': ConfigurationScope.RESOURCE,
'type': 'string',
'enum': ['none', 'welcomePage', 'readme', 'newUntitledFile', 'welcomePageInEmptyWorkbench', 'gettingStarted'],
'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 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.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 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 Getting Started page.")]
],
'default': 'gettingStarted',
'description': localize('workbench.startupEditor', "Controls which editor is shown at startup, if none are restored from the previous session.")
},
}
};
const configurationKey = 'workbench.startupEditor';
const oldConfigurationKey = 'workbench.welcome.enabled';
const telemetryFrom = 'welcomePage';
export class WelcomePageContribution implements IWorkbenchContribution {
private experimentManagementComplete: Promise<void>;
private tasExperimentService: ITASExperimentService | undefined;
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ -116,11 +68,7 @@ export class WelcomePageContribution implements IWorkbenchContribution {
@ILifecycleService private readonly lifecycleService: ILifecycleService,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
@ICommandService private readonly commandService: ICommandService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@ILogService private readonly logService: ILogService,
@optional(ITASExperimentService) tasExperimentService: ITASExperimentService,
) {
this.tasExperimentService = tasExperimentService;
// Run immediately to minimize time spent waiting for exp service.
this.experimentManagementComplete = this.manageDefaultValuesForGettingStartedExperiment().catch(onUnexpectedError);
@ -133,43 +81,6 @@ export class WelcomePageContribution implements IWorkbenchContribution {
if (this.lifecycleService.startupKind === StartupKind.ReloadedWindow || config.value !== config.defaultValue) {
return;
}
if (this.configurationService.getValue('workbench.gettingStartedTreatmentOverride')) {
await new Promise(resolve => setTimeout(resolve, 1000));
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).deregisterConfigurations([DEFAULT_STARTUP_EDITOR_CONFIG]);
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration(EXPERIMENTAL_GETTING_STARTED_STARTUP_EDITOR_CONFIG);
}
let someValueReturned = false;
type GettingStartedTreatmentData = { value: string; };
type GettingStartedTreatmentClassification = { value: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' }; };
const tasUseGettingStartedAsDefault = this.tasExperimentService?.getTreatment<boolean>('StartupGettingStarted')
.then(result => {
this.logService.trace('StartupGettingStarted:', result);
this.telemetryService.publicLog2<GettingStartedTreatmentData, GettingStartedTreatmentClassification>('gettingStartedTreatmentValue', { value: '' + !!result });
someValueReturned = true;
return result;
})
.catch(error => {
this.logService.error('Recieved error when consulting experiment service for getting started experiment', error);
this.telemetryService.publicLog2<GettingStartedTreatmentData, GettingStartedTreatmentClassification>('gettingStartedTreatmentValue', { value: 'err' });
someValueReturned = true;
return false;
});
const fallback = new Promise<false>(c => setTimeout(() => c(false), 2000)).then(
() => {
if (!someValueReturned) { this.logService.trace('Unable to read getting started treatment data in time, falling back to welcome'); }
someValueReturned = true;
}
);
const useGettingStartedAsDefault = !!await Promise.race([tasUseGettingStartedAsDefault, fallback]);
if (useGettingStartedAsDefault) {
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).deregisterConfigurations([DEFAULT_STARTUP_EDITOR_CONFIG]);
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration(EXPERIMENTAL_GETTING_STARTED_STARTUP_EDITOR_CONFIG);
}
}
private async run() {
@ -225,7 +136,7 @@ export class WelcomePageContribution implements IWorkbenchContribution {
await this.experimentManagementComplete;
const startupEditorSetting = this.configurationService.getValue(configurationKey);
const startupEditorTypeID = startupEditorSetting === 'gettingStarted' ? gettingStartedInputTypeId : welcomeInputTypeId;
const startupEditorTypeID = (startupEditorSetting === 'gettingStarted' || startupEditorSetting === 'gettingStartedInEmptyWorkbench') ? gettingStartedInputTypeId : welcomeInputTypeId;
const editor = this.editorService.activeEditor;
// Ensure that the welcome editor won't get opened more than once
@ -252,7 +163,10 @@ function isWelcomePageEnabled(configurationService: IConfigurationService, conte
if (startupEditor.value === 'readme' && startupEditor.userValue !== 'readme') {
console.error('Warning: `workbench.startupEditor: readme` setting ignored due to being set somewhere other than user settings');
}
return startupEditor.value === 'welcomePage' || startupEditor.value === 'gettingStarted' || startupEditor.userValue === 'readme' || startupEditor.value === 'welcomePageInEmptyWorkbench' && contextService.getWorkbenchState() === WorkbenchState.EMPTY;
return startupEditor.value === 'welcomePage'
|| startupEditor.value === 'gettingStarted'
|| startupEditor.userValue === 'readme'
|| (contextService.getWorkbenchState() === WorkbenchState.EMPTY && (startupEditor.value === 'welcomePageInEmptyWorkbench' || startupEditor.value === 'gettingStartedInEmptyWorkbench'));
}
export class WelcomePageAction extends Action {

View file

@ -131,10 +131,7 @@ export class WalkThroughInput extends EditorInput {
}
if (otherInput instanceof WalkThroughInput) {
let otherResourceEditorInput = <WalkThroughInput>otherInput;
// Compare by properties
return isEqual(otherResourceEditorInput.options.resource, this.options.resource);
return isEqual(otherInput.options.resource, this.options.resource);
}
return false;

View file

@ -45,6 +45,8 @@ import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IBannerItem, IBannerService } from 'vs/workbench/services/banner/browser/bannerService';
import { isVirtualWorkspace } from 'vs/platform/remote/common/remoteHosts';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
const BANNER_RESTRICTED_MODE = 'workbench.banner.restrictedMode';
const BANNER_VIRTUAL_WORKSPACE = 'workbench.banner.virtualWorkspace';
@ -77,6 +79,8 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben
@IStatusbarService private readonly statusbarService: IStatusbarService,
@IStorageService private readonly storageService: IStorageService,
@IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService,
@IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService,
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@IBannerService private readonly bannerService: IBannerService,
@IHostService private readonly hostService: IHostService,
) {
@ -84,25 +88,35 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben
this.statusbarEntryAccessor = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
if (isWorkspaceTrustEnabled(configurationService)) {
this.registerListeners();
this.createStatusbarEntry();
(async () => {
// Set empty workspace trust state
this.setEmptyWorkspaceTrustState();
await this.workspaceTrustManagementService.workspaceTrustInitialized;
// Show modal dialog
if (this.hostService.hasFocus) {
this.showModalOnStart();
} else {
const focusDisposable = this.hostService.onDidChangeFocus(focused => {
if (focused) {
focusDisposable.dispose();
this.showModalOnStart();
}
});
// Workaround until isTrusted from resolver is available pre-resolution
if (this.workbenchEnvironmentService.remoteAuthority) {
await this.remoteAuthorityResolverService.resolveAuthority(this.workbenchEnvironmentService.remoteAuthority);
}
}
if (isWorkspaceTrustEnabled(configurationService)) {
this.registerListeners();
this.createStatusbarEntry();
// Set empty workspace trust state
this.setEmptyWorkspaceTrustState();
// Show modal dialog
if (this.hostService.hasFocus) {
this.showModalOnStart();
} else {
const focusDisposable = this.hostService.onDidChangeFocus(focused => {
if (focused) {
focusDisposable.dispose();
this.showModalOnStart();
}
});
}
}
})();
}
private get startupPromptSetting(): 'always' | 'once' | 'never' {
@ -168,7 +182,7 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben
}
// Don't show modal prompt if workspace trust cannot be changed
if (!(await this.workspaceTrustManagementService.canSetWorkspaceTrust())) {
if (!(this.workspaceTrustManagementService.canSetWorkspaceTrust())) {
return;
}
@ -447,9 +461,6 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben
}
private registerListeners(): void {
this._register(this.workspaceTrustManagementService.onDidInitiateWorkspaceTrustRequestOnStartup(() => {
this.showModalOnStart();
}));
this._register(this.workspaceTrustRequestService.onDidInitiateWorkspaceTrustRequest(async requestOptions => {
// Message
@ -700,7 +711,7 @@ class WorkspaceTrustTelemetryContribution extends Disposable implements IWorkben
};
this.telemetryService.publicLog2<WorkspaceTrustInfoEvent, WorkspaceTrustInfoEventClassification>('workspaceTrustFolderCounts', {
trustedFoldersCount: this.workspaceTrustManagementService.getTrustedFolders().length,
trustedFoldersCount: this.workspaceTrustManagementService.getTrustedUris().length,
});
}

View file

@ -188,7 +188,7 @@ class WorkspaceTrustedUrisTable extends Disposable {
currentWorkspaceUris.push(currentWorkspace.configuration);
}
const entries = this.workspaceTrustManagementService.getTrustedFolders().map(uri => {
const entries = this.workspaceTrustManagementService.getTrustedUris().map(uri => {
let relatedToCurrentWorkspace = false;
for (const workspaceUri of currentWorkspaceUris) {
@ -215,7 +215,7 @@ class WorkspaceTrustedUrisTable extends Disposable {
}
acceptEdit(item: ITrustedUriItem, uri: URI) {
const trustedFolders = this.workspaceTrustManagementService.getTrustedFolders();
const trustedFolders = this.workspaceTrustManagementService.getTrustedUris();
const index = this.getIndexOfTrustedUriEntry(item);
if (index >= trustedFolders.length) {
@ -224,7 +224,7 @@ class WorkspaceTrustedUrisTable extends Disposable {
trustedFolders[index] = uri;
}
this.workspaceTrustManagementService.setTrustedFolders(trustedFolders);
this.workspaceTrustManagementService.setTrustedUris(trustedFolders);
this._onDidAcceptEdit.fire(item);
}
@ -803,13 +803,13 @@ export class WorkspaceTrustEditor extends EditorPane {
], xListIcon.classNamesArray);
if (this.workspaceTrustManagementService.isWorkpaceTrusted()) {
if (await this.workspaceTrustManagementService.canSetWorkspaceTrust()) {
if (this.workspaceTrustManagementService.canSetWorkspaceTrust()) {
this.addDontTrustButtonToElement(untrustedContainer);
} else {
this.addTrustedTextToElement(untrustedContainer);
}
} else {
if (await this.workspaceTrustManagementService.canSetWorkspaceTrust()) {
if (this.workspaceTrustManagementService.canSetWorkspaceTrust()) {
this.addTrustButtonToElement(trustedContainer);
}
}

View file

@ -44,7 +44,7 @@ import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/
import { LoggerChannelClient, LogLevelChannelClient } from 'vs/platform/log/common/logIpc';
import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
import { NativeLogService } from 'vs/workbench/services/log/electron-sandbox/logService';
import { RemoteWorkspaceTrustManagementService, WorkspaceTrustManagementService } from 'vs/workbench/services/workspaces/common/workspaceTrust';
import { WorkspaceTrustManagementService } from 'vs/workbench/services/workspaces/common/workspaceTrust';
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
import { registerWindowDriver } from 'vs/platform/driver/electron-sandbox/driver';
@ -264,10 +264,7 @@ export abstract class SharedDesktopMain extends Disposable {
]);
// Workspace Trust Service
const workspaceTrustManagementService = !environmentService.remoteAuthority ?
new WorkspaceTrustManagementService(configurationService, storageService, uriIdentityService, environmentService, configurationService) :
new RemoteWorkspaceTrustManagementService(configurationService, storageService, uriIdentityService, environmentService, configurationService, remoteAuthorityResolverService);
await workspaceTrustManagementService.initializeWorkspaceTrust();
const workspaceTrustManagementService = new WorkspaceTrustManagementService(configurationService, storageService, uriIdentityService, environmentService, configurationService, remoteAuthorityResolverService);
serviceCollection.set(IWorkspaceTrustManagementService, workspaceTrustManagementService);
// Update workspace trust so that configuration is updated accordingly

View file

@ -354,9 +354,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten
});
// Now that the canonical URI provider has been registered, we
// need to refresh workspace trust before resolving the authority
await this._workspaceTrustManagementService.recalculateWorkspaceTrust();
// need to wait for the trust state to be initialized
await this._workspaceTrustManagementService.workspaceTrustInitialized;
let resolverResult: ResolverResult;
try {

View file

@ -135,7 +135,7 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp
}
override matches(otherInput: unknown): boolean {
if (otherInput === this) {
if (super.matches(otherInput)) {
return true;
}

View file

@ -20,7 +20,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IRemoteAuthorityResolverService, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { WorkspaceTrustRequestOptions, IWorkspaceTrustManagementService, IWorkspaceTrustInfo, IWorkspaceTrustUriInfo, IWorkspaceTrustRequestService, IWorkspaceTrustTransitionParticipant, WorkspaceTrustUriResponse } from 'vs/platform/workspace/common/workspaceTrust';
import { isSingleFolderWorkspaceIdentifier, isUntitledWorkspace, toWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { Memento, MementoObject } from 'vs/workbench/common/memento';
@ -46,22 +46,56 @@ export function isWorkspaceTrustEnabled(configurationService: IConfigurationServ
return (configurationService.inspect<boolean>(WORKSPACE_TRUST_ENABLED).userValue ?? configurationService.inspect<boolean>(WORKSPACE_TRUST_ENABLED).defaultValue) ?? false;
}
export class CanonicalWorkspace implements IWorkspace {
constructor(
private readonly originalWorkspace: IWorkspace,
private readonly canonicalFolderUris: URI[],
private readonly canonicalConfiguration: URI | null | undefined
) { }
get folders(): IWorkspaceFolder[] {
return this.originalWorkspace.folders.map((folder, index) => {
return {
index: folder.index,
name: folder.name,
toResource: folder.toResource,
uri: this.canonicalFolderUris[index]
};
});
}
get configuration(): URI | null | undefined {
return this.canonicalConfiguration ?? this.originalWorkspace.configuration;
}
get id(): string {
return this.originalWorkspace.id;
}
}
export class WorkspaceTrustManagementService extends Disposable implements IWorkspaceTrustManagementService {
_serviceBrand: undefined;
private readonly storageKey = WORKSPACE_TRUST_STORAGE_KEY;
private _initialized: boolean;
private _workspaceTrustInitializedPromise: Promise<void>;
private _workspaceTrustInitializedPromiseResolve!: () => void;
private _remoteAuthority: ResolverResult | undefined;
private readonly _onDidChangeTrust = this._register(new Emitter<boolean>());
readonly onDidChangeTrust = this._onDidChangeTrust.event;
private readonly _onDidChangeTrustedFolders = this._register(new Emitter<void>());
readonly onDidChangeTrustedFolders = this._onDidChangeTrustedFolders.event;
protected readonly _onDidInitiateWorkspaceTrustRequestOnStartup = this._register(new Emitter<void>());
readonly onDidInitiateWorkspaceTrustRequestOnStartup = this._onDidInitiateWorkspaceTrustRequestOnStartup.event;
private _trustStateInfo: IWorkspaceTrustInfo;
private _canonicalWorkspace: IWorkspace;
protected readonly _trustState: WorkspaceTrustState;
private readonly _trustTransitionManager: WorkspaceTrustTransitionManager;
@ -70,33 +104,78 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork
@IConfigurationService protected readonly configurationService: IConfigurationService,
@IStorageService private readonly storageService: IStorageService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
@IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService,
@IWorkspaceContextService protected readonly workspaceService: IWorkspaceContextService
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService,
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
) {
super();
this._canonicalWorkspace = this.workspaceService.getWorkspace();
this._initialized = false;
this._workspaceTrustInitializedPromise = new Promise((resolve) => {
this._workspaceTrustInitializedPromiseResolve = resolve;
});
this._trustState = new WorkspaceTrustState(this.storageService);
this._trustTransitionManager = this._register(new WorkspaceTrustTransitionManager());
this._trustStateInfo = this.loadTrustInfo();
this._trustState.isTrusted = this.calculateWorkspaceTrust();
this.registerListeners();
}
//#region private interface
private registerListeners(): void {
this._register(this.workspaceService.onDidChangeWorkspaceFolders(async () => await this.recalculateWorkspaceTrust()));
this._register(this.workspaceService.onDidChangeWorkbenchState(async () => await this.recalculateWorkspaceTrust()));
// Resolve the workspace uris and resolve the initialization promise
this.resolveCanonicalWorkspaceUris().then(async () => {
this._initialized = true;
await this.updateWorkspaceTrust();
this._workspaceTrustInitializedPromiseResolve();
});
// Remote - resolve remote authority
if (this.environmentService.remoteAuthority) {
this.remoteAuthorityResolverService.resolveAuthority(this.environmentService.remoteAuthority)
.then(async result => {
this._remoteAuthority = result;
await this.updateWorkspaceTrust();
});
}
this._register(this.workspaceService.onDidChangeWorkspaceFolders(async () => await this.updateWorkspaceTrust()));
this._register(this.workspaceService.onDidChangeWorkbenchState(async () => await this.updateWorkspaceTrust()));
this._register(this.storageService.onDidChangeValue(async changeEvent => {
/* This will only execute if storage was changed by a user action in a separate window */
if (changeEvent.key === this.storageKey && JSON.stringify(this._trustStateInfo) !== JSON.stringify(this.loadTrustInfo())) {
this._trustStateInfo = this.loadTrustInfo();
this._onDidChangeTrustedFolders.fire();
await this.recalculateWorkspaceTrust();
await this.updateWorkspaceTrust();
}
}));
}
private async getCanonicalUri(uri: URI): Promise<URI> {
return this.environmentService.remoteAuthority && uri.scheme === Schemas.vscodeRemote ?
await this.remoteAuthorityResolverService.getCanonicalURI(uri) : uri;
}
private async resolveCanonicalWorkspaceUris(): Promise<void> {
const workspaceUris = this.workspaceService.getWorkspace().folders.map(f => f.uri);
const canonicalWorkspaceFolders = await Promise.all(workspaceUris.map(uri => this.getCanonicalUri(uri)));
let canonicalWorkspaceConfiguration = this.workspaceService.getWorkspace().configuration;
if (canonicalWorkspaceConfiguration && !isUntitledWorkspace(canonicalWorkspaceConfiguration, this.environmentService)) {
canonicalWorkspaceConfiguration = await this.getCanonicalUri(canonicalWorkspaceConfiguration);
}
this._canonicalWorkspace = new CanonicalWorkspace(this.workspaceService.getWorkspace(), canonicalWorkspaceFolders, canonicalWorkspaceConfiguration);
}
private loadTrustInfo(): IWorkspaceTrustInfo {
const infoAsString = this.storageService.get(this.storageKey, StorageScope.GLOBAL);
@ -127,12 +206,12 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork
this.storageService.store(this.storageKey, JSON.stringify(this._trustStateInfo), StorageScope.GLOBAL, StorageTarget.MACHINE);
this._onDidChangeTrustedFolders.fire();
await this.recalculateWorkspaceTrust();
await this.updateWorkspaceTrust();
}
protected getWorkspaceUris(): URI[] {
const workspaceUris = this.workspaceService.getWorkspace().folders.map(f => f.uri);
const workspaceConfiguration = this.workspaceService.getWorkspace().configuration;
private getWorkspaceUris(): URI[] {
const workspaceUris = this._canonicalWorkspace.folders.map(f => f.uri);
const workspaceConfiguration = this._canonicalWorkspace.configuration;
if (workspaceConfiguration && !isUntitledWorkspace(workspaceConfiguration, this.environmentService)) {
workspaceUris.push(workspaceConfiguration);
}
@ -140,11 +219,20 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork
return workspaceUris;
}
protected async calculateWorkspaceTrust(): Promise<boolean> {
private calculateWorkspaceTrust(): boolean {
if (!isWorkspaceTrustEnabled(this.configurationService)) {
return true;
}
if (!this._initialized) {
return false;
}
// Remote - remote authority explicitly sets workspace trust
if (this.environmentService.remoteAuthority && this._remoteAuthority?.options?.isTrusted !== undefined) {
return this._remoteAuthority.options.isTrusted;
}
if (this.environmentService.extensionTestsLocationURI) {
return true; // trust running tests with vscode-test
}
@ -155,36 +243,16 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork
return this._trustState.isTrusted ?? false;
}
const workspaceUris = this.getWorkspaceUris();
const trusted = await this.getUrisTrust(workspaceUris);
return trusted;
return this.getUrisTrust(this.getWorkspaceUris());
}
protected async getCanonicalUri(uri: URI): Promise<URI> {
return uri;
}
protected async getUrisTrust(uris: URI[]): Promise<boolean> {
let state = true;
for (const uri of uris) {
const { trusted } = await this.getUriTrustInfo(uri);
if (!trusted) {
state = trusted;
return state;
}
}
return state;
}
protected async updateWorkspaceTrust(trusted?: boolean): Promise<void> {
private async updateWorkspaceTrust(trusted?: boolean): Promise<void> {
if (!isWorkspaceTrustEnabled(this.configurationService)) {
return;
}
if (trusted === undefined) {
await this.resolveCanonicalWorkspaceUris();
trusted = await this.calculateWorkspaceTrust();
}
@ -200,19 +268,21 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork
this._onDidChangeTrust.fire(trusted);
}
get acceptsOutOfWorkspaceFiles(): boolean {
return this._trustState.acceptsOutOfWorkspaceFiles;
private getUrisTrust(uris: URI[]): boolean {
let state = true;
for (const uri of uris) {
const { trusted } = this.doGetUriTrustInfo(uri);
if (!trusted) {
state = trusted;
return state;
}
}
return state;
}
set acceptsOutOfWorkspaceFiles(value: boolean) {
this._trustState.acceptsOutOfWorkspaceFiles = value;
}
addWorkspaceTrustTransitionParticipant(participant: IWorkspaceTrustTransitionParticipant): IDisposable {
return this._trustTransitionManager.addWorkspaceTrustTransitionParticipant(participant);
}
async getUriTrustInfo(uri: URI): Promise<IWorkspaceTrustUriInfo> {
private doGetUriTrustInfo(uri: URI): IWorkspaceTrustUriInfo {
// Return trusted when workspace trust is disabled
if (!isWorkspaceTrustEnabled(this.configurationService)) {
return { trusted: true, uri };
@ -221,11 +291,10 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork
let resultState = false;
let maxLength = -1;
const canonicalUri = await this.getCanonicalUri(uri);
let resultUri = canonicalUri;
let resultUri = uri;
for (const trustInfo of this._trustStateInfo.uriTrustInfo) {
if (this.uriIdentityService.extUri.isEqualOrParent(canonicalUri, trustInfo.uri)) {
if (this.uriIdentityService.extUri.isEqualOrParent(uri, trustInfo.uri)) {
const fsPath = trustInfo.uri.fsPath;
if (fsPath.length > maxLength) {
maxLength = fsPath.length;
@ -238,21 +307,19 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork
return { trusted: resultState, uri: resultUri };
}
async setUrisTrust(uris: URI[], trusted: boolean): Promise<void> {
private async doSetUrisTrust(uris: URI[], trusted: boolean): Promise<void> {
let changed = false;
for (const uri of uris) {
const canonicalUri = await this.getCanonicalUri(uri);
if (trusted) {
const foundItem = this._trustStateInfo.uriTrustInfo.find(trustInfo => this.uriIdentityService.extUri.isEqual(trustInfo.uri, canonicalUri));
const foundItem = this._trustStateInfo.uriTrustInfo.find(trustInfo => this.uriIdentityService.extUri.isEqual(trustInfo.uri, uri));
if (!foundItem) {
this._trustStateInfo.uriTrustInfo.push({ uri: canonicalUri, trusted: true });
this._trustStateInfo.uriTrustInfo.push({ uri, trusted: true });
changed = true;
}
} else {
const previousLength = this._trustStateInfo.uriTrustInfo.length;
this._trustStateInfo.uriTrustInfo = this._trustStateInfo.uriTrustInfo.filter(trustInfo => !this.uriIdentityService.extUri.isEqual(trustInfo.uri, canonicalUri));
this._trustStateInfo.uriTrustInfo = this._trustStateInfo.uriTrustInfo.filter(trustInfo => !this.uriIdentityService.extUri.isEqual(trustInfo.uri, uri));
if (previousLength !== this._trustStateInfo.uriTrustInfo.length) {
changed = true;
}
@ -264,7 +331,46 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork
}
}
async canSetWorkspaceTrust(): Promise<boolean> {
//#endregion
//#region public interface
get workspaceTrustInitialized(): Promise<void> {
return this._workspaceTrustInitializedPromise;
}
get acceptsOutOfWorkspaceFiles(): boolean {
return this._trustState.acceptsOutOfWorkspaceFiles;
}
set acceptsOutOfWorkspaceFiles(value: boolean) {
this._trustState.acceptsOutOfWorkspaceFiles = value;
}
isWorkpaceTrusted(): boolean {
return this._trustState.isTrusted ?? false;
}
canSetParentFolderTrust(): boolean {
const workspaceIdentifier = toWorkspaceIdentifier(this._canonicalWorkspace);
return isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && workspaceIdentifier.uri.scheme === Schemas.file;
}
async setParentFolderTrust(trusted: boolean): Promise<void> {
const workspaceIdentifier = toWorkspaceIdentifier(this._canonicalWorkspace);
if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && workspaceIdentifier.uri.scheme === Schemas.file) {
const { parentPath } = splitName(workspaceIdentifier.uri.fsPath);
await this.setUrisTrust([URI.file(parentPath)], trusted);
}
}
canSetWorkspaceTrust(): boolean {
// Remote - remote authority not yet resolved, or remote authority explicitly sets workspace trust
if (this.environmentService.remoteAuthority && (!this._remoteAuthority || this._remoteAuthority.options?.isTrusted !== undefined)) {
return false;
}
// Empty workspace
if (this.workspaceService.getWorkbenchState() === WorkbenchState.EMPTY) {
return true;
@ -277,13 +383,13 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork
// Trusted workspace
// Can only be trusted explicitly in the single folder scenario
const workspaceIdentifier = toWorkspaceIdentifier(this.workspaceService.getWorkspace());
const workspaceIdentifier = toWorkspaceIdentifier(this._canonicalWorkspace);
if (!(isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && workspaceIdentifier.uri.scheme === Schemas.file)) {
return false;
}
// If the current folder isn't trusted directly, return false
const trustInfo = await this.getUriTrustInfo(workspaceIdentifier.uri);
const trustInfo = this.doGetUriTrustInfo(workspaceIdentifier.uri);
if (!trustInfo.trusted || !this.uriIdentityService.extUri.isEqual(workspaceIdentifier.uri, trustInfo.uri)) {
return false;
}
@ -291,7 +397,7 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork
// Check if the parent is also trusted
if (this.canSetParentFolderTrust()) {
const { parentPath } = splitName(workspaceIdentifier.uri.fsPath);
const parentPathTrustInfo = await this.getUriTrustInfo(URI.file(parentPath));
const parentPathTrustInfo = this.doGetUriTrustInfo(URI.file(parentPath));
if (parentPathTrustInfo.trusted) {
return false;
}
@ -300,32 +406,6 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork
return true;
}
canSetParentFolderTrust(): boolean {
const workspaceIdentifier = toWorkspaceIdentifier(this.workspaceService.getWorkspace());
return isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && workspaceIdentifier.uri.scheme === Schemas.file;
}
async initializeWorkspaceTrust(): Promise<void> {
this._trustState.isTrusted = isWorkspaceTrustEnabled(this.configurationService) ? await this.calculateWorkspaceTrust() : true;
}
isWorkpaceTrusted(): boolean {
return this._trustState.isTrusted ?? false;
}
async recalculateWorkspaceTrust(): Promise<void> {
await this.updateWorkspaceTrust();
}
async setParentFolderTrust(trusted: boolean): Promise<void> {
const workspaceIdentifier = toWorkspaceIdentifier(this.workspaceService.getWorkspace());
if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && workspaceIdentifier.uri.scheme === Schemas.file) {
const { parentPath } = splitName(workspaceIdentifier.uri.fsPath);
await this.setUrisTrust([URI.file(parentPath)], trusted);
}
}
async setWorkspaceTrust(trusted: boolean): Promise<void> {
// Empty workspace
if (this.workspaceService.getWorkbenchState() === WorkbenchState.EMPTY) {
@ -337,11 +417,24 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork
await this.setUrisTrust(workspaceFolders, trusted);
}
getTrustedFolders(): URI[] {
async getUriTrustInfo(uri: URI): Promise<IWorkspaceTrustUriInfo> {
// Return trusted when workspace trust is disabled
if (!isWorkspaceTrustEnabled(this.configurationService)) {
return { trusted: true, uri };
}
return this.doGetUriTrustInfo(await this.getCanonicalUri(uri));
}
async setUrisTrust(uris: URI[], trusted: boolean): Promise<void> {
this.doSetUrisTrust(await Promise.all(uris.map(uri => this.getCanonicalUri(uri))), trusted);
}
getTrustedUris(): URI[] {
return this._trustStateInfo.uriTrustInfo.map(info => info.uri);
}
async setTrustedFolders(uris: URI[]): Promise<void> {
async setTrustedUris(uris: URI[]): Promise<void> {
this._trustStateInfo.uriTrustInfo = [];
for (const uri of uris) {
const canonicalUri = await this.getCanonicalUri(uri);
@ -366,79 +459,12 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork
await this.saveTrustInfo();
}
}
export class RemoteWorkspaceTrustManagementService extends WorkspaceTrustManagementService {
private _remoteAuthority: ResolverResult | undefined;
constructor(
@IConfigurationService configurationService: IConfigurationService,
@IStorageService storageService: IStorageService,
@IUriIdentityService uriIdentityService: IUriIdentityService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IWorkspaceContextService workspaceService: IWorkspaceContextService,
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
) {
super(configurationService, storageService, uriIdentityService, environmentService, workspaceService);
// Remote - resolve remote authority
if (this.environmentService.remoteAuthority) {
this.remoteAuthorityResolverService.resolveAuthority(this.environmentService.remoteAuthority)
.then(async result => {
this._remoteAuthority = result;
await this.recalculateWorkspaceTrust();
// Show workspace trust modal dialog
this._onDidInitiateWorkspaceTrustRequestOnStartup.fire();
});
}
addWorkspaceTrustTransitionParticipant(participant: IWorkspaceTrustTransitionParticipant): IDisposable {
return this._trustTransitionManager.addWorkspaceTrustTransitionParticipant(participant);
}
override async canSetWorkspaceTrust(): Promise<boolean> {
// Remote - remote authority not yet resolved, or remote authority explicitly sets workspace trust
if (this.environmentService.remoteAuthority && (!this._remoteAuthority || this._remoteAuthority.options?.isTrusted !== undefined)) {
return false;
}
return super.canSetWorkspaceTrust();
}
override async initializeWorkspaceTrust(): Promise<void> {
this._trustState.isTrusted = !isWorkspaceTrustEnabled(this.configurationService);
}
protected override async calculateWorkspaceTrust(): Promise<boolean> {
if (!isWorkspaceTrustEnabled(this.configurationService)) {
return true;
}
if (this.environmentService.extensionTestsLocationURI) {
return true; // trust running tests with vscode-test
}
// Remote - remote authority explicitly sets workspace trust
if (this.environmentService.remoteAuthority && this._remoteAuthority?.options?.isTrusted !== undefined) {
return this._remoteAuthority.options.isTrusted;
}
if (this.workspaceService.getWorkbenchState() === WorkbenchState.EMPTY) {
// Use memento if present, otherwise default to restricted mode
// Workspace may transition to trusted based on the opened editors
return this._trustState.isTrusted ?? false;
}
// Workspace
const workspaceUris = this.getWorkspaceUris();
const trusted = await this.getUrisTrust(workspaceUris);
return trusted;
}
protected override async getCanonicalUri(uri: URI): Promise<URI> {
return this.environmentService.remoteAuthority ?
await this.remoteAuthorityResolverService.getCanonicalURI(uri) : uri;
}
//#endregion
}
export class WorkspaceTrustRequestService extends Disposable implements IWorkspaceTrustRequestService {

View file

@ -39,7 +39,7 @@ export class TestWorkspaceTrustManagementService implements IWorkspaceTrustManag
throw new Error('Method not implemented.');
}
getTrustedFolders(): URI[] {
getTrustedUris(): URI[] {
throw new Error('Method not implemented.');
}
@ -51,7 +51,7 @@ export class TestWorkspaceTrustManagementService implements IWorkspaceTrustManag
throw new Error('Method not implemented.');
}
async setTrustedFolders(folders: URI[]): Promise<void> {
async setTrustedUris(folders: URI[]): Promise<void> {
throw new Error('Method not implemented.');
}
@ -63,11 +63,7 @@ export class TestWorkspaceTrustManagementService implements IWorkspaceTrustManag
throw new Error('Method not implemented.');
}
canSetWorkspaceTrust(): Promise<boolean> {
throw new Error('Method not implemented.');
}
initializeWorkspaceTrust(): Promise<void> {
canSetWorkspaceTrust(): boolean {
throw new Error('Method not implemented.');
}
@ -75,8 +71,8 @@ export class TestWorkspaceTrustManagementService implements IWorkspaceTrustManag
return this.trusted;
}
recalculateWorkspaceTrust(): Promise<void> {
throw new Error('Method not implemented.');
get workspaceTrustInitialized(): Promise<void> {
return Promise.resolve();
}
async setWorkspaceTrust(trusted: boolean): Promise<void> {

View file

@ -9540,10 +9540,10 @@ typescript@^2.6.2:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4"
integrity sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q=
typescript@^4.3.0-dev.20210503:
version "4.3.0-dev.20210503"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.0-dev.20210503.tgz#0a3eb480676effd4975beb5a2f097530ed53550a"
integrity sha512-Gj3TQve5PLCZoPBy96Yp6Y3+jNLmms0i3ynhxEJAKgax7Fxui29/uG/DClbBtKz1peNhzXwikXVFFAV1BB/3mw==
typescript@^4.4.0-dev.20210528:
version "4.4.0-dev.20210528"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.0-dev.20210528.tgz#42453bc42e9d9df8ad0741c207c24d56407c0347"
integrity sha512-ACV+mYKC+PhWUXIDUL6qmFClIdrKc20KRxDePt8bniCgkKQD4XRYKl7m02paxJM3nTMRdlfjs0ncaslA5BA1GA==
typical@^4.0.0:
version "4.0.0"