Merge branch 'main' into tyriar/finalize_179476

This commit is contained in:
Daniel Imms 2023-08-24 05:58:18 -07:00 committed by GitHub
commit 8d76882313
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
138 changed files with 1661 additions and 821 deletions

View file

@ -19,13 +19,14 @@ This dev container includes configuration for a development container for workin
> **Note:** The Dev Containers extension requires the Visual Studio Code distribution of Code - OSS. See the [FAQ](https://aka.ms/vscode-remote/faq/license) for details.
4. Due to the size of the repository we strongly recommend cloning it on a Linux filesystem for better bind mount performance. On macOS we recommend using a Docker volume (press <kbd>F1</kbd> and select **Dev Containers: Clone Repository in Container Volume...**) and on Windows we recommend using a WSL folder:
- Make sure you are running a recent WSL version to get X11 and Wayland support.
- Use the WSL extension for VS Code to open the cloned folder in WSL.
- Press <kbd>F1</kbd> and select **Dev Containers: Reopen in Container**.
Next: **[Try it out!](#try-it)**
## Try it!
## Try it
To start working with Code - OSS, follow these steps:
@ -50,6 +51,6 @@ Next, let's try debugging.
Enjoy!
# Notes
## Notes
The container comes with VS Code Insiders installed. To run it from an Integrated Terminal use `VSCODE_IPC_HOOK_CLI= /usr/bin/code-insiders .`.

View file

@ -14,21 +14,21 @@ If you already have VS Code and Docker installed, you can click the badge above
2. **Important**: Docker needs at least **4 Cores and 8 GB of RAM** to run a full build with **9 GB of RAM** being recommended. 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.
> **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.
3. Install [Visual Studio Code Stable](https://code.visualstudio.com/) or [Insiders](https://code.visualstudio.com/insiders/) and the [Dev Containers](https://aka.ms/vscode-remote/download/containers) extension.
![Image of Dev Containers extension](https://microsoft.github.io/vscode-remote-release/images/dev-containers-extn.png)
![Image of Dev Containers extension](https://microsoft.github.io/vscode-remote-release/images/dev-containers-extn.png)
> **Note:** The Dev 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 Dev 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> or <kbd>F1</kbd> and select **Dev 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 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.
> **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][def] to connect to `localhost:5901` and enter `vscode` as the password.
Anything you start in VS Code, or the integrated terminal, will appear here.
@ -54,41 +54,42 @@ Next: **[Try it out!](#try-it)**
### Using VS Code with GitHub Codespaces
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.
You may see improved VNC responsiveness when accessing a codespace from VS Code client since you can use a [VNC Viewer][def]. Here's how to do it.
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).
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).
> **Note:** The GitHub Codespaces extension requires the Visual Studio Code distribution of Code - OSS.
> **Note:** The GitHub Codespaces extension requires the Visual Studio Code distribution of Code - OSS.
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 can select a different one later.
- Choose **Standard** (4-core, 8GB) as the size.
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.
- `microsoft/vscode` for the repository.
- Select any branch (e.g. **main**) - you can select a different one later.
- Choose **Standard** (4-core, 8GB) as the size.
3. After you have connected to the codespace, you can use a [VNC Viewer][def] to connect to `localhost:5901` and enter `vscode` as the password.
> **Tip:** You may also need change your VNC client's **Picture Quality** setting to **High** to get a full color desktop.
5. Anything you start in VS Code, or the integrated terminal, will appear here.
4. Anything you start in VS Code, or the integrated terminal, will appear here.
Next: **[Try it out!](#try-it)**
## Try it!
## 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 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
```
```bash
yarn install
bash scripts/code.sh
```
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.
2. After the build is complete, open a web browser or a [VNC Viewer][def] 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!
@ -98,8 +99,10 @@ Next, let's try debugging.
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.
> **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.
3. After a bit, Code - OSS will appear with the debugger attached!
Enjoy!
[def]: https://www.realvnc.com/en/connect/download/viewer/

View file

@ -104,6 +104,6 @@ If you believe the bot got something wrong, please open a new issue and let us k
If you are interested in writing code to fix issues,
please see [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) in the wiki.
# Thank You!
## Thank You
Your contributions to open source, large or small, make great projects like this possible. Thank you for taking the time to contribute.

View file

@ -1,4 +1,5 @@
# Visual Studio Code - Open Source ("Code - OSS")
[![Feature Requests](https://img.shields.io/github/issues/microsoft/vscode/feature-request.svg)](https://github.com/microsoft/vscode/issues?q=is%3Aopen+is%3Aissue+label%3Afeature-request+sort%3Areactions-%2B1-desc)
[![Bugs](https://img.shields.io/github/issues/microsoft/vscode/bug.svg)](https://github.com/microsoft/vscode/issues?utf8=✓&q=is%3Aissue+is%3Aopen+label%3Abug)
[![Gitter](https://img.shields.io/badge/chat-on%20gitter-yellow.svg)](https://gitter.im/Microsoft/vscode)
@ -60,9 +61,10 @@ VS Code includes a set of built-in extensions located in the [extensions](extens
This repository includes a Visual Studio Code Dev Containers / GitHub Codespaces development container.
- For [Dev Containers](https://aka.ms/vscode-remote/download/containers), use the **Dev Containers: Clone Repository in Container Volume...** command which creates a Docker volume for better disk I/O on macOS and Windows.
- If you already have VS Code and Docker installed, you can also click [here](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/vscode) to get started. This will cause VS Code to automatically install the Dev Containers extension if needed, clone the source code into a container volume, and spin up a dev container for use.
- For Codespaces, install the [GitHub Codespaces](https://marketplace.visualstudio.com/items?itemName=GitHub.codespaces) extension in VS Code, and use the **Codespaces: Create New Codespace** command.
* For [Dev Containers](https://aka.ms/vscode-remote/download/containers), use the **Dev Containers: Clone Repository in Container Volume...** command which creates a Docker volume for better disk I/O on macOS and Windows.
* If you already have VS Code and Docker installed, you can also click [here](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/vscode) to get started. This will cause VS Code to automatically install the Dev Containers extension if needed, clone the source code into a container volume, and spin up a dev container for use.
* For Codespaces, install the [GitHub Codespaces](https://marketplace.visualstudio.com/items?itemName=GitHub.codespaces) 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

@ -18,13 +18,13 @@ You should receive a response within 24 hours. If for some reason you do not, pl
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.

View file

@ -10,4 +10,5 @@ The Monaco Editor is the code editor that powers [VS Code](https://github.com/mi
This npm module contains the core editor functionality, as it comes from the [vscode repository](https://github.com/microsoft/vscode).
## License
[MIT](https://github.com/microsoft/vscode/blob/main/LICENSE.txt)

View file

@ -6,6 +6,8 @@
use crate::{constants::APPLICATION_NAME, util::errors::CodeError};
use async_trait::async_trait;
use std::path::{Path, PathBuf};
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::net::TcpListener;
use uuid::Uuid;
@ -44,7 +46,7 @@ cfg_if::cfg_if! {
} else {
use tokio::{time::sleep, io::ReadBuf};
use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions, NamedPipeClient, NamedPipeServer};
use std::{time::Duration, pin::Pin, task::{Context, Poll}, io};
use std::{time::Duration, io};
use pin_project::pin_project;
#[pin_project(project = AsyncPipeProj)]
@ -174,6 +176,57 @@ cfg_if::cfg_if! {
}
}
impl AsyncPipeListener {
pub fn into_pollable(self) -> PollableAsyncListener {
PollableAsyncListener {
listener: Some(self),
write_fut: tokio_util::sync::ReusableBoxFuture::new(make_accept_fut(None)),
}
}
}
pub struct PollableAsyncListener {
listener: Option<AsyncPipeListener>,
write_fut: tokio_util::sync::ReusableBoxFuture<
'static,
(AsyncPipeListener, Result<AsyncPipe, CodeError>),
>,
}
async fn make_accept_fut(
data: Option<AsyncPipeListener>,
) -> (AsyncPipeListener, Result<AsyncPipe, CodeError>) {
match data {
Some(mut l) => {
let c = l.accept().await;
(l, c)
}
None => unreachable!("this future should not be pollable in this state"),
}
}
impl hyper::server::accept::Accept for PollableAsyncListener {
type Conn = AsyncPipe;
type Error = CodeError;
fn poll_accept(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Self::Conn, Self::Error>>> {
if let Some(l) = self.listener.take() {
self.write_fut.set(make_accept_fut(Some(l)))
}
match self.write_fut.poll(cx) {
Poll::Ready((l, cnx)) => {
self.listener = Some(l);
Poll::Ready(Some(cnx))
}
Poll::Pending => Poll::Pending,
}
}
}
/// Gets a random name for a pipe/socket on the paltform
pub fn get_socket_name() -> PathBuf {
cfg_if::cfg_if! {

View file

@ -185,6 +185,9 @@ pub struct ServeWebArgs {
/// Host to listen on, defaults to 'localhost'
#[clap(long)]
pub host: Option<String>,
// The path to a socket file for the server to listen to.
#[clap(long)]
pub socket_path: Option<String>,
/// Port to listen on. If 0 is passed a random free port is picked.
#[clap(long, default_value_t = 8000)]
pub port: u16,

View file

@ -16,7 +16,9 @@ use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::pin;
use tokio::process::Command;
use crate::async_pipe::{get_socket_name, get_socket_rw_stream, AsyncPipe};
use crate::async_pipe::{
get_socket_name, get_socket_rw_stream, listen_socket_rw_stream, AsyncPipe,
};
use crate::constants::VSCODE_CLI_QUALITY;
use crate::download_cache::DownloadCache;
use crate::log;
@ -53,43 +55,53 @@ const RELEASE_CACHE_SECS: u64 = 60 * 60;
/// while new clients get new VS Code Server versions.
pub async fn serve_web(ctx: CommandContext, mut args: ServeWebArgs) -> Result<i32, AnyError> {
legal::require_consent(&ctx.paths, args.accept_server_license_terms)?;
let mut addr: SocketAddr = match &args.host {
Some(h) => h.parse().map_err(CodeError::InvalidHostAddress)?,
None => SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
};
addr.set_port(args.port);
let platform: crate::update_service::Platform = PreReqChecker::new().verify().await?;
if !args.without_connection_token {
// Ensure there's a defined connection token, since if multiple server versions
// are excuted, they will need to have a single shared token.
let connection_token = args
.connection_token
.clone()
.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
ctx.log.result(format!(
"Web UI available at http://{}?tkn={}",
addr, connection_token,
));
args.connection_token = Some(connection_token);
} else {
ctx.log
.result(format!("Web UI available at http://{}", addr));
args.connection_token = None;
args.connection_token = Some(
args.connection_token
.clone()
.unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
);
}
let cm = ConnectionManager::new(&ctx, platform, args);
let make_svc = make_service_fn(move |_conn| {
let cm = ConnectionManager::new(&ctx, platform, args.clone());
let make_svc = move || {
let cm = cm.clone();
let log = ctx.log.clone();
let log = cm.log.clone();
let service = service_fn(move |req| handle(cm.clone(), log.clone(), req));
async move { Ok::<_, Infallible>(service) }
});
};
let server = Server::bind(&addr).serve(make_svc);
let r = if let Some(s) = args.socket_path {
let socket = listen_socket_rw_stream(&PathBuf::from(&s)).await?;
ctx.log.result(format!("Web UI available on {}", s));
Server::builder(socket.into_pollable())
.serve(make_service_fn(|_| make_svc()))
.await
} else {
let addr: SocketAddr = match &args.host {
Some(h) => {
SocketAddr::new(h.parse().map_err(CodeError::InvalidHostAddress)?, args.port)
}
None => SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), args.port),
};
server.await.map_err(CodeError::CouldNotListenOnInterface)?;
let mut listening = format!("Web UI available at http://{}", addr);
if let Some(ct) = args.connection_token {
listening.push_str(&format!("?tkn={}", ct));
}
ctx.log.result(listening);
Server::bind(&addr)
.serve(make_service_fn(|_| make_svc()))
.await
};
r.map_err(CodeError::CouldNotListenOnInterface)?;
Ok(0)
}

View file

@ -14,7 +14,8 @@ The Git extension exposes an API, reachable by any other extension.
2. Include `git-base.d.ts` in your extension's compilation.
3. Get a hold of the API with the following snippet:
```ts
const gitBaseExtension = vscode.extensions.getExtension<GitBaseExtension>('vscode.git-base').exports;
const git = gitBaseExtension.getAPI(1);
```
```ts
const gitBaseExtension = vscode.extensions.getExtension<GitBaseExtension>('vscode.git-base').exports;
const git = gitBaseExtension.getAPI(1);
```

View file

@ -14,7 +14,7 @@ The Git extension exposes an API, reachable by any other extension.
2. Include `git.d.ts` in your extension's compilation.
3. Get a hold of the API with the following snippet:
```ts
const gitExtension = vscode.extensions.getExtension<GitExtension>('vscode.git').exports;
const git = gitExtension.getAPI(1);
```
```ts
const gitExtension = vscode.extensions.getExtension<GitExtension>('vscode.git').exports;
const git = gitExtension.getAPI(1);
```

View file

@ -1,10 +1,12 @@
The file `JavaScript.tmLanguage.json` is derived from [TypeScriptReact.tmLanguage](https://github.com/microsoft/TypeScript-TmLanguage/blob/master/TypeScriptReact.tmLanguage).
To update to the latest version:
- `cd extensions/typescript` and run `npm run update-grammars`
- don't forget to run the integration tests at `./scripts/test-integration.sh`
The script does the following changes:
- fileTypes .tsx -> .js & .jsx
- scopeName scope.tsx -> scope.js
- update all rule names .tsx -> .js

View file

@ -4,4 +4,4 @@
## Features
See [JSON in Visual Studio Code](https://code.visualstudio.com/docs/languages/json) to learn about the features of this extension.
See [JSON in Visual Studio Code](https://code.visualstudio.com/docs/languages/json) to learn about the features of this extension.

View file

@ -11,6 +11,7 @@ The JSON Language server provides language-specific smarts for editing, validati
### Server capabilities
The JSON language server supports requests on documents of language id `json` and `jsonc`.
- `json` documents are parsed and validated following the [JSON specification](https://tools.ietf.org/html/rfc7159).
- `jsonc` documents additionally accept single line (`//`) and multi-line comments (`/* ... */`). JSONC is a VSCode specific file format, intended for VSCode configuration files, without any aspirations to define a new common file format.
@ -25,12 +26,12 @@ The server implements the following capabilities of the language server protocol
- Semantic Selection for semantic selection for one or multiple cursor positions.
- [Goto Definition](https://microsoft.github.io/language-server-protocol/specification#textDocument_definition) for $ref references in JSON schemas
- [Diagnostics (Validation)](https://microsoft.github.io/language-server-protocol/specification#textDocument_publishDiagnostics) are pushed for all open documents
- syntax errors
- structural validation based on the document's [JSON schema](http://json-schema.org/).
- syntax errors
- structural validation based on the document's [JSON schema](http://json-schema.org/).
In order to load JSON schemas, the JSON server uses NodeJS `http` and `fs` modules. For all other features, the JSON server only relies on the documents and settings provided by the client through the LSP.
### Client requirements:
### Client requirements
The JSON language server expects the client to only send requests and notifications for documents of language id `json` and `jsonc`.
@ -56,8 +57,8 @@ Clients may send a `workspace/didChangeConfiguration` notification to notify the
The server supports the following settings:
- http
- `proxy`: The URL of the proxy server to use when fetching schema. When undefined or empty, no proxy is used.
- `proxyStrictSSL`: Whether the proxy server certificate should be verified against the list of supplied CAs.
- `proxy`: The URL of the proxy server to use when fetching schema. When undefined or empty, no proxy is used.
- `proxyStrictSSL`: Whether the proxy server certificate should be verified against the list of supplied CAs.
- json
- `format`
@ -72,6 +73,7 @@ The server supports the following settings:
- `resultLimit`: The max number of color decorators and outline symbols to be computed (for performance reasons)
- `jsonFoldingLimit`: The max number of folding ranges to be computed for json documents (for performance reasons)
- `jsoncFoldingLimit`: The max number of folding ranges to be computed for jsonc documents (for performance reasons)
```json
{
"http": {
@ -103,6 +105,7 @@ The server supports the following settings:
[JSON schemas](http://json-schema.org/) are essential for code assist, hovers, color decorators to work and are required for structural validation.
To find the schema for a given JSON document, the server uses the following mechanisms:
- JSON documents can define the schema URL using a `$schema` property
- The settings define a schema association based on the documents URL. Settings can either associate a schema URL to a file or path pattern, and they can directly provide a schema.
- Additionally, schema associations can also be provided by a custom 'schemaAssociations' configuration call.
@ -115,9 +118,9 @@ The `initializationOptions.handledSchemaProtocols` initialization option defines
```ts
let clientOptions: LanguageClientOptions = {
initializationOptions: {
handledSchemaProtocols: ['file'] // language server should only try to load file URLs
}
initializationOptions: {
handledSchemaProtocols: ['file'] // language server should only try to load file URLs
}
...
}
```
@ -132,6 +135,7 @@ If `handledSchemaProtocols` is not set, the JSON language server will load the f
Requests for schemas with URLs not handled by the server are forwarded to the client through an LSP request. This request is a JSON language server-specific, non-standardized, extension to the LSP.
Request:
- method: 'vscode/content'
- params: `string` - The schema URL to request.
- response: `string` - The content of the schema with the given URL
@ -146,6 +150,7 @@ The server will, as a response, clear the schema content from the cache and relo
In addition to the settings, schemas associations can also be provided through a notification from the client to the server. This notification is a JSON language server-specific, non-standardized, extension to the LSP.
Notification:
- method: 'json/schemaAssociations'
- params: `ISchemaAssociations` or `ISchemaAssociation[]` defined as follows
@ -183,11 +188,14 @@ interface ISchemaAssociation {
}
```
`ISchemaAssociations`
- keys: a file names or file path (separated by `/`). `*` can be used as a wildcard.
- values: An array of schema URLs
- keys: a file names or file path (separated by `/`). `*` can be used as a wildcard.
- values: An array of schema URLs
Notification:
- method: 'json/schemaContent'
- params: `string` the URL of the schema that has changed.
@ -226,6 +234,7 @@ The source code of the JSON language server can be found in the [VSCode reposito
File issues and pull requests in the [VSCode GitHub Issues](https://github.com/microsoft/vscode/issues). See the document [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) on how to build and run from source.
Most of the functionality of the server is located in libraries:
- [jsonc-parser](https://github.com/microsoft/node-jsonc-parser) contains the JSON parser and scanner.
- [vscode-json-languageservice](https://github.com/microsoft/vscode-json-languageservice) contains the implementation of all features as a re-usable library.
- [vscode-languageserver-node](https://github.com/microsoft/vscode-languageserver-node) contains the implementation of language server for NodeJS.

View file

@ -4,4 +4,4 @@
## Features
See [Markdown in Visual Studio Code](https://code.visualstudio.com/docs/languages/markdown) to learn about the features of this extension.
See [Markdown in Visual Studio Code](https://code.visualstudio.com/docs/languages/markdown) to learn about the features of this extension.

View file

@ -6,7 +6,6 @@ The Markdown language server powers VS Code's built-in markdown support, providi
This server uses the [Markdown Language Service](https://github.com/microsoft/vscode-markdown-languageservice) to implement almost all of the language features. You can use that library if you need a library for working with Markdown instead of a full language server.
## Server capabilities
- [Completions](https://microsoft.github.io/language-server-protocol/specification#textDocument_completion) for Markdown links.
@ -31,14 +30,13 @@ This server uses the [Markdown Language Service](https://github.com/microsoft/vs
- [Code Actions](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeAction)
- Organize link definitions source action.
- Extract link to definition refactoring.
- Organize link definitions source action.
- Extract link to definition refactoring.
- Updating links when a file is moved / renamed. Uses a custom `markdown/getEditForFileRenames` message.
- [Pull diagnostics (validation)](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_pullDiagnostics) for links.
## Client requirements
### Initialization options
@ -53,27 +51,27 @@ Clients may send a `workspace/didChangeConfiguration` notification to notify the
The server supports the following settings:
- `markdown`
- `suggest`
- `paths`
- `enabled` — Enable/disable path suggestions.
- `suggest`
- `paths`
- `enabled` — Enable/disable path suggestions.
- `occurrencesHighlight`
- `enabled` — Enable/disable highlighting of link occurrences.
- `occurrencesHighlight`
- `enabled` — Enable/disable highlighting of link occurrences.
- `validate`
- `enabled` Enable/disable all validation.
- `referenceLinks`
- `enabled` Enable/disable validation of reference links: `[text][ref]`
- `fragmentLinks`
- `enabled` Enable/disable validation of links to fragments in the current files: `[text](#head)`
- `fileLinks`
- `enabled` Enable/disable validation of links to file in the workspace.
- `markdownFragmentLinks` Enable/disable validation of links to headers in other Markdown files. Use `inherit` to inherit the `fragmentLinks` setting.
- `ignoredLinks` Array of glob patterns for files that should not be validated.
- `unusedLinkDefinitions`
- `enabled` Enable/disable validation of unused link definitions.
- `duplicateLinkDefinitions`
- `enabled` Enable/disable validation of duplicated link definitions.
- `validate`
- `enabled` Enable/disable all validation.
- `referenceLinks`
- `enabled` Enable/disable validation of reference links: `[text][ref]`
- `fragmentLinks`
- `enabled` Enable/disable validation of links to fragments in the current files: `[text](#head)`
- `fileLinks`
- `enabled` Enable/disable validation of links to file in the workspace.
- `markdownFragmentLinks` Enable/disable validation of links to headers in other Markdown files. Use `inherit` to inherit the `fragmentLinks` setting.
- `ignoredLinks` Array of glob patterns for files that should not be validated.
- `unusedLinkDefinitions`
- `enabled` Enable/disable validation of unused link definitions.
- `duplicateLinkDefinitions`
- `enabled` Enable/disable validation of duplicated link definitions.
### Custom requests
@ -109,7 +107,6 @@ Delete a previously created file watcher.
Get a list of all markdown files in the workspace.
## Contribute
The source code of the Markdown language server can be found in the [VSCode repository](https://github.com/microsoft/vscode) at [extensions/markdown-language-features/server](https://github.com/microsoft/vscode/tree/master/extensions/markdown-language-features/server).
@ -132,4 +129,3 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the [MIT](https://github.com/microsoft/vscode/blob/master/LICENSE.txt) License.

View file

@ -16,7 +16,6 @@ This extension provides basic preview for images, audio and video files.
- `.webp`
- `.avif`
### Supported audio formats
- `.mp3`

View file

@ -28,7 +28,7 @@ The extension supports running a script as a task from a folder in the Explorer.
### Others
The extension fetches data from https://registry.npmjs.org and https://registry.bower.io to provide auto-completion and information on hover features on npm dependencies.
The extension fetches data from <https://registry.npmjs.org> and <https://registry.bower.io> to provide auto-completion and information on hover features on npm dependencies.
## Settings
@ -40,5 +40,3 @@ The extension fetches data from https://registry.npmjs.org and https://registry.
- `npm.scriptExplorerAction` - The default click action: `open` or `run`, the default is `open`.
- `npm.enableRunFromFolder` - Enable running npm scripts from the context menu of folders in Explorer, the default is `false`.
- `npm.scriptCodeLens.enable` - Enable/disable the code lenses to run a script, the default is `false`.

View file

@ -4,4 +4,4 @@
## Features
See [PHP in Visual Studio Code](https://code.visualstudio.com/docs/languages/php) to learn about the features of this extension.
See [PHP in Visual Studio Code](https://code.visualstudio.com/docs/languages/php) to learn about the features of this extension.

View file

@ -2,5 +2,4 @@
**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled.
Provides a very basic browser preview using an iframe embedded in a [webview](). This extension is primarily meant to be used by other extensions for showing simple web content.
Provides a very basic browser preview using an iframe embedded in a [webviewW](). This extension is primarily meant to be used by other extensions for showing simple web content.

View file

@ -34,6 +34,7 @@
"meta.embedded",
"source.groovy.embedded",
"string meta.image.inline.markdown",
"variable.legacy.builtin.python"
],
"settings": {
"foreground": "#D4D4D4"

View file

@ -19,7 +19,8 @@
"scope": [
"meta.embedded",
"source.groovy.embedded",
"string meta.image.inline.markdown"
"string meta.image.inline.markdown",
"variable.legacy.builtin.python"
],
"settings": {
"foreground": "#FFFFFF"

View file

@ -3,7 +3,11 @@
"name": "Light High Contrast",
"tokenColors": [
{
"scope": ["meta.embedded", "source.groovy.embedded"],
"scope": [
"meta.embedded",
"source.groovy.embedded",
"variable.legacy.builtin.python"
],
"settings": {
"foreground": "#292929"
}

View file

@ -38,7 +38,8 @@
"scope": [
"meta.embedded",
"source.groovy.embedded",
"string meta.image.inline.markdown"
"string meta.image.inline.markdown",
"variable.legacy.builtin.python"
],
"settings": {
"foreground": "#000000ff"

View file

@ -64,7 +64,8 @@
"scope": [
"meta.embedded",
"source.groovy.embedded",
"string meta.image.inline.markdown"
"string meta.image.inline.markdown",
"variable.legacy.builtin.python"
],
"settings": {
"foreground": "#d3af86"

View file

@ -71,7 +71,8 @@
{
"scope": [
"meta.embedded",
"source.groovy.embedded"
"source.groovy.embedded",
"variable.legacy.builtin.python"
],
"settings": {
"foreground": "#C5C8C6"

View file

@ -111,7 +111,8 @@
"scope": [
"meta.embedded",
"source.groovy.embedded",
"string meta.image.inline.markdown"
"string meta.image.inline.markdown",
"variable.legacy.builtin.python"
],
"settings": {
"foreground": "#F8F8F2"

View file

@ -10,7 +10,8 @@
"scope": [
"meta.embedded",
"source.groovy.embedded",
"string meta.image.inline.markdown"
"string meta.image.inline.markdown",
"variable.legacy.builtin.python"
],
"settings": {
"foreground": "#333333"

View file

@ -70,7 +70,8 @@
"scope": [
"meta.embedded",
"source.groovy.embedded",
"string meta.image.inline.markdown"
"string meta.image.inline.markdown",
"variable.legacy.builtin.python"
],
"settings": {
"foreground": "#F8F8F8"

View file

@ -10,7 +10,8 @@
"scope": [
"meta.embedded",
"source.groovy.embedded",
"string meta.image.inline.markdown"
"string meta.image.inline.markdown",
"variable.legacy.builtin.python"
],
"settings": {
"foreground": "#839496"

View file

@ -10,7 +10,8 @@
"scope": [
"meta.embedded",
"source.groovy.embedded",
"string meta.image.inline.markdown"
"string meta.image.inline.markdown",
"variable.legacy.builtin.python"
],
"settings": {
"foreground": "#657B83"

View file

@ -70,7 +70,8 @@
"meta.embedded",
"source.groovy.embedded",
"meta.jsx.children",
"string meta.image.inline.markdown"
"string meta.image.inline.markdown",
"variable.legacy.builtin.python"
],
"settings": {
//"background": "#002451",

View file

@ -1,6 +1,7 @@
The file `TypeScript.tmLanguage.json` and `TypeScriptReact.tmLanguage.json` are derived from [TypeScript.tmLanguage](https://github.com/microsoft/TypeScript-TmLanguage/blob/master/TypeScript.tmLanguage) and [TypeScriptReact.tmLanguage](https://github.com/microsoft/TypeScript-TmLanguage/blob/master/TypeScriptReact.tmLanguage).
To update to the latest version:
- `cd extensions/typescript` and run `npm run update-grammars`
- don't forget to run the integration tests at `./scripts/test-integration.sh`

View file

@ -6,6 +6,7 @@
import * as vscode from 'vscode';
import { DocumentSelector } from '../configuration/documentSelector';
import { LanguageDescription } from '../configuration/languageDescription';
import { TelemetryReporter } from '../logging/telemetry';
import { API } from '../tsServer/api';
import type * as Proto from '../tsServer/protocol/protocol';
import { Location, Position } from '../typeConverters';
@ -29,13 +30,16 @@ class TypeScriptInlayHintsProvider extends Disposable implements vscode.InlayHin
public static readonly minVersion = API.v440;
private readonly _onDidChangeInlayHints = new vscode.EventEmitter<void>();
private readonly _onDidChangeInlayHints = this._register(new vscode.EventEmitter<void>());
public readonly onDidChangeInlayHints = this._onDidChangeInlayHints.event;
private hasReportedTelemetry = false;
constructor(
private readonly language: LanguageDescription,
private readonly client: ITypeScriptServiceClient,
private readonly fileConfigurationManager: FileConfigurationManager
private readonly fileConfigurationManager: FileConfigurationManager,
private readonly telemetryReporter: TelemetryReporter,
) {
super();
@ -54,31 +58,47 @@ class TypeScriptInlayHintsProvider extends Disposable implements vscode.InlayHin
}));
}
async provideInlayHints(model: vscode.TextDocument, range: vscode.Range, token: vscode.CancellationToken): Promise<vscode.InlayHint[]> {
async provideInlayHints(model: vscode.TextDocument, range: vscode.Range, token: vscode.CancellationToken): Promise<vscode.InlayHint[] | undefined> {
const filepath = this.client.toOpenTsFilePath(model);
if (!filepath) {
return [];
return;
}
if (!areInlayHintsEnabledForFile(this.language, model)) {
return [];
return;
}
const start = model.offsetAt(range.start);
const length = model.offsetAt(range.end) - start;
await this.fileConfigurationManager.ensureConfigurationForDocument(model, token);
if (token.isCancellationRequested) {
return;
}
if (!this.hasReportedTelemetry) {
this.hasReportedTelemetry = true;
/* __GDPR__
"inlayHints.provide" : {
"owner": "mjbvz",
"${include}": [
"${TypeScriptCommonProperties}"
]
}
*/
this.telemetryReporter.logTelemetry('inlayHints.provide', {});
}
const response = await this.client.execute('provideInlayHints', { file: filepath, start, length }, token);
if (response.type !== 'response' || !response.success || !response.body) {
return [];
return;
}
return response.body.map(hint => {
const result = new vscode.InlayHint(
Position.fromLocation(hint.position),
this.convertInlayHintText(model.uri, hint),
hint.kind && fromProtocolInlayHintKind(hint.kind)
this.convertInlayHintText(hint),
fromProtocolInlayHintKind(hint.kind)
);
result.paddingLeft = hint.whitespaceBefore;
result.paddingRight = hint.whitespaceAfter;
@ -86,19 +106,18 @@ class TypeScriptInlayHintsProvider extends Disposable implements vscode.InlayHin
});
}
private convertInlayHintText(resource: vscode.Uri, tsHint: Proto.InlayHintItem): string | vscode.InlayHintLabelPart[] {
private convertInlayHintText(tsHint: Proto.InlayHintItem): string | vscode.InlayHintLabelPart[] {
if (tsHint.displayParts) {
return tsHint.displayParts.map((part): vscode.InlayHintLabelPart => {
const out = new vscode.InlayHintLabelPart(part.text);
if (part.span) {
out.location = Location.fromTextSpan(resource, part.span);
out.location = Location.fromTextSpan(this.client.toResource(part.span.file), part.span);
}
return out;
});
}
return tsHint.text;
}
}
@ -128,13 +147,14 @@ export function register(
selector: DocumentSelector,
language: LanguageDescription,
client: ITypeScriptServiceClient,
fileConfigurationManager: FileConfigurationManager
fileConfigurationManager: FileConfigurationManager,
telemetryReporter: TelemetryReporter,
) {
return conditionalRegistration([
requireMinVersion(client, TypeScriptInlayHintsProvider.minVersion),
requireSomeCapability(client, ClientCapability.Semantic),
], () => {
const provider = new TypeScriptInlayHintsProvider(language, client, fileConfigurationManager);
const provider = new TypeScriptInlayHintsProvider(language, client, fileConfigurationManager, telemetryReporter);
return vscode.languages.registerInlayHintsProvider(selector.semantic, provider);
});
}

View file

@ -74,7 +74,7 @@ export default class LanguageProvider extends Disposable {
import('./languageFeatures/formatting').then(provider => this._register(provider.register(selector, this.description, this.client, this.fileConfigurationManager))),
import('./languageFeatures/hover').then(provider => this._register(provider.register(selector, this.client, this.fileConfigurationManager))),
import('./languageFeatures/implementations').then(provider => this._register(provider.register(selector, this.client))),
import('./languageFeatures/inlayHints').then(provider => this._register(provider.register(selector, this.description, this.client, this.fileConfigurationManager))),
import('./languageFeatures/inlayHints').then(provider => this._register(provider.register(selector, this.description, this.client, this.fileConfigurationManager, this.telemetryReporter))),
import('./languageFeatures/jsDocCompletions').then(provider => this._register(provider.register(selector, this.description, this.client, this.fileConfigurationManager))),
import('./languageFeatures/linkedEditing').then(provider => this._register(provider.register(selector, this.client))),
import('./languageFeatures/organizeImports').then(provider => this._register(provider.register(selector, this.client, this.commandManager, this.fileConfigurationManager, this.telemetryReporter))),

View file

@ -1,4 +1,5 @@
# vscode-wasm-typescript
Language server host for typescript using vscode's sync-api in the browser
## TODOs
@ -22,33 +23,33 @@ Language server host for typescript using vscode's sync-api in the browser
- LATER: Turns out you can skip the existing server by depending on tsserverlibrary instead of tsserver.
- [x] figure out a webpack-native way to generate tsserver.web.js if possible
- [x] path rewriting is pretty loosey-goosey; likely to be incorrect some of the time
- invert the logic from TypeScriptServiceClient.normalizedPath for requests
- invert the function from webServer.ts for responses (maybe)
- something with getWorkspaceRootForResource (or anything else that checks `resouce.scheme`)
- invert the logic from TypeScriptServiceClient.normalizedPath for requests
- invert the function from webServer.ts for responses (maybe)
- something with getWorkspaceRootForResource (or anything else that checks `resouce.scheme`)
- [x] put files one level down from virtual root
- [x] fill in missing environment files like lib.dom.d.ts
- toResource's isWeb branch *probably* knows where to find this, just need to put it in the virtual FS
- I guess during setup in serverProcess.browser.ts.
- Not sure whether it needs to have the data or just a fs entry.
- Wait, I don't know how files get added to the FS normally.
- toResource's isWeb branch *probably* knows where to find this, just need to put it in the virtual FS
- I guess during setup in serverProcess.browser.ts.
- Not sure whether it needs to have the data or just a fs entry.
- Wait, I don't know how files get added to the FS normally.
- [x] cancellation should only retain one cancellation checker
- the one that matches the current request id
- but that means tracking (or retrieving from tsserver) the request id (aka seq?)
- and correctly setting/resetting it on the cancellation token too.
- I looked at the tsserver code. I think the web case is close to the single-pipe node case,
- the one that matches the current request id
- but that means tracking (or retrieving from tsserver) the request id (aka seq?)
- and correctly setting/resetting it on the cancellation token too.
- I looked at the tsserver code. I think the web case is close to the single-pipe node case,
so I just require that requestId is set in order to call the *current* cancellation checker.
- Any incoming message with a cancellation checker will overwrite the current one.
- Any incoming message with a cancellation checker will overwrite the current one.
- [x] Cancellation code in vscode is suspiciously prototypey.
- Specifically, it adds the vscode-wasm cancellation to original cancellation code, but should actually switch to the former for web only.
- looks like `isWeb()` is a way to check for being on the web
- Specifically, it adds the vscode-wasm cancellation to original cancellation code, but should actually switch to the former for web only.
- looks like `isWeb()` is a way to check for being on the web
- [x] create multiple watchers
- on-demand instead of watching everything and checking on watch firing
- on-demand instead of watching everything and checking on watch firing
- [x] get file watching to work
- it could *already* work, I just don't know how to test it
- look at extensions/markdown-language-features/src/client/fileWatchingManager.ts to see if I can use that
- later: it is OK. its main difference is that you can watch files in not-yet-created directories, and it maintains
- it could *already* work, I just don't know how to test it
- look at extensions/markdown-language-features/src/client/fileWatchingManager.ts to see if I can use that
- later: it is OK. its main difference is that you can watch files in not-yet-created directories, and it maintains
a web of directory watches that then check whether the file is eventually created.
- even later: well, it works even though it is similar to my code.
- even later: well, it works even though it is similar to my code.
I'm not sure what is different.
- [x] copy fileWatchingManager.ts to web/ ; there's no sharing code between extensions
- [x] Find out scheme the web actually uses instead of vscode-test-web (or switch over entirely to isWeb)
@ -106,6 +107,7 @@ Language server host for typescript using vscode's sync-api in the browser
- so I can just redo whatever that did and it'll be fine
### Done
- [x] need to update 0.2 -> 0.7.* API (once it's working properly)
- [x] including reshuffling the webpack hack if needed
- [x] need to use the settings recommended by Sheetal
@ -113,7 +115,7 @@ Language server host for typescript using vscode's sync-api in the browser
- [x] sync-api-client says fs is rooted at memfs:/sample-folder; the protocol 'memfs:' is confusing our file parsing I think
- [x] nothing ever seems to find tsconfig.json
- [x] messages aren't actually coming through, just the message from the first request
- fixed by simplifying the listener setup for now
- fixed by simplifying the listener setup for now
- [x] once messages work, you can probably log by postMessage({ type: 'log', body: "some logging text" })
- [x] implement realpath, modifiedtime, resolvepath, then turn semantic mode on
- [x] file watching implemented with saved map of filename to callback, and forwarding
@ -125,6 +127,7 @@ Language server host for typescript using vscode's sync-api in the browser
## Notes
messages received by extension AND host use paths like ^/memfs/ts-nul-authority/sample-folder/file.ts
- problem: pretty sure the extension doesn't know what to do with that: it's not putting down error spans in file.ts
- question: why is the extension requesting quickinfo in that URI format? And it works! (probably because the result is a tooltip, not an in-file span)
- problem: weird concatenations with memfs:/ in the middle
@ -140,15 +143,14 @@ but readFile is getting called with things like memfs:/sample-folder/memfs:/type
watchDirectory with /sample-folder/^ and directoryExists with /sample-folder/^/memfs/ts-nul-authority/sample-folder/workspaces/
watchFile with /sample-folder/memfs:/sample-folder/memfs:/lib.es2020.full.d.ts
### LATER:
### LATER
OK, so the paths that tsserver has look like this: ^/scheme/mount/whatever.ts
but the paths the filesystem has look like this: scheme:/whatever.ts (not sure about 'mount', that's only when cloning from the fs)
so you have to shave off the scheme that the host combined with the path and put on the scheme that the vfs is using.
### LATER 2:
### LATER 2
Some commands ask for getExecutingFilePath or getCurrentDirectory and cons up a path themselves.
This works, because URI.from({ scheme, path }) matches what the fs has in it
Problem: In *some* messages (all?), vscode then refers to /x.ts and ^/vscode-test-web/mount/x.ts (or ^/memfs/ts-nul-authority/x.ts)

View file

@ -45,14 +45,12 @@
"textSearchProvider",
"timeline",
"tokenInformation",
"treeItemCheckbox",
"treeViewActiveItem",
"treeViewActiveItem",
"treeViewReveal",
"workspaceTrust",
"telemetry",
"windowActivity",
"interactiveUserActions",
"envCollectionWorkspace"
"interactiveUserActions"
],
"private": true,
"activationEvents": [],

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { deepStrictEqual, doesNotThrow, equal, ok, strictEqual, throws } from 'assert';
import { commands, ConfigurationTarget, Disposable, env, EnvironmentVariableMutator, EnvironmentVariableMutatorOptions, EnvironmentVariableMutatorType, EventEmitter, ExtensionContext, extensions, ExtensionTerminalOptions, GlobalEnvironmentVariableCollection, Pseudoterminal, Terminal, TerminalDimensions, TerminalExitReason, TerminalOptions, TerminalState, UIKind, Uri, window, workspace } from 'vscode';
import { commands, ConfigurationTarget, Disposable, env, EnvironmentVariableMutator, EnvironmentVariableMutatorOptions, EnvironmentVariableMutatorType, EventEmitter, ExtensionContext, extensions, ExtensionTerminalOptions, Pseudoterminal, Terminal, TerminalDimensions, TerminalExitReason, TerminalOptions, TerminalState, UIKind, Uri, window, workspace } from 'vscode';
import { assertNoRpc, poll } from '../utils';
// Disable terminal tests:
@ -912,8 +912,7 @@ import { assertNoRpc, poll } from '../utils';
});
test('get and forEach should work (scope)', () => {
// TODO: Remove cast once `envCollectionWorkspace` API is finalized.
const collection = extensionContext.environmentVariableCollection as GlobalEnvironmentVariableCollection;
const collection = extensionContext.environmentVariableCollection;
disposables.push({ dispose: () => collection.clear() });
const scope = { workspaceFolder: { uri: Uri.file('workspace1'), name: 'workspace1', index: 0 } };
const scopedCollection = collection.getScoped(scope);

View file

@ -103,4 +103,4 @@ Pop
* Multiple definitions and terms are possible
* Definitions can include multiple paragraphs too
*[ABBR]: Markdown plus abbreviations (produces an <abbr> tag)
*[ABBR]: Markdown plus abbreviations (produces an <abbr> tag)

View file

@ -1907,14 +1907,14 @@
"c": "reduce",
"t": "source.python meta.function-call.python variable.legacy.builtin.python",
"r": {
"dark_plus": "variable: #9CDCFE",
"light_plus": "variable: #001080",
"dark_vs": "default: #D4D4D4",
"light_vs": "default: #000000",
"hc_black": "variable: #9CDCFE",
"dark_modern": "variable: #9CDCFE",
"hc_light": "variable: #001080",
"light_modern": "variable: #001080"
"dark_plus": "variable.legacy.builtin.python: #D4D4D4",
"light_plus": "variable.legacy.builtin.python: #000000",
"dark_vs": "variable.legacy.builtin.python: #D4D4D4",
"light_vs": "variable.legacy.builtin.python: #000000",
"hc_black": "variable.legacy.builtin.python: #FFFFFF",
"dark_modern": "variable.legacy.builtin.python: #D4D4D4",
"hc_light": "variable.legacy.builtin.python: #292929",
"light_modern": "variable.legacy.builtin.python: #000000"
}
},
{
@ -6233,14 +6233,14 @@
"c": "raw_input",
"t": "source.python meta.function-call.python variable.legacy.builtin.python",
"r": {
"dark_plus": "variable: #9CDCFE",
"light_plus": "variable: #001080",
"dark_vs": "default: #D4D4D4",
"light_vs": "default: #000000",
"hc_black": "variable: #9CDCFE",
"dark_modern": "variable: #9CDCFE",
"hc_light": "variable: #001080",
"light_modern": "variable: #001080"
"dark_plus": "variable.legacy.builtin.python: #D4D4D4",
"light_plus": "variable.legacy.builtin.python: #000000",
"dark_vs": "variable.legacy.builtin.python: #D4D4D4",
"light_vs": "variable.legacy.builtin.python: #000000",
"hc_black": "variable.legacy.builtin.python: #FFFFFF",
"dark_modern": "variable.legacy.builtin.python: #D4D4D4",
"hc_light": "variable.legacy.builtin.python: #292929",
"light_modern": "variable.legacy.builtin.python: #000000"
}
},
{

View file

@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.82.0",
"distro": "1591281180fd2cd18935e6847131d2d4213b7b69",
"distro": "56bfb9ce4a8d2298f475a1b8d9c7a7b5a72204f2",
"author": {
"name": "Microsoft Corporation"
},

View file

@ -1218,7 +1218,7 @@ export interface IAbstractTreeOptions<T, TFilterData = void> extends IAbstractTr
readonly collapseByDefault?: boolean; // defaults to false
readonly filter?: ITreeFilter<T, TFilterData>;
readonly dnd?: ITreeDragAndDrop<T>;
readonly additionalScrollHeight?: number;
readonly paddingBottom?: number;
readonly findWidgetEnabled?: boolean;
readonly findWidgetStyles?: IFindWidgetStyles;
readonly defaultFindVisibility?: TreeVisibility | ((e: T) => TreeVisibility);

View file

@ -198,6 +198,13 @@ class ElementPath {
);
}
public static isChildOfOverflowGuard(path: Uint8Array): boolean {
return (
path.length >= 1
&& path[0] === PartFingerprint.OverflowGuard
);
}
public static isChildOfOverflowingContentWidgets(path: Uint8Array): boolean {
return (
path.length >= 1
@ -538,6 +545,11 @@ export class MouseTargetFactory {
let result: IMouseTarget | null = null;
if (!ElementPath.isChildOfOverflowGuard(request.targetPath) && !ElementPath.isChildOfOverflowingContentWidgets(request.targetPath)) {
// We only render dom nodes inside the overflow guard or in the overflowing content widgets
result = result || request.fulfillUnknown();
}
result = result || MouseTargetFactory._hitTestContentWidget(ctx, resolvedRequest);
result = result || MouseTargetFactory._hitTestOverlayWidget(ctx, resolvedRequest);
result = result || MouseTargetFactory._hitTestMinimap(ctx, resolvedRequest);

View file

@ -18,6 +18,7 @@ import { applyStyle } from 'vs/editor/browser/widget/diffEditorWidget2/utils';
import { DiffReview } from 'vs/editor/browser/widget/diffReview';
import { EditorFontLigatures, EditorOption, IComputedEditorOptions } from 'vs/editor/common/config/editorOptions';
import { LineRange } from 'vs/editor/common/core/lineRange';
import { OffsetRange } from 'vs/editor/common/core/offsetRange';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { LineRangeMapping, SimpleLineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
@ -163,7 +164,7 @@ class ViewModel extends Disposable {
const groups = this.groups.get();
if (!groups || groups.length <= 1) { return; }
subtransaction(tx, tx => {
this._currentGroupIdx.set((this._currentGroupIdx.get() + groups.length + delta) % groups.length, tx);
this._currentGroupIdx.set(OffsetRange.ofLength(groups.length).clipCyclic(this._currentGroupIdx.get() + delta), tx);
this._currentElementIdx.set(0, tx);
});
}
@ -175,7 +176,7 @@ class ViewModel extends Disposable {
const group = this.currentGroup.get();
if (!group || group.lines.length <= 1) { return; }
transaction(tx => {
this._currentElementIdx.set((this._currentElementIdx.get() + group.lines.length + delta) % group.lines.length, tx);
this._currentElementIdx.set(OffsetRange.ofLength(group.lines.length).clip(this._currentElementIdx.get() + delta), tx);
});
}

View file

@ -34,13 +34,13 @@ export class DiffEditorDecorations extends Disposable {
return null;
}
const currentMove = this._diffModel.read(reader)!.syncedMovedTexts.read(reader);
const movedTextToCompare = this._diffModel.read(reader)!.movedTextToCompare.read(reader);
const renderIndicators = this._options.renderIndicators.read(reader);
const showEmptyDecorations = this._options.showEmptyDecorations.read(reader);
const originalDecorations: IModelDeltaDecoration[] = [];
const modifiedDecorations: IModelDeltaDecoration[] = [];
if (!currentMove) {
if (!movedTextToCompare) {
for (const m of diff.mappings) {
if (!m.lineRangeMapping.originalRange.isEmpty) {
originalDecorations.push({ range: m.lineRangeMapping.originalRange.toInclusiveRange()!, options: renderIndicators ? diffLineDeleteDecorationBackgroundWithIndicator : diffLineDeleteDecorationBackground });
@ -68,14 +68,14 @@ export class DiffEditorDecorations extends Disposable {
}
}
if (!m.lineRangeMapping.modifiedRange.isEmpty && this._options.shouldRenderRevertArrows.read(reader) && !currentMove) {
if (!m.lineRangeMapping.modifiedRange.isEmpty && this._options.shouldRenderRevertArrows.read(reader) && !movedTextToCompare) {
modifiedDecorations.push({ range: Range.fromPositions(new Position(m.lineRangeMapping.modifiedRange.startLineNumber, 1)), options: arrowRevertChange });
}
}
}
if (currentMove) {
for (const m of currentMove.changes) {
if (movedTextToCompare) {
for (const m of movedTextToCompare.changes) {
const fullRangeOriginal = m.originalRange.toInclusiveRange();
if (fullRangeOriginal) {
originalDecorations.push({ range: fullRangeOriginal, options: renderIndicators ? diffLineDeleteDecorationBackgroundWithIndicator : diffLineDeleteDecorationBackground });
@ -91,12 +91,13 @@ export class DiffEditorDecorations extends Disposable {
}
}
}
const activeMovedText = this._diffModel.read(reader)!.activeMovedText.read(reader);
for (const m of diff.movedTexts) {
originalDecorations.push({
range: m.lineRangeMapping.original.toInclusiveRange()!, options: {
description: 'moved',
blockClassName: 'movedOriginal' + (m === currentMove ? ' currentMove' : ''),
blockClassName: 'movedOriginal' + (m === activeMovedText ? ' currentMove' : ''),
blockPadding: [MovedBlocksLinesPart.movedCodeBlockPadding, 0, MovedBlocksLinesPart.movedCodeBlockPadding, MovedBlocksLinesPart.movedCodeBlockPadding],
}
});
@ -104,7 +105,7 @@ export class DiffEditorDecorations extends Disposable {
modifiedDecorations.push({
range: m.lineRangeMapping.modified.toInclusiveRange()!, options: {
description: 'moved',
blockClassName: 'movedModified' + (m === currentMove ? ' currentMove' : ''),
blockClassName: 'movedModified' + (m === activeMovedText ? ' currentMove' : ''),
blockPadding: [4, 0, 4, 4],
}
});

View file

@ -48,7 +48,21 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo
}
);
public readonly syncedMovedTexts = observableValue<MovedText | undefined>('syncedMovedText', undefined);
public readonly movedTextToCompare = observableValue<MovedText | undefined>('movedTextToCompare', undefined);
private readonly _activeMovedText = observableValue<MovedText | undefined>('activeMovedText', undefined);
private readonly _hoveredMovedText = observableValue<MovedText | undefined>('hoveredMovedText', undefined);
public readonly activeMovedText = derived(r => this.movedTextToCompare.read(r) ?? this._hoveredMovedText.read(r) ?? this._activeMovedText.read(r));
public setActiveMovedText(movedText: MovedText | undefined): void {
this._activeMovedText.set(movedText, undefined);
}
public setHoveredMovedText(movedText: MovedText | undefined): void {
this._hoveredMovedText.set(movedText, undefined);
}
constructor(
public readonly model: IDiffEditorModel,
@ -114,8 +128,8 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo
transaction(tx => {
this._diff.set(DiffState.fromDiffResult(this._lastDiff!), tx);
updateUnchangedRegions(result, tx);
const currentSyncedMovedText = this.syncedMovedTexts.get();
this.syncedMovedTexts.set(currentSyncedMovedText ? this._lastDiff!.moves.find(m => m.lineRangeMapping.modified.intersect(currentSyncedMovedText.lineRangeMapping.modified)) : undefined, tx);
const currentSyncedMovedText = this.movedTextToCompare.get();
this.movedTextToCompare.set(currentSyncedMovedText ? this._lastDiff!.moves.find(m => m.lineRangeMapping.modified.intersect(currentSyncedMovedText.lineRangeMapping.modified)) : undefined, tx);
});
}
}
@ -132,8 +146,8 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo
transaction(tx => {
this._diff.set(DiffState.fromDiffResult(this._lastDiff!), tx);
updateUnchangedRegions(result, tx);
const currentSyncedMovedText = this.syncedMovedTexts.get();
this.syncedMovedTexts.set(currentSyncedMovedText ? this._lastDiff!.moves.find(m => m.lineRangeMapping.modified.intersect(currentSyncedMovedText.lineRangeMapping.modified)) : undefined, tx);
const currentSyncedMovedText = this.movedTextToCompare.get();
this.movedTextToCompare.set(currentSyncedMovedText ? this._lastDiff!.moves.find(m => m.lineRangeMapping.modified.intersect(currentSyncedMovedText.lineRangeMapping.modified)) : undefined, tx);
});
}
}
@ -180,8 +194,8 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo
const state = DiffState.fromDiffResult(result);
this._diff.set(state, tx);
this._isDiffUpToDate.set(true, tx);
const currentSyncedMovedText = this.syncedMovedTexts.get();
this.syncedMovedTexts.set(currentSyncedMovedText ? this._lastDiff.moves.find(m => m.lineRangeMapping.modified.intersect(currentSyncedMovedText.lineRangeMapping.modified)) : undefined, tx);
const currentSyncedMovedText = this.movedTextToCompare.get();
this.movedTextToCompare.set(currentSyncedMovedText ? this._lastDiff.moves.find(m => m.lineRangeMapping.modified.intersect(currentSyncedMovedText.lineRangeMapping.modified)) : undefined, tx);
});
}));
}

View file

@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Codicon } from 'vs/base/common/codicons';
import { KeyCode } from 'vs/base/common/keyCodes';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction2, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { findFocusedDiffEditor } from 'vs/editor/browser/widget/diffEditor.contribution';
@ -128,3 +129,29 @@ export class SwitchSide extends EditorAction2 {
}
registerAction2(SwitchSide);
export class ExitCompareMove extends EditorAction2 {
constructor() {
super({
id: 'diffEditor.exitCompareMove',
title: { value: localize('exitCompareMove', "Exit Compare Move"), original: 'Exit Compare Move' },
icon: Codicon.close,
precondition: EditorContextKeys.comparingMovedCode,
f1: false,
category: diffEditorCategory,
keybinding: {
weight: 10000,
primary: KeyCode.Escape,
}
});
}
runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: unknown[]): void {
const diffEditor = findFocusedDiffEditor(accessor);
if (diffEditor instanceof DiffEditorWidget2) {
diffEditor.exitCompareMove();
}
}
}
registerAction2(ExitCompareMove);

View file

@ -111,6 +111,12 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
isEmbeddedDiffEditorKey.set(this._options.isInEmbeddedEditor.read(reader));
}));
const comparingMovedCodeKey = EditorContextKeys.comparingMovedCode.bindTo(this._contextKeyService);
this._register(autorun(reader => {
/** @description update comparingMovedCodeKey */
comparingMovedCodeKey.set(!!this._diffModel.read(reader)?.movedTextToCompare.read(reader));
}));
const diffEditorRenderSideBySideInlineBreakpointReachedContextKeyValue = EditorContextKeys.diffEditorRenderSideBySideInlineBreakpointReached.bindTo(this._contextKeyService);
this._register(autorun(reader => {
/** @description update accessibleDiffViewerVisible context key */
@ -223,19 +229,6 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
),
}));
this._register(this._editors.original.onDidChangeCursorPosition(e => {
const m = this._diffModel.get();
if (!m) { return; }
const movedText = m.diff.get()!.movedTexts.find(m => m.lineRangeMapping.original.contains(e.position.lineNumber));
m.syncedMovedTexts.set(movedText, undefined);
}));
this._register(this._editors.modified.onDidChangeCursorPosition(e => {
const m = this._diffModel.get();
if (!m) { return; }
const movedText = m.diff.get()!.movedTexts.find(m => m.lineRangeMapping.modified.contains(e.position.lineNumber));
m.syncedMovedTexts.set(movedText, undefined);
}));
// Revert change when an arrow is clicked.
this._register(this._editors.modified.onMouseDown(event => {
if (!event.event.rightButton && event.target.position && event.target.element?.className.includes('arrow-revert-change')) {
@ -518,6 +511,12 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
}
destination.focus();
}
exitCompareMove(): void {
const model = this._diffModel.get();
if (!model) { return; }
model.movedTextToCompare.set(undefined, undefined);
}
}
function translatePosition(posInOriginal: Position, mappings: LineRangeMapping[]): Range {

View file

@ -103,7 +103,7 @@ export class ViewZoneManager extends Disposable {
const alignmentsSyncedMovedText = derived<ILineRangeAlignment[] | null>((reader) => {
/** @description alignments */
const syncedMovedText = this._diffModel.read(reader)?.syncedMovedTexts.read(reader);
const syncedMovedText = this._diffModel.read(reader)?.movedTextToCompare.read(reader);
if (!syncedMovedText) { return null; }
state.read(reader);
const mappings = syncedMovedText.changes.map(c => new DiffMapping(c));
@ -171,7 +171,7 @@ export class ViewZoneManager extends Disposable {
const modLineHeight = this._editors.modified.getOption(EditorOption.lineHeight);
const syncedMovedText = this._diffModel.read(reader)?.syncedMovedTexts.read(reader);
const syncedMovedText = this._diffModel.read(reader)?.movedTextToCompare.read(reader);
const mightContainNonBasicASCII = this._editors.original.getModel()?.mightContainNonBasicASCII() ?? false;
const mightContainRTL = this._editors.original.getModel()?.mightContainRTL() ?? false;
@ -424,7 +424,7 @@ export class ViewZoneManager extends Disposable {
this._register(autorun(reader => {
/** @description update editor top offsets */
const m = this._diffModel.read(reader)?.syncedMovedTexts.read(reader);
const m = this._diffModel.read(reader)?.movedTextToCompare.read(reader);
let deltaOrigToMod = 0;
if (m) {

View file

@ -3,15 +3,23 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { h } from 'vs/base/browser/dom';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { Action } from 'vs/base/common/actions';
import { booleanComparator, compareBy, findMaxIdxBy, numberComparator, tieBreakComparators } from 'vs/base/common/arrays';
import { Codicon } from 'vs/base/common/codicons';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { IObservable, autorun, derived, keepAlive, observableFromEvent, observableSignalFromEvent, observableValue } from 'vs/base/common/observable';
import { IObservable, autorun, autorunWithStore, constObservable, derived, derivedWithStore, keepAlive, observableFromEvent, observableSignalFromEvent, observableValue } from 'vs/base/common/observable';
import { ThemeIcon } from 'vs/base/common/themables';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { DiffEditorEditors } from 'vs/editor/browser/widget/diffEditorWidget2/diffEditorEditors';
import { DiffEditorViewModel } from 'vs/editor/browser/widget/diffEditorWidget2/diffEditorViewModel';
import { PlaceholderViewZone, ViewZoneOverlayWidget, applyStyle, applyViewZones } from 'vs/editor/browser/widget/diffEditorWidget2/utils';
import { EditorLayoutInfo } from 'vs/editor/common/config/editorOptions';
import { LineRange } from 'vs/editor/common/core/lineRange';
import { OffsetRange, OffsetRangeSet } from 'vs/editor/common/core/offsetRange';
import { MovedText } from 'vs/editor/common/diff/linesDiffComputer';
import { localize } from 'vs/nls';
export class MovedBlocksLinesPart extends Disposable {
public static readonly movedCodeBlockPadding = 4;
@ -51,9 +59,50 @@ export class MovedBlocksLinesPart extends Disposable {
}));
this._register(keepAlive(this._state, true));
const movedBlockViewZones = derived(reader => {
const model = this._diffModel.read(reader);
const d = model?.diff.read(reader);
if (!d) { return []; }
return d.movedTexts.map(move => ({
move,
original: new PlaceholderViewZone(constObservable(move.lineRangeMapping.original.startLineNumber - 1), 18),
modified: new PlaceholderViewZone(constObservable(move.lineRangeMapping.modified.startLineNumber - 1), 18),
}));
});
this._register(applyViewZones(this._editors.original, movedBlockViewZones.map(zones => zones.map(z => z.original))));
this._register(applyViewZones(this._editors.modified, movedBlockViewZones.map(zones => zones.map(z => z.modified))));
this._register(autorunWithStore((reader, store) => {
const blocks = movedBlockViewZones.read(reader);
for (const b of blocks) {
store.add(new MovedBlockOverlayWidget(this._editors.original, b.original, b.move, 'original', this._diffModel.get()!));
store.add(new MovedBlockOverlayWidget(this._editors.modified, b.modified, b.move, 'modified', this._diffModel.get()!));
}
}));
this._register(this._editors.original.onDidChangeCursorPosition(e => {
const m = this._diffModel.get();
if (!m) { return; }
const movedText = m.diff.get()!.movedTexts.find(m => m.lineRangeMapping.original.contains(e.position.lineNumber));
if (movedText !== m.movedTextToCompare.get()) {
m.movedTextToCompare.set(undefined, undefined);
}
m.setActiveMovedText(movedText);
}));
this._register(this._editors.modified.onDidChangeCursorPosition(e => {
const m = this._diffModel.get();
if (!m) { return; }
const movedText = m.diff.get()!.movedTexts.find(m => m.lineRangeMapping.modified.contains(e.position.lineNumber));
if (movedText !== m.movedTextToCompare.get()) {
m.movedTextToCompare.set(undefined, undefined);
}
m.setActiveMovedText(movedText);
}));
}
private readonly _state = derived(reader => {
private readonly _state = derivedWithStore('state', (reader, store) => {
/** @description update moved blocks lines */
this._element.replaceChildren();
@ -125,21 +174,35 @@ export class MovedBlocksLinesPart extends Disposable {
rect.setAttribute('height', `${rectHeight}`);
this._element.appendChild(rect);
const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
if (line.move === model.syncedMovedTexts.read(reader)) {
path.classList.add('currentMove');
}
path.setAttribute('d', `M ${0} ${line.from} L ${verticalY} ${line.from} L ${verticalY} ${line.to} L ${right - arrowWidth} ${line.to}`);
path.setAttribute('fill', 'none');
this._element.appendChild(path);
g.appendChild(path);
const arrowRight = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
arrowRight.classList.add('arrow');
if (line.move === model.syncedMovedTexts.read(reader)) {
arrowRight.classList.add('currentMove');
}
store.add(autorun(reader => {
path.classList.toggle('currentMove', line.move === model.activeMovedText.read(reader));
arrowRight.classList.toggle('currentMove', line.move === model.activeMovedText.read(reader));
}));
arrowRight.setAttribute('points', `${right - arrowWidth},${line.to - arrowHeight / 2} ${right},${line.to} ${right - arrowWidth},${line.to + arrowHeight / 2}`);
this._element.appendChild(arrowRight);
g.appendChild(arrowRight);
this._element.appendChild(g);
/*
TODO@hediet
path.addEventListener('mouseenter', () => {
model.setHoveredMovedText(line.move);
});
path.addEventListener('mouseleave', () => {
model.setHoveredMovedText(undefined);
});*/
idx++;
}
@ -184,3 +247,84 @@ class LinesLayout {
return this._trackCount;
}
}
class MovedBlockOverlayWidget extends ViewZoneOverlayWidget {
private readonly _nodes = h('div.diff-moved-code-block', { style: { marginRight: '4px' } }, [
h('div.text-content@textContent'),
h('div.action-bar@actionBar'),
]);
constructor(
private readonly _editor: ICodeEditor,
_viewZone: PlaceholderViewZone,
private readonly _move: MovedText,
private readonly _kind: 'original' | 'modified',
private readonly _diffModel: DiffEditorViewModel,
) {
const root = h('div.diff-hidden-lines-widget');
super(_editor, _viewZone, root.root);
root.root.appendChild(this._nodes.root);
const editorLayout = observableFromEvent(this._editor.onDidLayoutChange, () => this._editor.getLayoutInfo());
this._register(applyStyle(this._nodes.root, {
paddingRight: editorLayout.map(l => l.verticalScrollbarWidth)
}));
let text: string;
if (_move.changes.length > 0) {
text = this._kind === 'original' ? localize(
'codeMovedToWithChanges',
'Code moved with changes to line {0}-{1}',
this._move.lineRangeMapping.modified.startLineNumber,
this._move.lineRangeMapping.modified.endLineNumberExclusive
) : localize(
'codeMovedFromWithChanges',
'Code moved with changes from line {0}-{1}',
this._move.lineRangeMapping.original.startLineNumber,
this._move.lineRangeMapping.original.endLineNumberExclusive
);
} else {
text = this._kind === 'original' ? localize(
'codeMovedTo',
'Code moved to line {0}-{1}',
this._move.lineRangeMapping.modified.startLineNumber,
this._move.lineRangeMapping.modified.endLineNumberExclusive
) : localize(
'codeMovedFrom',
'Code moved from line {0}-{1}',
this._move.lineRangeMapping.original.startLineNumber,
this._move.lineRangeMapping.original.endLineNumberExclusive
);
}
const actionBar = this._register(new ActionBar(this._nodes.actionBar, {
highlightToggledItems: true,
}));
const caption = new Action(
'',
text,
'',
false,
);
actionBar.push(caption, { icon: false, label: true });
const actionCompare = new Action(
'',
'Compare',
ThemeIcon.asClassName(Codicon.compareChanges),
true,
() => {
this._editor.focus();
this._diffModel.movedTextToCompare.set(this._diffModel.movedTextToCompare.get() ? undefined : this._move, undefined);
},
);
this._register(autorun(reader => {
const isActive = this._diffModel.movedTextToCompare.read(reader) === _move;
actionCompare.checked = isActive;
}));
actionBar.push(actionCompare, { icon: true, label: false });
}
}

View file

@ -86,6 +86,10 @@
stroke: var(--vscode-diffEditor-moveActive-border);
}
.monaco-diff-editor .moved-blocks-lines path {
pointer-events: visiblestroke;
}
.monaco-diff-editor .moved-blocks-lines .arrow {
fill: var(--vscode-diffEditor-move-border);
}
@ -121,3 +125,15 @@
.monaco-editor .fold-unchanged {
cursor: pointer;
}
.monaco-diff-editor .diff-moved-code-block {
display: flex;
justify-content: flex-end;
margin-top: -4px;
}
.monaco-diff-editor .diff-moved-code-block .action-bar .action-label.codicon {
width: 12px;
height: 12px;
font-size: 12px;
}

View file

@ -268,6 +268,8 @@ export interface CSSStyle {
top: number | string;
visibility: 'visible' | 'hidden' | 'collapse';
display: 'block' | 'inline' | 'inline-block' | 'flex' | 'none';
paddingLeft: number | string;
paddingRight: number | string;
}
export function applyStyle(domNode: HTMLElement, style: Partial<{ [TKey in keyof CSSStyle]: CSSStyle[TKey] | IObservable<CSSStyle[TKey] | undefined> | undefined }>) {
@ -280,6 +282,7 @@ export function applyStyle(domNode: HTMLElement, style: Partial<{ [TKey in keyof
if (typeof val === 'number') {
val = `${val}px`;
}
key = key.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
domNode.style[key as any] = val as any;
}
});

View file

@ -34,6 +34,10 @@ export class OffsetRange {
return new OffsetRange(start, endExclusive);
}
public static ofLength(length: number): OffsetRange {
return new OffsetRange(0, length);
}
constructor(public readonly start: number, public readonly endExclusive: number) {
if (start > endExclusive) {
throw new BugIndicatingError(`Invalid range: ${this.toString()}`);
@ -102,6 +106,36 @@ export class OffsetRange {
public slice<T>(arr: T[]): T[] {
return arr.slice(this.start, this.endExclusive);
}
/**
* Returns the given value if it is contained in this instance, otherwise the closest value that is contained.
* The range must not be empty.
*/
public clip(value: number): number {
if (this.isEmpty) {
throw new BugIndicatingError(`Invalid clipping range: ${this.toString()}`);
}
return Math.max(this.start, Math.min(this.endExclusive - 1, value));
}
/**
* Returns `r := value + k * length` such that `r` is contained in this range.
* The range must not be empty.
*
* E.g. `[5, 10).clipCyclic(10) === 5`, `[5, 10).clipCyclic(11) === 6` and `[5, 10).clipCyclic(4) === 9`.
*/
public clipCyclic(value: number): number {
if (this.isEmpty) {
throw new BugIndicatingError(`Invalid clipping range: ${this.toString()}`);
}
if (value < this.start) {
return this.endExclusive - ((this.start - value) % this.length);
}
if (value >= this.endExclusive) {
return this.start + ((value - this.start) % this.length);
}
return value;
}
}
export class OffsetRangeSet {

View file

@ -27,6 +27,7 @@ export namespace EditorContextKeys {
export const readOnly = new RawContextKey<boolean>('editorReadonly', false, nls.localize('editorReadonly', "Whether the editor is read-only"));
export const inDiffEditor = new RawContextKey<boolean>('inDiffEditor', false, nls.localize('inDiffEditor', "Whether the context is a diff editor"));
export const isEmbeddedDiffEditor = new RawContextKey<boolean>('isEmbeddedDiffEditor', false, nls.localize('isEmbeddedDiffEditor', "Whether the context is an embedded diff editor"));
export const comparingMovedCode = new RawContextKey<boolean>('comparingMovedCode', false, nls.localize('comparingMovedCode', "Whether a moved code block is selected for comparison"));
export const accessibleDiffViewerVisible = new RawContextKey<boolean>('accessibleDiffViewerVisible', false, nls.localize('accessibleDiffViewerVisible', "Whether the accessible diff viewer is visible"));
export const diffEditorRenderSideBySideInlineBreakpointReached = new RawContextKey<boolean>('diffEditorRenderSideBySideInlineBreakpointReached', false, nls.localize('diffEditorRenderSideBySideInlineBreakpointReached', "Whether the diff editor render side by side inline breakpoint is reached"));
export const columnSelection = new RawContextKey<boolean>('editorColumnSelection', false, nls.localize('editorColumnSelection', "Whether `editor.columnSelection` is enabled"));

View file

@ -22,13 +22,13 @@ interface ActionGroup {
const uncategorizedCodeActionGroup = Object.freeze<ActionGroup>({ kind: CodeActionKind.Empty, title: localize('codeAction.widget.id.more', 'More Actions...') });
const codeActionGroups = Object.freeze<ActionGroup[]>([
{ kind: CodeActionKind.QuickFix, title: localize('codeAction.widget.id.quickfix', 'Quick Fix...') },
{ kind: CodeActionKind.RefactorExtract, title: localize('codeAction.widget.id.extract', 'Extract...'), icon: Codicon.wrench },
{ kind: CodeActionKind.RefactorInline, title: localize('codeAction.widget.id.inline', 'Inline...'), icon: Codicon.wrench },
{ kind: CodeActionKind.RefactorRewrite, title: localize('codeAction.widget.id.convert', 'Rewrite...'), icon: Codicon.wrench },
{ kind: CodeActionKind.RefactorMove, title: localize('codeAction.widget.id.move', 'Move...'), icon: Codicon.wrench },
{ kind: CodeActionKind.SurroundWith, title: localize('codeAction.widget.id.surround', 'Surround With...'), icon: Codicon.symbolSnippet },
{ kind: CodeActionKind.Source, title: localize('codeAction.widget.id.source', 'Source Action...'), icon: Codicon.symbolFile },
{ kind: CodeActionKind.QuickFix, title: localize('codeAction.widget.id.quickfix', 'Quick Fix:') },
{ kind: CodeActionKind.RefactorExtract, title: localize('codeAction.widget.id.extract', 'Extract:'), icon: Codicon.wrench },
{ kind: CodeActionKind.RefactorInline, title: localize('codeAction.widget.id.inline', 'Inline:'), icon: Codicon.wrench },
{ kind: CodeActionKind.RefactorRewrite, title: localize('codeAction.widget.id.convert', 'Rewrite:'), icon: Codicon.wrench },
{ kind: CodeActionKind.RefactorMove, title: localize('codeAction.widget.id.move', 'Move:'), icon: Codicon.wrench },
{ kind: CodeActionKind.SurroundWith, title: localize('codeAction.widget.id.surround', 'Surround With:'), icon: Codicon.symbolSnippet },
{ kind: CodeActionKind.Source, title: localize('codeAction.widget.id.source', 'Source Action:'), icon: Codicon.symbolFile },
uncategorizedCodeActionGroup,
]);

View file

@ -117,7 +117,7 @@ export class InlineCompletionsController extends Disposable {
this._register(editor.onDidChangeCursorPosition(e => transaction(tx => {
/** @description onDidChangeCursorPosition */
this.updateObservables(tx, VersionIdChangeReason.Other);
if (e.reason === CursorChangeReason.Explicit) {
if (e.reason === CursorChangeReason.Explicit || e.source === 'api') {
this.model.get()?.stop(tx);
}
})));

View file

@ -370,7 +370,6 @@ export class StickyScrollController extends Disposable implements IEditorContrib
private _readConfiguration() {
const options = this._editor.getOption(EditorOption.stickyScroll);
if (options.enabled === false) {
this._editor.removeOverlayWidget(this._stickyScrollWidget);
this._sessionStore.clear();
@ -429,10 +428,11 @@ export class StickyScrollController extends Disposable implements IEditorContrib
}
private _renderStickyScroll() {
if (!(this._editor.hasModel())) {
const model = this._editor.getModel();
if (!model || model.isTooLargeForTokenization()) {
this._stickyScrollWidget.setState(undefined);
return;
}
const model = this._editor.getModel();
const stickyLineVersion = this._stickyLineCandidateProvider.getVersionId();
if (stickyLineVersion === undefined || stickyLineVersion === model.getVersionId()) {
this._widgetState = this.findScrollWidgetState();

View file

@ -99,9 +99,11 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget {
return this._lineNumbers;
}
setState(state: StickyScrollWidgetState): void {
dom.clearNode(this._lineNumbersDomNode);
dom.clearNode(this._linesDomNode);
setState(state: StickyScrollWidgetState | undefined): void {
this._clearStickyWidget();
if (!state) {
return;
}
this._stickyLines = [];
const editorLineHeight = this._editor.getOption(EditorOption.lineHeight);
const futureWidgetHeight = state.startLineNumbers.length * editorLineHeight + state.lastLineRelativePosition;
@ -129,6 +131,12 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget {
this._rootDomNode.style.width = `${layoutInfo.width - layoutInfo.minimap.minimapCanvasOuterWidth - layoutInfo.verticalScrollbarWidth}px`;
}
private _clearStickyWidget() {
dom.clearNode(this._lineNumbersDomNode);
dom.clearNode(this._linesDomNode);
this._rootDomNode.style.display = 'none';
}
private _renderRootNode(): void {
if (!this._editor._getViewModel()) {
@ -145,7 +153,11 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget {
const editorLineHeight = this._editor.getOption(EditorOption.lineHeight);
const widgetHeight: number = this._lineNumbers.length * editorLineHeight + this._lastLineRelativePosition;
this._rootDomNode.style.display = widgetHeight > 0 ? 'block' : 'none';
if (widgetHeight === 0) {
this._clearStickyWidget();
return;
}
this._rootDomNode.style.display = 'block';
this._lineNumbersDomNode.style.height = `${widgetHeight}px`;
this._linesDomNodeScrollable.style.height = `${widgetHeight}px`;
this._rootDomNode.style.height = `${widgetHeight}px`;

View file

@ -154,7 +154,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
}
async toggleAppliationScope(extension: ILocalExtension, fromProfileLocation: URI): Promise<ILocalExtension> {
if (isApplicationScopedExtension(extension.manifest)) {
if (isApplicationScopedExtension(extension.manifest) || extension.isBuiltin) {
return extension;
}

View file

@ -1145,7 +1145,7 @@ function workbenchTreeDataPreamble<T, TFilterData, TOptions extends IAbstractTre
const horizontalScrolling = options.horizontalScrolling !== undefined ? options.horizontalScrolling : Boolean(configurationService.getValue(horizontalScrollingKey));
const [workbenchListOptions, disposable] = instantiationService.invokeFunction(toWorkbenchListOptions, options);
const additionalScrollHeight = options.additionalScrollHeight;
const paddingBottom = options.paddingBottom;
const renderIndentGuides = options.renderIndentGuides !== undefined ? options.renderIndentGuides : configurationService.getValue<RenderIndentGuides>(treeRenderIndentGuidesKey);
return {
@ -1162,7 +1162,7 @@ function workbenchTreeDataPreamble<T, TFilterData, TOptions extends IAbstractTre
defaultFindMatchType: getDefaultTreeFindMatchType(configurationService),
horizontalScrolling,
scrollByPage: Boolean(configurationService.getValue(scrollByPageKey)),
additionalScrollHeight,
paddingBottom: paddingBottom,
hideTwistiesOfChildlessElements: options.hideTwistiesOfChildlessElements,
expandOnlyOnTwistieClick: options.expandOnlyOnTwistieClick ?? (configurationService.getValue<'singleClick' | 'doubleClick'>(treeExpandMode) === 'doubleClick'),
contextViewProvider: contextViewService as IContextViewProvider,

View file

@ -4,6 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import { VSBuffer, encodeBase64 } from 'vs/base/common/buffer';
import { Emitter, Event, PauseableEmitter } from 'vs/base/common/event';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { ISocket, SocketCloseEvent, SocketDiagnostics, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net';
export const makeRawSocketHeaders = (path: string, query: string, deubgLabel: string) => {
// https://tools.ietf.org/html/rfc6455#section-4
@ -24,3 +27,119 @@ export const makeRawSocketHeaders = (path: string, query: string, deubgLabel: st
};
export const socketRawEndHeaderSequence = VSBuffer.fromString('\r\n\r\n');
export interface RemoteSocketHalf {
onData: Emitter<VSBuffer>;
onClose: Emitter<SocketCloseEvent>;
onEnd: Emitter<void>;
}
/** Should be called immediately after making a ManagedSocket to make it ready for data flow. */
export async function connectManagedSocket<T extends ManagedSocket>(
socket: T,
path: string, query: string, debugLabel: string,
half: RemoteSocketHalf
): Promise<T> {
socket.write(VSBuffer.fromString(makeRawSocketHeaders(path, query, debugLabel)));
const d = new DisposableStore();
try {
return await new Promise<T>((resolve, reject) => {
let dataSoFar: VSBuffer | undefined;
d.add(socket.onData(d_1 => {
if (!dataSoFar) {
dataSoFar = d_1;
} else {
dataSoFar = VSBuffer.concat([dataSoFar, d_1], dataSoFar.byteLength + d_1.byteLength);
}
const index = dataSoFar.indexOf(socketRawEndHeaderSequence);
if (index === -1) {
return;
}
resolve(socket);
// pause data events until the socket consumer is hooked up. We may
// immediately emit remaining data, but if not there may still be
// microtasks queued which would fire data into the abyss.
socket.pauseData();
const rest = dataSoFar.slice(index + socketRawEndHeaderSequence.byteLength);
if (rest.byteLength) {
half.onData.fire(rest);
}
}));
d.add(socket.onClose(err => reject(err ?? new Error('socket closed'))));
d.add(socket.onEnd(() => reject(new Error('socket ended'))));
});
} catch (e) {
socket.dispose();
throw e;
} finally {
d.dispose();
}
}
export abstract class ManagedSocket extends Disposable implements ISocket {
private readonly pausableDataEmitter = this._register(new PauseableEmitter<VSBuffer>());
public onData: Event<VSBuffer> = (...args) => {
if (this.pausableDataEmitter.isPaused) {
queueMicrotask(() => this.pausableDataEmitter.resume());
}
return this.pausableDataEmitter.event(...args);
};
public onClose: Event<SocketCloseEvent>;
public onEnd: Event<void>;
private readonly didDisposeEmitter = this._register(new Emitter<void>());
public onDidDispose = this.didDisposeEmitter.event;
private ended = false;
protected constructor(
private readonly debugLabel: string,
half: RemoteSocketHalf,
) {
super();
this._register(half.onData);
this._register(half.onData.event(data => this.pausableDataEmitter.fire(data)));
this.onClose = this._register(half.onClose).event;
this.onEnd = this._register(half.onEnd).event;
}
/** Pauses data events until a new listener comes in onData() */
public pauseData() {
this.pausableDataEmitter.pause();
}
/** Flushes data to the socket. */
public drain(): Promise<void> {
return Promise.resolve();
}
/** Ends the remote socket. */
public end(): void {
this.ended = true;
this.closeRemote();
}
public abstract write(buffer: VSBuffer): void;
protected abstract closeRemote(): void;
traceSocketEvent(type: SocketDiagnosticsEventType, data?: any): void {
SocketDiagnostics.traceSocketEvent(this, this.debugLabel, type, data);
}
override dispose(): void {
if (!this.ended) {
this.closeRemote();
}
this.didDisposeEmitter.fire();
super.dispose();
}
}

View file

@ -10,14 +10,15 @@ import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import { Barrier } from 'vs/base/common/async';
import { Disposable } from 'vs/base/common/lifecycle';
import { OS } from 'vs/base/common/platform';
import { ISocket } from 'vs/base/parts/ipc/common/ipc.net';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { connectRemoteAgentTunnel, IAddressProvider, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection';
import { AbstractTunnelService, isAllInterfaces, ISharedTunnelsService as ISharedTunnelsService, isLocalhost, isPortPrivileged, isTunnelProvider, ITunnelProvider, ITunnelService, RemoteTunnel, TunnelPrivacyId } from 'vs/platform/tunnel/common/tunnel';
import { ISignService } from 'vs/platform/sign/common/sign';
import { OS } from 'vs/base/common/platform';
import { IAddressProvider, IConnectionOptions, connectRemoteAgentTunnel } from 'vs/platform/remote/common/remoteAgentConnection';
import { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService';
import { ISignService } from 'vs/platform/sign/common/sign';
import { AbstractTunnelService, ISharedTunnelsService, ITunnelProvider, ITunnelService, RemoteTunnel, TunnelPrivacyId, isAllInterfaces, isLocalhost, isPortPrivileged, isTunnelProvider } from 'vs/platform/tunnel/common/tunnel';
async function createRemoteTunnel(options: IConnectionOptions, defaultTunnelHost: string, tunnelRemoteHost: string, tunnelRemotePort: number, tunnelLocalPort?: number): Promise<RemoteTunnel> {
let readyTunnel: NodeRemoteTunnel | undefined;
@ -32,7 +33,7 @@ async function createRemoteTunnel(options: IConnectionOptions, defaultTunnelHost
return readyTunnel!;
}
class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
export class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
public readonly tunnelRemotePort: number;
public tunnelLocalPort!: number;
@ -113,7 +114,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
const tunnelRemoteHost = (isLocalhost(this.tunnelRemoteHost) || isAllInterfaces(this.tunnelRemoteHost)) ? 'localhost' : this.tunnelRemoteHost;
const protocol = await connectRemoteAgentTunnel(this._options, tunnelRemoteHost, this.tunnelRemotePort);
const remoteSocket = (<NodeSocket>protocol.getSocket()).socket;
const remoteSocket = protocol.getSocket();
const dataChunk = protocol.readEntireBuffer();
protocol.dispose();
@ -132,17 +133,19 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
if (localSocket.localAddress) {
this._socketsDispose.delete(localSocket.localAddress);
}
remoteSocket.destroy();
if (remoteSocket instanceof NodeSocket) {
remoteSocket.socket.destroy();
} else {
remoteSocket.end();
}
});
remoteSocket.on('end', () => localSocket.end());
remoteSocket.on('close', () => localSocket.end());
remoteSocket.on('error', () => {
localSocket.destroy();
});
if (remoteSocket instanceof NodeSocket) {
this._mirrorNodeSocket(localSocket, remoteSocket);
} else {
this._mirrorGenericSocket(localSocket, remoteSocket);
}
localSocket.pipe(remoteSocket);
remoteSocket.pipe(localSocket);
if (localSocket.localAddress) {
this._socketsDispose.set(localSocket.localAddress, () => {
// Need to end instead of unpipe, otherwise whatever is connected locally could end up "stuck" with whatever state it had until manually exited.
@ -151,6 +154,25 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
});
}
}
private _mirrorGenericSocket(localSocket: net.Socket, remoteSocket: ISocket) {
remoteSocket.onClose(() => localSocket.destroy());
remoteSocket.onEnd(() => localSocket.end());
remoteSocket.onData(d => localSocket.write(d.buffer));
localSocket.resume();
}
private _mirrorNodeSocket(localSocket: net.Socket, remoteNodeSocket: NodeSocket) {
const remoteSocket = remoteNodeSocket.socket;
remoteSocket.on('end', () => localSocket.end());
remoteSocket.on('close', () => localSocket.end());
remoteSocket.on('error', () => {
localSocket.destroy();
});
remoteSocket.pipe(localSocket);
localSocket.pipe(remoteSocket);
}
}
export class BaseTunnelService extends AbstractTunnelService {

View file

@ -7,9 +7,8 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { Disposable, DisposableMap } from 'vs/base/common/lifecycle';
import { ExtHostAiRelatedInformationShape, ExtHostContext, MainContext, MainThreadAiRelatedInformationShape } from 'vs/workbench/api/common/extHost.protocol';
import { RelatedInformationType } from 'vs/workbench/api/common/extHostTypes';
import { IAiRelatedInformationProvider, IAiRelatedInformationService } from 'vs/workbench/services/aiRelatedInformation/common/aiRelatedInformation';
import { IAiRelatedInformationProvider, IAiRelatedInformationService, RelatedInformationResult } from 'vs/workbench/services/aiRelatedInformation/common/aiRelatedInformation';
import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { RelatedInformationResult } from 'vscode';
@extHostNamedCustomer(MainContext.MainThreadAiRelatedInformation)
export class MainThreadAiRelatedInformation extends Disposable implements MainThreadAiRelatedInformationShape {
@ -29,13 +28,13 @@ export class MainThreadAiRelatedInformation extends Disposable implements MainTh
return this._aiRelatedInformationService.getRelatedInformation(query, types, CancellationToken.None);
}
$registerAiRelatedInformationProvider(handle: number, types: RelatedInformationType[]): void {
$registerAiRelatedInformationProvider(handle: number, type: RelatedInformationType): void {
const provider: IAiRelatedInformationProvider = {
provideAiRelatedInformation: (query, types, token) => {
return this._proxy.$provideAiRelatedInformation(handle, query, types, token);
provideAiRelatedInformation: (query, token) => {
return this._proxy.$provideAiRelatedInformation(handle, query, token);
},
};
this._registrations.set(handle, this._aiRelatedInformationService.registerAiRelatedInformationProvider(types, provider));
this._registrations.set(handle, this._aiRelatedInformationService.registerAiRelatedInformationProvider(type, provider));
}
$unregisterAiRelatedInformationProvider(handle: number): void {

View file

@ -5,6 +5,7 @@
import { DeferredPromise } from 'vs/base/common/async';
import { Emitter } from 'vs/base/common/event';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { Disposable, DisposableMap } from 'vs/base/common/lifecycle';
import { revive } from 'vs/base/common/marshalling';
import { URI, UriComponents } from 'vs/base/common/uri';
@ -19,13 +20,13 @@ import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/ext
export class MainThreadChat extends Disposable implements MainThreadChatShape {
private readonly _providerRegistrations = this._register(new DisposableMap<number>());
private readonly _activeRequestProgressCallbacks = new Map<string, (progress: IChatProgress) => (DeferredPromise<string> | void)>();
private readonly _activeRequestProgressCallbacks = new Map<string, (progress: IChatProgress) => (DeferredPromise<string | IMarkdownString> | void)>();
private readonly _stateEmitters = new Map<number, Emitter<any>>();
private readonly _proxy: ExtHostChatShape;
private _responsePartHandlePool = 0;
private readonly _activeResponsePartPromises = new Map<string, DeferredPromise<string | { treeData: IChatResponseProgressFileTreeData }>>();
private readonly _activeResponsePartPromises = new Map<string, DeferredPromise<string | IMarkdownString | { treeData: IChatResponseProgressFileTreeData }>>();
constructor(
extHostContext: IExtHostContext,
@ -134,7 +135,7 @@ export class MainThreadChat extends Disposable implements MainThreadChatShape {
if ('placeholder' in progress) {
const responsePartId = `${id}_${++this._responsePartHandlePool}`;
const deferredContentPromise = new DeferredPromise<string | { treeData: IChatResponseProgressFileTreeData }>();
const deferredContentPromise = new DeferredPromise<string | IMarkdownString | { treeData: IChatResponseProgressFileTreeData }>();
this._activeResponsePartPromises.set(responsePartId, deferredContentPromise);
this._activeRequestProgressCallbacks.get(id)?.({ ...progress, resolvedContent: deferredContentPromise.p });
return this._responsePartHandlePool;

View file

@ -3,15 +3,15 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { MainContext, ExtHostContext, MainThreadManagedSocketsShape, ExtHostManagedSocketsShape } from 'vs/workbench/api/common/extHost.protocol';
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { ManagedRemoteConnection, RemoteConnectionType } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { VSBuffer } from 'vs/base/common/buffer';
import { Emitter } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { ISocket, SocketCloseEventType } from 'vs/base/parts/ipc/common/ipc.net';
import { ManagedSocket, RemoteSocketHalf, connectManagedSocket } from 'vs/platform/remote/common/managedSocket';
import { ManagedRemoteConnection, RemoteConnectionType } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRemoteSocketFactoryService, ISocketFactory } from 'vs/platform/remote/common/remoteSocketFactoryService';
import { ISocket, SocketCloseEvent, SocketCloseEventType, SocketDiagnostics, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net';
import { Emitter, Event, PauseableEmitter } from 'vs/base/common/event';
import { makeRawSocketHeaders, socketRawEndHeaderSequence } from 'vs/platform/remote/common/managedSocket';
import { ExtHostContext, ExtHostManagedSocketsShape, MainContext, MainThreadManagedSocketsShape } from 'vs/workbench/api/common/extHost.protocol';
import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers';
@extHostNamedCustomer(MainContext.MainThreadManagedSockets)
export class MainThreadManagedSockets extends Disposable implements MainThreadManagedSocketsShape {
@ -51,7 +51,7 @@ export class MainThreadManagedSockets extends Disposable implements MainThreadMa
};
that._remoteSockets.set(socketId, half);
ManagedSocket.connect(socketId, that._proxy, path, query, debugLabel, half)
MainThreadManagedSocket.connect(socketId, that._proxy, path, query, debugLabel, half)
.then(
socket => {
socket.onDidDispose(() => that._remoteSockets.delete(socketId));
@ -91,117 +91,35 @@ export class MainThreadManagedSockets extends Disposable implements MainThreadMa
}
}
export interface RemoteSocketHalf {
onData: Emitter<VSBuffer>;
onClose: Emitter<SocketCloseEvent>;
onEnd: Emitter<void>;
}
export class ManagedSocket extends Disposable implements ISocket {
export class MainThreadManagedSocket extends ManagedSocket {
public static connect(
socketId: number,
proxy: ExtHostManagedSocketsShape,
path: string, query: string, debugLabel: string,
half: RemoteSocketHalf
): Promise<ManagedSocket> {
const socket = new ManagedSocket(socketId, proxy, debugLabel, half.onClose, half.onData, half.onEnd);
socket.write(VSBuffer.fromString(makeRawSocketHeaders(path, query, debugLabel)));
const d = new DisposableStore();
return new Promise<ManagedSocket>((resolve, reject) => {
let dataSoFar: VSBuffer | undefined;
d.add(socket.onData(d => {
if (!dataSoFar) {
dataSoFar = d;
} else {
dataSoFar = VSBuffer.concat([dataSoFar, d], dataSoFar.byteLength + d.byteLength);
}
const index = dataSoFar.indexOf(socketRawEndHeaderSequence);
if (index === -1) {
return;
}
resolve(socket);
// pause data events until the socket consumer is hooked up. We may
// immediately emit remaining data, but if not there may still be
// microtasks queued which would fire data into the abyss.
socket.pauseData();
const rest = dataSoFar.slice(index + socketRawEndHeaderSequence.byteLength);
if (rest.byteLength) {
half.onData.fire(rest);
}
}));
d.add(socket.onClose(err => reject(err ?? new Error('socket closed'))));
d.add(socket.onEnd(() => reject(new Error('socket ended'))));
}).finally(() => d.dispose());
): Promise<MainThreadManagedSocket> {
const socket = new MainThreadManagedSocket(socketId, proxy, debugLabel, half);
return connectManagedSocket(socket, path, query, debugLabel, half);
}
private readonly pausableDataEmitter = this._register(new PauseableEmitter<VSBuffer>());
public onData: Event<VSBuffer> = (...args) => {
if (this.pausableDataEmitter.isPaused) {
queueMicrotask(() => this.pausableDataEmitter.resume());
}
return this.pausableDataEmitter.event(...args);
};
public onClose: Event<SocketCloseEvent>;
public onEnd: Event<void>;
private readonly didDisposeEmitter = this._register(new Emitter<void>());
public onDidDispose = this.didDisposeEmitter.event;
private ended = false;
private constructor(
private readonly socketId: number,
private readonly proxy: ExtHostManagedSocketsShape,
private readonly debugLabel: string,
onCloseEmitter: Emitter<SocketCloseEvent>,
onDataEmitter: Emitter<VSBuffer>,
onEndEmitter: Emitter<void>,
debugLabel: string,
half: RemoteSocketHalf,
) {
super();
this._register(onDataEmitter);
this._register(onDataEmitter.event(data => this.pausableDataEmitter.fire(data)));
this.onClose = this._register(onCloseEmitter).event;
this.onEnd = this._register(onEndEmitter).event;
super(debugLabel, half);
}
/** Pauses data events until a new listener comes in onData() */
pauseData() {
this.pausableDataEmitter.pause();
}
write(buffer: VSBuffer): void {
public override write(buffer: VSBuffer): void {
this.proxy.$remoteSocketWrite(this.socketId, buffer);
}
end(): void {
this.ended = true;
protected override closeRemote(): void {
this.proxy.$remoteSocketEnd(this.socketId);
}
drain(): Promise<void> {
public override drain(): Promise<void> {
return this.proxy.$remoteSocketDrain(this.socketId);
}
traceSocketEvent(type: SocketDiagnosticsEventType, data?: any): void {
SocketDiagnostics.traceSocketEvent(this, this.debugLabel, type, data);
}
override dispose(): void {
if (!this.ended) {
this.proxy.$remoteSocketEnd(this.socketId);
}
this.didDisposeEmitter.fire();
super.dispose();
}
}

View file

@ -1332,9 +1332,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension, 'aiRelatedInformation');
return extHostAiRelatedInformation.getRelatedInformation(extension, query, types);
},
registerRelatedInformationProvider(types: vscode.RelatedInformationType[], provider: vscode.RelatedInformationProvider) {
registerRelatedInformationProvider(type: vscode.RelatedInformationType, provider: vscode.RelatedInformationProvider) {
checkProposedApiEnabled(extension, 'aiRelatedInformation');
return extHostAiRelatedInformation.registerRelatedInformationProvider(extension, types, provider);
return extHostAiRelatedInformation.registerRelatedInformationProvider(extension, type, provider);
},
registerEmbeddingVectorProvider(model: string, provider: vscode.EmbeddingVectorProvider) {
checkProposedApiEnabled(extension, 'aiRelatedInformation');

View file

@ -1216,7 +1216,7 @@ export interface IChatResponseProgressFileTreeData {
children?: IChatResponseProgressFileTreeData[];
}
export type IChatResponseProgressDto = { content: string } | { requestId: string } | { placeholder: string } | { treeData: IChatResponseProgressFileTreeData };
export type IChatResponseProgressDto = { content: string | IMarkdownString } | { requestId: string } | { placeholder: string } | { treeData: IChatResponseProgressFileTreeData };
export interface MainThreadChatShape extends IDisposable {
$registerChatProvider(handle: number, id: string): Promise<void>;
@ -1675,12 +1675,12 @@ export interface MainThreadSemanticSimilarityShape extends IDisposable {
}
export interface ExtHostAiRelatedInformationShape {
$provideAiRelatedInformation(handle: number, query: string, types: RelatedInformationType[], token: CancellationToken): Promise<RelatedInformationResult[]>;
$provideAiRelatedInformation(handle: number, query: string, token: CancellationToken): Promise<RelatedInformationResult[]>;
}
export interface MainThreadAiRelatedInformationShape {
$getAiRelatedInformation(query: string, types: RelatedInformationType[]): Promise<RelatedInformationResult[]>;
$registerAiRelatedInformationProvider(handle: number, types: RelatedInformationType[]): void;
$registerAiRelatedInformationProvider(handle: number, type: RelatedInformationType): void;
$unregisterAiRelatedInformationProvider(handle: number): void;
}

View file

@ -5,7 +5,7 @@
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ExtHostAiRelatedInformationShape, IMainContext, MainContext, MainThreadAiRelatedInformationShape } from 'vs/workbench/api/common/extHost.protocol';
import type { CancellationToken, RelatedInformationProvider, RelatedInformationResult, RelatedInformationType } from 'vscode';
import type { CancellationToken, RelatedInformationProvider, RelatedInformationType, RelatedInformationResult } from 'vscode';
import { Disposable } from 'vs/workbench/api/common/extHostTypes';
export class ExtHostRelatedInformation implements ExtHostAiRelatedInformationShape {
@ -18,7 +18,7 @@ export class ExtHostRelatedInformation implements ExtHostAiRelatedInformationSha
this._proxy = mainContext.getProxy(MainContext.MainThreadAiRelatedInformation);
}
async $provideAiRelatedInformation(handle: number, query: string, types: RelatedInformationType[], token: CancellationToken): Promise<RelatedInformationResult[]> {
async $provideAiRelatedInformation(handle: number, query: string, token: CancellationToken): Promise<RelatedInformationResult[]> {
if (this._relatedInformationProviders.size === 0) {
throw new Error('No semantic similarity providers registered');
}
@ -28,8 +28,7 @@ export class ExtHostRelatedInformation implements ExtHostAiRelatedInformationSha
throw new Error('Semantic similarity provider not found');
}
// TODO: should this return undefined or an empty array?
const result = await provider.provideRelatedInformation(query, types, token) ?? [];
const result = await provider.provideRelatedInformation(query, token) ?? [];
return result;
}
@ -37,11 +36,11 @@ export class ExtHostRelatedInformation implements ExtHostAiRelatedInformationSha
return this._proxy.$getAiRelatedInformation(query, types);
}
registerRelatedInformationProvider(extension: IExtensionDescription, types: RelatedInformationType[], provider: RelatedInformationProvider): Disposable {
registerRelatedInformationProvider(extension: IExtensionDescription, type: RelatedInformationType, provider: RelatedInformationProvider): Disposable {
const handle = this._nextHandle;
this._nextHandle++;
this._relatedInformationProviders.set(handle, provider);
this._proxy.$registerAiRelatedInformationProvider(handle, types);
this._proxy.$registerAiRelatedInformationProvider(handle, type);
return new Disposable(() => {
this._proxy.$unregisterAiRelatedInformationProvider(handle);
this._relatedInformationProviders.delete(handle);

View file

@ -232,6 +232,10 @@ export class ExtHostChat implements ExtHostChatShape {
const [progressHandle, progressContent] = res;
this._proxy.$acceptResponseProgress(handle, sessionId, progressContent, progressHandle ?? undefined);
});
} else if ('content' in progress) {
this._proxy.$acceptResponseProgress(handle, sessionId, {
content: typeof progress.content === 'string' ? progress.content : typeConvert.MarkdownString.from(progress.content)
});
} else {
this._proxy.$acceptResponseProgress(handle, sessionId, progress);
}

View file

@ -861,9 +861,11 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
performance.mark(`code/extHost/willResolveAuthority/${authorityPrefix}`);
result = await resolver.resolve(remoteAuthority, { resolveAttempt, execServer });
performance.mark(`code/extHost/didResolveAuthorityOK/${authorityPrefix}`);
// todo@connor4312: we probably need to chain tunnels too, how does this work with 'public' tunnels?
logInfo(`setting tunnel factory...`);
this._register(await this._extHostTunnelService.setTunnelFactory(resolver));
this._register(await this._extHostTunnelService.setTunnelFactory(
resolver,
ExtHostManagedResolvedAuthority.isManagedResolvedAuthority(result) ? result : undefined
));
} else {
logInfo(`invoking resolveExecServer() for ${remoteAuthority}`);
performance.mark(`code/extHost/willResolveExecServer/${authorityPrefix}`);

View file

@ -929,10 +929,6 @@ class UnifiedEnvironmentVariableCollection {
}
getScopedEnvironmentVariableCollection(scope: vscode.EnvironmentVariableScope | undefined): IEnvironmentVariableCollection {
if (this._extension && scope) {
// TODO: This should be removed when the env var extension API(s) are stabilized
checkProposedApiEnabled(this._extension, 'envCollectionWorkspace');
}
const scopedCollectionKey = this.getScopeKey(scope);
let scopedCollection = this.scopedCollections.get(scopedCollectionKey);
if (!scopedCollection) {

View file

@ -54,7 +54,7 @@ export interface IExtHostTunnelService extends ExtHostTunnelServiceShape {
openTunnel(extension: IExtensionDescription, forward: TunnelOptions): Promise<vscode.Tunnel | undefined>;
getTunnels(): Promise<vscode.TunnelDescription[]>;
onDidChangeTunnels: vscode.Event<void>;
setTunnelFactory(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable>;
setTunnelFactory(provider: vscode.RemoteAuthorityResolver | undefined, managedRemoteAuthority: vscode.ManagedResolvedAuthority | undefined): Promise<IDisposable>;
registerPortsAttributesProvider(portSelector: PortAttributesSelector, provider: vscode.PortAttributesProvider): IDisposable;
registerTunnelProvider(provider: vscode.TunnelProvider, information: vscode.TunnelInformation): Promise<IDisposable>;
}
@ -165,7 +165,15 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
}));
}
async setTunnelFactory(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> {
/**
* Applies the tunnel metadata and factory found in the remote authority
* resolver to the tunnel system.
*
* `managedRemoteAuthority` should be be passed if the resolver returned on.
* If this is the case, the tunnel cannot be connected to via a websocket from
* the share process, so a synethic tunnel factory is used as a default.
*/
async setTunnelFactory(provider: vscode.RemoteAuthorityResolver | undefined, managedRemoteAuthority: vscode.ManagedResolvedAuthority | undefined): Promise<IDisposable> {
// Do not wait for any of the proxy promises here.
// It will delay startup and there is nothing that needs to be waited for.
if (provider) {
@ -176,8 +184,9 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
this._showCandidatePort = provider.showCandidatePort;
this._proxy.$setCandidateFilter();
}
if (provider.tunnelFactory) {
this._forwardPortProvider = provider.tunnelFactory;
const tunnelFactory = provider.tunnelFactory ?? (managedRemoteAuthority ? this.makeManagedTunnelFactory(managedRemoteAuthority) : undefined);
if (tunnelFactory) {
this._forwardPortProvider = tunnelFactory;
let privacyOptions = provider.tunnelFeatures?.privacyOptions ?? [];
if (provider.tunnelFeatures?.public && (privacyOptions.length === 0)) {
privacyOptions = [
@ -210,6 +219,10 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
});
}
protected makeManagedTunnelFactory(_authority: vscode.ManagedResolvedAuthority): vscode.RemoteAuthorityResolver['tunnelFactory'] {
return undefined; // may be overridden
}
async $closeTunnel(remote: { host: string; port: number }, silent?: boolean): Promise<void> {
if (this._extensionTunnels.has(remote.host)) {
const hostMap = this._extensionTunnels.get(remote.host)!;

View file

@ -24,6 +24,8 @@ import { NodeExtHostVariableResolverProviderService } from 'vs/workbench/api/nod
import { IExtHostVariableResolverProvider } from 'vs/workbench/api/common/extHostVariableResolverService';
import { ExtHostLogService } from 'vs/workbench/api/common/extHostLogService';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ISignService } from 'vs/platform/sign/common/sign';
import { SignService } from 'vs/platform/sign/node/signService';
// #########################################################################
// ### ###
@ -34,6 +36,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
registerSingleton(IExtHostExtensionService, ExtHostExtensionService, InstantiationType.Eager);
registerSingleton(ILoggerService, ExtHostLoggerService, InstantiationType.Delayed);
registerSingleton(ILogService, new SyncDescriptor(ExtHostLogService, [false], true));
registerSingleton(ISignService, SignService, InstantiationType.Delayed);
registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths, InstantiationType.Eager);
registerSingleton(IExtHostDebugService, ExtHostDebugService, InstantiationType.Eager);

View file

@ -4,17 +4,26 @@
*--------------------------------------------------------------------------------------------*/
import { exec } from 'child_process';
import { VSBuffer } from 'vs/base/common/buffer';
import { Emitter } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { MovingAverage } from 'vs/base/common/numbers';
import { isLinux } from 'vs/base/common/platform';
import * as resources from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import * as pfs from 'vs/base/node/pfs';
import { ISocket, SocketCloseEventType } from 'vs/base/parts/ipc/common/ipc.net';
import { ILogService } from 'vs/platform/log/common/log';
import { ManagedSocket, RemoteSocketHalf, connectManagedSocket } from 'vs/platform/remote/common/managedSocket';
import { ManagedRemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { ISignService } from 'vs/platform/sign/common/sign';
import { isAllInterfaces, isLocalhost } from 'vs/platform/tunnel/common/tunnel';
import { NodeRemoteTunnel } from 'vs/platform/tunnel/node/tunnelService';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { ExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
import { CandidatePort } from 'vs/workbench/services/remote/common/tunnelModel';
import * as vscode from 'vscode';
export function getSockets(stdout: string): Record<string, { pid: number; socket: number }> {
const lines = stdout.trim().split('\n');
@ -171,8 +180,9 @@ export class NodeExtHostTunnelService extends ExtHostTunnelService {
constructor(
@IExtHostRpcService extHostRpc: IExtHostRpcService,
@IExtHostInitDataService initData: IExtHostInitDataService,
@ILogService logService: ILogService
@IExtHostInitDataService private readonly initData: IExtHostInitDataService,
@ILogService logService: ILogService,
@ISignService private readonly signService: ISignService,
) {
super(extHostRpc, initData, logService);
if (isLinux && initData.remote.isRemote && initData.remote.authority) {
@ -297,4 +307,102 @@ export class NodeExtHostTunnelService extends ExtHostTunnelService {
}
});
}
protected override makeManagedTunnelFactory(authority: vscode.ManagedResolvedAuthority): vscode.RemoteAuthorityResolver['tunnelFactory'] {
return async (tunnelOptions) => {
const t = new NodeRemoteTunnel(
{
commit: this.initData.commit,
quality: this.initData.quality,
logService: this.logService,
ipcLogger: null,
// services and address providers have stubs since we don't need
// the connection identification that the renderer process uses
remoteSocketFactoryService: {
_serviceBrand: undefined,
async connect(_connectTo: ManagedRemoteConnection, path: string, query: string, debugLabel: string): Promise<ISocket> {
const result = await authority.makeConnection();
return ExtHostManagedSocket.connect(result, path, query, debugLabel);
},
register() {
throw new Error('not implemented');
},
},
addressProvider: {
getAddress() {
return Promise.resolve({
connectTo: new ManagedRemoteConnection(0),
connectionToken: authority.connectionToken,
});
},
},
signService: this.signService,
},
'localhost',
tunnelOptions.remoteAddress.host || 'localhost',
tunnelOptions.remoteAddress.port,
tunnelOptions.localAddressPort,
);
await t.waitForReady();
const disposeEmitter = new Emitter<void>();
return {
localAddress: t.localAddress,
remoteAddress: { port: t.tunnelRemotePort, host: t.tunnelRemoteHost },
onDidDispose: disposeEmitter.event,
dispose: () => {
t.dispose();
disposeEmitter.fire();
disposeEmitter.dispose();
},
};
};
}
}
class ExtHostManagedSocket extends ManagedSocket {
public static connect(
passing: vscode.ManagedMessagePassing,
path: string, query: string, debugLabel: string,
): Promise<ExtHostManagedSocket> {
const d = new DisposableStore();
const half: RemoteSocketHalf = {
onClose: d.add(new Emitter()),
onData: d.add(new Emitter()),
onEnd: d.add(new Emitter()),
};
d.add(passing.onDidReceiveMessage(d => half.onData.fire(VSBuffer.wrap(d))));
d.add(passing.onDidEnd(() => half.onEnd.fire()));
d.add(passing.onDidClose(error => half.onClose.fire({
type: SocketCloseEventType.NodeSocketCloseEvent,
error,
hadError: !!error
})));
const socket = new ExtHostManagedSocket(passing, debugLabel, half);
socket._register(d);
return connectManagedSocket(socket, path, query, debugLabel, half);
}
constructor(
private readonly passing: vscode.ManagedMessagePassing,
debugLabel: string,
half: RemoteSocketHalf,
) {
super(debugLabel, half);
}
public override write(buffer: VSBuffer): void {
this.passing.send(buffer.buffer);
}
protected override closeRemote(): void {
this.passing.end();
}
public override async drain(): Promise<void> {
await this.passing.drain?.();
}
}

View file

@ -10,7 +10,8 @@ import { Emitter } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { SocketCloseEvent } from 'vs/base/parts/ipc/common/ipc.net';
import { mock } from 'vs/base/test/common/mock';
import { ManagedSocket, RemoteSocketHalf } from 'vs/workbench/api/browser/mainThreadManagedSockets';
import { RemoteSocketHalf } from 'vs/platform/remote/common/managedSocket';
import { MainThreadManagedSocket } from 'vs/workbench/api/browser/mainThreadManagedSockets';
import { ExtHostManagedSocketsShape } from 'vs/workbench/api/common/extHost.protocol';
suite('MainThreadManagedSockets', () => {
@ -68,7 +69,7 @@ suite('MainThreadManagedSockets', () => {
});
async function doConnect() {
const socket = ManagedSocket.connect(1, extHost, '/hello', 'world=true', '', half);
const socket = MainThreadManagedSocket.connect(1, extHost, '/hello', 'world=true', '', half);
await extHost.expectEvent(evt => evt.data && evt.data.startsWith('GET ws://localhost/hello?world=true&skipWebSocketFrames=true HTTP/1.1\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Key:'), 'websocket open event');
half.onData.fire(VSBuffer.fromString('Opened successfully ;)\r\n\r\n'));
return await socket;
@ -79,7 +80,7 @@ suite('MainThreadManagedSockets', () => {
});
test('includes trailing connection data', async () => {
const socketProm = ManagedSocket.connect(1, extHost, '/hello', 'world=true', '', half);
const socketProm = MainThreadManagedSocket.connect(1, extHost, '/hello', 'world=true', '', half);
await extHost.expectEvent(evt => evt.data && evt.data.includes('GET ws://localhost'), 'websocket open event');
half.onData.fire(VSBuffer.fromString('Opened successfully ;)\r\n\r\nSome trailing data'));
const socket = await socketProm;

View file

@ -361,6 +361,13 @@ function registerDiffEditorCommands(): void {
handler: accessor => navigateInDiffEditor(accessor, true)
});
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: GOTO_NEXT_CHANGE,
title: { value: localize('compare.nextChange', "Go to Next Change"), original: 'Go to Next Change' },
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: GOTO_PREVIOUS_CHANGE,
weight: KeybindingWeight.WorkbenchContrib,
@ -369,6 +376,13 @@ function registerDiffEditorCommands(): void {
handler: accessor => navigateInDiffEditor(accessor, false)
});
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: GOTO_PREVIOUS_CHANGE,
title: { value: localize('compare.previousChange', "Go to Previous Change"), original: 'Go to Previous Change' },
}
});
function getActiveTextDiffEditor(accessor: ServicesAccessor): TextDiffEditor | undefined {
const editorService = accessor.get(IEditorService);

View file

@ -297,6 +297,10 @@
padding-right: 5px; /* with tab sizing shrink/fixed and badges, we want a right-padding because the close button is hidden */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink:not(.tab-actions-left):not(.tab-actions-off) .tab-label {
padding-right: 5px; /* ensure that the gradient does not show when tab actions show https://github.com/microsoft/vscode/issues/189625*/
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sticky-compact:not(.has-icon) .monaco-icon-label {
text-align: center; /* ensure that sticky-compact tabs without icon have label centered */
}

View file

@ -32,7 +32,7 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la
import { isFullscreen } from 'vs/base/browser/browser';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { BrowserFeatures } from 'vs/base/browser/canIUse';
import { KeyCode } from 'vs/base/common/keyCodes';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IsMacNativeContext, IsWebContext } from 'vs/platform/contextkey/common/contextkeys';
import { ICommandService } from 'vs/platform/commands/common/commands';
@ -440,7 +440,7 @@ export class CustomMenubarControl extends MenubarControl {
id: `workbench.actions.menubar.focus`,
title: { value: localize('focusMenu', "Focus Application Menu"), original: 'Focus Application Menu' },
keybinding: {
primary: KeyCode.F10,
primary: KeyMod.Alt | KeyCode.F10,
weight: KeybindingWeight.WorkbenchContrib,
when: IsWebContext
},

View file

@ -613,7 +613,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
// Pass Focus to Viewer
this.tree.domFocus();
} else if (this.tree) {
} else if (this.tree && this.treeContainer && !this.treeContainer.classList.contains('hide')) {
this.tree.domFocus();
} else {
this.domNode.focus();
@ -1017,6 +1017,9 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
this.domNode.setAttribute('tabindex', '0');
} else if (this.treeContainer) {
this.treeContainer.classList.remove('hide');
if (this.domNode === DOM.getActiveElement()) {
this.focus();
}
this.domNode.removeAttribute('tabindex');
}
}
@ -1217,7 +1220,8 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
matches: matches ? matches : createMatches(element.filterData),
strikethrough: treeItemLabel?.strikethrough,
disabledCommand: !commandEnabled,
labelEscapeNewLines: true
labelEscapeNewLines: true,
forceLabel: !!node.label
});
} else {
templateData.resourceLabel.setResource({ name: label, description }, {

View file

@ -282,7 +282,6 @@ export class InlineCompletionsAccessibleViewContribution extends Disposable {
this._register(AccessibleViewAction.addImplementation(95, 'inline-completions', accessor => {
const accessibleViewService = accessor.get(IAccessibleViewService);
const codeEditorService = accessor.get(ICodeEditorService);
const contextViewService = accessor.get(IContextViewService);
const show = () => {
const editor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor();
if (!editor) {
@ -311,12 +310,12 @@ export class InlineCompletionsAccessibleViewContribution extends Disposable {
editor.focus();
},
next() {
contextViewService.hideContextView();
setTimeout(() => model.next().then(() => show()), 50);
model.next();
setTimeout(() => show(), 50);
},
previous() {
contextViewService.hideContextView();
setTimeout(() => model.previous().then(() => show()), 50);
model.previous();
setTimeout(() => show(), 50);
},
options: this._options
});

View file

@ -1,8 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.interactive-response-progress-tree .monaco-tl-row:hover {
background-color: var(--vscode-list-hoverBackground);
}

View file

@ -72,6 +72,7 @@ import { createFileIconThemableTreeContainerScope } from 'vs/workbench/contrib/f
import { IFilesConfiguration } from 'vs/workbench/contrib/files/common/files';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { distinct } from 'vs/base/common/arrays';
import { IPlaceholderMarkdownString } from 'vs/workbench/contrib/chat/common/chatModel';
const $ = dom.$;
@ -315,12 +316,12 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
}
}
private basicRenderElement(markdownValue: (IMarkdownString | IChatResponseProgressFileTreeData)[], element: ChatTreeItem, index: number, templateData: IChatListItemTemplate) {
private basicRenderElement(value: ReadonlyArray<IMarkdownString | IChatResponseProgressFileTreeData>, element: ChatTreeItem, index: number, templateData: IChatListItemTemplate) {
const fillInIncompleteTokens = isResponseVM(element) && (!element.isComplete || element.isCanceled || element.errorDetails?.responseIsFiltered || element.errorDetails?.responseIsIncomplete);
dom.clearNode(templateData.value);
let fileTreeIndex = 0;
for (const data of markdownValue) {
for (const data of value) {
const result = 'value' in data
? this.renderMarkdown(data, element, templateData.elementDisposables, templateData, fillInIncompleteTokens)
: this.renderTreeData(data, element, templateData.elementDisposables, templateData, fileTreeIndex++);
@ -414,7 +415,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
if (isInteractiveProgressTreeData(part)) {
partsToRender[index] = part;
} else {
const wordCountResult = this.getDataForProgressiveRender(element, part);
const wordCountResult = this.getDataForProgressiveRender(element, part, { renderedWordCount: 0, lastRenderTime: 0 });
if (wordCountResult !== undefined) {
partsToRender[index] = {
renderedWordCount: wordCountResult.actualWordCount,
@ -467,8 +468,11 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
// Avoid doing progressive rendering for multiple markdown parts simultaneously
else if (!hasRenderedOneMarkdownBlock) {
const value = wordCountResults[index].value;
result = this.renderMarkdown(new MarkdownString(value), element, disposables, templateData, true);
const { value } = wordCountResults[index];
const isPlaceholder = isPlaceholderMarkdown(currentResponseData[index]);
result = isPlaceholder
? this.renderPlaceholder(new MarkdownString(value), templateData)
: this.renderMarkdown(new MarkdownString(value), element, disposables, templateData, true);
hasRenderedOneMarkdownBlock = true;
}
@ -551,6 +555,18 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
};
}
private renderPlaceholder(markdown: IMarkdownString, templateData: IChatListItemTemplate): IMarkdownRenderResult {
const codicon = $('.interactive-response-codicon-details', undefined, renderIcon({ id: 'sync~spin' }));
codicon.classList.add('interactive-response-placeholder-codicon');
const result = dom.append(templateData.value, codicon);
const content = this.renderer.render(markdown);
content.element.className = 'interactive-response-placeholder-content';
result.appendChild(content.element);
return { element: result, dispose: () => content.dispose() };
}
private renderMarkdown(markdown: IMarkdownString, element: ChatTreeItem, disposables: DisposableStore, templateData: IChatListItemTemplate, fillInIncompleteTokens = false): IMarkdownRenderResult {
const disposablesList: IDisposable[] = [];
let codeBlockIndex = 0;
@ -561,8 +577,9 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
const toRender = usedSlashCommand ? markdown.value.slice(usedSlashCommand.command.length + 2) : markdown.value;
markdown = new MarkdownString(toRender, {
isTrusted: {
enabledCommands: ['vscode.open']
},
// Disable all other config options except isTrusted
enabledCommands: typeof markdown.isTrusted === 'object' ? markdown.isTrusted?.enabledCommands : [] ?? []
}
});
const codeblocks: IChatCodeBlockInfo[] = [];
@ -622,7 +639,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
return ref;
}
private getDataForProgressiveRender(element: IChatResponseViewModel, data: IMarkdownString, renderData: IChatResponseMarkdownRenderData = { renderedWordCount: 0, lastRenderTime: 0, isFullyRendered: false }): IWordCountResult | undefined {
private getDataForProgressiveRender(element: IChatResponseViewModel, data: IMarkdownString, renderData: Pick<IChatResponseMarkdownRenderData, 'lastRenderTime' | 'renderedWordCount'>): IWordCountResult | undefined {
const rate = this.getProgressiveRenderRate(element);
const numWordsToRender = renderData.lastRenderTime === 0 ?
1 :
@ -1189,3 +1206,7 @@ class ChatListTreeDataSource implements IAsyncDataSource<IChatResponseProgressFi
function isInteractiveProgressTreeData(item: IChatResponseProgressFileTreeData | IChatResponseMarkdownRenderData | IMarkdownString): item is IChatResponseProgressFileTreeData {
return 'label' in item;
}
function isPlaceholderMarkdown(item: IPlaceholderMarkdownString | IMarkdownString | IChatResponseProgressFileTreeData): item is IPlaceholderMarkdownString {
return 'isPlaceholder' in item;
}

View file

@ -436,10 +436,29 @@
.interactive-response-progress-tree {
margin: 16px 0px;
border-radius: 4px;
border: 1px solid var(--vscode-input-border, transparent);
}
.interactive-response-progress-tree.focused {
border-color: var(--vscode-focusBorder, transparent);
}
.interactive-item-container .value .interactive-response-placeholder-codicon .codicon {
color: var(--vscode-editorGhostText-foreground);
}
.interactive-item-container .value .interactive-response-placeholder-content {
color: var(--vscode-editorGhostText-foreground);
font-size: 12px;
}
.interactive-response .interactive-response-codicon-details {
display: flex;
align-items: start;
gap: 6px;
}
.interactive-response-progress-tree .monaco-list .monaco-scrollable-element .monaco-list-rows {
border: 1px solid var(--vscode-input-border,transparent);
border-radius: 4px;
width: auto;
}

View file

@ -5,7 +5,7 @@
import { DeferredPromise } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent';
import { IMarkdownString, MarkdownString, isMarkdownString } from 'vs/base/common/htmlContent';
import { Disposable } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
@ -23,9 +23,9 @@ export interface IChatRequestModel {
}
export interface IResponse {
readonly value: (IMarkdownString | IChatResponseProgressFileTreeData)[];
readonly value: (IMarkdownString | IPlaceholderMarkdownString | IChatResponseProgressFileTreeData)[];
onDidChangeValue: Event<void>;
updateContent(responsePart: string | { treeData: IChatResponseProgressFileTreeData } | { placeholder: string; resolvedContent?: Promise<string | { treeData: IChatResponseProgressFileTreeData }> }, quiet?: boolean): void;
updateContent(responsePart: string | IMarkdownString | { treeData: IChatResponseProgressFileTreeData } | { placeholder: string; resolvedContent?: Promise<string | IMarkdownString | { treeData: IChatResponseProgressFileTreeData }> }, quiet?: boolean): void;
asString(): string;
}
@ -88,8 +88,11 @@ export class ChatRequestModel implements IChatRequestModel {
}
}
export interface IPlaceholderMarkdownString extends IMarkdownString {
isPlaceholder: boolean;
}
type ResponsePart = { string: IMarkdownString; resolving?: boolean } | { treeData: IChatResponseProgressFileTreeData; resolving?: boolean };
type ResponsePart = { string: IMarkdownString; isPlaceholder?: boolean } | { treeData: IChatResponseProgressFileTreeData; isPlaceholder?: undefined };
export class Response implements IResponse {
private _onDidChangeValue = new Emitter<void>();
public get onDidChangeValue() {
@ -99,11 +102,11 @@ export class Response implements IResponse {
// responseParts internally tracks all the response parts, including strings which are currently resolving, so that they can be updated when they do resolve
private _responseParts: ResponsePart[];
// responseData externally presents the response parts with consolidated contiguous strings (including strings which were previously resolving)
private _responseData: (IMarkdownString | IChatResponseProgressFileTreeData)[];
private _responseData: (IMarkdownString | IPlaceholderMarkdownString | IChatResponseProgressFileTreeData)[];
// responseRepr externally presents the response parts with consolidated contiguous strings (excluding tree data)
private _responseRepr: string;
get value(): (IMarkdownString | IChatResponseProgressFileTreeData)[] {
get value(): (IMarkdownString | IPlaceholderMarkdownString | IChatResponseProgressFileTreeData)[] {
return this._responseData;
}
@ -122,32 +125,36 @@ export class Response implements IResponse {
return this._responseRepr;
}
updateContent(responsePart: string | { treeData: IChatResponseProgressFileTreeData } | { placeholder: string; resolvedContent?: Promise<string | { treeData: IChatResponseProgressFileTreeData }> }, quiet?: boolean): void {
if (typeof responsePart === 'string') {
updateContent(responsePart: string | IMarkdownString | { treeData: IChatResponseProgressFileTreeData } | { placeholder: string; resolvedContent?: Promise<string | { treeData: IChatResponseProgressFileTreeData }> }, quiet?: boolean): void {
if (typeof responsePart === 'string' || isMarkdownString(responsePart)) {
const responsePartLength = this._responseParts.length - 1;
const lastResponsePart = this._responseParts[responsePartLength];
if (lastResponsePart.resolving === true || isCompleteInteractiveProgressTreeData(lastResponsePart)) {
if (lastResponsePart.isPlaceholder === true || isCompleteInteractiveProgressTreeData(lastResponsePart)) {
// The last part is resolving or a tree data item, start a new part
this._responseParts.push({ string: new MarkdownString(responsePart) });
this._responseParts.push({ string: typeof responsePart === 'string' ? new MarkdownString(responsePart) : responsePart });
} else {
// Combine this part with the last, non-resolving string part
this._responseParts[responsePartLength] = { string: new MarkdownString(lastResponsePart.string.value + responsePart) };
if (isMarkdownString(responsePart)) {
this._responseParts[responsePartLength] = { string: new MarkdownString(lastResponsePart.string.value + responsePart.value, responsePart) };
} else {
this._responseParts[responsePartLength] = { string: new MarkdownString(lastResponsePart.string.value + responsePart, lastResponsePart.string) };
}
}
this._updateRepr(quiet);
} else if ('placeholder' in responsePart) {
// Add a new resolving part
const responsePosition = this._responseParts.push({ string: new MarkdownString(responsePart.placeholder), resolving: true }) - 1;
const responsePosition = this._responseParts.push({ string: new MarkdownString(responsePart.placeholder), isPlaceholder: true }) - 1;
this._updateRepr(quiet);
responsePart.resolvedContent?.then((content) => {
// Replace the resolving part's content with the resolved response
if (typeof content === 'string') {
this._responseParts[responsePosition] = { string: new MarkdownString(content), resolving: true };
this._responseParts[responsePosition] = { string: new MarkdownString(content), isPlaceholder: true };
this._updateRepr(quiet);
} else if (content.treeData) {
this._responseParts[responsePosition] = { treeData: content.treeData, resolving: true };
this._responseParts[responsePosition] = { treeData: content.treeData };
this._updateRepr(quiet);
}
});
@ -160,6 +167,8 @@ export class Response implements IResponse {
this._responseData = this._responseParts.map(part => {
if (isCompleteInteractiveProgressTreeData(part)) {
return part.treeData;
} else if (part.isPlaceholder) {
return { ...part.string, isPlaceholder: true };
}
return part.string;
});
@ -245,7 +254,7 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel
this._id = 'response_' + ChatResponseModel.nextId++;
}
updateContent(responsePart: string | { treeData: IChatResponseProgressFileTreeData } | { placeholder: string; resolvedContent?: Promise<string | { treeData: IChatResponseProgressFileTreeData }> }, quiet?: boolean) {
updateContent(responsePart: string | IMarkdownString | { treeData: IChatResponseProgressFileTreeData } | { placeholder: string; resolvedContent?: Promise<string | IMarkdownString | { treeData: IChatResponseProgressFileTreeData }> }, quiet?: boolean) {
this._response.updateContent(responsePart, quiet);
}

View file

@ -52,7 +52,7 @@ export interface IChatResponseProgressFileTreeData {
}
export type IChatProgress =
{ content: string } | { requestId: string } | { treeData: IChatResponseProgressFileTreeData } | { placeholder: string; resolvedContent: Promise<string | { treeData: IChatResponseProgressFileTreeData }> };
{ content: string | IMarkdownString } | { requestId: string } | { treeData: IChatResponseProgressFileTreeData } | { placeholder: string; resolvedContent: Promise<string | IMarkdownString | { treeData: IChatResponseProgressFileTreeData }> };
export interface IPersistedChatState { }
export interface IChatProvider {

View file

@ -450,7 +450,7 @@ export class ChatService extends Disposable implements IChatService {
gotProgress = true;
if ('content' in progress) {
this.trace('sendRequest', `Provider returned progress for session ${model.sessionId}, ${progress.content.length} chars`);
this.trace('sendRequest', `Provider returned progress for session ${model.sessionId}, ${typeof progress.content === 'string' ? progress.content.length : progress.content.value.length} chars`);
} else if ('placeholder' in progress) {
this.trace('sendRequest', `Provider returned placeholder for session ${model.sessionId}, ${progress.placeholder}`);
} else if (isCompleteInteractiveProgressTreeData(progress)) {

View file

@ -44,7 +44,7 @@ export class LargeFileOptimizationsWarner extends Disposable implements IEditorC
'Variable 0 will be a file name.'
]
},
"{0}: tokenization, wrapping and folding have been turned off for this large file in order to reduce memory usage and avoid freezing or crashing.",
"{0}: tokenization, wrapping, folding and sticky scroll have been turned off for this large file in order to reduce memory usage and avoid freezing or crashing.",
path.basename(model.uri.path)
);

View file

@ -15,9 +15,7 @@ const unresolvedCommentViewIcon = registerColor('commentsView.unresolvedIcon', {
const resolvedCommentBorder = registerColor('editorCommentsWidget.resolvedBorder', { dark: resolvedCommentViewIcon, light: resolvedCommentViewIcon, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('resolvedCommentBorder', 'Color of borders and arrow for resolved comments.'));
const unresolvedCommentBorder = registerColor('editorCommentsWidget.unresolvedBorder', { dark: unresolvedCommentViewIcon, light: unresolvedCommentViewIcon, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('unresolvedCommentBorder', 'Color of borders and arrow for unresolved comments.'));
export const commentThreadRangeBackground = registerColor('editorCommentsWidget.rangeBackground', { dark: transparent(unresolvedCommentBorder, .1), light: transparent(unresolvedCommentBorder, .1), hcDark: transparent(unresolvedCommentBorder, .1), hcLight: transparent(unresolvedCommentBorder, .1) }, nls.localize('commentThreadRangeBackground', 'Color of background for comment ranges.'));
export const commentThreadRangeBorder = registerColor('editorCommentsWidget.rangeBorder', { dark: transparent(unresolvedCommentBorder, .4), light: transparent(unresolvedCommentBorder, .4), hcDark: transparent(unresolvedCommentBorder, .4), hcLight: transparent(unresolvedCommentBorder, .4) }, nls.localize('commentThreadRangeBorder', 'Color of border for comment ranges.'));
export const commentThreadRangeActiveBackground = registerColor('editorCommentsWidget.rangeActiveBackground', { dark: transparent(unresolvedCommentBorder, .1), light: transparent(unresolvedCommentBorder, .1), hcDark: transparent(unresolvedCommentBorder, .1), hcLight: transparent(unresolvedCommentBorder, .1) }, nls.localize('commentThreadActiveRangeBackground', 'Color of background for currently selected or hovered comment range.'));
export const commentThreadRangeActiveBorder = registerColor('editorCommentsWidget.rangeActiveBorder', { dark: transparent(unresolvedCommentBorder, .4), light: transparent(unresolvedCommentBorder, .4), hcDark: transparent(unresolvedCommentBorder, .4), hcLight: transparent(unresolvedCommentBorder, .2) }, nls.localize('commentThreadActiveRangeBorder', 'Color of border for currently selected or hovered comment range.'));
const commentThreadStateBorderColors = new Map([
[languages.CommentThreadState.Unresolved, unresolvedCommentBorder],

View file

@ -44,7 +44,8 @@ export class CommentThreadRangeDecorator extends Disposable {
description: CommentThreadRangeDecorator.description,
isWholeLine: false,
zIndex: 20,
className: 'comment-thread-range'
className: 'comment-thread-range',
shouldFillLineOnLineBreak: true
};
this.decorationOptions = ModelDecorationOptions.createDynamic(decorationOptions);
@ -53,7 +54,8 @@ export class CommentThreadRangeDecorator extends Disposable {
description: CommentThreadRangeDecorator.description,
isWholeLine: false,
zIndex: 20,
className: 'comment-thread-range-current'
className: 'comment-thread-range-current',
shouldFillLineOnLineBreak: true
};
this.activeDecorationOptions = ModelDecorationOptions.createDynamic(activeDecorationOptions);

View file

@ -487,21 +487,12 @@ div.preview.inline .monaco-editor .comment-range-glyph {
background: var(--vscode-editorGutter-commentRangeForeground);
}
.monaco-editor .comment-thread-range,
.monaco-editor .comment-thread-range-current {
border-width: 1px;
border-style: solid;
box-sizing: border-box;
}
.monaco-editor .comment-thread-range {
background-color: var(--vscode-editorCommentsWidget-rangeBackground);
border-color: var(--vscode-editorCommentsWidget-rangeBorder);
}
.monaco-editor .comment-thread-range-current {
background-color: var(--vscode-editorCommentsWidget-rangeActiveBackground);
border-color: var(--vscode-editorCommentsWidget-rangeActiveBorder);
}
.monaco-editor .margin-view-overlays .comment-range-glyph.line-hover,

View file

@ -474,7 +474,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
id: STEP_OVER_ID,
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyCode.F10,
secondary: isWeb ? [(KeyMod.Alt | KeyCode.F10)] : undefined, // Keep Alt-F10 for web for backwards-compatibility
when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'),
handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
const contextKeyService = accessor.get(IContextKeyService);

View file

@ -803,7 +803,7 @@ export class ExtensionEditor extends EditorPane {
#scroll-to-top span.icon::before {
content: "";
display: block;
background: var(--vscode-button-foreground);
background: var(--vscode-button-secondaryForeground);
/* Chevron up icon */
webkit-mask-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjIuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxNiAxNiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTYgMTY7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbDojRkZGRkZGO30KCS5zdDF7ZmlsbDpub25lO30KPC9zdHlsZT4KPHRpdGxlPnVwY2hldnJvbjwvdGl0bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik04LDUuMWwtNy4zLDcuM0wwLDExLjZsOC04bDgsOGwtMC43LDAuN0w4LDUuMXoiLz4KPHJlY3QgY2xhc3M9InN0MSIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2Ii8+Cjwvc3ZnPgo=');
-webkit-mask-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjIuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxNiAxNiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTYgMTY7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbDojRkZGRkZGO30KCS5zdDF7ZmlsbDpub25lO30KPC9zdHlsZT4KPHRpdGxlPnVwY2hldnJvbjwvdGl0bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik04LDUuMWwtNy4zLDcuM0wwLDExLjZsOC04bDgsOGwtMC43LDAuN0w4LDUuMXoiLz4KPHJlY3QgY2xhc3M9InN0MSIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2Ii8+Cjwvc3ZnPgo=');
@ -855,12 +855,10 @@ export class ExtensionEditor extends EditorPane {
extensionPackReadme.style.maxWidth = '882px';
const extensionPack = append(extensionPackReadme, $('div', { class: 'extension-pack' }));
if (manifest.extensionPack!.length <= 3) {
if (manifest.extensionPack!.length < 3) {
extensionPackReadme.classList.add('one-row');
} else if (manifest.extensionPack!.length <= 6) {
} else if (manifest.extensionPack!.length < 5) {
extensionPackReadme.classList.add('two-rows');
} else if (manifest.extensionPack!.length <= 9) {
extensionPackReadme.classList.add('three-rows');
} else {
extensionPackReadme.classList.add('more-rows');
}

View file

@ -1421,7 +1421,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi
menu: {
id: MenuId.ExtensionContext,
group: '2_configure',
when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.has('isDefaultApplicationScopedExtension').negate()),
when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.has('isDefaultApplicationScopedExtension').negate(), ContextKeyExpr.has('isBuiltinExtension').negate()),
order: 3
},
run: async (accessor: ServicesAccessor, id: string) => {

View file

@ -1431,7 +1431,7 @@ export function getAriaLabelForExtension(extension: IExtension | null): string {
if (!extension) {
return '';
}
const publisher = extension.publisherDomain?.verified ? localize('extension.arialabel.verifiedPublihser', "Verified Publisher {0}", extension.publisherDisplayName) : localize('extension.arialabel.publihser', "Publisher {0}", extension.publisherDisplayName);
const publisher = extension.publisherDomain?.verified ? localize('extension.arialabel.verifiedPublisher', "Verified Publisher {0}", extension.publisherDisplayName) : localize('extension.arialabel.publisher', "Publisher {0}", extension.publisherDisplayName);
const deprecated = extension?.deprecationInfo ? localize('extension.arialabel.deprecated', "Deprecated") : '';
const rating = extension?.rating ? localize('extension.arialabel.rating', "Rated {0} out of 5 stars by {1} users", extension.rating.toFixed(2), extension.ratingCount) : '';
return `${extension.displayName}, ${deprecated ? `${deprecated}, ` : ''}${extension.version}, ${publisher}, ${extension.description} ${rating ? `, ${rating}` : ''}`;

View file

@ -1622,7 +1622,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
}
async toggleApplyExtensionToAllProfiles(extension: IExtension): Promise<void> {
if (!extension.local || isApplicationScopedExtension(extension.local.manifest)) {
if (!extension.local || isApplicationScopedExtension(extension.local.manifest) || extension.isBuiltin) {
return;
}
await this.extensionManagementService.toggleAppliationScope(extension.local, this.userDataProfileService.currentProfile.extensionsResource);

View file

@ -281,7 +281,7 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
const language = model.getLanguageId();
const languageName = this.languageService.getLanguageName(language);
if (importantRecommendations.size &&
this.promptRecommendedExtensionForFileType(languageName && isImportantRecommendationForLanguage && language !== PLAINTEXT_LANGUAGE_ID ? localize('languageName', "{0} language", languageName) : basename(uri), language, [...importantRecommendations])) {
this.promptRecommendedExtensionForFileType(languageName && isImportantRecommendationForLanguage && language !== PLAINTEXT_LANGUAGE_ID ? localize('languageName', "the {0} language", languageName) : basename(uri), language, [...importantRecommendations])) {
return;
}
}

View file

@ -517,10 +517,6 @@
height: 224px;
}
.extension-editor > .body > .content > .details > .readme-container > .extension-pack-readme.three-rows > .extension-pack {
height: 306px;
}
.extension-editor > .body > .content > .details > .readme-container > .extension-pack-readme.more-rows > .extension-pack {
height: 326px;
}

View file

@ -450,7 +450,7 @@ export class ExplorerView extends ViewPane implements IExplorerView {
}
return false;
},
additionalScrollHeight: ExplorerDelegate.ITEM_HEIGHT,
paddingBottom: ExplorerDelegate.ITEM_HEIGHT,
overrideStyles: {
listBackground: SIDE_BAR_BACKGROUND
}

View file

@ -1391,11 +1391,11 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
const resourceEdit = new ResourceFileEdit(resource, newResource, { copy: true, overwrite: allowOverwrite });
resourceFileEdits.push(resourceEdit);
}
const labelSufix = getFileOrFolderLabelSufix(sources);
const labelSuffix = getFileOrFolderLabelSuffix(sources);
await this.explorerService.applyBulkEdit(resourceFileEdits, {
confirmBeforeUndo: explorerConfig.confirmUndo === UndoConfirmLevel.Default || explorerConfig.confirmUndo === UndoConfirmLevel.Verbose,
undoLabel: localize('copy', "Copy {0}", labelSufix),
progressLabel: localize('copying', "Copying {0}", labelSufix),
undoLabel: localize('copy', "Copy {0}", labelSuffix),
progressLabel: localize('copying', "Copying {0}", labelSuffix),
});
const editors = resourceFileEdits.filter(edit => {
@ -1410,11 +1410,11 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
// Do not allow moving readonly items
const resourceFileEdits = sources.filter(source => !source.isReadonly).map(source => new ResourceFileEdit(source.resource, joinPath(target.resource, source.name)));
const labelSufix = getFileOrFolderLabelSufix(sources);
const labelSuffix = getFileOrFolderLabelSuffix(sources);
const options = {
confirmBeforeUndo: this.configurationService.getValue<IFilesConfiguration>().explorer.confirmUndo === UndoConfirmLevel.Verbose,
undoLabel: localize('move', "Move {0}", labelSufix),
progressLabel: localize('moving', "Moving {0}", labelSufix)
undoLabel: localize('move', "Move {0}", labelSuffix),
progressLabel: localize('moving', "Moving {0}", labelSuffix)
};
try {
@ -1518,7 +1518,7 @@ export class ExplorerCompressionDelegate implements ITreeCompressionDelegate<Exp
}
}
function getFileOrFolderLabelSufix(items: ExplorerItem[]): string {
function getFileOrFolderLabelSuffix(items: ExplorerItem[]): string {
if (items.length === 1) {
return items[0].name;
}

View file

@ -208,6 +208,7 @@ export class NotebookCellOutline implements IOutline<OutlineEntry> {
}
private _outlineProvider: NotebookCellOutlineProvider | undefined;
private _localDisposables = new DisposableStore();
constructor(
private readonly _editor: INotebookEditorPane,
@ -221,9 +222,14 @@ export class NotebookCellOutline implements IOutline<OutlineEntry> {
if (!notebookEditor?.hasModel()) {
this._outlineProvider?.dispose();
this._outlineProvider = undefined;
this._localDisposables.clear();
} else {
this._outlineProvider?.dispose();
this._localDisposables.clear();
this._outlineProvider = instantiationService.createInstance(NotebookCellOutlineProvider, notebookEditor, _target);
this._localDisposables.add(this._outlineProvider.onDidChange(e => {
this._onDidChange.fire(e);
}));
}
};
@ -231,8 +237,6 @@ export class NotebookCellOutline implements IOutline<OutlineEntry> {
installSelectionListener();
}));
installSelectionListener();
const treeDataSource: IDataSource<this, OutlineEntry> = { getChildren: parent => parent instanceof NotebookCellOutline ? (this._outlineProvider?.entries ?? []) : parent.children };
const delegate = new NotebookOutlineVirtualDelegate();
@ -315,6 +319,7 @@ export class NotebookCellOutline implements IOutline<OutlineEntry> {
this._dispoables.dispose();
this._entriesDisposables.dispose();
this._outlineProvider?.dispose();
this._localDisposables.dispose();
}
}

Some files were not shown because too many files have changed in this diff Show more