mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
Merge branch 'main' into tyriar/finalize_179476
This commit is contained in:
commit
8d76882313
|
@ -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 .`.
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
14
SECURITY.md
14
SECURITY.md
|
@ -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.
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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! {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
```
|
||||
|
|
|
@ -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);
|
||||
```
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ This extension provides basic preview for images, audio and video files.
|
|||
- `.webp`
|
||||
- `.avif`
|
||||
|
||||
|
||||
### Supported audio formats
|
||||
|
||||
- `.mp3`
|
||||
|
|
|
@ -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`.
|
||||
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
"meta.embedded",
|
||||
"source.groovy.embedded",
|
||||
"string meta.image.inline.markdown",
|
||||
"variable.legacy.builtin.python"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#D4D4D4"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -71,7 +71,8 @@
|
|||
{
|
||||
"scope": [
|
||||
"meta.embedded",
|
||||
"source.groovy.embedded"
|
||||
"source.groovy.embedded",
|
||||
"variable.legacy.builtin.python"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#C5C8C6"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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`
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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))),
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -45,14 +45,12 @@
|
|||
"textSearchProvider",
|
||||
"timeline",
|
||||
"tokenInformation",
|
||||
"treeItemCheckbox",
|
||||
"treeViewActiveItem",
|
||||
"treeViewActiveItem",
|
||||
"treeViewReveal",
|
||||
"workspaceTrust",
|
||||
"telemetry",
|
||||
"windowActivity",
|
||||
"interactiveUserActions",
|
||||
"envCollectionWorkspace"
|
||||
"interactiveUserActions"
|
||||
],
|
||||
"private": true,
|
||||
"activationEvents": [],
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "code-oss-dev",
|
||||
"version": "1.82.0",
|
||||
"distro": "1591281180fd2cd18935e6847131d2d4213b7b69",
|
||||
"distro": "56bfb9ce4a8d2298f475a1b8d9c7a7b5a72204f2",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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],
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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,
|
||||
]);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
})));
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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`;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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}`);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)!;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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?.();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 */
|
||||
}
|
||||
|
|
|
@ -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
|
||||
},
|
||||
|
|
|
@ -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 }, {
|
||||
|
|
|
@ -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
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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}` : ''}`;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue