Merge remote-tracking branch 'origin/main' into merogge/notebook-verbosity

This commit is contained in:
rebornix 2023-05-26 14:58:12 -07:00
commit 39f618bb38
No known key found for this signature in database
GPG key ID: 181FC90D15393C20
147 changed files with 4241 additions and 745 deletions

View file

@ -0,0 +1,67 @@
# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2
# Reference: https://github.com/microsoft/vscode/wiki/How-to-Contribute
properties:
resources:
- resource: Microsoft.WinGet.DSC/WinGetPackage
directives:
description: Install Git
allowPrerelease: true
settings:
id: Git.Git
source: winget
- resource: Microsoft.WinGet.DSC/WinGetPackage
id: npm
directives:
description: Install NodeJS version >=16.17.x and <17
allowPrerelease: true
settings:
id: OpenJS.NodeJS.LTS
version: "16.20.0"
source: winget
- resource: NpmDsc/NpmPackage
id: yarn
dependsOn:
- npm
directives:
description: Install Yarn
allowPrerelease: true
settings:
Name: 'yarn'
Global: true
PackageDirectory: '${WinGetConfigRoot}\..\'
- resource: Microsoft.WinGet.DSC/WinGetPackage
directives:
description: Install Python 3.10
allowPrerelease: true
settings:
id: Python.Python.3.10
source: winget
- resource: Microsoft.WinGet.DSC/WinGetPackage
id: vsPackage
directives:
description: Install Visual Studio 2022 (any edition is OK)
allowPrerelease: true
settings:
id: Microsoft.VisualStudio.2022.BuildTools
source: winget
- resource: Microsoft.VisualStudio.DSC/VSComponents
dependsOn:
- vsPackage
directives:
description: Install required VS workloads
allowPrerelease: true
settings:
productId: Microsoft.VisualStudio.Product.BuildTools
channelId: VisualStudio.17.Release
includeRecommended: true
components:
- Microsoft.VisualStudio.Workload.VCTools
- resource: YarnDsc/YarnInstall
dependsOn:
- npm
directives:
description: Install dependencies
allowPrerelease: true
settings:
PackageDirectory: '${WinGetConfigRoot}\..\'
configurationVersion: 0.2.0

View file

@ -1303,7 +1303,7 @@ SOFTWARE.
---------------------------------------------------------
jlelong/vscode-latex-basics 1.5.1 - MIT
jlelong/vscode-latex-basics 1.5.2 - MIT
https://github.com/jlelong/vscode-latex-basics
Copyright (c) vscode-latex-basics authors
@ -1654,8 +1654,8 @@ THE SOFTWARE.
---------------------------------------------------------
language-php 0.48.1 - MIT
https://github.com/atom/language-php
language-php 0.49.0 - MIT
https://github.com/KapitanOczywisty/language-php
The MIT License (MIT)
@ -2184,7 +2184,7 @@ THE SOFTWARE.
---------------------------------------------------------
microsoft/vscode-css 0.45.1 - MIT License
microsoft/vscode-css 0.0.0 - MIT License
https://github.com/microsoft/vscode-css
MIT License
@ -2748,7 +2748,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SO
---------------------------------------------------------
trond-snekvik/vscode-rst 1.5.1 - MIT
trond-snekvik/vscode-rst 1.5.2 - MIT
https://github.com/trond-snekvik/vscode-rst
The MIT License (MIT)

View file

@ -290,6 +290,10 @@
"name": "vs/workbench/contrib/welcomeWalkthrough",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/contrib/welcomeDialog",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/contrib/outline",
"project": "vscode-workbench"

View file

@ -51,6 +51,8 @@
"--vscode-commandCenter-foreground",
"--vscode-commandCenter-inactiveBorder",
"--vscode-commandCenter-inactiveForeground",
"--vscode-commentsView-resolvedIcon",
"--vscode-commentsView-unresolvedIcon",
"--vscode-contrastActiveBorder",
"--vscode-contrastBorder",
"--vscode-debugConsole-errorForeground",
@ -97,6 +99,7 @@
"--vscode-diffEditor-removedLineBackground",
"--vscode-diffEditor-removedTextBackground",
"--vscode-diffEditor-removedTextBorder",
"--vscode-diffEditor-unchangedRegionBackground",
"--vscode-diffEditorGutter-insertedLineBackground",
"--vscode-diffEditorGutter-removedLineBackground",
"--vscode-diffEditorOverview-insertedForeground",
@ -233,6 +236,8 @@
"--vscode-editorOverviewRuler-background",
"--vscode-editorOverviewRuler-border",
"--vscode-editorOverviewRuler-bracketMatchForeground",
"--vscode-editorOverviewRuler-commentForeground",
"--vscode-editorOverviewRuler-commentUnresolvedForeground",
"--vscode-editorOverviewRuler-commonContentForeground",
"--vscode-editorOverviewRuler-currentContentForeground",
"--vscode-editorOverviewRuler-deletedForeground",
@ -309,6 +314,8 @@
"--vscode-interactiveEditor-border",
"--vscode-interactiveEditor-regionHighlight",
"--vscode-interactiveEditor-shadow",
"--vscode-interactiveEditorDiff-inserted",
"--vscode-interactiveEditorDiff-removed",
"--vscode-interactiveEditorInput-background",
"--vscode-interactiveEditorInput-border",
"--vscode-interactiveEditorInput-focusBorder",
@ -457,7 +464,6 @@
"--vscode-problemsErrorIcon-foreground",
"--vscode-problemsInfoIcon-foreground",
"--vscode-problemsWarningIcon-foreground",
"--vscode-problemsSuccessIcon-foreground",
"--vscode-profileBadge-background",
"--vscode-profileBadge-foreground",
"--vscode-progressBar-background",
@ -520,6 +526,8 @@
"--vscode-statusBar-noFolderBackground",
"--vscode-statusBar-noFolderBorder",
"--vscode-statusBar-noFolderForeground",
"--vscode-statusBar-offlineBackground",
"--vscode-statusBar-offlineForeground",
"--vscode-statusBarItem-activeBackground",
"--vscode-statusBarItem-compactHoverBackground",
"--vscode-statusBarItem-errorBackground",
@ -733,4 +741,4 @@
"--z-index-run-button-container",
"--zoom-factor"
]
}
}

View file

@ -342,5 +342,55 @@
{ // License is MIT/Apache and tool doesn't look in subfolders
"name": "dirs-sys-next",
"fullLicenseTextUri": "https://raw.githubusercontent.com/xdg-rs/dirs/master/dirs-sys/LICENSE-MIT"
},
{
"name": "https-proxy-agent",
"fullLicenseText": [
"(The MIT License)",
"Copyright (c) 2013 Nathan Rajlich <nathan@tootallnate.net>",
"Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:",
"The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.",
"THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
]
},
{
"name": "data-uri-to-buffer",
"fullLicenseText": [
"(The MIT License)",
"Copyright (c) 2014 Nathan Rajlich <nathan@tootallnate.net>",
"Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:",
"The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.",
"THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
]
},
{
"name": "socks-proxy-agent",
"fullLicenseText": [
"(The MIT License)",
"Copyright (c) 2013 Nathan Rajlich <nathan@tootallnate.net>",
"Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:",
"The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.",
"THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
]
},
{
"name": "http-proxy-agent",
"fullLicenseText": [
"(The MIT License)",
"Copyright (c) 2013 Nathan Rajlich <nathan@tootallnate.net>",
"Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:",
"The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.",
"THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
]
},
{
"name": "agent-base",
"fullLicenseText": [
"(The MIT License)",
"Copyright (c) 2013 Nathan Rajlich <nathan@tootallnate.net>",
"Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:",
"The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.",
"THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
]
}
]

View file

@ -35,7 +35,7 @@ use crate::{
},
util::{
app_lock::AppMutex,
errors::{wrap, AnyError},
errors::{wrap, AnyError, CodeError},
prereqs::PreReqChecker,
},
};
@ -247,17 +247,25 @@ pub async fn kill(ctx: CommandContext) -> Result<i32, AnyError> {
}
pub async fn status(ctx: CommandContext) -> Result<i32, AnyError> {
let status: protocol::singleton::Status = do_single_rpc_call(
let status = do_single_rpc_call::<_, protocol::singleton::Status>(
&ctx.paths.tunnel_lockfile(),
ctx.log.clone(),
protocol::singleton::METHOD_STATUS,
protocol::EmptyObject {},
)
.await?;
.await;
ctx.log.result(serde_json::to_string(&status).unwrap());
Ok(0)
match status {
Err(CodeError::NoRunningTunnel) => {
ctx.log.result(CodeError::NoRunningTunnel.to_string());
Ok(1)
}
Err(e) => Err(e.into()),
Ok(s) => {
ctx.log.result(serde_json::to_string(&s).unwrap());
Ok(0)
}
}
}
/// Removes unused servers.

View file

@ -273,30 +273,21 @@ impl<S: Serialization, C: Send + Sync + 'static> RpcMethodBuilder<S, C> {
/// Builds into a usable, sync rpc dispatcher.
pub fn build(mut self, log: log::Logger) -> RpcDispatcher<S, C> {
let streams: Arc<tokio::sync::Mutex<HashMap<u32, WriteHalf<DuplexStream>>>> =
Arc::new(tokio::sync::Mutex::new(HashMap::new()));
let streams = Streams::default();
let s1 = streams.clone();
self.register_async(METHOD_STREAM_ENDED, move |m: StreamEndedParams, _| {
let s1 = s1.clone();
async move {
if let Some(mut s) = s1.lock().await.remove(&m.stream) {
let _ = s.shutdown().await;
}
s1.remove(m.stream).await;
Ok(())
}
});
let s2 = streams.clone();
self.register_async(METHOD_STREAM_DATA, move |m: StreamDataIncomingParams, _| {
let s2 = s2.clone();
async move {
let mut lock = s2.lock().await;
if let Some(stream) = lock.get_mut(&m.stream) {
let _ = stream.write_all(&m.segment).await;
}
Ok(())
}
self.register_sync(METHOD_STREAM_DATA, move |m: StreamDataIncomingParams, _| {
s2.write(m.stream, m.segment);
Ok(())
});
RpcDispatcher {
@ -400,7 +391,7 @@ pub struct RpcDispatcher<S, C> {
serializer: Arc<S>,
methods: Arc<HashMap<&'static str, Method>>,
calls: Arc<Mutex<HashMap<u32, DispatchMethod>>>,
streams: Arc<tokio::sync::Mutex<HashMap<u32, WriteHalf<DuplexStream>>>>,
streams: Streams,
}
static MESSAGE_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
@ -483,10 +474,9 @@ impl<S: Serialization, C: Send + Sync> RpcDispatcher<S, C> {
return;
}
let mut streams_map = self.streams.lock().await;
for (stream_id, duplex) in dto.streams {
let (mut read, write) = tokio::io::split(duplex);
streams_map.insert(stream_id, write);
self.streams.insert(stream_id, write);
let write_tx = write_tx.clone();
let serial = self.serializer.clone();
@ -538,6 +528,90 @@ impl<S: Serialization, C: Send + Sync> RpcDispatcher<S, C> {
}
}
struct StreamRec {
write: Option<WriteHalf<DuplexStream>>,
q: Vec<Vec<u8>>,
}
#[derive(Clone, Default)]
struct Streams {
map: Arc<std::sync::Mutex<HashMap<u32, StreamRec>>>,
}
impl Streams {
pub async fn remove(&self, id: u32) {
let stream = self.map.lock().unwrap().remove(&id);
if let Some(s) = stream {
// if there's no 'write' right now, it'll shut down in the write_loop
if let Some(mut w) = s.write {
let _ = w.shutdown().await;
}
}
}
pub fn write(&self, id: u32, buf: Vec<u8>) {
let mut map = self.map.lock().unwrap();
if let Some(s) = map.get_mut(&id) {
s.q.push(buf);
if let Some(w) = s.write.take() {
tokio::spawn(write_loop(id, w, self.map.clone()));
}
}
}
pub fn insert(&self, id: u32, stream: WriteHalf<DuplexStream>) {
self.map.lock().unwrap().insert(
id,
StreamRec {
write: Some(stream),
q: Vec::new(),
},
);
}
}
/// Write loop started by `Streams.write`. It takes the WriteHalf, and
/// runs until there's no more items in the 'write queue'. At that point, if the
/// record still exists in the `streams` (i.e. we haven't shut down), it'll
/// return the WriteHalf so that the next `write` call starts
/// the loop again. Otherwise, it'll shut down the WriteHalf.
///
/// This is the equivalent of the same write_loop in the server_multiplexer.
/// I couldn't figure out a nice way to abstract it without introducing
/// performance overhead...
async fn write_loop(
id: u32,
mut w: WriteHalf<DuplexStream>,
streams: Arc<std::sync::Mutex<HashMap<u32, StreamRec>>>,
) {
let mut items_vec = vec![];
loop {
{
let mut lock = streams.lock().unwrap();
let stream_rec = match lock.get_mut(&id) {
Some(b) => b,
None => break,
};
if stream_rec.q.is_empty() {
stream_rec.write = Some(w);
return;
}
std::mem::swap(&mut stream_rec.q, &mut items_vec);
}
for item in items_vec.drain(..) {
if w.write_all(&item).await.is_err() {
break;
}
}
}
let _ = w.shutdown().await; // got here from `break` above, meaning our record got cleared. Close the bridge if so
}
const METHOD_STREAMS_STARTED: &str = "streams_started";
const METHOD_STREAM_DATA: &str = "stream_data";
const METHOD_STREAM_ENDED: &str = "stream_ended";

View file

@ -611,6 +611,7 @@ async fn handle_serve(
) -> Result<EmptyObject, AnyError> {
// fill params.extensions into code_server_args.install_extensions
let mut csa = c.code_server_args.clone();
csa.connection_token = params.connection_token;
csa.install_extensions.extend(params.extensions.into_iter());
let params_raw = ServerParamsRaw {
@ -1034,8 +1035,7 @@ async fn handle_spawn_cli(
// CLI args to spawn a server; contracted with clients that they should _not_ provide these.
p.arg("--verbose");
p.arg("tunnel");
p.arg("stdio");
p.arg("command-shell");
p.envs(&params.env);
p.stdin(Stdio::piped());

View file

@ -105,7 +105,7 @@ impl ServerMultiplexer {
}
}
/// Write loop started by `handle_server_message`. It take sthe ServerBridge, and
/// Write loop started by `handle_server_message`. It takes the ServerBridge, and
/// runs until there's no more items in the 'write queue'. At that point, if the
/// record still exists in the bridges_lock (i.e. we haven't shut down), it'll
/// return the ServerBridge so that the next handle_server_message call starts

View file

@ -4,11 +4,9 @@
*--------------------------------------------------------------------------------------------*/
use async_trait::async_trait;
use std::{marker::PhantomData, sync::Arc};
use tokio::{
sync::{
broadcast, mpsc,
watch::{self, error::RecvError},
},
use tokio::sync::{
broadcast, mpsc,
watch::{self, error::RecvError},
};
#[derive(Clone)]

View file

@ -994,7 +994,7 @@
]
},
"dependencies": {
"vscode-languageclient": "^8.2.0-next.0",
"vscode-languageclient": "^8.2.0-next.1",
"vscode-uri": "^3.0.7"
},
"devDependencies": {

View file

@ -10,9 +10,9 @@
"main": "./out/node/cssServerMain",
"browser": "./dist/browser/cssServerMain",
"dependencies": {
"@vscode/l10n": "^0.0.13",
"vscode-css-languageservice": "^6.2.5",
"vscode-languageserver": "^8.2.0-next.0",
"@vscode/l10n": "^0.0.14",
"vscode-css-languageservice": "^6.2.6",
"vscode-languageserver": "^8.2.0-next.1",
"vscode-uri": "^3.0.7"
},
"devDependencies": {

View file

@ -12,17 +12,17 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae"
integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==
"@vscode/l10n@^0.0.13":
version "0.0.13"
resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.13.tgz#f51ff130b8c98f189476c5f812d214b8efb09590"
integrity sha512-A3uY356uOU9nGa+TQIT/i3ziWUgJjVMUrGGXSrtRiTwklyCFjGVWIOHoEIHbJpiyhDkJd9kvIWUOfXK1IkK8XQ==
"@vscode/l10n@^0.0.14":
version "0.0.14"
resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.14.tgz#431e5814c35c3cb11ee21873bc70a4b0fbf90fcf"
integrity sha512-/yrv59IEnmh655z1oeDnGcvMYwnEzNzHLgeYcQCkhYX0xBvYWrAuefoiLcPBUkMpJsb46bqQ6Yv4pwTTQ4d3Qg==
vscode-css-languageservice@^6.2.5:
version "6.2.5"
resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-6.2.5.tgz#bcea53dac7b61c1ff483668a7403042779ab27b0"
integrity sha512-/1oyBZK3jfx6A0cA46FCUpy6OlqEsMT47LUIldCIP1YMKRYezJ9No+aNj9IM0AqhRZ92DxZ1DmU5lJ+biuiacA==
vscode-css-languageservice@^6.2.6:
version "6.2.6"
resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-6.2.6.tgz#bc26c2abaaa2eb117b143fdb9387ee1701d9661a"
integrity sha512-SA2WkeOecIpUiEbZnjOsP/fI5CRITZEiQGSHXKiDQDwLApfKcnLhZwMtOBbIifSzESVcQa7b/shX/nbnF4NoCg==
dependencies:
"@vscode/l10n" "^0.0.13"
"@vscode/l10n" "^0.0.14"
vscode-languageserver-textdocument "^1.0.8"
vscode-languageserver-types "^3.17.3"
vscode-uri "^3.0.7"
@ -32,10 +32,10 @@ vscode-jsonrpc@8.2.0-next.0:
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0-next.0.tgz#41409413c8cebf10f2f1b7cc87e330f0e292814c"
integrity sha512-13jYzaFQpTz5qQ2P+l5c/iTVsj1wUpflP0CR/v4XaEpM0oToLEXZBTcuuox1WaGIbu3Av3xxmGNU4Hydl1iNKg==
vscode-languageserver-protocol@3.17.4-next.0:
version "3.17.4-next.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.4-next.0.tgz#21e4dce218c1cf0ee65ea7fdb62a2c08e075ee8b"
integrity sha512-l//t/BY+GHkH9N0VrHN0zLB+KV42LD0EDtzjGL+p/6xqUVEZegbsZg+6ubvqjE8LhyWcTtpA6pLRaczua6+3GQ==
vscode-languageserver-protocol@3.17.4-next.1:
version "3.17.4-next.1"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.4-next.1.tgz#a15480e1bc663853ae90ded226efafc5ab333616"
integrity sha512-qrK4BycgPR/+nkRN9PRVTblkLp+kUPUmAgF6rDhFzZIPXW4/MqWwFUT8uswIMGdlTPPgCEkFO/AYEZK1fDXODg==
dependencies:
vscode-jsonrpc "8.2.0-next.0"
vscode-languageserver-types "3.17.4-next.0"
@ -55,12 +55,12 @@ vscode-languageserver-types@^3.17.3:
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz#72d05e47b73be93acb84d6e311b5786390f13f64"
integrity sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==
vscode-languageserver@^8.2.0-next.0:
version "8.2.0-next.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.2.0-next.0.tgz#ad9bcef650e0bae9d32591ebbda3eb5e548418a7"
integrity sha512-Pw1pdR+hZvaeeVvbmEscUJG0SMMa5//iV+OnFuv4cPPvk+ohe4mtrVAcI06Z9HIMxDu+2McqY1b8JbVvyRKFtg==
vscode-languageserver@^8.2.0-next.1:
version "8.2.0-next.1"
resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.2.0-next.1.tgz#ad2558d74392b1cfaccd427febe9a368fc328f8b"
integrity sha512-994AXMKBijzjlnpf8p9M+ntsNJDjR8pr55NJPYxKjy/nUhVkg962dAomelH6Z94401kBZmSbfP/K/20cB54aFA==
dependencies:
vscode-languageserver-protocol "3.17.4-next.0"
vscode-languageserver-protocol "3.17.4-next.1"
vscode-uri@^3.0.7:
version "3.0.7"

View file

@ -45,19 +45,19 @@ vscode-jsonrpc@8.2.0-next.0:
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0-next.0.tgz#41409413c8cebf10f2f1b7cc87e330f0e292814c"
integrity sha512-13jYzaFQpTz5qQ2P+l5c/iTVsj1wUpflP0CR/v4XaEpM0oToLEXZBTcuuox1WaGIbu3Av3xxmGNU4Hydl1iNKg==
vscode-languageclient@^8.2.0-next.0:
version "8.2.0-next.0"
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-8.2.0-next.0.tgz#7415c831c4c1e38cc08e710e744c36c5d2251bd8"
integrity sha512-Gqr47Up5VDuRT8JrfB0QFGXR9ngQDkfIJfbT0xmM4OIsISqH3uUgXHTAhekRtS4N6aER3UvGXUrrRWQUaBGYHA==
vscode-languageclient@^8.2.0-next.1:
version "8.2.0-next.1"
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-8.2.0-next.1.tgz#a3f98b80cfa3225fde0583aa6a5c9b20219fa37e"
integrity sha512-oITaqHQ10PM3zXCUu/104wriMeDutXMkQXMaRBWh1jKihcNcUBLC/os7RhqiVGypY0nl+F0pwStAf4Koc8inaw==
dependencies:
minimatch "^5.1.0"
semver "^7.3.7"
vscode-languageserver-protocol "3.17.4-next.0"
vscode-languageserver-protocol "3.17.4-next.1"
vscode-languageserver-protocol@3.17.4-next.0:
version "3.17.4-next.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.4-next.0.tgz#21e4dce218c1cf0ee65ea7fdb62a2c08e075ee8b"
integrity sha512-l//t/BY+GHkH9N0VrHN0zLB+KV42LD0EDtzjGL+p/6xqUVEZegbsZg+6ubvqjE8LhyWcTtpA6pLRaczua6+3GQ==
vscode-languageserver-protocol@3.17.4-next.1:
version "3.17.4-next.1"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.4-next.1.tgz#a15480e1bc663853ae90ded226efafc5ab333616"
integrity sha512-qrK4BycgPR/+nkRN9PRVTblkLp+kUPUmAgF6rDhFzZIPXW4/MqWwFUT8uswIMGdlTPPgCEkFO/AYEZK1fDXODg==
dependencies:
vscode-jsonrpc "8.2.0-next.0"
vscode-languageserver-types "3.17.4-next.0"

View file

@ -259,7 +259,7 @@
},
"dependencies": {
"@vscode/extension-telemetry": "^0.7.5",
"vscode-languageclient": "^8.2.0-next.0",
"vscode-languageclient": "^8.2.0-next.1",
"vscode-uri": "^3.0.7"
},
"devDependencies": {

View file

@ -9,10 +9,10 @@
},
"main": "./out/node/htmlServerMain",
"dependencies": {
"@vscode/l10n": "^0.0.13",
"vscode-css-languageservice": "^6.2.5",
"@vscode/l10n": "^0.0.14",
"vscode-css-languageservice": "^6.2.6",
"vscode-html-languageservice": "^5.0.5",
"vscode-languageserver": "^8.2.0-next.0",
"vscode-languageserver": "^8.2.0-next.1",
"vscode-languageserver-textdocument": "^1.0.8",
"vscode-uri": "^3.0.7"
},

View file

@ -17,12 +17,17 @@
resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.13.tgz#f51ff130b8c98f189476c5f812d214b8efb09590"
integrity sha512-A3uY356uOU9nGa+TQIT/i3ziWUgJjVMUrGGXSrtRiTwklyCFjGVWIOHoEIHbJpiyhDkJd9kvIWUOfXK1IkK8XQ==
vscode-css-languageservice@^6.2.5:
version "6.2.5"
resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-6.2.5.tgz#bcea53dac7b61c1ff483668a7403042779ab27b0"
integrity sha512-/1oyBZK3jfx6A0cA46FCUpy6OlqEsMT47LUIldCIP1YMKRYezJ9No+aNj9IM0AqhRZ92DxZ1DmU5lJ+biuiacA==
"@vscode/l10n@^0.0.14":
version "0.0.14"
resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.14.tgz#431e5814c35c3cb11ee21873bc70a4b0fbf90fcf"
integrity sha512-/yrv59IEnmh655z1oeDnGcvMYwnEzNzHLgeYcQCkhYX0xBvYWrAuefoiLcPBUkMpJsb46bqQ6Yv4pwTTQ4d3Qg==
vscode-css-languageservice@^6.2.6:
version "6.2.6"
resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-6.2.6.tgz#bc26c2abaaa2eb117b143fdb9387ee1701d9661a"
integrity sha512-SA2WkeOecIpUiEbZnjOsP/fI5CRITZEiQGSHXKiDQDwLApfKcnLhZwMtOBbIifSzESVcQa7b/shX/nbnF4NoCg==
dependencies:
"@vscode/l10n" "^0.0.13"
"@vscode/l10n" "^0.0.14"
vscode-languageserver-textdocument "^1.0.8"
vscode-languageserver-types "^3.17.3"
vscode-uri "^3.0.7"
@ -42,10 +47,10 @@ vscode-jsonrpc@8.2.0-next.0:
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0-next.0.tgz#41409413c8cebf10f2f1b7cc87e330f0e292814c"
integrity sha512-13jYzaFQpTz5qQ2P+l5c/iTVsj1wUpflP0CR/v4XaEpM0oToLEXZBTcuuox1WaGIbu3Av3xxmGNU4Hydl1iNKg==
vscode-languageserver-protocol@3.17.4-next.0:
version "3.17.4-next.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.4-next.0.tgz#21e4dce218c1cf0ee65ea7fdb62a2c08e075ee8b"
integrity sha512-l//t/BY+GHkH9N0VrHN0zLB+KV42LD0EDtzjGL+p/6xqUVEZegbsZg+6ubvqjE8LhyWcTtpA6pLRaczua6+3GQ==
vscode-languageserver-protocol@3.17.4-next.1:
version "3.17.4-next.1"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.4-next.1.tgz#a15480e1bc663853ae90ded226efafc5ab333616"
integrity sha512-qrK4BycgPR/+nkRN9PRVTblkLp+kUPUmAgF6rDhFzZIPXW4/MqWwFUT8uswIMGdlTPPgCEkFO/AYEZK1fDXODg==
dependencies:
vscode-jsonrpc "8.2.0-next.0"
vscode-languageserver-types "3.17.4-next.0"
@ -65,12 +70,12 @@ vscode-languageserver-types@^3.17.3:
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz#72d05e47b73be93acb84d6e311b5786390f13f64"
integrity sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==
vscode-languageserver@^8.2.0-next.0:
version "8.2.0-next.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.2.0-next.0.tgz#ad9bcef650e0bae9d32591ebbda3eb5e548418a7"
integrity sha512-Pw1pdR+hZvaeeVvbmEscUJG0SMMa5//iV+OnFuv4cPPvk+ohe4mtrVAcI06Z9HIMxDu+2McqY1b8JbVvyRKFtg==
vscode-languageserver@^8.2.0-next.1:
version "8.2.0-next.1"
resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.2.0-next.1.tgz#ad2558d74392b1cfaccd427febe9a368fc328f8b"
integrity sha512-994AXMKBijzjlnpf8p9M+ntsNJDjR8pr55NJPYxKjy/nUhVkg962dAomelH6Z94401kBZmSbfP/K/20cB54aFA==
dependencies:
vscode-languageserver-protocol "3.17.4-next.0"
vscode-languageserver-protocol "3.17.4-next.1"
vscode-uri@^3.0.7:
version "3.0.7"

View file

@ -385,19 +385,19 @@ vscode-jsonrpc@8.2.0-next.0:
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0-next.0.tgz#41409413c8cebf10f2f1b7cc87e330f0e292814c"
integrity sha512-13jYzaFQpTz5qQ2P+l5c/iTVsj1wUpflP0CR/v4XaEpM0oToLEXZBTcuuox1WaGIbu3Av3xxmGNU4Hydl1iNKg==
vscode-languageclient@^8.2.0-next.0:
version "8.2.0-next.0"
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-8.2.0-next.0.tgz#7415c831c4c1e38cc08e710e744c36c5d2251bd8"
integrity sha512-Gqr47Up5VDuRT8JrfB0QFGXR9ngQDkfIJfbT0xmM4OIsISqH3uUgXHTAhekRtS4N6aER3UvGXUrrRWQUaBGYHA==
vscode-languageclient@^8.2.0-next.1:
version "8.2.0-next.1"
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-8.2.0-next.1.tgz#a3f98b80cfa3225fde0583aa6a5c9b20219fa37e"
integrity sha512-oITaqHQ10PM3zXCUu/104wriMeDutXMkQXMaRBWh1jKihcNcUBLC/os7RhqiVGypY0nl+F0pwStAf4Koc8inaw==
dependencies:
minimatch "^5.1.0"
semver "^7.3.7"
vscode-languageserver-protocol "3.17.4-next.0"
vscode-languageserver-protocol "3.17.4-next.1"
vscode-languageserver-protocol@3.17.4-next.0:
version "3.17.4-next.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.4-next.0.tgz#21e4dce218c1cf0ee65ea7fdb62a2c08e075ee8b"
integrity sha512-l//t/BY+GHkH9N0VrHN0zLB+KV42LD0EDtzjGL+p/6xqUVEZegbsZg+6ubvqjE8LhyWcTtpA6pLRaczua6+3GQ==
vscode-languageserver-protocol@3.17.4-next.1:
version "3.17.4-next.1"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.4-next.1.tgz#a15480e1bc663853ae90ded226efafc5ab333616"
integrity sha512-qrK4BycgPR/+nkRN9PRVTblkLp+kUPUmAgF6rDhFzZIPXW4/MqWwFUT8uswIMGdlTPPgCEkFO/AYEZK1fDXODg==
dependencies:
vscode-jsonrpc "8.2.0-next.0"
vscode-languageserver-types "3.17.4-next.0"

View file

@ -160,7 +160,7 @@
"dependencies": {
"@vscode/extension-telemetry": "^0.7.5",
"request-light": "^0.7.0",
"vscode-languageclient": "^8.2.0-next.0"
"vscode-languageclient": "^8.2.0-next.1"
},
"devDependencies": {
"@types/node": "16.x"

View file

@ -12,11 +12,11 @@
},
"main": "./out/node/jsonServerMain",
"dependencies": {
"@vscode/l10n": "^0.0.13",
"@vscode/l10n": "^0.0.14",
"jsonc-parser": "^3.2.0",
"request-light": "^0.7.0",
"vscode-json-languageservice": "^5.3.5",
"vscode-languageserver": "^8.2.0-next.0",
"vscode-languageserver": "^8.2.0-next.1",
"vscode-uri": "^3.0.7"
},
"devDependencies": {

View file

@ -17,6 +17,11 @@
resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.13.tgz#f51ff130b8c98f189476c5f812d214b8efb09590"
integrity sha512-A3uY356uOU9nGa+TQIT/i3ziWUgJjVMUrGGXSrtRiTwklyCFjGVWIOHoEIHbJpiyhDkJd9kvIWUOfXK1IkK8XQ==
"@vscode/l10n@^0.0.14":
version "0.0.14"
resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.14.tgz#431e5814c35c3cb11ee21873bc70a4b0fbf90fcf"
integrity sha512-/yrv59IEnmh655z1oeDnGcvMYwnEzNzHLgeYcQCkhYX0xBvYWrAuefoiLcPBUkMpJsb46bqQ6Yv4pwTTQ4d3Qg==
jsonc-parser@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76"
@ -43,10 +48,10 @@ vscode-jsonrpc@8.2.0-next.0:
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0-next.0.tgz#41409413c8cebf10f2f1b7cc87e330f0e292814c"
integrity sha512-13jYzaFQpTz5qQ2P+l5c/iTVsj1wUpflP0CR/v4XaEpM0oToLEXZBTcuuox1WaGIbu3Av3xxmGNU4Hydl1iNKg==
vscode-languageserver-protocol@3.17.4-next.0:
version "3.17.4-next.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.4-next.0.tgz#21e4dce218c1cf0ee65ea7fdb62a2c08e075ee8b"
integrity sha512-l//t/BY+GHkH9N0VrHN0zLB+KV42LD0EDtzjGL+p/6xqUVEZegbsZg+6ubvqjE8LhyWcTtpA6pLRaczua6+3GQ==
vscode-languageserver-protocol@3.17.4-next.1:
version "3.17.4-next.1"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.4-next.1.tgz#a15480e1bc663853ae90ded226efafc5ab333616"
integrity sha512-qrK4BycgPR/+nkRN9PRVTblkLp+kUPUmAgF6rDhFzZIPXW4/MqWwFUT8uswIMGdlTPPgCEkFO/AYEZK1fDXODg==
dependencies:
vscode-jsonrpc "8.2.0-next.0"
vscode-languageserver-types "3.17.4-next.0"
@ -66,12 +71,12 @@ vscode-languageserver-types@^3.17.3:
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz#72d05e47b73be93acb84d6e311b5786390f13f64"
integrity sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==
vscode-languageserver@^8.2.0-next.0:
version "8.2.0-next.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.2.0-next.0.tgz#ad9bcef650e0bae9d32591ebbda3eb5e548418a7"
integrity sha512-Pw1pdR+hZvaeeVvbmEscUJG0SMMa5//iV+OnFuv4cPPvk+ohe4mtrVAcI06Z9HIMxDu+2McqY1b8JbVvyRKFtg==
vscode-languageserver@^8.2.0-next.1:
version "8.2.0-next.1"
resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.2.0-next.1.tgz#ad2558d74392b1cfaccd427febe9a368fc328f8b"
integrity sha512-994AXMKBijzjlnpf8p9M+ntsNJDjR8pr55NJPYxKjy/nUhVkg962dAomelH6Z94401kBZmSbfP/K/20cB54aFA==
dependencies:
vscode-languageserver-protocol "3.17.4-next.0"
vscode-languageserver-protocol "3.17.4-next.1"
vscode-uri@^3.0.7:
version "3.0.7"

View file

@ -390,19 +390,19 @@ vscode-jsonrpc@8.2.0-next.0:
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0-next.0.tgz#41409413c8cebf10f2f1b7cc87e330f0e292814c"
integrity sha512-13jYzaFQpTz5qQ2P+l5c/iTVsj1wUpflP0CR/v4XaEpM0oToLEXZBTcuuox1WaGIbu3Av3xxmGNU4Hydl1iNKg==
vscode-languageclient@^8.2.0-next.0:
version "8.2.0-next.0"
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-8.2.0-next.0.tgz#7415c831c4c1e38cc08e710e744c36c5d2251bd8"
integrity sha512-Gqr47Up5VDuRT8JrfB0QFGXR9ngQDkfIJfbT0xmM4OIsISqH3uUgXHTAhekRtS4N6aER3UvGXUrrRWQUaBGYHA==
vscode-languageclient@^8.2.0-next.1:
version "8.2.0-next.1"
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-8.2.0-next.1.tgz#a3f98b80cfa3225fde0583aa6a5c9b20219fa37e"
integrity sha512-oITaqHQ10PM3zXCUu/104wriMeDutXMkQXMaRBWh1jKihcNcUBLC/os7RhqiVGypY0nl+F0pwStAf4Koc8inaw==
dependencies:
minimatch "^5.1.0"
semver "^7.3.7"
vscode-languageserver-protocol "3.17.4-next.0"
vscode-languageserver-protocol "3.17.4-next.1"
vscode-languageserver-protocol@3.17.4-next.0:
version "3.17.4-next.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.4-next.0.tgz#21e4dce218c1cf0ee65ea7fdb62a2c08e075ee8b"
integrity sha512-l//t/BY+GHkH9N0VrHN0zLB+KV42LD0EDtzjGL+p/6xqUVEZegbsZg+6ubvqjE8LhyWcTtpA6pLRaczua6+3GQ==
vscode-languageserver-protocol@3.17.4-next.1:
version "3.17.4-next.1"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.4-next.1.tgz#a15480e1bc663853ae90ded226efafc5ab333616"
integrity sha512-qrK4BycgPR/+nkRN9PRVTblkLp+kUPUmAgF6rDhFzZIPXW4/MqWwFUT8uswIMGdlTPPgCEkFO/AYEZK1fDXODg==
dependencies:
vscode-jsonrpc "8.2.0-next.0"
vscode-languageserver-types "3.17.4-next.0"

View file

@ -25,6 +25,7 @@
["`", "`"]
],
"folding": {
"offSide": true,
"markers": {
"start": "^\\s*--\\s*#region\\b",
"end": "^\\s*--\\s*#endregion\\b"

View file

@ -19,13 +19,18 @@ export const memFs = 'memfs';
export const vscodeVfs = 'vscode-vfs';
export const officeScript = 'office-script';
export const semanticSupportedSchemes = isWeb() && vscode.workspace.workspaceFolders ?
vscode.workspace.workspaceFolders.map(folder => folder.uri.scheme) : [
export function getSemanticSupportedSchemes() {
if (isWeb() && vscode.workspace.workspaceFolders) {
return vscode.workspace.workspaceFolders.map(folder => folder.uri.scheme);
}
return [
file,
untitled,
walkThroughSnippet,
vscodeNotebookCell,
];
}
/**
* File scheme for which JS/TS language feature should be disabled

View file

@ -175,7 +175,7 @@ class MoveToFileRefactorCommand implements Command {
if (response.type !== 'response' || !response.body) {
return;
}
const defaultUri = vscode.Uri.joinPath(Utils.dirname(document.uri), response.body.newFileName);
const defaultUri = this.client.toResource(response.body.newFileName);
const selectExistingFileItem: vscode.QuickPickItem = {
label: vscode.l10n.t("Select existing file..."),

View file

@ -46,7 +46,7 @@ export default class LanguageProvider extends Disposable {
const syntax: vscode.DocumentFilter[] = [];
for (const language of this.description.languageIds) {
syntax.push({ language });
for (const scheme of fileSchemes.semanticSupportedSchemes) {
for (const scheme of fileSchemes.getSemanticSupportedSchemes()) {
semantic.push({ language, scheme });
}
}

View file

@ -719,15 +719,13 @@ export default class TypeScriptServiceClient extends Disposable implements IType
}
switch (capability) {
case ClientCapability.Semantic:
{
return fileSchemes.semanticSupportedSchemes.includes(resource.scheme);
}
case ClientCapability.Semantic: {
return fileSchemes.getSemanticSupportedSchemes().includes(resource.scheme);
}
case ClientCapability.Syntax:
case ClientCapability.EnhancedSyntax:
{
return true;
}
case ClientCapability.EnhancedSyntax: {
return true;
}
}
}

View file

@ -95,17 +95,26 @@ function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient
const logNormal = log.bind(null, ts.server.LogLevel.normal);
const logVerbose = log.bind(null, ts.server.LogLevel.verbose);
const noopWatcher: ts.FileWatcher = { close() { } };
return {
watchFile(path: string, callback: ts.FileWatcherCallback, pollingInterval?: number, options?: ts.WatchOptions): ts.FileWatcher {
if (looksLikeLibDtsPath(path)) { // We don't support watching lib files on web since they are readonly
return { close() { } };
return noopWatcher;
}
logVerbose('fs.watchFile', { path });
let uri: URI;
try {
uri = toResource(path);
} catch (e) {
console.error(e);
return noopWatcher;
}
watchFiles.set(path, { path, callback, pollingInterval, options });
watchId++;
fsWatcher.postMessage({ type: 'watchFile', uri: toResource(path), id: watchId });
fsWatcher.postMessage({ type: 'watchFile', uri, id: watchId });
return {
close() {
logVerbose('fs.watchFile.close', { path });
@ -118,9 +127,17 @@ function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient
watchDirectory(path: string, callback: ts.DirectoryWatcherCallback, recursive?: boolean, options?: ts.WatchOptions): ts.FileWatcher {
logVerbose('fs.watchDirectory', { path });
let uri: URI;
try {
uri = toResource(path);
} catch (e) {
console.error(e);
return noopWatcher;
}
watchDirectories.set(path, { path, callback, recursive, options });
watchId++;
fsWatcher.postMessage({ type: 'watchDirectory', recursive, uri: toResource(path), id: watchId });
fsWatcher.postMessage({ type: 'watchDirectory', recursive, uri, id: watchId });
return {
close() {
logVerbose('fs.watchDirectory.close', { path });

View file

@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.79.0",
"distro": "7272f69cc607298dc986708a73bd978aa7c3af3d",
"distro": "b28dee681d8f2ede89c55ce436c03ed09560565b",
"author": {
"name": "Microsoft Corporation"
},

View file

@ -1865,7 +1865,7 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } & Partia
el.appendChild(c);
} else if (typeof c === 'string') {
el.append(c);
} else {
} else if ('root' in c) {
Object.assign(result, c);
el.appendChild(c.root);
}

View file

@ -219,7 +219,9 @@ export interface IListView<T> extends ISpliceable<T>, IDisposable {
readonly scrollableElementDomNode: HTMLElement;
readonly length: number;
readonly contentHeight: number;
readonly contentWidth: number;
readonly onDidChangeContentHeight: Event<number>;
readonly onDidChangeContentWidth: Event<number>;
readonly renderHeight: number;
readonly scrollHeight: number;
readonly firstVisibleIndex: number;
@ -310,8 +312,11 @@ export class ListView<T> implements IListView<T> {
private readonly disposables: DisposableStore = new DisposableStore();
private readonly _onDidChangeContentHeight = new Emitter<number>();
private readonly _onDidChangeContentWidth = new Emitter<number>();
readonly onDidChangeContentHeight: Event<number> = Event.latch(this._onDidChangeContentHeight.event, undefined, this.disposables);
readonly onDidChangeContentWidth: Event<number> = Event.latch(this._onDidChangeContentWidth.event, undefined, this.disposables);
get contentHeight(): number { return this.rangeMap.size; }
get contentWidth(): number { return this.scrollWidth ?? 0; }
get onDidScroll(): Event<ScrollEvent> { return this.scrollableElement.onScroll; }
get onWillScroll(): Event<ScrollEvent> { return this.scrollableElement.onWillScroll; }
@ -689,6 +694,7 @@ export class ListView<T> implements IListView<T> {
this.scrollWidth = scrollWidth;
this.scrollableElement.setScrollDimensions({ scrollWidth: scrollWidth === 0 ? 0 : (scrollWidth + 10) });
this._onDidChangeContentWidth.fire(this.scrollWidth);
}
updateWidth(index: number): void {
@ -702,6 +708,7 @@ export class ListView<T> implements IListView<T> {
if (typeof item.width !== 'undefined' && item.width > this.scrollWidth) {
this.scrollWidth = item.width;
this.scrollableElement.setScrollDimensions({ scrollWidth: this.scrollWidth + 10 });
this._onDidChangeContentWidth.fire(this.scrollWidth);
}
}

View file

@ -1506,10 +1506,18 @@ export class List<T> implements ISpliceable<T>, IDisposable {
return this.view.contentHeight;
}
get contentWidth(): number {
return this.view.contentWidth;
}
get onDidChangeContentHeight(): Event<number> {
return this.view.onDidChangeContentHeight;
}
get onDidChangeContentWidth(): Event<number> {
return this.view.onDidChangeContentWidth;
}
get scrollTop(): number {
return this.view.getScrollTop();
}

View file

@ -742,7 +742,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
return label;
},
getWidgetAriaLabel: () => localize({ key: 'selectBox', comment: ['Behave like native select dropdown element.'] }, "Select Box"),
getRole: () => 'option',
getRole: () => isMacintosh ? '' : 'option',
getWidgetRole: () => 'listbox'
}
});

View file

@ -1706,10 +1706,18 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
return this.view.contentHeight;
}
get contentWidth(): number {
return this.view.contentWidth;
}
get onDidChangeContentHeight(): Event<number> {
return this.view.onDidChangeContentHeight;
}
get onDidChangeContentWidth(): Event<number> {
return this.view.onDidChangeContentWidth;
}
get scrollTop(): number {
return this.view.scrollTop;
}

View file

@ -438,10 +438,18 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
return this.tree.contentHeight;
}
get contentWidth(): number {
return this.tree.contentWidth;
}
get onDidChangeContentHeight(): Event<number> {
return this.tree.onDidChangeContentHeight;
}
get onDidChangeContentWidth(): Event<number> {
return this.tree.onDidChangeContentWidth;
}
get scrollTop(): number {
return this.tree.scrollTop;
}

View file

@ -23,6 +23,41 @@ export function autorunHandleChanges<TChangeSummary>(
return new AutorunObserver(debugName, fn, options.createEmptyChangeSummary, options.handleChange);
}
// TODO@hediet rename to autorunWithStore
export function autorunWithStore2(
debugName: string,
fn: (reader: IReader, store: DisposableStore) => void,
): IDisposable {
return autorunWithStore(fn, debugName);
}
export function autorunWithStoreHandleChanges<TChangeSummary>(
debugName: string,
options: {
createEmptyChangeSummary?: () => TChangeSummary;
handleChange: (context: IChangeContext, changeSummary: TChangeSummary) => boolean;
},
fn: (reader: IReader, changeSummary: TChangeSummary, store: DisposableStore) => void
): IDisposable {
const store = new DisposableStore();
const disposable = autorunHandleChanges(
debugName,
{
createEmptyChangeSummary: options.createEmptyChangeSummary,
handleChange: options.handleChange,
},
(reader, changeSummary) => {
store.clear();
fn(reader, changeSummary, store);
}
);
return toDisposable(() => {
disposable.dispose();
store.dispose();
});
}
// TODO@hediet deprecate, rename to autorunWithStoreEx
export function autorunWithStore(
fn: (reader: IReader, store: DisposableStore) => void,
debugName: string
@ -144,13 +179,13 @@ export class AutorunObserver<TChangeSummary = any> implements IObserver, IReader
}
public handlePossibleChange(observable: IObservable<any>): void {
if (this.state === AutorunState.upToDate && this.dependencies.has(observable)) {
if (this.state === AutorunState.upToDate && this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) {
this.state = AutorunState.dependenciesMightHaveChanged;
}
}
public handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void {
if (this.dependencies.has(observable)) {
if (this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) {
const shouldReact = this._handleChange ? this._handleChange({
changedObservable: observable,
change,

View file

@ -7,6 +7,11 @@ import { IDisposable } from 'vs/base/common/lifecycle';
import type { derived } from 'vs/base/common/observableImpl/derived';
import { getLogger } from 'vs/base/common/observableImpl/logging';
/**
* Represents an observable value.
* @template T The type of the value.
* @template TChange The type of delta information (usually `void` and only used in advanced scenarios).
*/
export interface IObservable<T, TChange = unknown> {
/**
* Returns the current value.
@ -248,6 +253,10 @@ export function getFunctionName(fn: Function): string | undefined {
export interface ISettableObservable<T, TChange = void> extends IObservable<T, TChange>, ISettable<T, TChange> {
}
/**
* Creates an observable value.
* Observers get informed when the value changes.
*/
export function observableValue<T, TChange = void>(name: string, initialValue: T): ISettableObservable<T, TChange> {
return new ObservableValue(name, initialValue);
}

View file

@ -196,7 +196,7 @@ export class Derived<T, TChangeSummary = any> extends BaseObservable<T, void> im
public handlePossibleChange<T>(observable: IObservable<T, unknown>): void {
// In all other states, observers already know that we might have changed.
if (this.state === DerivedState.upToDate && this.dependencies.has(observable)) {
if (this.state === DerivedState.upToDate && this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) {
this.state = DerivedState.dependenciesMightHaveChanged;
for (const r of this.observers) {
r.handlePossibleChange(this);
@ -205,22 +205,19 @@ export class Derived<T, TChangeSummary = any> extends BaseObservable<T, void> im
}
public handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void {
const isUpToDate = this.state === DerivedState.upToDate;
let shouldReact = true;
if (this._handleChange && this.dependencies.has(observable)) {
shouldReact = this._handleChange({
if (this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) {
const shouldReact = this._handleChange ? this._handleChange({
changedObservable: observable,
change,
didChange: o => o === observable as any,
}, this.changeSummary!);
}
if (shouldReact && (this.state === DerivedState.dependenciesMightHaveChanged || isUpToDate) && this.dependencies.has(observable)) {
this.state = DerivedState.stale;
if (isUpToDate) {
for (const r of this.observers) {
r.handlePossibleChange(this);
}, this.changeSummary!) : true;
const wasUpToDate = this.state === DerivedState.upToDate;
if (shouldReact && (this.state === DerivedState.dependenciesMightHaveChanged || wasUpToDate)) {
this.state = DerivedState.stale;
if (wasUpToDate) {
for (const r of this.observers) {
r.handlePossibleChange(this);
}
}
}
}

View file

@ -198,6 +198,9 @@ class FromEventObservableSignal extends BaseObservable<void> {
}
}
/**
* Creates a signal that can be triggered to invalidate observers.
*/
export function observableSignal<TDelta = void>(
debugName: string
): IObservableSignal<TDelta> {
@ -287,6 +290,10 @@ export function wasEventTriggeredRecently(event: Event<any>, timeoutMs: number,
export function keepAlive(observable: IObservable<any>, forceRecompute?: boolean): IDisposable {
const o = new KeepAliveObserver(forceRecompute ?? false);
observable.addObserver(o);
if (forceRecompute) {
observable.reportChanges();
}
return toDisposable(() => {
observable.removeObserver(o);
});

View file

@ -191,6 +191,8 @@ export interface IProductConfiguration {
readonly 'editSessions.store'?: Omit<ConfigurationSyncStore, 'insidersUrl' | 'stableUrl'>;
readonly darwinUniversalAssetId?: string;
readonly profileTemplatesUrl?: string;
readonly commonlyUsedSettings?: string[];
}
export interface ITunnelApplicationConfig {
@ -201,6 +203,11 @@ export interface ITunnelApplicationConfig {
export interface IExtensionRecommendations {
readonly onFileOpen: IFileOpenCondition[];
readonly onSettingsEditorOpen?: ISettingsEditorOpenCondition;
}
export interface ISettingsEditorOpenCondition {
readonly prerelease: boolean | string;
}
export interface IExtensionRecommendationCondition {

View file

@ -37,8 +37,13 @@ export enum RimRafMode {
* - `UNLINK`: direct removal from disk
* - `MOVE`: faster variant that first moves the target to temp dir and then
* deletes it in the background without waiting for that to finish.
* the optional `moveToPath` allows to override where to rename the
* path to before deleting it.
*/
async function rimraf(path: string, mode = RimRafMode.UNLINK): Promise<void> {
async function rimraf(path: string, mode: RimRafMode.UNLINK): Promise<void>;
async function rimraf(path: string, mode: RimRafMode.MOVE, moveToPath?: string): Promise<void>;
async function rimraf(path: string, mode?: RimRafMode, moveToPath?: string): Promise<void>;
async function rimraf(path: string, mode = RimRafMode.UNLINK, moveToPath?: string): Promise<void> {
if (isRootOrDriveLetter(path)) {
throw new Error('rimraf - will refuse to recursively delete root');
}
@ -49,12 +54,11 @@ async function rimraf(path: string, mode = RimRafMode.UNLINK): Promise<void> {
}
// delete: via move
return rimrafMove(path);
return rimrafMove(path, moveToPath);
}
async function rimrafMove(path: string): Promise<void> {
async function rimrafMove(path: string, moveToPath = randomPath(tmpdir())): Promise<void> {
try {
const pathInTemp = randomPath(tmpdir());
try {
// Intentionally using `fs.promises` here to skip
// the patched graceful-fs method that can result
@ -64,7 +68,7 @@ async function rimrafMove(path: string): Promise<void> {
// than necessary and we have a fallback to delete
// via unlink.
// https://github.com/microsoft/vscode/issues/139908
await fs.promises.rename(path, pathInTemp);
await fs.promises.rename(path, moveToPath);
} catch (error) {
if (error.code === 'ENOENT') {
return; // ignore - path to delete did not exist
@ -74,7 +78,7 @@ async function rimrafMove(path: string): Promise<void> {
}
// Delete but do not return as promise
rimrafUnlink(pathInTemp).catch(error => {/* ignore */ });
rimrafUnlink(moveToPath).catch(error => {/* ignore */ });
} catch (error) {
if (error.code !== 'ENOENT') {
throw error;

View file

@ -10,7 +10,7 @@ import { timeout } from 'vs/base/common/async';
import { VSBuffer } from 'vs/base/common/buffer';
import { randomPath } from 'vs/base/common/extpath';
import { FileAccess } from 'vs/base/common/network';
import { join, sep } from 'vs/base/common/path';
import { basename, dirname, join, sep } from 'vs/base/common/path';
import { isWindows } from 'vs/base/common/platform';
import { configureFlushOnWrite, Promises, RimRafMode, rimrafSync, SymlinkSupport, writeFileSync } from 'vs/base/node/pfs';
import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils';
@ -91,6 +91,14 @@ flakySuite('PFS', function () {
assert.ok(!fs.existsSync(testDir));
});
test('rimraf - simple - move (with moveToPath)', async () => {
fs.writeFileSync(join(testDir, 'somefile.txt'), 'Contents');
fs.writeFileSync(join(testDir, 'someOtherFile.txt'), 'Contents');
await Promises.rm(testDir, RimRafMode.MOVE, join(dirname(testDir), `${basename(testDir)}.vsctmp`));
assert.ok(!fs.existsSync(testDir));
});
test('rimraf - path does not exist - move', async () => {
const nonExistingDir = join(testDir, 'unknown-move');
await Promises.rm(nonExistingDir, RimRafMode.MOVE);

View file

@ -21,6 +21,7 @@ import { IEditorWhitespace, IViewModel } from 'vs/editor/common/viewModel';
import { InjectedText } from 'vs/editor/common/modelLineProjectionData';
import { ILineChange, IDiffComputationResult } from 'vs/editor/common/diff/smartLinesDiffComputer';
import { IDimension } from 'vs/editor/common/core/dimension';
import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash';
/**
* A view zone is a full horizontal rectangle that 'pushes' text down.
@ -1210,6 +1211,21 @@ export interface IDiffEditor extends editorCommon.IEditor {
* Update the editor's options after the editor has been created.
*/
updateOptions(newOptions: IDiffEditorOptions): void;
/**
* @internal
*/
setBoundarySashes(sashes: IBoundarySashes): void;
/**
* @internal
*/
goToDiff(target: 'next' | 'previous'): void;
/**
* @internal
*/
revealFirstDiff(): unknown;
}
/**

View file

@ -450,9 +450,13 @@ export abstract class EditorAction2 extends Action2 {
// precondition does hold
return editor.invokeWithinContext((editorAccessor) => {
const kbService = editorAccessor.get(IContextKeyService);
if (kbService.contextMatchesRules(withNullAsUndefined(this.desc.precondition))) {
return this.runEditorCommand(editorAccessor, editor!, ...args);
const logService = editorAccessor.get(ILogService);
const enabled = kbService.contextMatchesRules(withNullAsUndefined(this.desc.precondition));
if (!enabled) {
logService.debug(`[EditorAction2] NOT running command because its precondition is FALSE`, this.desc.id, this.desc.precondition?.serialize());
return;
}
return this.runEditorCommand(editorAccessor, editor!, ...args);
});
}

View file

@ -60,6 +60,7 @@ import { getThemeTypeSelector, IColorTheme, IThemeService, registerThemingPartic
import { ThemeIcon } from 'vs/base/common/themables';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { DiffNavigator } from 'vs/editor/browser/widget/diffNavigator';
export interface IDiffCodeEditorWidgetOptions {
originalEditor?: ICodeEditorWidgetOptions;
@ -242,6 +243,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
private isEmbeddedDiffEditorKey: IContextKey<boolean>;
private _diffNavigator: DiffNavigator | undefined;
constructor(
domElement: HTMLElement,
options: Readonly<editorBrowser.IDiffEditorConstructionOptions>,
@ -289,7 +292,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
renderOverviewRuler: true,
diffWordWrap: 'inherit',
diffAlgorithm: 'advanced',
accessibilityVerbose: false
accessibilityVerbose: false,
experimental: {
collapseUnchangedRegions: false,
},
});
this.isEmbeddedDiffEditorKey = EditorContextKeys.isEmbeddedDiffEditor.bindTo(this._contextKeyService);
@ -860,12 +866,20 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
this._layoutOverviewViewport();
this._onDidChangeModel.fire();
// Diff navigator
this._diffNavigator = this._register(this._instantiationService.createInstance(DiffNavigator, this, {
alwaysRevealFirst: false,
findResultLoop: this.getModifiedEditor().getOption(EditorOption.find).loop
}));
}
public getContainerDomNode(): HTMLElement {
return this._domElement;
}
// #region editorBrowser.IDiffEditor: Delegating to modified Editor
public getVisibleColumnFromPosition(position: IPosition): number {
return this._modifiedEditor.getVisibleColumnFromPosition(position);
}
@ -978,6 +992,24 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
return this._modifiedEditor.getSupportedActions();
}
public focus(): void {
this._modifiedEditor.focus();
}
public trigger(source: string | null | undefined, handlerId: string, payload: any): void {
this._modifiedEditor.trigger(source, handlerId, payload);
}
public createDecorationsCollection(decorations?: IModelDeltaDecoration[]): editorCommon.IEditorDecorationsCollection {
return this._modifiedEditor.createDecorationsCollection(decorations);
}
public changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => any): any {
return this._modifiedEditor.changeDecorations(callback);
}
// #endregion
public saveViewState(): editorCommon.IDiffEditorViewState {
const originalViewState = this._originalEditor.saveViewState();
const modifiedViewState = this._modifiedEditor.saveViewState();
@ -999,9 +1031,6 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
this._elementSizeObserver.observe(dimension);
}
public focus(): void {
this._modifiedEditor.focus();
}
public hasTextFocus(): boolean {
return this._originalEditor.hasTextFocus() || this._modifiedEditor.hasTextFocus();
@ -1023,18 +1052,6 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
this._cleanViewZonesAndDecorations();
}
public trigger(source: string | null | undefined, handlerId: string, payload: any): void {
this._modifiedEditor.trigger(source, handlerId, payload);
}
public createDecorationsCollection(decorations?: IModelDeltaDecoration[]): editorCommon.IEditorDecorationsCollection {
return this._modifiedEditor.createDecorationsCollection(decorations);
}
public changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => any): any {
return this._modifiedEditor.changeDecorations(callback);
}
//------------ end IDiffEditor methods
@ -1531,6 +1548,21 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
equivalentLineNumber: this._getEquivalentLineForModifiedLineNumber(lineNumber)
};
}
public goToDiff(target: 'previous' | 'next'): void {
if (target === 'next') {
this._diffNavigator?.next();
} else {
this._diffNavigator?.previous();
}
}
public revealFirstDiff(): void {
// This is a hack, but it works.
if (this._diffNavigator) {
this._diffNavigator.revealFirst = true;
}
}
}
interface IDataSource {
@ -2780,6 +2812,9 @@ function validateDiffEditorOptions(options: Readonly<IDiffEditorOptions>, defaul
diffWordWrap: validateDiffWordWrap(options.diffWordWrap, defaults.diffWordWrap),
diffAlgorithm: validateStringSetOption(options.diffAlgorithm, defaults.diffAlgorithm, ['legacy', 'advanced'], { 'smart': 'legacy', 'experimental': 'advanced' }),
accessibilityVerbose: validateBooleanOption(options.accessibilityVerbose, defaults.accessibilityVerbose),
experimental: {
collapseUnchangedRegions: false,
},
};
}

View file

@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
export const diffFullLineAddDecoration = ModelDecorationOptions.register({
className: 'line-insert',
description: 'line-insert',
isWholeLine: true,
});
export const diffFullLineDeleteDecoration = ModelDecorationOptions.register({
className: 'line-delete',
description: 'line-delete',
isWholeLine: true,
});
export const diffAddDecoration = ModelDecorationOptions.register({
className: 'char-insert',
description: 'char-insert',
});
export const diffDeleteDecoration = ModelDecorationOptions.register({
className: 'char-delete',
description: 'char-delete',
});

View file

@ -0,0 +1,170 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IDimension } from 'vs/editor/common/core/dimension';
import { IPosition, Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import { ISelection, Selection } from 'vs/editor/common/core/selection';
import { IEditor, IEditorAction, IEditorDecorationsCollection, IEditorModel, IEditorViewState, ScrollType } from 'vs/editor/common/editorCommon';
import { IModelDecorationsChangeAccessor, IModelDeltaDecoration } from 'vs/editor/common/model';
export abstract class DelegatingEditor extends Disposable implements IEditor {
private static idCounter = 0;
private readonly _id = ++DelegatingEditor.idCounter;
private readonly _onDidDispose = this._register(new Emitter<void>());
public readonly onDidDispose = this._onDidDispose.event;
protected abstract get _targetEditor(): CodeEditorWidget;
getId(): string { return this.getEditorType() + ':' + this._id; }
abstract getEditorType(): string;
abstract updateOptions(newOptions: IEditorOptions): void;
abstract onVisible(): void;
abstract onHide(): void;
abstract layout(dimension?: IDimension | undefined): void;
abstract hasTextFocus(): boolean;
abstract saveViewState(): IEditorViewState | null;
abstract restoreViewState(state: IEditorViewState | null): void;
abstract getModel(): IEditorModel | null;
abstract setModel(model: IEditorModel | null): void;
// #region editorBrowser.IDiffEditor: Delegating to modified Editor
public getVisibleColumnFromPosition(position: IPosition): number {
return this._targetEditor.getVisibleColumnFromPosition(position);
}
public getStatusbarColumn(position: IPosition): number {
return this._targetEditor.getStatusbarColumn(position);
}
public getPosition(): Position | null {
return this._targetEditor.getPosition();
}
public setPosition(position: IPosition, source: string = 'api'): void {
this._targetEditor.setPosition(position, source);
}
public revealLine(lineNumber: number, scrollType: ScrollType = ScrollType.Smooth): void {
this._targetEditor.revealLine(lineNumber, scrollType);
}
public revealLineInCenter(lineNumber: number, scrollType: ScrollType = ScrollType.Smooth): void {
this._targetEditor.revealLineInCenter(lineNumber, scrollType);
}
public revealLineInCenterIfOutsideViewport(lineNumber: number, scrollType: ScrollType = ScrollType.Smooth): void {
this._targetEditor.revealLineInCenterIfOutsideViewport(lineNumber, scrollType);
}
public revealLineNearTop(lineNumber: number, scrollType: ScrollType = ScrollType.Smooth): void {
this._targetEditor.revealLineNearTop(lineNumber, scrollType);
}
public revealPosition(position: IPosition, scrollType: ScrollType = ScrollType.Smooth): void {
this._targetEditor.revealPosition(position, scrollType);
}
public revealPositionInCenter(position: IPosition, scrollType: ScrollType = ScrollType.Smooth): void {
this._targetEditor.revealPositionInCenter(position, scrollType);
}
public revealPositionInCenterIfOutsideViewport(position: IPosition, scrollType: ScrollType = ScrollType.Smooth): void {
this._targetEditor.revealPositionInCenterIfOutsideViewport(position, scrollType);
}
public revealPositionNearTop(position: IPosition, scrollType: ScrollType = ScrollType.Smooth): void {
this._targetEditor.revealPositionNearTop(position, scrollType);
}
public getSelection(): Selection | null {
return this._targetEditor.getSelection();
}
public getSelections(): Selection[] | null {
return this._targetEditor.getSelections();
}
public setSelection(range: IRange, source?: string): void;
public setSelection(editorRange: Range, source?: string): void;
public setSelection(selection: ISelection, source?: string): void;
public setSelection(editorSelection: Selection, source?: string): void;
public setSelection(something: any, source: string = 'api'): void {
this._targetEditor.setSelection(something, source);
}
public setSelections(ranges: readonly ISelection[], source: string = 'api'): void {
this._targetEditor.setSelections(ranges, source);
}
public revealLines(startLineNumber: number, endLineNumber: number, scrollType: ScrollType = ScrollType.Smooth): void {
this._targetEditor.revealLines(startLineNumber, endLineNumber, scrollType);
}
public revealLinesInCenter(startLineNumber: number, endLineNumber: number, scrollType: ScrollType = ScrollType.Smooth): void {
this._targetEditor.revealLinesInCenter(startLineNumber, endLineNumber, scrollType);
}
public revealLinesInCenterIfOutsideViewport(startLineNumber: number, endLineNumber: number, scrollType: ScrollType = ScrollType.Smooth): void {
this._targetEditor.revealLinesInCenterIfOutsideViewport(startLineNumber, endLineNumber, scrollType);
}
public revealLinesNearTop(startLineNumber: number, endLineNumber: number, scrollType: ScrollType = ScrollType.Smooth): void {
this._targetEditor.revealLinesNearTop(startLineNumber, endLineNumber, scrollType);
}
public revealRange(range: IRange, scrollType: ScrollType = ScrollType.Smooth, revealVerticalInCenter: boolean = false, revealHorizontal: boolean = true): void {
this._targetEditor.revealRange(range, scrollType, revealVerticalInCenter, revealHorizontal);
}
public revealRangeInCenter(range: IRange, scrollType: ScrollType = ScrollType.Smooth): void {
this._targetEditor.revealRangeInCenter(range, scrollType);
}
public revealRangeInCenterIfOutsideViewport(range: IRange, scrollType: ScrollType = ScrollType.Smooth): void {
this._targetEditor.revealRangeInCenterIfOutsideViewport(range, scrollType);
}
public revealRangeNearTop(range: IRange, scrollType: ScrollType = ScrollType.Smooth): void {
this._targetEditor.revealRangeNearTop(range, scrollType);
}
public revealRangeNearTopIfOutsideViewport(range: IRange, scrollType: ScrollType = ScrollType.Smooth): void {
this._targetEditor.revealRangeNearTopIfOutsideViewport(range, scrollType);
}
public revealRangeAtTop(range: IRange, scrollType: ScrollType = ScrollType.Smooth): void {
this._targetEditor.revealRangeAtTop(range, scrollType);
}
public getSupportedActions(): IEditorAction[] {
return this._targetEditor.getSupportedActions();
}
public focus(): void {
this._targetEditor.focus();
}
public trigger(source: string | null | undefined, handlerId: string, payload: any): void {
this._targetEditor.trigger(source, handlerId, payload);
}
public createDecorationsCollection(decorations?: IModelDeltaDecoration[]): IEditorDecorationsCollection {
return this._targetEditor.createDecorationsCollection(decorations);
}
public changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => any): any {
return this._targetEditor.changeDecorations(callback);
}
// #endregion
}

View file

@ -0,0 +1,74 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Sash, Orientation, ISashEvent, IBoundarySashes, SashState } from 'vs/base/browser/ui/sash/sash';
import { Disposable } from 'vs/base/common/lifecycle';
import { IObservable, IReader, autorun, derived, observableValue } from 'vs/base/common/observable';
export class DiffEditorSash extends Disposable {
private readonly _sashRatio = observableValue<number | undefined>('sashRatio', undefined);
public readonly sashLeft = derived('sashLeft', reader => {
const ratio = this._sashRatio.read(reader) ?? this._defaultSashRatio.read(reader);
return this._computeSashLeft(ratio, reader);
});
private readonly _sash = this._register(new Sash(this._domNode, {
getVerticalSashTop: (_sash: Sash): number => 0,
getVerticalSashLeft: (_sash: Sash): number => this.sashLeft.get(),
getVerticalSashHeight: (_sash: Sash): number => this._dimensions.height.get(),
}, { orientation: Orientation.VERTICAL }));
private _startSashPosition: number | undefined = undefined;
constructor(
private readonly _enableSplitViewResizing: IObservable<boolean>,
private readonly _defaultSashRatio: IObservable<number>,
private readonly _domNode: HTMLElement,
private readonly _dimensions: { height: IObservable<number>; width: IObservable<number> },
) {
super();
this._register(this._sash.onDidStart(() => {
this._startSashPosition = this.sashLeft.get();
}));
this._register(this._sash.onDidChange((e: ISashEvent) => {
const contentWidth = this._dimensions.width.get();
const sashPosition = this._computeSashLeft((this._startSashPosition! + (e.currentX - e.startX)) / contentWidth, undefined);
this._sashRatio.set(sashPosition / contentWidth, undefined);
}));
this._register(this._sash.onDidEnd(() => this._sash.layout()));
this._register(this._sash.onDidReset(() => this._sashRatio.set(undefined, undefined)));
this._register(autorun('update sash layout', (reader) => {
const enabled = this._enableSplitViewResizing.read(reader);
this._sash.state = enabled ? SashState.Enabled : SashState.Disabled;
this.sashLeft.read(reader);
this._sash.layout();
}));
}
setBoundarySashes(sashes: IBoundarySashes): void {
this._sash.orthogonalEndSash = sashes.bottom;
}
private _computeSashLeft(desiredRatio: number, reader: IReader | undefined): number {
const contentWidth = this._dimensions.width.read(reader);
const midPoint = Math.floor(this._defaultSashRatio.read(reader) * contentWidth);
const sashLeft = this._enableSplitViewResizing.read(reader) ? Math.floor(desiredRatio * contentWidth) : midPoint;
const MINIMUM_EDITOR_WIDTH = 100;
if (contentWidth <= MINIMUM_EDITOR_WIDTH * 2) {
return midPoint;
}
if (sashLeft < MINIMUM_EDITOR_WIDTH) {
return MINIMUM_EDITOR_WIDTH;
}
if (sashLeft > contentWidth - MINIMUM_EDITOR_WIDTH) {
return contentWidth - MINIMUM_EDITOR_WIDTH;
}
return sashLeft;
}
}

View file

@ -0,0 +1,57 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Codicon } from 'vs/base/common/codicons';
import { ThemeIcon } from 'vs/base/common/themables';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { localize } from 'vs/nls';
import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ContextKeyEqualsExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
export class ToggleCollapseUnchangedRegions extends Action2 {
constructor() {
super({
id: 'diffEditor.toggleCollapseUnchangedRegions',
title: { value: localize('toggleCollapseUnchangedRegions', "Toggle Collapse Unchanged Regions"), original: 'Toggle Collapse Unchanged Regions' },
icon: Codicon.map,
precondition: ContextKeyEqualsExpr.create('diffEditorVersion', 2),
});
}
run(accessor: ServicesAccessor, ...args: unknown[]): void {
const configurationService = accessor.get(IConfigurationService);
const newValue = !configurationService.getValue<boolean>('diffEditor.experimental.collapseUnchangedRegions');
configurationService.updateValue('diffEditor.experimental.collapseUnchangedRegions', newValue);
}
}
registerAction2(ToggleCollapseUnchangedRegions);
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
command: {
id: new ToggleCollapseUnchangedRegions().desc.id,
title: localize('collapseUnchangedRegions', "Collapse Unchanged Regions"),
icon: Codicon.map
},
group: 'navigation',
when: ContextKeyExpr.and(
ContextKeyExpr.has('config.diffEditor.experimental.collapseUnchangedRegions'),
ContextKeyEqualsExpr.create('diffEditorVersion', 2)
)
});
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
command: {
id: new ToggleCollapseUnchangedRegions().desc.id,
title: localize('showUnchangedRegions', "Show Unchanged Regions"),
icon: ThemeIcon.modify(Codicon.map, 'disabled'),
},
group: 'navigation',
when: ContextKeyExpr.and(
ContextKeyExpr.has('config.diffEditor.experimental.collapseUnchangedRegions').negate(),
ContextKeyEqualsExpr.create('diffEditorVersion', 2)
)
});

View file

@ -0,0 +1,499 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { h } from 'vs/base/browser/dom';
import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash';
import { findLast } from 'vs/base/common/arrays';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { IObservable, ISettableObservable, derived, keepAlive, observableValue, waitForState } from 'vs/base/common/observable';
import { Constants } from 'vs/base/common/uint';
import 'vs/css!./style';
import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration';
import { ICodeEditor, IDiffEditor, IDiffEditorConstructionOptions, IDiffLineInformation } from 'vs/editor/browser/editorBrowser';
import { EditorExtensionsRegistry, IDiffEditorContributionDescription } from 'vs/editor/browser/editorExtensions';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
import { IDiffCodeEditorWidgetOptions } from 'vs/editor/browser/widget/diffEditorWidget';
import { diffAddDecoration, diffDeleteDecoration, diffFullLineAddDecoration, diffFullLineDeleteDecoration } from 'vs/editor/browser/widget/diffEditorWidget2/decorations';
import { DiffEditorSash } from 'vs/editor/browser/widget/diffEditorWidget2/diffEditorSash';
import { ViewZoneAlignment } from 'vs/editor/browser/widget/diffEditorWidget2/lineAlignment';
import { OverviewRulerPart } from 'vs/editor/browser/widget/diffEditorWidget2/overviewRulerPart';
import { UnchangedRangesFeature } from 'vs/editor/browser/widget/diffEditorWidget2/unchangedRanges';
import { ObservableElementSizeObserver, applyObservableDecorations } from 'vs/editor/browser/widget/diffEditorWidget2/utils';
import { WorkerBasedDocumentDiffProvider } from 'vs/editor/browser/widget/workerBasedDocumentDiffProvider';
import { EditorOptions, IDiffEditorOptions, ValidDiffEditorBaseOptions, clampedFloat, clampedInt, boolean as validateBooleanOption, stringSet as validateStringSetOption } from 'vs/editor/common/config/editorOptions';
import { IDimension } from 'vs/editor/common/core/dimension';
import { Position } from 'vs/editor/common/core/position';
import { LineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
import { IDiffComputationResult, ILineChange } from 'vs/editor/common/diff/smartLinesDiffComputer';
import { EditorType, IContentSizeChangedEvent, IDiffEditorModel, IDiffEditorViewState } from 'vs/editor/common/editorCommon';
import { IModelDeltaDecoration } from 'vs/editor/common/model';
import { localize } from 'vs/nls';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { DelegatingEditor } from './delegatingEditorImpl';
import { DiffModel } from './diffModel';
const diffEditorDefaultOptions: ValidDiffEditorBaseOptions = {
enableSplitViewResizing: true,
splitViewDefaultRatio: 0.5,
renderSideBySide: true,
renderMarginRevertIcon: true,
maxComputationTime: 5000,
maxFileSize: 50,
ignoreTrimWhitespace: true,
renderIndicators: true,
originalEditable: false,
diffCodeLens: false,
renderOverviewRuler: true,
diffWordWrap: 'inherit',
diffAlgorithm: 'advanced',
accessibilityVerbose: false,
experimental: {
collapseUnchangedRegions: false,
}
};
export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
private readonly elements = h('div.monaco-diff-editor.side-by-side', { style: { position: 'relative', height: '100%' } }, [
h('div.editor.original@original', { style: { position: 'absolute', height: '100%' } }),
h('div.editor.modified@modified', { style: { position: 'absolute', height: '100%' } }),
]);
private readonly _model = observableValue<IDiffEditorModel | null>('diffEditorModel', null);
public readonly onDidChangeModel = Event.fromObservableLight(this._model);
private readonly _diffModel = observableValue<DiffModel | null>('diffModel', null);
private readonly _onDidContentSizeChange = this._register(new Emitter<IContentSizeChangedEvent>());
public readonly onDidContentSizeChange = this._onDidContentSizeChange.event;
private readonly _modifiedEditor: CodeEditorWidget;
private readonly _originalEditor: CodeEditorWidget;
private readonly _contextKeyService = this._register(this._parentContextKeyService.createScoped(this._domElement));
private readonly _instantiationService = this._parentInstantiationService.createChild(
new ServiceCollection([IContextKeyService, this._contextKeyService])
);
private readonly _rootSizeObserver: ObservableElementSizeObserver;
private readonly _options: ISettableObservable<ValidDiffEditorBaseOptions>;
private _isHandlingScrollEvent = false;
private readonly _sash: DiffEditorSash;
private readonly _renderOverviewRuler: IObservable<boolean>;
constructor(
private readonly _domElement: HTMLElement,
options: Readonly<IDiffEditorConstructionOptions>,
codeEditorWidgetOptions: IDiffCodeEditorWidgetOptions,
@IContextKeyService private readonly _parentContextKeyService: IContextKeyService,
@IInstantiationService private readonly _parentInstantiationService: IInstantiationService,
@ICodeEditorService codeEditorService: ICodeEditorService,
) {
super();
codeEditorService.willCreateDiffEditor();
this._contextKeyService.createKey('isInDiffEditor', true);
this._contextKeyService.createKey('diffEditorVersion', 2);
this._contextKeyService.createKey('isInEmbeddedDiffEditor',
typeof options.isInEmbeddedEditor !== 'undefined' ? options.isInEmbeddedEditor : false
);
this._options = observableValue<ValidDiffEditorBaseOptions>('options', validateDiffEditorOptions(options || {}, diffEditorDefaultOptions));
this._domElement.appendChild(this.elements.root);
this._rootSizeObserver = this._register(new ObservableElementSizeObserver(this.elements.root, options.dimension));
this._rootSizeObserver.setAutomaticLayout(options.automaticLayout ?? false);
this._originalEditor = this._createLeftHandSideEditor(options, codeEditorWidgetOptions.originalEditor || {});
this._modifiedEditor = this._createRightHandSideEditor(options, codeEditorWidgetOptions.modifiedEditor || {});
this._register(applyObservableDecorations(this._originalEditor, this._decorations.map(d => d?.originalDecorations || [])));
this._register(applyObservableDecorations(this._modifiedEditor, this._decorations.map(d => d?.modifiedDecorations || [])));
this._renderOverviewRuler = this._options.map(o => o.renderOverviewRuler);
this._sash = this._register(new DiffEditorSash(
this._options.map(o => o.enableSplitViewResizing),
this._options.map(o => o.splitViewDefaultRatio),
this.elements.root,
{
height: this._rootSizeObserver.height,
width: this._rootSizeObserver.width.map((w, reader) => w - (this._renderOverviewRuler.read(reader) ? OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH : 0)),
}
));
this._register(new UnchangedRangesFeature(this._originalEditor, this._modifiedEditor, this._diffModel));
this._register(new ViewZoneAlignment(this._originalEditor, this._modifiedEditor, this._diffModel));
this._register(this._instantiationService.createInstance(OverviewRulerPart,
this._originalEditor,
this._modifiedEditor,
this.elements.root,
this._diffModel,
this._rootSizeObserver.width,
this._rootSizeObserver.height,
this._layoutInfo.map(i => i.modifiedEditor),
this._renderOverviewRuler,
));
this._createDiffEditorContributions();
codeEditorService.addDiffEditor(this);
this._register(keepAlive(this._layoutInfo, true));
}
private readonly _layoutInfo = derived('modifiedEditorLayoutInfo', (reader) => {
const width = this._rootSizeObserver.width.read(reader);
const height = this._rootSizeObserver.height.read(reader);
const sashLeft = this._sash.sashLeft.read(reader);
this.elements.original.style.width = sashLeft + 'px';
this.elements.original.style.left = '0px';
this.elements.modified.style.width = (width - sashLeft) + 'px';
this.elements.modified.style.left = sashLeft + 'px';
this._originalEditor.layout({ width: sashLeft, height: height });
this._modifiedEditor.layout({
width: width - sashLeft -
(this._renderOverviewRuler.read(reader) ? OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH : 0),
height
});
return { modifiedEditor: this._modifiedEditor.getLayoutInfo() };
});
private readonly _decorations = derived('decorations', (reader) => {
const diff = this._diffModel.read(reader)?.diff.read(reader);
if (!diff) {
return null;
}
const originalDecorations: IModelDeltaDecoration[] = [];
const modifiedDecorations: IModelDeltaDecoration[] = [];
for (const c of diff.changes) {
const fullRangeOriginal = c.originalRange.toInclusiveRange();
if (fullRangeOriginal) {
originalDecorations.push({ range: fullRangeOriginal, options: diffFullLineDeleteDecoration });
}
const fullRangeModified = c.modifiedRange.toInclusiveRange();
if (fullRangeModified) {
modifiedDecorations.push({ range: fullRangeModified, options: diffFullLineAddDecoration });
}
for (const i of c.innerChanges || []) {
originalDecorations.push({ range: i.originalRange, options: diffDeleteDecoration });
modifiedDecorations.push({ range: i.modifiedRange, options: diffAddDecoration });
}
}
return { originalDecorations, modifiedDecorations };
});
private _createDiffEditorContributions() {
const contributions: IDiffEditorContributionDescription[] = EditorExtensionsRegistry.getDiffEditorContributions();
for (const desc of contributions) {
try {
this._register(this._instantiationService.createInstance(desc.ctor, this));
} catch (err) {
onUnexpectedError(err);
}
}
}
private _createLeftHandSideEditor(options: Readonly<IDiffEditorConstructionOptions>, codeEditorWidgetOptions: ICodeEditorWidgetOptions): CodeEditorWidget {
const editor = this._createInnerEditor(this._instantiationService, this.elements.original, this._adjustOptionsForLeftHandSide(options), codeEditorWidgetOptions);
const isInDiffLeftEditorKey = this._contextKeyService.createKey<boolean>('isInDiffLeftEditor', editor.hasWidgetFocus());
this._register(editor.onDidFocusEditorWidget(() => isInDiffLeftEditorKey.set(true)));
this._register(editor.onDidBlurEditorWidget(() => isInDiffLeftEditorKey.set(false)));
return editor;
}
private _createRightHandSideEditor(options: Readonly<IDiffEditorConstructionOptions>, codeEditorWidgetOptions: ICodeEditorWidgetOptions): CodeEditorWidget {
const editor = this._createInnerEditor(this._instantiationService, this.elements.modified, this._adjustOptionsForRightHandSide(options), codeEditorWidgetOptions);
const isInDiffRightEditorKey = this._contextKeyService.createKey<boolean>('isInDiffRightEditor', editor.hasWidgetFocus());
this._register(editor.onDidFocusEditorWidget(() => isInDiffRightEditorKey.set(true)));
this._register(editor.onDidBlurEditorWidget(() => isInDiffRightEditorKey.set(false)));
// Revert change when an arrow is clicked.
/*TODO
this._register(editor.onMouseDown(event => {
if (!event.event.rightButton && event.target.position && event.target.element?.className.includes('arrow-revert-change')) {
const lineNumber = event.target.position.lineNumber;
const viewZone = event.target as editorBrowser.IMouseTargetViewZone | undefined;
const change = this._diffComputationResult?.changes.find(c =>
// delete change
viewZone?.detail.afterLineNumber === c.modifiedStartLineNumber ||
// other changes
(c.modifiedEndLineNumber > 0 && c.modifiedStartLineNumber === lineNumber));
if (change) {
this.revertChange(change);
}
event.event.stopPropagation();
this._updateDecorations();
return;
}
}));*/
return editor;
}
protected _createInnerEditor(instantiationService: IInstantiationService, container: HTMLElement, options: Readonly<IEditorConstructionOptions>, editorWidgetOptions: ICodeEditorWidgetOptions): CodeEditorWidget {
const editor = instantiationService.createInstance(CodeEditorWidget, container, options, editorWidgetOptions);
this._register(editor.onDidContentSizeChange(e => {
const width = this._originalEditor.getContentWidth() + this._modifiedEditor.getContentWidth() + OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH;
const height = Math.max(this._modifiedEditor.getContentHeight(), this._originalEditor.getContentHeight());
this._onDidContentSizeChange.fire({
contentHeight: height,
contentWidth: width,
contentHeightChanged: e.contentHeightChanged,
contentWidthChanged: e.contentWidthChanged
});
}));
this._register(editor.onDidScrollChange((e) => {
if (this._isHandlingScrollEvent) {
return;
}
if (!e.scrollTopChanged && !e.scrollLeftChanged && !e.scrollHeightChanged) {
return;
}
this._isHandlingScrollEvent = true;
try {
const otherEditor = editor === this._originalEditor ? this._modifiedEditor : this._originalEditor;
otherEditor.setScrollPosition({
scrollLeft: e.scrollLeft,
scrollTop: e.scrollTop
});
} finally {
this._isHandlingScrollEvent = false;
}
}));
return editor;
}
private _adjustOptionsForLeftHandSide(options: Readonly<IDiffEditorConstructionOptions>): IEditorConstructionOptions {
const result = this._adjustOptionsForSubEditor(options);
if (!options.renderSideBySide) {
// never wrap hidden editor
result.wordWrapOverride1 = 'off';
result.wordWrapOverride2 = 'off';
} else {
result.wordWrapOverride1 = this._options.get().diffWordWrap;
}
if (options.originalAriaLabel) {
result.ariaLabel = options.originalAriaLabel;
}
result.ariaLabel = this._updateAriaLabel(result.ariaLabel);
result.readOnly = !options.originalEditable;
result.dropIntoEditor = { enabled: !result.readOnly };
result.extraEditorClassName = 'original-in-monaco-diff-editor';
return result;
}
private _adjustOptionsForRightHandSide(options: Readonly<IDiffEditorConstructionOptions>): IEditorConstructionOptions {
const result = this._adjustOptionsForSubEditor(options);
if (options.modifiedAriaLabel) {
result.ariaLabel = options.modifiedAriaLabel;
}
result.ariaLabel = this._updateAriaLabel(result.ariaLabel);
result.wordWrapOverride1 = this._options.get().diffWordWrap;
result.revealHorizontalRightPadding = EditorOptions.revealHorizontalRightPadding.defaultValue + OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH;
result.scrollbar!.verticalHasArrows = false;
result.extraEditorClassName = 'modified-in-monaco-diff-editor';
return result;
}
private _adjustOptionsForSubEditor(options: Readonly<IDiffEditorConstructionOptions>): IEditorConstructionOptions {
const clonedOptions = {
...options,
dimension: {
height: 0,
width: 0
},
};
clonedOptions.inDiffEditor = true;
clonedOptions.automaticLayout = false;
// Clone scrollbar options before changing them
clonedOptions.scrollbar = { ...(clonedOptions.scrollbar || {}) };
clonedOptions.scrollbar.vertical = 'visible';
clonedOptions.folding = false;
clonedOptions.codeLens = this._options.get().diffCodeLens;
clonedOptions.fixedOverflowWidgets = true;
// clonedOptions.lineDecorationsWidth = '2ch';
// Clone minimap options before changing them
clonedOptions.minimap = { ...(clonedOptions.minimap || {}) };
clonedOptions.minimap.enabled = false;
return clonedOptions;
}
private _updateAriaLabel(ariaLabel: string | undefined): string | undefined {
const ariaNavigationTip = localize('diff-aria-navigation-tip', ' use Shift + F7 to navigate changes');
if (this._options.get().accessibilityVerbose) {
return ariaLabel + ariaNavigationTip;
} else if (ariaLabel) {
return ariaLabel.replaceAll(ariaNavigationTip, '');
}
return undefined;
}
protected override get _targetEditor(): CodeEditorWidget { return this._modifiedEditor; }
override getEditorType(): string { return EditorType.IDiffEditor; }
override onVisible(): void {
// TODO: Only compute diffs when diff editor is visible
this._originalEditor.onVisible();
this._modifiedEditor.onVisible();
}
override onHide(): void {
this._originalEditor.onHide();
this._modifiedEditor.onHide();
}
override layout(dimension?: IDimension | undefined): void {
this._rootSizeObserver.observe(dimension);
}
override hasTextFocus(): boolean {
return this._originalEditor.hasTextFocus() || this._modifiedEditor.hasTextFocus();
}
override saveViewState(): IDiffEditorViewState | null {
return null;
//throw new Error('Method not implemented.');
}
override restoreViewState(state: IDiffEditorViewState | null): void {
//throw new Error('Method not implemented.');
}
override getModel(): IDiffEditorModel | null { return this._model.get(); }
override setModel(model: IDiffEditorModel | null): void {
this._originalEditor.setModel(model ? model.original : null);
this._modifiedEditor.setModel(model ? model.modified : null);
this._model.set(model, undefined);
this._diffModel.set(model ? new DiffModel(
model,
this._options.map(o => o.ignoreTrimWhitespace),
this._options.map(o => o.maxComputationTime),
this._options.map(o => o.experimental.collapseUnchangedRegions!),
this._instantiationService.createInstance(WorkerBasedDocumentDiffProvider, this._options.get())
) : null, undefined);
}
override updateOptions(_newOptions: IDiffEditorOptions): void {
const newOptions = validateDiffEditorOptions(_newOptions, this._options.get());
this._options.set(newOptions, undefined);
this._modifiedEditor.updateOptions(this._adjustOptionsForRightHandSide(_newOptions));
this._originalEditor.updateOptions(this._adjustOptionsForLeftHandSide(_newOptions));
}
getContainerDomNode(): HTMLElement { return this._domElement; }
getOriginalEditor(): ICodeEditor { return this._originalEditor; }
getModifiedEditor(): ICodeEditor { return this._modifiedEditor; }
setBoundarySashes(sashes: IBoundarySashes): void {
this._sash.setBoundarySashes(sashes);
}
readonly onDidUpdateDiff: Event<void> = e => {
return { dispose: () => { } };
};
get ignoreTrimWhitespace(): boolean {
return this._options.get().ignoreTrimWhitespace;
}
get maxComputationTime(): number {
return this._options.get().maxComputationTime;
}
get renderSideBySide(): boolean {
return this._options.get().renderSideBySide;
}
getLineChanges(): ILineChange[] | null {
return null;
//throw new Error('Method not implemented.');
}
getDiffComputationResult(): IDiffComputationResult | null {
return null;
//throw new Error('Method not implemented.');
}
getDiffLineInformationForOriginal(lineNumber: number): IDiffLineInformation | null {
return null;
//throw new Error('Method not implemented.');
}
getDiffLineInformationForModified(lineNumber: number): IDiffLineInformation | null {
return null;
//throw new Error('Method not implemented.');
}
private _goTo(diff: LineRangeMapping): void {
this._modifiedEditor.setPosition(new Position(diff.modifiedRange.startLineNumber, 1));
this._modifiedEditor.revealRangeInCenter(diff.modifiedRange.toExclusiveRange());
}
goToDiff(target: 'previous' | 'next'): void {
const diffs = this._diffModel.get()?.diff.get()?.changes;
if (!diffs || diffs.length === 0) {
return;
}
const curLineNumber = this._modifiedEditor.getPosition()!.lineNumber;
let diff: LineRangeMapping | undefined;
if (target === 'next') {
diff = diffs.find(d => d.modifiedRange.startLineNumber > curLineNumber) ?? diffs[0];
} else {
diff = findLast(diffs, d => d.modifiedRange.startLineNumber < curLineNumber) ?? diffs[diffs.length - 1];
}
this._goTo(diff);
}
revealFirstDiff(): void {
const diffModel = this._diffModel.get();
if (!diffModel) {
return;
}
// wait for the diff computation to finish
waitForState(diffModel.isDiffUpToDate, s => s).then(() => {
const diffs = diffModel.diff.get()?.changes;
if (!diffs || diffs.length === 0) {
return;
}
this._goTo(diffs[0]);
});
}
}
function validateDiffEditorOptions(options: Readonly<IDiffEditorOptions>, defaults: ValidDiffEditorBaseOptions): ValidDiffEditorBaseOptions {
return {
enableSplitViewResizing: validateBooleanOption(options.enableSplitViewResizing, defaults.enableSplitViewResizing),
splitViewDefaultRatio: clampedFloat(options.splitViewDefaultRatio, 0.5, 0.1, 0.9),
renderSideBySide: validateBooleanOption(options.renderSideBySide, defaults.renderSideBySide),
renderMarginRevertIcon: validateBooleanOption(options.renderMarginRevertIcon, defaults.renderMarginRevertIcon),
maxComputationTime: clampedInt(options.maxComputationTime, defaults.maxComputationTime, 0, Constants.MAX_SAFE_SMALL_INTEGER),
maxFileSize: clampedInt(options.maxFileSize, defaults.maxFileSize, 0, Constants.MAX_SAFE_SMALL_INTEGER),
ignoreTrimWhitespace: validateBooleanOption(options.ignoreTrimWhitespace, defaults.ignoreTrimWhitespace),
renderIndicators: validateBooleanOption(options.renderIndicators, defaults.renderIndicators),
originalEditable: validateBooleanOption(options.originalEditable, defaults.originalEditable),
diffCodeLens: validateBooleanOption(options.diffCodeLens, defaults.diffCodeLens),
renderOverviewRuler: validateBooleanOption(options.renderOverviewRuler, defaults.renderOverviewRuler),
diffWordWrap: validateStringSetOption<'off' | 'on' | 'inherit'>(options.diffWordWrap, defaults.diffWordWrap, ['off', 'on', 'inherit']),
diffAlgorithm: validateStringSetOption(options.diffAlgorithm, defaults.diffAlgorithm, ['legacy', 'advanced'], { 'smart': 'legacy', 'experimental': 'advanced' }),
accessibilityVerbose: validateBooleanOption(options.accessibilityVerbose, defaults.accessibilityVerbose),
experimental: {
collapseUnchangedRegions: validateBooleanOption(options.experimental?.collapseUnchangedRegions, defaults.experimental.collapseUnchangedRegions!),
},
};
}

View file

@ -0,0 +1,344 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { RunOnceScheduler } from 'vs/base/common/async';
import { Disposable } from 'vs/base/common/lifecycle';
import { IObservable, IReader, ITransaction, derived, observableSignal, observableSignalFromEvent, observableValue, transaction } from 'vs/base/common/observable';
import { autorunWithStore2 } from 'vs/base/common/observableImpl/autorun';
import { LineRange } from 'vs/editor/common/core/lineRange';
import { Range } from 'vs/editor/common/core/range';
import { IDocumentDiff, IDocumentDiffProvider } from 'vs/editor/common/diff/documentDiffProvider';
import { LineRangeMapping, RangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
import { lineRangeMappingFromRangeMappings } from 'vs/editor/common/diff/standardLinesDiffComputer';
import { IDiffEditorModel } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import { TextEditInfo } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper';
import { combineTextEditInfos } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/combineTextEditInfos';
import { lengthAdd, lengthDiffNonNegative, lengthOfRange, lengthToPosition, lengthZero, positionToLength } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length';
export class DiffModel extends Disposable {
private readonly _isDiffUpToDate = observableValue<boolean>('isDiffUpToDate', false);
public readonly isDiffUpToDate: IObservable<boolean> = this._isDiffUpToDate;
private readonly _diff = observableValue<IDocumentDiff | undefined>('diff', undefined);
public readonly diff: IObservable<IDocumentDiff | undefined> = this._diff;
private readonly _unchangedRegions = observableValue<{ regions: UnchangedRegion[]; originalDecorationIds: string[]; modifiedDecorationIds: string[] }>('unchangedRegion', { regions: [], originalDecorationIds: [], modifiedDecorationIds: [] });
public readonly unchangedRegions: IObservable<UnchangedRegion[]> = derived('unchangedRegions', r =>
this.hideUnchangedRegions.read(r) ? this._unchangedRegions.read(r).regions : []
);
constructor(
model: IDiffEditorModel,
ignoreTrimWhitespace: IObservable<boolean>,
maxComputationTimeMs: IObservable<number>,
private readonly hideUnchangedRegions: IObservable<boolean>,
documentDiffProvider: IDocumentDiffProvider,
) {
super();
const contentChangedSignal = observableSignal('contentChangedSignal');
const debouncer = this._register(new RunOnceScheduler(() => contentChangedSignal.trigger(undefined), 200));
this._register(model.modified.onDidChangeContent((e) => {
const diff = this._diff.get();
if (!diff) {
return;
}
const textEdits = TextEditInfo.fromModelContentChanges(e.changes);
this._diff.set(
applyModifiedEdits(diff, textEdits, model.original, model.modified),
undefined
);
debouncer.schedule();
}));
this._register(model.original.onDidChangeContent((e) => {
const diff = this._diff.get();
if (!diff) {
return;
}
const textEdits = TextEditInfo.fromModelContentChanges(e.changes);
this._diff.set(
applyOriginalEdits(diff, textEdits, model.original, model.modified),
undefined
);
debouncer.schedule();
}));
const documentDiffProviderOptionChanged = observableSignalFromEvent('documentDiffProviderOptionChanged', documentDiffProvider.onDidChange);
this._register(autorunWithStore2('compute diff', async (reader, store) => {
debouncer.cancel();
contentChangedSignal.read(reader);
documentDiffProviderOptionChanged.read(reader);
const ignoreTrimWhitespaceVal = ignoreTrimWhitespace.read(reader);
const maxComputationTimeMsVal = maxComputationTimeMs.read(reader);
this._isDiffUpToDate.set(false, undefined);
let originalTextEditInfos: TextEditInfo[] = [];
store.add(model.original.onDidChangeContent((e) => {
const edits = TextEditInfo.fromModelContentChanges(e.changes);
originalTextEditInfos = combineTextEditInfos(originalTextEditInfos, edits);
}));
let modifiedTextEditInfos: TextEditInfo[] = [];
store.add(model.modified.onDidChangeContent((e) => {
const edits = TextEditInfo.fromModelContentChanges(e.changes);
modifiedTextEditInfos = combineTextEditInfos(modifiedTextEditInfos, edits);
}));
let result = await documentDiffProvider.computeDiff(model.original, model.modified, {
ignoreTrimWhitespace: ignoreTrimWhitespaceVal,
maxComputationTimeMs: maxComputationTimeMsVal,
});
result = applyOriginalEdits(result, originalTextEditInfos, model.original, model.modified);
result = applyModifiedEdits(result, modifiedTextEditInfos, model.original, model.modified);
const newUnchangedRegions = UnchangedRegion.fromDiffs(result.changes, model.original.getLineCount(), model.modified.getLineCount());
// Transfer state from cur state
const lastUnchangedRegions = this._unchangedRegions.get();
const lastUnchangedRegionsOrigRanges = lastUnchangedRegions.originalDecorationIds
.map(id => model.original.getDecorationRange(id))
.filter(r => !!r)
.map(r => LineRange.fromRange(r!));
const lastUnchangedRegionsModRanges = lastUnchangedRegions.modifiedDecorationIds
.map(id => model.modified.getDecorationRange(id))
.filter(r => !!r)
.map(r => LineRange.fromRange(r!));
for (const r of newUnchangedRegions) {
for (let i = 0; i < lastUnchangedRegions.regions.length; i++) {
if (r.originalRange.intersectsStrict(lastUnchangedRegionsOrigRanges[i])
&& r.modifiedRange.intersectsStrict(lastUnchangedRegionsModRanges[i])) {
r.setState(
lastUnchangedRegions.regions[i].visibleLineCountTop.get(),
lastUnchangedRegions.regions[i].visibleLineCountBottom.get(),
undefined,
);
break;
}
}
}
const originalDecorationIds = model.original.deltaDecorations(
lastUnchangedRegions.originalDecorationIds,
newUnchangedRegions.map(r => ({ range: r.originalRange.toInclusiveRange()!, options: { description: 'unchanged' } }))
);
const modifiedDecorationIds = model.modified.deltaDecorations(
lastUnchangedRegions.modifiedDecorationIds,
newUnchangedRegions.map(r => ({ range: r.modifiedRange.toInclusiveRange()!, options: { description: 'unchanged' } }))
);
transaction(tx => {
this._diff.set(result, tx);
this._isDiffUpToDate.set(true, tx);
this._unchangedRegions.set(
{
regions: newUnchangedRegions,
originalDecorationIds,
modifiedDecorationIds
},
tx
);
});
}));
}
public revealModifiedLine(lineNumber: number, tx: ITransaction): void {
const unchangedRegions = this._unchangedRegions.get().regions;
for (const r of unchangedRegions) {
if (r.getHiddenModifiedRange(undefined).contains(lineNumber)) {
r.showAll(tx); // TODO only unhide what is needed
return;
}
}
}
public revealOriginalLine(lineNumber: number, tx: ITransaction): void {
const unchangedRegions = this._unchangedRegions.get().regions;
for (const r of unchangedRegions) {
if (r.getHiddenOriginalRange(undefined).contains(lineNumber)) {
r.showAll(tx); // TODO only unhide what is needed
return;
}
}
}
}
export class UnchangedRegion {
public static fromDiffs(changes: LineRangeMapping[], originalLineCount: number, modifiedLineCount: number): UnchangedRegion[] {
const inversedMappings = LineRangeMapping.inverse(changes, originalLineCount, modifiedLineCount);
const result: UnchangedRegion[] = [];
const minHiddenLineCount = 3;
const minContext = 3;
for (const mapping of inversedMappings) {
let origStart = mapping.originalRange.startLineNumber;
let modStart = mapping.modifiedRange.startLineNumber;
let length = mapping.originalRange.length;
if (origStart === 1 && length > minContext + minHiddenLineCount) {
length -= minContext;
result.push(new UnchangedRegion(origStart, modStart, length, 0, 0));
} else if (origStart + length === originalLineCount + 1 && length > minContext + minHiddenLineCount) {
origStart += minContext;
modStart += minContext;
length -= minContext;
result.push(new UnchangedRegion(origStart, modStart, length, 0, 0));
} else if (length > minContext * 2 + minHiddenLineCount) {
origStart += minContext;
modStart += minContext;
length -= minContext * 2;
result.push(new UnchangedRegion(origStart, modStart, length, 0, 0));
}
}
return result;
}
public get originalRange(): LineRange {
return LineRange.ofLength(this.originalLineNumber, this.lineCount);
}
public get modifiedRange(): LineRange {
return LineRange.ofLength(this.modifiedLineNumber, this.lineCount);
}
private readonly _visibleLineCountTop = observableValue<number>('visibleLineCountTop', 0);
public readonly visibleLineCountTop: IObservable<number> = this._visibleLineCountTop;
private readonly _visibleLineCountBottom = observableValue<number>('visibleLineCountBottom', 0);
public readonly visibleLineCountBottom: IObservable<number> = this._visibleLineCountBottom;
constructor(
public readonly originalLineNumber: number,
public readonly modifiedLineNumber: number,
public readonly lineCount: number,
visibleLineCountTop: number,
visibleLineCountBottom: number,
) {
this._visibleLineCountTop.set(visibleLineCountTop, undefined);
this._visibleLineCountBottom.set(visibleLineCountBottom, undefined);
}
public getHiddenOriginalRange(reader: IReader | undefined): LineRange {
return LineRange.ofLength(
this.originalLineNumber + this._visibleLineCountTop.read(reader),
this.lineCount - this._visibleLineCountTop.read(reader) - this._visibleLineCountBottom.read(reader),
);
}
public getHiddenModifiedRange(reader: IReader | undefined): LineRange {
return LineRange.ofLength(
this.modifiedLineNumber + this._visibleLineCountTop.read(reader),
this.lineCount - this._visibleLineCountTop.read(reader) - this._visibleLineCountBottom.read(reader),
);
}
public showMoreAbove(tx: ITransaction | undefined): void {
const maxVisibleLineCountTop = this.lineCount - this._visibleLineCountBottom.get();
this._visibleLineCountTop.set(Math.min(this._visibleLineCountTop.get() + 10, maxVisibleLineCountTop), tx);
}
public showMoreBelow(tx: ITransaction | undefined): void {
const maxVisibleLineCountBottom = this.lineCount - this._visibleLineCountTop.get();
this._visibleLineCountBottom.set(Math.min(this._visibleLineCountBottom.get() + 10, maxVisibleLineCountBottom), tx);
}
public showAll(tx: ITransaction | undefined): void {
this._visibleLineCountBottom.set(this.lineCount - this._visibleLineCountTop.get(), tx);
}
public setState(visibleLineCountTop: number, visibleLineCountBottom: number, tx: ITransaction | undefined): void {
visibleLineCountTop = Math.min(visibleLineCountTop, this.lineCount);
visibleLineCountBottom = Math.min(visibleLineCountBottom, this.lineCount - visibleLineCountTop);
this._visibleLineCountTop.set(visibleLineCountTop, tx);
this._visibleLineCountBottom.set(visibleLineCountBottom, tx);
}
}
function applyOriginalEdits(diff: IDocumentDiff, textEdits: TextEditInfo[], originalTextModel: ITextModel, modifiedTextModel: ITextModel): IDocumentDiff {
if (textEdits.length === 0) {
return diff;
}
const diffTextEdits = diff.changes.flatMap(c => c.innerChanges!.map(c => new TextEditInfo(
positionToLength(c.modifiedRange.getStartPosition()),
positionToLength(c.modifiedRange.getEndPosition()),
lengthOfRange(c.originalRange).toLength(),
)));
const combined = combineTextEditInfos(diffTextEdits, textEdits);
let lastModifiedEndOffset = lengthZero;
let lastOriginalEndOffset = lengthZero;
const rangeMappings = combined.map(c => {
const originalStartOffset = lengthAdd(lastOriginalEndOffset, lengthDiffNonNegative(lastModifiedEndOffset, c.startOffset));
lastModifiedEndOffset = c.endOffset;
lastOriginalEndOffset = lengthAdd(originalStartOffset, c.newLength);
return new RangeMapping(
Range.fromPositions(lengthToPosition(originalStartOffset), lengthToPosition(lastOriginalEndOffset)),
Range.fromPositions(lengthToPosition(c.startOffset), lengthToPosition(c.endOffset)),
);
});
const changes = lineRangeMappingFromRangeMappings(
rangeMappings,
originalTextModel.getLinesContent(),
modifiedTextModel.getLinesContent(),
);
return {
identical: false,
quitEarly: false,
changes,
};
}
function applyModifiedEdits(diff: IDocumentDiff, textEdits: TextEditInfo[], originalTextModel: ITextModel, modifiedTextModel: ITextModel): IDocumentDiff {
if (textEdits.length === 0) {
return diff;
}
const diffTextEdits = diff.changes.flatMap(c => c.innerChanges!.map(c => new TextEditInfo(
positionToLength(c.originalRange.getStartPosition()),
positionToLength(c.originalRange.getEndPosition()),
lengthOfRange(c.modifiedRange).toLength(),
)));
const combined = combineTextEditInfos(diffTextEdits, textEdits);
let lastOriginalEndOffset = lengthZero;
let lastModifiedEndOffset = lengthZero;
const rangeMappings = combined.map(c => {
const modifiedStartOffset = lengthAdd(lastModifiedEndOffset, lengthDiffNonNegative(lastOriginalEndOffset, c.startOffset));
lastOriginalEndOffset = c.endOffset;
lastModifiedEndOffset = lengthAdd(modifiedStartOffset, c.newLength);
return new RangeMapping(
Range.fromPositions(lengthToPosition(c.startOffset), lengthToPosition(c.endOffset)),
Range.fromPositions(lengthToPosition(modifiedStartOffset), lengthToPosition(lastModifiedEndOffset)),
);
});
const changes = lineRangeMappingFromRangeMappings(
rangeMappings,
originalTextModel.getLinesContent(),
modifiedTextModel.getLinesContent(),
);
return {
identical: false,
quitEarly: false,
changes,
};
}

View file

@ -0,0 +1,237 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ArrayQueue } from 'vs/base/common/arrays';
import { Disposable } from 'vs/base/common/lifecycle';
import { IObservable, observableSignalFromEvent, derived } from 'vs/base/common/observable';
import { autorunWithStore2 } from 'vs/base/common/observableImpl/autorun';
import { IViewZone } from 'vs/editor/browser/editorBrowser';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { DiffModel } from 'vs/editor/browser/widget/diffEditorWidget2/diffModel';
import { joinCombine } from 'vs/editor/browser/widget/diffEditorWidget2/utils';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { LineRange } from 'vs/editor/common/core/lineRange';
import { Position } from 'vs/editor/common/core/position';
import { LineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
export class ViewZoneAlignment extends Disposable {
constructor(
private readonly _originalEditor: CodeEditorWidget,
private readonly _modifiedEditor: CodeEditorWidget,
private readonly _diffModel: IObservable<DiffModel | null>,
) {
super();
let isChangingViewZones = false;
const origViewZonesChanged = observableSignalFromEvent(
'origViewZonesChanged',
e => this._originalEditor.onDidChangeViewZones((args) => { if (!isChangingViewZones) { e(args); } })
);
const modViewZonesChanged = observableSignalFromEvent(
'modViewZonesChanged',
e => this._modifiedEditor.onDidChangeViewZones((args) => { if (!isChangingViewZones) { e(args); } })
);
const alignmentViewZoneIdsOrig = new Set<string>();
const alignmentViewZoneIdsMod = new Set<string>();
const alignments = derived<IRangeAlignment[] | null>('alignments', (reader) => {
const diff = this._diffModel.read(reader)?.diff.read(reader);
if (!diff) { return null; }
origViewZonesChanged.read(reader);
modViewZonesChanged.read(reader);
return computeRangeAlignment(this._originalEditor, this._modifiedEditor, diff.changes, alignmentViewZoneIdsOrig, alignmentViewZoneIdsMod);
});
function createFakeLinesDiv(): HTMLElement {
const r = document.createElement('div');
r.className = 'diagonal-fill';
return r;
}
const alignmentViewZones = derived<{ orig: IViewZone[]; mod: IViewZone[] }>('alignment viewzones', (reader) => {
const alignments_ = alignments.read(reader);
const origViewZones: IViewZone[] = [];
const modViewZones: IViewZone[] = [];
if (alignments_) {
for (const a of alignments_) {
const delta = a.modifiedHeightInPx - a.originalHeightInPx;
if (delta > 0) {
origViewZones.push({
afterLineNumber: a.originalRange.endLineNumberExclusive - 1,
domNode: createFakeLinesDiv(),
heightInPx: delta,
});
} else {
modViewZones.push({
afterLineNumber: a.modifiedRange.endLineNumberExclusive - 1,
domNode: createFakeLinesDiv(),
heightInPx: -delta,
});
}
}
}
return { orig: origViewZones, mod: modViewZones };
});
this._register(autorunWithStore2('alignment viewzones', (reader) => {
const alignmentViewZones_ = alignmentViewZones.read(reader);
isChangingViewZones = true;
this._originalEditor.changeViewZones((aOrig) => {
for (const id of alignmentViewZoneIdsOrig) { aOrig.removeZone(id); }
alignmentViewZoneIdsOrig.clear();
for (const z of alignmentViewZones_.orig) { alignmentViewZoneIdsOrig.add(aOrig.addZone(z)); }
});
this._modifiedEditor.changeViewZones(aMod => {
for (const id of alignmentViewZoneIdsMod) { aMod.removeZone(id); }
alignmentViewZoneIdsMod.clear();
for (const z of alignmentViewZones_.mod) { alignmentViewZoneIdsMod.add(aMod.addZone(z)); }
});
isChangingViewZones = false;
}));
}
}
interface AdditionalLineHeightInfo {
lineNumber: number;
heightInPx: number;
}
function getAdditionalLineHeights(editor: CodeEditorWidget, viewZonesToIgnore: ReadonlySet<string>): readonly AdditionalLineHeightInfo[] {
const viewZoneHeights: { lineNumber: number; heightInPx: number }[] = [];
const wrappingZoneHeights: { lineNumber: number; heightInPx: number }[] = [];
const hasWrapping = editor.getOption(EditorOption.wrappingInfo).wrappingColumn !== -1;
const coordinatesConverter = editor._getViewModel()!.coordinatesConverter;
if (hasWrapping) {
for (let i = 1; i <= editor.getModel()!.getLineCount(); i++) {
const lineCount = coordinatesConverter.getModelLineViewLineCount(i);
if (lineCount > 1) {
wrappingZoneHeights.push({ lineNumber: i, heightInPx: lineCount - 1 });
}
}
}
for (const w of editor.getWhitespaces()) {
if (viewZonesToIgnore.has(w.id)) {
continue;
}
const modelLineNumber = coordinatesConverter.convertViewPositionToModelPosition(
new Position(w.afterLineNumber, 1)
).lineNumber;
viewZoneHeights.push({ lineNumber: modelLineNumber, heightInPx: w.height });
}
const result = joinCombine(
viewZoneHeights,
wrappingZoneHeights,
v => v.lineNumber,
(v1, v2) => ({ lineNumber: v1.lineNumber, heightInPx: v1.heightInPx + v2.heightInPx })
);
return result;
}
interface IRangeAlignment {
originalRange: LineRange;
modifiedRange: LineRange;
// accounts for foreign viewzones and line wrapping
originalHeightInPx: number;
modifiedHeightInPx: number;
}
function computeRangeAlignment(
originalEditor: CodeEditorWidget,
modifiedEditor: CodeEditorWidget,
diffs: LineRangeMapping[],
originalEditorAlignmentViewZones: ReadonlySet<string>,
modifiedEditorAlignmentViewZones: ReadonlySet<string>,
): IRangeAlignment[] {
const originalLineHeightOverrides = new ArrayQueue(getAdditionalLineHeights(originalEditor, originalEditorAlignmentViewZones));
const modifiedLineHeightOverrides = new ArrayQueue(getAdditionalLineHeights(modifiedEditor, modifiedEditorAlignmentViewZones));
const origLineHeight = originalEditor.getOption(EditorOption.lineHeight);
const modLineHeight = modifiedEditor.getOption(EditorOption.lineHeight);
const result: IRangeAlignment[] = [];
let lastOriginalLineNumber = 0;
let lastModifiedLineNumber = 0;
function handleAlignmentsOutsideOfDiffs(untilOriginalLineNumberExclusive: number, untilModifiedLineNumberExclusive: number) {
while (true) {
let origNext = originalLineHeightOverrides.peek();
let modNext = modifiedLineHeightOverrides.peek();
if (origNext && origNext.lineNumber >= untilOriginalLineNumberExclusive) {
origNext = undefined;
}
if (modNext && modNext.lineNumber >= untilModifiedLineNumberExclusive) {
modNext = undefined;
}
if (!origNext && !modNext) {
break;
}
const distOrig = origNext ? origNext.lineNumber - lastOriginalLineNumber : Number.MAX_VALUE;
const distNext = modNext ? modNext.lineNumber - lastModifiedLineNumber : Number.MAX_VALUE;
if (distOrig < distNext) {
originalLineHeightOverrides.dequeue();
modNext = {
lineNumber: origNext!.lineNumber - lastOriginalLineNumber + lastModifiedLineNumber,
heightInPx: 0,
};
} else if (distOrig > distNext) {
modifiedLineHeightOverrides.dequeue();
origNext = {
lineNumber: modNext!.lineNumber - lastModifiedLineNumber + lastOriginalLineNumber,
heightInPx: 0,
};
} else {
originalLineHeightOverrides.dequeue();
modifiedLineHeightOverrides.dequeue();
}
result.push({
originalRange: LineRange.ofLength(origNext!.lineNumber, 1),
modifiedRange: LineRange.ofLength(modNext!.lineNumber, 1),
originalHeightInPx: origLineHeight + origNext!.heightInPx,
modifiedHeightInPx: modLineHeight + modNext!.heightInPx,
});
}
}
for (const c of diffs) {
handleAlignmentsOutsideOfDiffs(c.originalRange.startLineNumber, c.modifiedRange.startLineNumber);
const originalAdditionalHeight = originalLineHeightOverrides
.takeWhile(v => v.lineNumber < c.originalRange.endLineNumberExclusive)
?.reduce((p, c) => p + c.heightInPx, 0) ?? 0;
const modifiedAdditionalHeight = modifiedLineHeightOverrides
.takeWhile(v => v.lineNumber < c.modifiedRange.endLineNumberExclusive)
?.reduce((p, c) => p + c.heightInPx, 0) ?? 0;
result.push({
originalRange: c.originalRange,
modifiedRange: c.modifiedRange,
originalHeightInPx: c.originalRange.length * origLineHeight + originalAdditionalHeight,
modifiedHeightInPx: c.modifiedRange.length * modLineHeight + modifiedAdditionalHeight,
});
lastOriginalLineNumber = c.originalRange.endLineNumberExclusive;
lastModifiedLineNumber = c.modifiedRange.endLineNumberExclusive;
}
handleAlignmentsOutsideOfDiffs(Number.MAX_VALUE, Number.MAX_VALUE);
return result;
}

View file

@ -0,0 +1,149 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { EventType, addDisposableListener, addStandardDisposableListener, h } from 'vs/base/browser/dom';
import { createFastDomNode } from 'vs/base/browser/fastDomNode';
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
import { Color } from 'vs/base/common/color';
import { Disposable } from 'vs/base/common/lifecycle';
import { IObservable, autorun, derived, observableFromEvent } from 'vs/base/common/observable';
import { autorunWithStore2 } from 'vs/base/common/observableImpl/autorun';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { DiffModel } from 'vs/editor/browser/widget/diffEditorWidget2/diffModel';
import { appendRemoveOnDispose } from 'vs/editor/browser/widget/diffEditorWidget2/utils';
import { EditorLayoutInfo } from 'vs/editor/common/config/editorOptions';
import { LineRange } from 'vs/editor/common/core/lineRange';
import { OverviewRulerZone } from 'vs/editor/common/viewModel/overviewZoneManager';
import { defaultInsertColor, defaultRemoveColor, diffInserted, diffOverviewRulerInserted, diffOverviewRulerRemoved, diffRemoved } from 'vs/platform/theme/common/colorRegistry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
export class OverviewRulerPart extends Disposable {
public static readonly ONE_OVERVIEW_WIDTH = 15;
public static readonly ENTIRE_DIFF_OVERVIEW_WIDTH = OverviewRulerPart.ONE_OVERVIEW_WIDTH * 2;
constructor(
private readonly _originalEditor: CodeEditorWidget,
private readonly _modifiedEditor: CodeEditorWidget,
private readonly _rootElement: HTMLElement,
private readonly _diffModel: IObservable<DiffModel | null>,
private readonly _rootWidth: IObservable<number>,
private readonly _rootHeight: IObservable<number>,
private readonly _modifiedEditorLayoutInfo: IObservable<EditorLayoutInfo | null>,
public readonly renderOverviewRuler: IObservable<boolean>,
@IThemeService private readonly _themeService: IThemeService,
) {
super();
const currentColorTheme = observableFromEvent(this._themeService.onDidColorThemeChange, () => this._themeService.getColorTheme());
const currentColors = derived('colors', reader => {
const theme = currentColorTheme.read(reader);
const insertColor = theme.getColor(diffOverviewRulerInserted) || (theme.getColor(diffInserted) || defaultInsertColor).transparent(2);
const removeColor = theme.getColor(diffOverviewRulerRemoved) || (theme.getColor(diffRemoved) || defaultRemoveColor).transparent(2);
return { insertColor, removeColor };
});
const scrollTopObservable = observableFromEvent(this._modifiedEditor.onDidScrollChange, () => this._modifiedEditor.getScrollTop());
const scrollHeightObservable = observableFromEvent(this._modifiedEditor.onDidScrollChange, () => this._modifiedEditor.getScrollHeight());
// overview ruler
this._register(autorunWithStore2('create diff editor overview ruler if enabled', (reader, store) => {
if (!this.renderOverviewRuler.read(reader)) {
return;
}
const viewportDomElement = createFastDomNode(document.createElement('div'));
viewportDomElement.setClassName('diffViewport');
viewportDomElement.setPosition('absolute');
const diffOverviewRoot = h('div.diffOverview', {
style: { position: 'absolute', top: '0px', width: OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH + 'px' }
}).root;
store.add(appendRemoveOnDispose(diffOverviewRoot, viewportDomElement.domNode));
store.add(addStandardDisposableListener(diffOverviewRoot, EventType.POINTER_DOWN, (e) => {
this._modifiedEditor.delegateVerticalScrollbarPointerDown(e);
}));
store.add(addDisposableListener(diffOverviewRoot, EventType.MOUSE_WHEEL, (e: IMouseWheelEvent) => {
this._modifiedEditor.delegateScrollFromMouseWheelEvent(e);
}, { passive: false }));
store.add(appendRemoveOnDispose(this._rootElement, diffOverviewRoot));
store.add(autorunWithStore2('recreate overview rules when model changes', (reader, store) => {
const m = this._diffModel.read(reader);
const originalOverviewRuler = this._originalEditor.createOverviewRuler('original diffOverviewRuler');
if (originalOverviewRuler) {
store.add(originalOverviewRuler);
store.add(appendRemoveOnDispose(diffOverviewRoot, originalOverviewRuler.getDomNode()));
}
const modifiedOverviewRuler = this._modifiedEditor.createOverviewRuler('modified diffOverviewRuler');
if (modifiedOverviewRuler) {
store.add(modifiedOverviewRuler);
store.add(appendRemoveOnDispose(diffOverviewRoot, modifiedOverviewRuler.getDomNode()));
}
if (!originalOverviewRuler || !modifiedOverviewRuler) {
// probably no model
return;
}
store.add(autorun('set overview ruler zones', (reader) => {
const colors = currentColors.read(reader);
const diff = m?.diff.read(reader)?.changes;
function createZones(ranges: LineRange[], color: Color) {
return ranges
.filter(d => d.length > 0)
.map(r => new OverviewRulerZone(r.startLineNumber, r.endLineNumberExclusive, r.length, color.toString()));
}
originalOverviewRuler?.setZones(createZones((diff || []).map(d => d.originalRange), colors.removeColor));
modifiedOverviewRuler?.setZones(createZones((diff || []).map(d => d.modifiedRange), colors.insertColor));
}));
store.add(autorun('layout overview ruler', (reader) => {
const height = this._rootHeight.read(reader);
const width = this._rootWidth.read(reader);
const layoutInfo = this._modifiedEditorLayoutInfo.read(reader);
if (layoutInfo) {
const freeSpace = OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH - 2 * OverviewRulerPart.ONE_OVERVIEW_WIDTH;
originalOverviewRuler.setLayout({
top: 0,
height: height,
right: freeSpace + OverviewRulerPart.ONE_OVERVIEW_WIDTH,
width: OverviewRulerPart.ONE_OVERVIEW_WIDTH,
});
modifiedOverviewRuler.setLayout({
top: 0,
height: height,
right: 0,
width: OverviewRulerPart.ONE_OVERVIEW_WIDTH,
});
const scrollTop = scrollTopObservable.read(reader);
const scrollHeight = scrollHeightObservable.read(reader);
const computedAvailableSize = Math.max(0, layoutInfo.height);
const computedRepresentableSize = Math.max(0, computedAvailableSize - 2 * 0);
const computedRatio = scrollHeight > 0 ? (computedRepresentableSize / scrollHeight) : 0;
const computedSliderSize = Math.max(0, Math.floor(layoutInfo.height * computedRatio));
const computedSliderPosition = Math.floor(scrollTop * computedRatio);
viewportDomElement.setTop(computedSliderPosition);
viewportDomElement.setHeight(computedSliderSize);
} else {
viewportDomElement.setTop(0);
viewportDomElement.setHeight(0);
}
diffOverviewRoot.style.height = height + 'px';
diffOverviewRoot.style.left = (width - OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH) + 'px';
viewportDomElement.setWidth(OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH);
}));
}));
}));
}
}

View file

@ -0,0 +1,61 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.diff-hidden-lines.showTop {
margin: -5px 0;
}
.diff-hidden-lines.showTop .top {
height: 7px;
background-color: var(--vscode-diffEditor-unchangedRegionBackground);
mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='7' viewBox='0 0 10 10' preserveAspectRatio='none'> <polygon points='0,10 5,0 10,10' style='fill:white' /> </svg>") repeat 0px 0px;
-webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='7' viewBox='0 0 10 10' preserveAspectRatio='none'> <polygon points='0,10 5,0 10,10' style='fill:white' /> </svg>") repeat 0px 0px;
}
.diff-hidden-lines.showBottom .bottom {
height: 7px;
background-color: var(--vscode-diffEditor-unchangedRegionBackground);
mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='7' viewBox='0 0 10 10' preserveAspectRatio='none'> <polygon points='0,0 5,10 10,0' style='fill:white' /> </svg>") repeat 10px 0px;
-webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='7' viewBox='0 0 10 10' preserveAspectRatio='none'> <polygon points='0,0 5,10 10,0' style='fill:white' /> </svg>") repeat 10px 0px;
}
.diff-hidden-lines .center {
background: var(--vscode-diffEditor-unchangedRegionBackground);
padding: 0px 3px;
overflow: hidden;
display: block;
text-overflow: ellipsis;
white-space: nowrap;
}
.diff-hidden-lines .center > span,
.diff-hidden-lines .center > a {
user-select: none;
-webkit-user-select: none;
white-space: nowrap;
}
.diff-hidden-lines .center > a {
text-decoration: none;
}
.diff-hidden-lines .center > a:hover {
cursor: pointer;
color: var(--vscode-editorLink-activeForeground) !important;
}
.diff-hidden-lines .center > a:hover .codicon {
color: var(--vscode-editorLink-activeForeground) !important;
}
.diff-hidden-lines .center .codicon {
vertical-align: middle;
color: currentColor !important;
color: var(--vscode-editorCodeLens-foreground);
}
.merge-editor-conflict-actions > a:hover .codicon::before {
cursor: pointer;
}

View file

@ -0,0 +1,224 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { $, h, reset } from 'vs/base/browser/dom';
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { Disposable } from 'vs/base/common/lifecycle';
import { IObservable, transaction, constObservable } from 'vs/base/common/observable';
import { autorun, autorunWithStore2 } from 'vs/base/common/observableImpl/autorun';
import { isDefined } from 'vs/base/common/types';
import { ICodeEditor, IOverlayWidget, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { DiffModel } from 'vs/editor/browser/widget/diffEditorWidget2/diffModel';
export class UnchangedRangesFeature extends Disposable {
constructor(
private readonly _originalEditor: CodeEditorWidget,
private readonly _modifiedEditor: CodeEditorWidget,
private readonly _diffModel: IObservable<DiffModel | null>,
) {
super();
const unchangedRegionViewZoneIdsOrig: string[] = [];
const unchangedRegionViewZoneIdsMod: string[] = [];
this._register(this._originalEditor.onDidChangeCursorPosition(e => {
const m = this._diffModel.get();
transaction(tx => {
for (const s of this._originalEditor.getSelections() || []) {
m?.revealOriginalLine(s.getStartPosition().lineNumber, tx);
m?.revealOriginalLine(s.getEndPosition().lineNumber, tx);
}
});
}));
this._register(this._modifiedEditor.onDidChangeCursorPosition(e => {
const m = this._diffModel.get();
transaction(tx => {
for (const s of this._modifiedEditor.getSelections() || []) {
m?.revealModifiedLine(s.getStartPosition().lineNumber, tx);
m?.revealModifiedLine(s.getEndPosition().lineNumber, tx);
}
});
}));
this._register(autorunWithStore2('update folded unchanged regions', (reader, store) => {
const unchangedRegions = this._diffModel.read(reader)?.unchangedRegions.read(reader);
if (!unchangedRegions) {
return;
}
// TODO@hediet This might cause unnecessary updates of alignment viewzones if this runs too late
this._originalEditor.changeViewZones((aOrig) => {
this._modifiedEditor.changeViewZones(aMod => {
for (const id of unchangedRegionViewZoneIdsOrig) {
aOrig.removeZone(id);
}
unchangedRegionViewZoneIdsOrig.length = 0;
for (const id of unchangedRegionViewZoneIdsMod) {
aMod.removeZone(id);
}
unchangedRegionViewZoneIdsMod.length = 0;
for (const r of unchangedRegions) {
const atTop = r.modifiedLineNumber !== 1;
const atBottom = r.modifiedRange.endLineNumberExclusive !== this._modifiedEditor.getModel()!.getLineCount() + 1;
const hiddenOriginalRange = r.getHiddenOriginalRange(reader);
const hiddenModifiedRange = r.getHiddenModifiedRange(reader);
if (hiddenOriginalRange.isEmpty) {
continue;
}
store.add(new CollapsedCodeActionsContentWidget(this._originalEditor, aOrig, hiddenOriginalRange.startLineNumber - 1, 30, constObservable<IContentWidgetAction[]>([
{
text: `${hiddenOriginalRange.length} Lines Hidden`
},
{
text: '$(chevron-up) Show More',
async action() { r.showMoreAbove(undefined); },
},
{
text: '$(chevron-down) Show More',
async action() { r.showMoreBelow(undefined); },
},
{
text: '$(close) Show All',
async action() { r.showAll(undefined); },
}
]), unchangedRegionViewZoneIdsOrig, atTop, atBottom));
store.add(new CollapsedCodeActionsContentWidget(this._modifiedEditor, aMod, hiddenModifiedRange.startLineNumber - 1, 30, constObservable<IContentWidgetAction[]>([
{
text: '$(chevron-up) Show More',
async action() { r.showMoreAbove(undefined); },
},
{
text: '$(chevron-down) Show More',
async action() { r.showMoreBelow(undefined); },
},
{
text: '$(close) Show All',
async action() { r.showAll(undefined); },
}
]), unchangedRegionViewZoneIdsMod, atTop, atBottom));
}
});
});
this._originalEditor.setHiddenAreas(unchangedRegions.map(r => r.getHiddenOriginalRange(reader).toInclusiveRange()).filter(isDefined));
this._modifiedEditor.setHiddenAreas(unchangedRegions.map(r => r.getHiddenModifiedRange(reader).toInclusiveRange()).filter(isDefined));
}));
}
}
// TODO@hediet avoid code duplication with FixedZoneWidget in merge editor
abstract class FixedZoneWidget extends Disposable {
private static counter = 0;
private readonly overlayWidgetId = `fixedZoneWidget-${FixedZoneWidget.counter++}`;
private readonly viewZoneId: string;
protected readonly widgetDomNode = h('div.fixed-zone-widget').root;
private readonly overlayWidget: IOverlayWidget = {
getId: () => this.overlayWidgetId,
getDomNode: () => this.widgetDomNode,
getPosition: () => null
};
constructor(
private readonly editor: ICodeEditor,
viewZoneAccessor: IViewZoneChangeAccessor,
afterLineNumber: number,
height: number,
viewZoneIdsToCleanUp: string[],
) {
super();
this.viewZoneId = viewZoneAccessor.addZone({
domNode: document.createElement('div'),
afterLineNumber: afterLineNumber,
heightInPx: height,
onComputedHeight: (height) => {
this.widgetDomNode.style.height = `${height}px`;
},
onDomNodeTop: (top) => {
this.widgetDomNode.style.top = `${top}px`;
},
showInHiddenAreas: true,
});
viewZoneIdsToCleanUp.push(this.viewZoneId);
this.widgetDomNode.style.left = this.editor.getLayoutInfo().contentLeft + 'px';
this.editor.addOverlayWidget(this.overlayWidget);
this._register({
dispose: () => {
this.editor.removeOverlayWidget(this.overlayWidget);
},
});
}
}
class CollapsedCodeActionsContentWidget extends FixedZoneWidget {
private readonly _domNode = h('div.diff-hidden-lines', { className: [this.showTopZigZag ? 'showTop' : '', this.showBottomZigZag ? 'showBottom' : ''].join(' ') }, [
h('div.top'),
h('div.center@content'),
h('div.bottom'),
]);
constructor(
editor: ICodeEditor,
viewZoneAccessor: IViewZoneChangeAccessor,
afterLineNumber: number,
height: number,
items: IObservable<IContentWidgetAction[]>,
viewZoneIdsToCleanUp: string[],
public readonly showTopZigZag: boolean,
public readonly showBottomZigZag: boolean,
) {
super(editor, viewZoneAccessor, afterLineNumber, height, viewZoneIdsToCleanUp);
this.widgetDomNode.appendChild(this._domNode.root);
this._register(autorun('update commands', (reader) => {
const i = items.read(reader);
this.setState(i);
}));
}
private setState(items: IContentWidgetAction[]) {
const children: HTMLElement[] = [];
let isFirst = true;
for (const item of items) {
if (isFirst) {
isFirst = false;
} else {
children.push($('span', undefined, '\u00a0|\u00a0'));
}
const title = renderLabelWithIcons(item.text);
if (item.action) {
children.push($('a', { title: item.tooltip, role: 'button', onclick: () => item.action!() }, ...title));
} else {
children.push($('span', { title: item.tooltip }, ...title));
}
}
reset(this._domNode.content, ...children);
}
}
interface IContentWidgetAction {
text: string;
tooltip?: string;
action?: () => Promise<void>;
}

View file

@ -0,0 +1,121 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDimension } from 'vs/base/browser/dom';
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { IObservable, ISettableObservable, autorun, observableFromEvent, observableValue, transaction } from 'vs/base/common/observable';
import { ElementSizeObserver } from 'vs/editor/browser/config/elementSizeObserver';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IModelDeltaDecoration } from 'vs/editor/common/model';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export function joinCombine<T>(arr1: readonly T[], arr2: readonly T[], keySelector: (val: T) => number, combine: (v1: T, v2: T) => T): readonly T[] {
if (arr1.length === 0) {
return arr2;
}
if (arr2.length === 0) {
return arr1;
}
const result: T[] = [];
let i = 0;
let j = 0;
while (i < arr1.length && j < arr2.length) {
const val1 = arr1[i];
const val2 = arr2[j];
const key1 = keySelector(val1);
const key2 = keySelector(val2);
if (key1 < key2) {
result.push(val1);
i++;
} else if (key1 > key2) {
result.push(val2);
j++;
} else {
result.push(combine(val1, val2));
i++;
j++;
}
}
while (i < arr1.length) {
result.push(arr1[i]);
i++;
}
while (j < arr2.length) {
result.push(arr2[j]);
j++;
}
return result;
}
// TODO make utility
export function applyObservableDecorations(editor: ICodeEditor, decorations: IObservable<IModelDeltaDecoration[]>): IDisposable {
const d = new DisposableStore();
const decorationsCollection = editor.createDecorationsCollection();
d.add(autorun(`Apply decorations from ${decorations.debugName}`, reader => {
const d = decorations.read(reader);
decorationsCollection.set(d);
}));
d.add({
dispose: () => {
decorationsCollection.clear();
}
});
return d;
}
export function appendRemoveOnDispose(parent: HTMLElement, child: HTMLElement) {
parent.appendChild(child);
return toDisposable(() => {
parent.removeChild(child);
});
}
export function observableConfigValue<T>(key: string, defaultValue: T, configurationService: IConfigurationService): IObservable<T> {
return observableFromEvent(
(handleChange) => configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(key)) {
handleChange(e);
}
}),
() => configurationService.getValue<T>(key) ?? defaultValue,
);
}
export class ObservableElementSizeObserver extends Disposable {
private readonly elementSizeObserver: ElementSizeObserver;
private readonly _width: ISettableObservable<number>;
public get width(): ISettableObservable<number> { return this._width; }
private readonly _height: ISettableObservable<number>;
public get height(): ISettableObservable<number> { return this._height; }
constructor(element: HTMLElement | null, dimension: IDimension | undefined) {
super();
this.elementSizeObserver = this._register(new ElementSizeObserver(element, dimension));
this._width = observableValue('width', this.elementSizeObserver.getWidth());
this._height = observableValue('height', this.elementSizeObserver.getHeight());
this._register(this.elementSizeObserver.onDidChange(e => transaction(tx => {
this._width.set(this.elementSizeObserver.getWidth(), tx);
this._height.set(this.elementSizeObserver.getHeight(), tx);
})));
}
public observe(dimension?: IDimension): void {
this.elementSizeObserver.observe(dimension);
}
public setAutomaticLayout(automaticLayout: boolean): void {
if (automaticLayout) {
this.elementSizeObserver.startObserving();
} else {
this.elementSizeObserver.stopObserving();
}
}
}

View file

@ -55,7 +55,7 @@ export class DiffNavigator extends Disposable implements IDiffNavigator {
readonly onDidUpdate: Event<this> = this._onDidUpdate.event;
private disposed: boolean;
private revealFirst: boolean;
public revealFirst: boolean;
private nextIdx: number;
private ranges: IDiffRange[];
private ignoreSelectionChange: boolean;
@ -78,8 +78,6 @@ export class DiffNavigator extends Disposable implements IDiffNavigator {
this.ignoreSelectionChange = false;
this.revealFirst = Boolean(this._options.alwaysRevealFirst);
// hook up to diff editor for diff, disposal, and caret move
this._register(this._editor.onDidDispose(() => this.dispose()));
this._register(this._editor.onDidUpdateDiff(() => this._onDiffUpdated()));
if (this._options.followsCaret) {
@ -91,11 +89,6 @@ export class DiffNavigator extends Disposable implements IDiffNavigator {
this.nextIdx = -1;
}));
}
if (this._options.alwaysRevealFirst) {
this._register(this._editor.getModifiedEditor().onDidChangeModel((e) => {
this.revealFirst = true;
}));
}
// init things
this._init();

View file

@ -16,6 +16,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { Codicon } from 'vs/base/common/codicons';
import { ThemeIcon } from 'vs/base/common/themables';
import { EndOfLineSequence, ITextModel } from 'vs/editor/common/model';
import { isIOS } from 'vs/base/common/platform';
export interface IDiffLinesChange {
readonly originalStartLineNumber: number;
@ -145,8 +146,11 @@ export class InlineDiffMargin extends Disposable {
}));
}
const useShadowDOM = editor.getOption(EditorOption.useShadowDOM) && !isIOS; // Do not use shadow dom on IOS #122035
const showContextMenu = (x: number, y: number) => {
this._contextMenuService.showContextMenu({
domForShadowRoot: useShadowDOM ? editor.getDomNode() ?? undefined : undefined,
getAnchor: () => {
return {
x,

View file

@ -6,7 +6,9 @@
import { Emitter, Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { StopWatch } from 'vs/base/common/stopwatch';
import { LineRange } from 'vs/editor/common/core/lineRange';
import { IDocumentDiff, IDocumentDiffProvider, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider';
import { LineRangeMapping, RangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
import { ITextModel } from 'vs/editor/common/model';
import { DiffAlgorithmName, IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@ -35,6 +37,26 @@ export class WorkerBasedDocumentDiffProvider implements IDocumentDiffProvider, I
return this.diffAlgorithm.computeDiff(original, modified, options);
}
// This significantly speeds up the case when the original file is empty
if (original.getLineCount() === 1 && original.getLineMaxColumn(1) === 1) {
return {
changes: [
new LineRangeMapping(
new LineRange(1, 1),
new LineRange(1, modified.getLineCount()),
[
new RangeMapping(
original.getFullModelRange(),
modified.getFullModelRange(),
)
]
)
],
identical: false,
quitEarly: false,
};
}
const sw = StopWatch.create(true);
const result = await this.editorWorkerService.computeDiff(original.uri, modified.uri, options, this.diffAlgorithm);
const timeMs = sw.elapsed();

View file

@ -202,6 +202,16 @@ const editorConfiguration: IConfigurationNode = {
],
tags: ['experimental'],
},
'diffEditor.experimental.collapseUnchangedRegions': {
type: 'boolean',
default: false,
description: nls.localize('collapseUnchangedRegions', "Controls whether the diff editor shows unchanged regions. Only works when 'diffEditor.experimental.useVersion2' is set."),
},
'diffEditor.experimental.useVersion2': {
type: 'boolean',
default: false,
description: nls.localize('useVersion2', "Controls whether the diff editor uses the new or the old implementation."),
}
}
};

View file

@ -794,6 +794,13 @@ export interface IDiffEditorBaseOptions {
* Whether the diff editor aria label should be verbose.
*/
accessibilityVerbose?: boolean;
experimental?: {
/**
* Defaults to false.
*/
collapseUnchangedRegions?: boolean;
};
}
/**
@ -5077,7 +5084,7 @@ export const EditorOptions = {
screenReaderAnnounceInlineSuggestion: register(new EditorBooleanOption(
EditorOption.screenReaderAnnounceInlineSuggestion, 'screenReaderAnnounceInlineSuggestion', false,
{
description: nls.localize('screenReaderAnnounceInlineSuggestion', "Control whether inline suggestions are announced by a screen reader. Note that this does not work on macOS with VoiceOver."),
description: nls.localize('screenReaderAnnounceInlineSuggestion', "Control whether inline suggestions are announced by a screen reader."),
tags: ['accessibility']
}
)),

View file

@ -4,11 +4,16 @@
*--------------------------------------------------------------------------------------------*/
import { BugIndicatingError } from 'vs/base/common/errors';
import { Range } from 'vs/editor/common/core/range';
/**
* A range of lines (1-based).
*/
export class LineRange {
public static fromRange(range: Range): LineRange {
return new LineRange(range.startLineNumber, range.endLineNumber);
}
/**
* @param lineRanges An array of sorted line ranges.
*/
@ -78,6 +83,10 @@ export class LineRange {
return result;
}
public static ofLength(startLineNumber: number, length: number): LineRange {
return new LineRange(startLineNumber, startLineNumber + length);
}
/**
* The start line number.
*/
@ -154,6 +163,10 @@ export class LineRange {
return undefined;
}
public intersectsStrict(other: LineRange): boolean {
return this.startLineNumber < other.endLineNumberExclusive && other.startLineNumber < this.endLineNumberExclusive;
}
public overlapOrTouch(other: LineRange): boolean {
return this.startLineNumber <= other.endLineNumberExclusive && other.startLineNumber <= this.endLineNumberExclusive;
}
@ -161,4 +174,15 @@ export class LineRange {
public equals(b: LineRange): boolean {
return this.startLineNumber === b.startLineNumber && this.endLineNumberExclusive === b.endLineNumberExclusive;
}
public toInclusiveRange(): Range | null {
if (this.isEmpty) {
return null;
}
return new Range(this.startLineNumber, 1, this.endLineNumberExclusive - 1, Number.MAX_SAFE_INTEGER);
}
public toExclusiveRange(): Range {
return new Range(this.startLineNumber, 1, this.endLineNumberExclusive, 1);
}
}

View file

@ -28,7 +28,7 @@ export interface IDocumentDiffProvider {
*/
export interface IDocumentDiffProviderOptions {
/**
* When set to true, the diff should ignore whitespace changes.i
* When set to true, the diff should ignore whitespace changes.
*/
ignoreTrimWhitespace: boolean;

View file

@ -32,6 +32,34 @@ export class LinesDiff {
* Maps a line range in the original text model to a line range in the modified text model.
*/
export class LineRangeMapping {
public static inverse(mapping: LineRangeMapping[], originalLineCount: number, modifiedLineCount: number): LineRangeMapping[] {
const result: LineRangeMapping[] = [];
let lastOriginalEndLineNumber = 1;
let lastModifiedEndLineNumber = 1;
for (const m of mapping) {
const r = new LineRangeMapping(
new LineRange(lastOriginalEndLineNumber, m.originalRange.startLineNumber),
new LineRange(lastModifiedEndLineNumber, m.modifiedRange.startLineNumber),
undefined
);
if (!r.modifiedRange.isEmpty) {
result.push(r);
}
lastOriginalEndLineNumber = m.originalRange.endLineNumberExclusive;
lastModifiedEndLineNumber = m.modifiedRange.endLineNumberExclusive;
}
const r = new LineRangeMapping(
new LineRange(lastOriginalEndLineNumber, originalLineCount + 1),
new LineRange(lastModifiedEndLineNumber, modifiedLineCount + 1),
undefined
);
if (!r.modifiedRange.isEmpty) {
result.push(r);
}
return result;
}
/**
* The line range in the original text model.
*/

View file

@ -3,9 +3,24 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Length, lengthAdd, lengthDiffNonNegative, lengthLessThanEqual, LengthObj, lengthToObj, toLength } from './length';
import { Range } from 'vs/editor/common/core/range';
import { Length, lengthAdd, lengthDiffNonNegative, lengthLessThanEqual, LengthObj, lengthOfString, lengthToObj, positionToLength, toLength } from './length';
import { IModelContentChange } from 'vs/editor/common/textModelEvents';
export class TextEditInfo {
public static fromModelContentChanges(changes: IModelContentChange[]): TextEditInfo[] {
// Must be sorted in ascending order
const edits = changes.map(c => {
const range = Range.lift(c.range);
return new TextEditInfo(
positionToLength(range.getStartPosition()),
positionToLength(range.getEndPosition()),
lengthOfString(c.text)
);
}).reverse();
return edits;
}
constructor(
public readonly startOffset: Length,
public readonly endOffset: Length,

View file

@ -14,7 +14,7 @@ import { ResolvedLanguageConfiguration } from 'vs/editor/common/languages/langua
import { AstNode, AstNodeKind } from './ast';
import { TextEditInfo } from './beforeEditPositionMapper';
import { LanguageAgnosticBracketTokens } from './brackets';
import { Length, lengthAdd, lengthGreaterThanEqual, lengthLessThan, lengthLessThanEqual, lengthOfString, lengthsToRange, lengthZero, positionToLength, toLength } from './length';
import { Length, lengthAdd, lengthGreaterThanEqual, lengthLessThan, lengthLessThanEqual, lengthsToRange, lengthZero, positionToLength, toLength } from './length';
import { parseDocument } from './parser';
import { DenseKeyProvider } from './smallImmutableSet';
import { FastTokenizer, TextBufferTokenizer } from './tokenizer';
@ -103,16 +103,7 @@ export class BracketPairsTree extends Disposable {
}
public handleContentChanged(change: IModelContentChangedEvent) {
// Must be sorted in ascending order
const edits = change.changes.map(c => {
const range = Range.lift(c.range);
return new TextEditInfo(
positionToLength(range.getStartPosition()),
positionToLength(range.getEndPosition()),
lengthOfString(c.text)
);
}).reverse();
const edits = TextEditInfo.fromModelContentChanges(change.changes);
this.handleEdits(edits, false);
}

View file

@ -216,6 +216,14 @@ export function lengthsToRange(lengthStart: Length, lengthEnd: Length): Range {
return new Range(lineCount + 1, colCount + 1, lineCount2 + 1, colCount2 + 1);
}
export function lengthOfRange(range: Range): LengthObj {
if (range.startLineNumber === range.endLineNumber) {
return new LengthObj(0, range.endColumn - range.startColumn);
} else {
return new LengthObj(range.endLineNumber - range.startLineNumber, range.endColumn - 1);
}
}
export function lengthCompare(length1: Length, length2: Length): number {
const l1 = length1 as any as number;
const l2 = length2 as any as number;

View file

@ -60,7 +60,7 @@ class Parser {
this._itemsConstructed = 0;
this._itemsFromCache = 0;
let result = this.parseList(SmallImmutableSet.getEmpty());
let result = this.parseList(SmallImmutableSet.getEmpty(), 0);
if (!result) {
result = ListAstNode.getEmpty();
}
@ -70,6 +70,7 @@ class Parser {
private parseList(
openedBracketIds: SmallImmutableSet<OpeningBracketId>,
level: number,
): AstNode | null {
const items: AstNode[] = [];
@ -86,7 +87,7 @@ class Parser {
break;
}
child = this.parseChild(openedBracketIds);
child = this.parseChild(openedBracketIds, level + 1);
}
if (child.kind === AstNodeKind.List && child.childrenLength === 0) {
@ -129,6 +130,7 @@ class Parser {
private parseChild(
openedBracketIds: SmallImmutableSet<number>,
level: number,
): AstNode {
this._itemsConstructed++;
@ -142,8 +144,13 @@ class Parser {
return token.astNode as TextAstNode;
case TokenKind.OpeningBracket: {
if (level > 300) {
// To prevent stack overflows
return new TextAstNode(token.length);
}
const set = openedBracketIds.merge(token.bracketIds);
const child = this.parseList(set);
const child = this.parseList(set, level + 1);
const nextToken = this.tokenizer.peek();
if (

View file

@ -104,7 +104,7 @@ interface NodePosition {
*/
node: TreeNode;
/**
* remainer in current piece.
* remainder in current piece.
*/
remainder: number;
/**

View file

@ -44,7 +44,7 @@ export interface ITextResourceConfigurationService {
*
* @param resource - Resource for which the configuration has to be fetched.
* @param position - Position in the resource for which configuration has to be fetched.
* @param section - Section of the configuraion.
* @param section - Section of the configuration.
*
*/
getValue<T>(resource: URI | undefined, section?: string): T;

View file

@ -610,6 +610,7 @@ export class ContentHoverWidget extends Disposable implements IContentWidget {
if (this._visibleData) {
const stoleFocus = this._visibleData.stoleFocus;
this._setVisibleData(null);
this._hoverFocusedKey.set(false);
this._editor.layoutContentWidget(this);
if (stoleFocus) {
this._editor.focus();

View file

@ -27,10 +27,10 @@ import { registerThemingParticipant } from 'vs/platform/theme/common/themeServic
import { HoverParticipantRegistry } from 'vs/editor/contrib/hover/browser/hoverTypes';
import { MarkdownHoverParticipant } from 'vs/editor/contrib/hover/browser/markdownHoverParticipant';
import { MarkerHoverParticipant } from 'vs/editor/contrib/hover/browser/markerHoverParticipant';
import 'vs/css!./hover';
import { InlineSuggestionHintsContentWidget } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ResultKind } from 'vs/platform/keybinding/common/keybindingResolver';
import 'vs/css!./hover';
export class ModesHoverController implements IEditorContribution {

View file

@ -198,7 +198,7 @@ export class ToggleAlwaysShowInlineSuggestionToolbar extends Action2 {
group: 'secondary',
order: 10,
}],
toggled: InlineCompletionContextKeys.alwaysShowInlineSuggestionToolbar,
toggled: ContextKeyExpr.equals('config.editor.inlineSuggest.showToolbar', 'always')
});
}

View file

@ -15,7 +15,6 @@ export class InlineCompletionContextKeys extends Disposable {
public static readonly inlineSuggestionVisible = new RawContextKey<boolean>('inlineSuggestionVisible', false, localize('inlineSuggestionVisible', "Whether an inline suggestion is visible"));
public static readonly inlineSuggestionHasIndentation = new RawContextKey<boolean>('inlineSuggestionHasIndentation', false, localize('inlineSuggestionHasIndentation', "Whether the inline suggestion starts with whitespace"));
public static readonly inlineSuggestionHasIndentationLessThanTabSize = new RawContextKey<boolean>('inlineSuggestionHasIndentationLessThanTabSize', true, localize('inlineSuggestionHasIndentationLessThanTabSize', "Whether the inline suggestion starts with whitespace that is less than what would be inserted by tab"));
public static readonly alwaysShowInlineSuggestionToolbar = new RawContextKey<boolean>('alwaysShowInlineSuggestionToolbar', false, localize('alwaysShowInlineSuggestionToolbar', "Whether the inline suggestion toolbar should always be visible"));
public static readonly suppressSuggestions = new RawContextKey<boolean | undefined>('inlineSuggestionSuppressSuggestions', undefined, localize('suppressSuggestions', "Whether suggestions should be suppressed for the current suggestion"));
public readonly inlineCompletionVisible = InlineCompletionContextKeys.inlineSuggestionVisible.bindTo(this.contextKeyService);

View file

@ -20,7 +20,7 @@ import { IModelContentChangedEvent } from 'vs/editor/common/textModelEvents';
import { inlineSuggestCommitId } from 'vs/editor/contrib/inlineCompletions/browser/commandIds';
import { GhostTextWidget } from 'vs/editor/contrib/inlineCompletions/browser/ghostTextWidget';
import { InlineCompletionContextKeys } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys';
import { InlineSuggestionHintsContentWidget } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget';
import { InlineCompletionsHintsWidget, InlineSuggestionHintsContentWidget } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget';
import { InlineCompletionsModel, VersionIdChangeReason } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel';
import { SuggestWidgetAdaptor } from 'vs/editor/contrib/inlineCompletions/browser/suggestWidgetInlineCompletionProvider';
import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService';
@ -191,6 +191,8 @@ export class InlineCompletionsController extends Disposable {
});
}
}));
this._register(new InlineCompletionsHintsWidget(this.editor, this.model, this.instantiationService));
}
/**

View file

@ -12,6 +12,7 @@ import { RunOnceScheduler } from 'vs/base/common/async';
import { Codicon } from 'vs/base/common/codicons';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { IObservable, autorun, derived, observableFromEvent } from 'vs/base/common/observable';
import { autorunWithStore2 } from 'vs/base/common/observableImpl/autorun';
import { OS } from 'vs/base/common/platform';
import { ThemeIcon } from 'vs/base/common/themables';
import 'vs/css!./inlineCompletionsHintsWidget';
@ -35,14 +36,14 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
export class InlineCompletionsHintsWidget extends Disposable {
private readonly showToolbar = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineSuggest).showToolbar);
private readonly alwaysShowToolbar = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineSuggest).showToolbar === 'always');
private sessionPosition: Position | undefined = undefined;
private readonly position = derived('position', reader => {
const ghostText = this.model.ghostText.read(reader);
const ghostText = this.model.read(reader)?.ghostText.read(reader);
if (this.showToolbar.read(reader) !== 'always' || !ghostText) {
if (!this.alwaysShowToolbar.read(reader) || !ghostText || ghostText.parts.length === 0) {
this.sessionPosition = undefined;
return null;
}
@ -53,37 +54,44 @@ export class InlineCompletionsHintsWidget extends Disposable {
}
const position = new Position(ghostText.lineNumber, Math.min(firstColumn, this.sessionPosition?.column ?? Number.MAX_SAFE_INTEGER));
this.sessionPosition = position;
return position;
});
private readonly contentWidget = this._register(this.instantiationService.createInstance(
InlineSuggestionHintsContentWidget,
this.editor,
true,
this.position,
this.model.selectedInlineCompletionIndex,
this.model.inlineCompletionsCount,
this.model.selectedInlineCompletion.map(v => v?.inlineCompletion.source.inlineCompletions.commands ?? []),
));
constructor(
private readonly editor: ICodeEditor,
private readonly model: InlineCompletionsModel,
private readonly model: IObservable<InlineCompletionsModel | undefined>,
@IInstantiationService private readonly instantiationService: IInstantiationService,
) {
super();
editor.addContentWidget(this.contentWidget);
this._register(toDisposable(() => editor.removeContentWidget(this.contentWidget)));
this._register(autorun('request explicit', reader => {
const position = this.position.read(reader);
if (!position) {
this._register(autorunWithStore2('setup content widget', (reader, store) => {
const model = this.model.read(reader);
if (!model || !this.alwaysShowToolbar.read(reader)) {
return;
}
if (this.model.lastTriggerKind.read(reader) !== InlineCompletionTriggerKind.Explicit) {
this.model.triggerExplicitly();
}
const contentWidget = store.add(this.instantiationService.createInstance(
InlineSuggestionHintsContentWidget,
this.editor,
true,
this.position,
model.selectedInlineCompletionIndex,
model.inlineCompletionsCount,
model.selectedInlineCompletion.map(v => v?.inlineCompletion.source.inlineCompletions.commands ?? []),
));
editor.addContentWidget(contentWidget);
store.add(toDisposable(() => editor.removeContentWidget(contentWidget)));
store.add(autorun('request explicit', reader => {
const position = this.position.read(reader);
if (!position) {
return;
}
if (model.lastTriggerKind.read(reader) !== InlineCompletionTriggerKind.Explicit) {
model.triggerExplicitly();
}
}));
}));
}
}

View file

@ -89,10 +89,10 @@ export class InlineCompletionsSource extends Disposable {
}
}
this._updateOperation.clear();
transaction(tx => {
target.set(completions, tx);
});
this._updateOperation.clear();
return true;
})();

View file

@ -157,7 +157,7 @@ abstract class StickyModelCandidateProvider<T> implements IStickyModelCandidateP
public abstract get provider(): LanguageFeatureRegistry<object> | null;
public computeStickyModel(textModel: ITextModel, modelVersionId: number, token: CancellationToken): { statusPromise: Promise<Status> | Status; modelPromise: CancelablePromise<T | null> | null } {
if (!this.isProviderValid(textModel)) {
if (token.isCancellationRequested || !this.isProviderValid(textModel)) {
return { statusPromise: this._invalid(), modelPromise: null };
}
const providerModelPromise = createCancelablePromise(token => this.createModelFromProvider(textModel, modelVersionId, token));

View file

@ -432,7 +432,7 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider {
const model = this.editor.getModel();
if (model) {
const range = model.validateRange(new Range(where.startLineNumber, 1, where.endLineNumber + 1, 1));
this.revealRange(range, range.endLineNumber === model.getLineCount());
this.revealRange(range, range.startLineNumber === model.getLineCount());
}
}

15
src/vs/monaco.d.ts vendored
View file

@ -2348,7 +2348,7 @@ declare namespace monaco.editor {
*/
export interface IDocumentDiffProviderOptions {
/**
* When set to true, the diff should ignore whitespace changes.i
* When set to true, the diff should ignore whitespace changes.
*/
ignoreTrimWhitespace: boolean;
/**
@ -2374,10 +2374,12 @@ declare namespace monaco.editor {
*/
readonly changes: LineRangeMapping[];
}
/**
* A range of lines (1-based).
*/
export class LineRange {
static fromRange(range: Range): LineRange;
/**
* @param lineRanges An array of sorted line ranges.
*/
@ -2387,6 +2389,7 @@ declare namespace monaco.editor {
* @param lineRanges2 Must be sorted.
*/
static join(lineRanges1: readonly LineRange[], lineRanges2: readonly LineRange[]): readonly LineRange[];
static ofLength(startLineNumber: number, length: number): LineRange;
/**
* The start line number.
*/
@ -2422,14 +2425,18 @@ declare namespace monaco.editor {
* If the ranges don't even touch, the result is undefined.
*/
intersect(other: LineRange): LineRange | undefined;
intersectsStrict(other: LineRange): boolean;
overlapOrTouch(other: LineRange): boolean;
equals(b: LineRange): boolean;
toInclusiveRange(): Range | null;
toExclusiveRange(): Range;
}
/**
* Maps a line range in the original text model to a line range in the modified text model.
*/
export class LineRangeMapping {
static inverse(mapping: LineRangeMapping[], originalLineCount: number, modifiedLineCount: number): LineRangeMapping[];
/**
* The line range in the original text model.
*/
@ -3860,6 +3867,12 @@ declare namespace monaco.editor {
* Whether the diff editor aria label should be verbose.
*/
accessibilityVerbose?: boolean;
experimental?: {
/**
* Defaults to false.
*/
collapseUnchangedRegions?: boolean;
};
}
/**

View file

@ -155,7 +155,9 @@ export class MenuEntryActionViewItem extends ActionViewItem {
super.render(container);
container.classList.add('menu-entry');
this._updateItemClass(this._menuItemAction.item);
if (this.options.icon) {
this._updateItemClass(this._menuItemAction.item);
}
let mouseOver = false;

View file

@ -40,6 +40,11 @@ export interface IExtensionResourceLoaderService {
*/
readonly supportsExtensionGalleryResources: boolean;
/**
* Return true if the given URI is a extension gallery resource.
*/
isExtensionGalleryResource(uri: URI): boolean;
/**
* Computes the URL of a extension gallery resource. Returns `undefined` if gallery does not provide extension resources.
*/
@ -104,8 +109,8 @@ export abstract class AbstractExtensionResourceLoaderService implements IExtensi
public abstract readExtensionResource(uri: URI): Promise<string>;
protected isExtensionGalleryResource(uri: URI) {
return this._extensionGalleryAuthority && this._extensionGalleryAuthority === this._getExtensionGalleryAuthority(uri);
isExtensionGalleryResource(uri: URI): boolean {
return !!this._extensionGalleryAuthority && this._extensionGalleryAuthority === this._getExtensionGalleryAuthority(uri);
}
protected async getExtensionGalleryRequestHeaders(): Promise<IHeaders> {

View file

@ -269,8 +269,8 @@ export class HTMLFileSystemProvider implements IFileSystemProviderWithFileReadWr
const file = await fileHandle.getFile();
const contents = new Uint8Array(await file.arrayBuffer());
await this.writeFile(to, contents, { create: true, overwrite: opts.overwrite, unlock: false });
await this.delete(from, { recursive: false, useTrash: false });
await this.writeFile(to, contents, { create: true, overwrite: opts.overwrite, unlock: false, atomic: false });
await this.delete(from, { recursive: false, useTrash: false, atomic: false });
}
// File API does not support any real rename otherwise

View file

@ -311,7 +311,7 @@ export class IndexedDBFileSystemProvider extends Disposable implements IFileSyst
throw createFileSystemProviderError('Cannot rename files with different types', FileSystemProviderErrorCode.Unknown);
}
// delete the target file if exists
await this.delete(to, { recursive: true, useTrash: false });
await this.delete(to, { recursive: true, useTrash: false, atomic: false });
}
const toTargetResource = (path: string): URI => this.extUri.joinPath(to, this.extUri.relativePath(from, from.with({ path })) || '');
@ -339,7 +339,7 @@ export class IndexedDBFileSystemProvider extends Disposable implements IFileSyst
await this.bulkWrite(targetFiles);
}
await this.delete(from, { recursive: true, useTrash: false });
await this.delete(from, { recursive: true, useTrash: false, atomic: false });
}
async delete(resource: URI, opts: IFileDeleteOptions): Promise<void> {

View file

@ -53,6 +53,8 @@ export class DiskFileSystemProviderClient extends Disposable implements
FileSystemProviderCapabilities.FileFolderCopy |
FileSystemProviderCapabilities.FileWriteUnlock |
FileSystemProviderCapabilities.FileAtomicRead |
FileSystemProviderCapabilities.FileAtomicWrite |
FileSystemProviderCapabilities.FileAtomicDelete |
FileSystemProviderCapabilities.FileClone;
if (this.extraCapabilities.pathCaseSensitive) {

View file

@ -14,7 +14,7 @@ import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from
import { TernarySearchTree } from 'vs/base/common/ternarySearchTree';
import { Schemas } from 'vs/base/common/network';
import { mark } from 'vs/base/common/performance';
import { extUri, extUriIgnorePathCase, IExtUri, isAbsolutePath } from 'vs/base/common/resources';
import { basename, dirname, extUri, extUriIgnorePathCase, IExtUri, isAbsolutePath, joinPath } from 'vs/base/common/resources';
import { consumeStream, isReadableBufferedStream, isReadableStream, listenStream, newWriteableStream, peekReadable, peekStream, transform } from 'vs/base/common/stream';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
@ -396,7 +396,17 @@ export class FileService extends Disposable implements IFileService {
// write file: buffered
else {
await this.doWriteBuffered(provider, resource, options, bufferOrReadableOrStreamOrBufferedStream instanceof VSBuffer ? bufferToReadable(bufferOrReadableOrStreamOrBufferedStream) : bufferOrReadableOrStreamOrBufferedStream);
const contents = bufferOrReadableOrStreamOrBufferedStream instanceof VSBuffer ? bufferToReadable(bufferOrReadableOrStreamOrBufferedStream) : bufferOrReadableOrStreamOrBufferedStream;
// atomic write
if (options?.atomic !== false && options?.atomic?.postfix) {
await this.doWriteBufferedAtomic(provider, resource, joinPath(dirname(resource), `${basename(resource)}${options.atomic.postfix}`), options, contents);
}
// non-atomic write
else {
await this.doWriteBuffered(provider, resource, options, contents);
}
}
// events
@ -416,6 +426,18 @@ export class FileService extends Disposable implements IFileService {
throw new Error(localize('writeFailedUnlockUnsupported', "Unable to unlock file '{0}' because provider does not support it.", this.resourceForError(resource)));
}
// Validate atomic support
const atomic = !!options?.atomic;
if (atomic) {
if (!(provider.capabilities & FileSystemProviderCapabilities.FileAtomicWrite)) {
throw new Error(localize('writeFailedAtomicUnsupported', "Unable to atomically write file '{0}' because provider does not support it.", this.resourceForError(resource)));
}
if (unlock) {
throw new Error(localize('writeFailedAtomicUnlock', "Unable to unlock file '{0}' because atomic write is enabled.", this.resourceForError(resource)));
}
}
// Validate via file stat meta data
let stat: IStat | undefined = undefined;
try {
@ -579,7 +601,7 @@ export class FileService extends Disposable implements IFileService {
}
if (error instanceof TooLargeFileOperationError) {
return new TooLargeFileOperationError(message, error.fileOperationResult, error.size, error.options);
return new TooLargeFileOperationError(message, error.fileOperationResult, error.size, error.options as IReadFileOptions);
}
return new FileOperationError(message, toFileOperationResult(error), options);
@ -959,6 +981,16 @@ export class FileService extends Disposable implements IFileService {
throw new Error(localize('deleteFailedTrashUnsupported', "Unable to delete file '{0}' via trash because provider does not support it.", this.resourceForError(resource)));
}
// Validate atomic support
const atomic = options?.atomic;
if (atomic && !(provider.capabilities & FileSystemProviderCapabilities.FileAtomicDelete)) {
throw new Error(localize('deleteFailedAtomicUnsupported', "Unable to delete file '{0}' atomically because provider does not support it.", this.resourceForError(resource)));
}
if (useTrash && atomic) {
throw new Error(localize('deleteFailedTrashAndAtomicUnsupported', "Unable to atomically delete file '{0}' because using trash is enabled.", this.resourceForError(resource)));
}
// Validate delete
let stat: IStat | undefined = undefined;
try {
@ -990,9 +1022,10 @@ export class FileService extends Disposable implements IFileService {
const useTrash = !!options?.useTrash;
const recursive = !!options?.recursive;
const atomic = options?.atomic ?? false;
// Delete through provider
await provider.delete(resource, { recursive, useTrash });
await provider.delete(resource, { recursive, useTrash, atomic });
// Events
this._onDidRunOperation.fire(new FileOperationEvent(resource, FileOperation.DELETE));
@ -1122,6 +1155,28 @@ export class FileService extends Disposable implements IFileService {
private readonly writeQueue = this._register(new ResourceQueue());
private async doWriteBufferedAtomic(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, tempResource: URI, options: IWriteFileOptions | undefined, readableOrStreamOrBufferedStream: VSBufferReadable | VSBufferReadableStream | VSBufferReadableBufferedStream): Promise<void> {
// Write to temp resource first
await this.doWriteBuffered(provider, tempResource, options, readableOrStreamOrBufferedStream);
try {
// Rename over existing to ensure atomic replace
await provider.rename(tempResource, resource, { overwrite: true });
} catch (error) {
// Cleanup in case of rename error
try {
await provider.delete(tempResource, { recursive: false, useTrash: false, atomic: false });
} catch (error) {
// ignore - we want the outer error to bubble up
}
throw error;
}
}
private async doWriteBuffered(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, options: IWriteFileOptions | undefined, readableOrStreamOrBufferedStream: VSBufferReadable | VSBufferReadableStream | VSBufferReadableBufferedStream): Promise<void> {
return this.writeQueue.queueFor(resource, this.getExtUri(provider).providerExtUri).queue(async () => {
@ -1237,7 +1292,7 @@ export class FileService extends Disposable implements IFileService {
}
// Write through the provider
await provider.writeFile(resource, buffer.buffer, { create: true, overwrite: true, unlock: options?.unlock ?? false });
await provider.writeFile(resource, buffer.buffer, { create: true, overwrite: true, unlock: options?.unlock ?? false, atomic: options?.atomic ?? false });
}
private async doPipeBuffered(sourceProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, source: URI, targetProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, target: URI): Promise<void> {
@ -1291,7 +1346,7 @@ export class FileService extends Disposable implements IFileService {
}
private async doPipeUnbufferedQueued(sourceProvider: IFileSystemProviderWithFileReadWriteCapability, source: URI, targetProvider: IFileSystemProviderWithFileReadWriteCapability, target: URI): Promise<void> {
return targetProvider.writeFile(target, await sourceProvider.readFile(source), { create: true, overwrite: true, unlock: false });
return targetProvider.writeFile(target, await sourceProvider.readFile(source), { create: true, overwrite: true, unlock: false, atomic: false });
}
private async doPipeUnbufferedToBuffered(sourceProvider: IFileSystemProviderWithFileReadWriteCapability, source: URI, targetProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, target: URI): Promise<void> {

View file

@ -283,7 +283,44 @@ export interface IFileAtomicReadOptions {
* to from a different process. If you need such atomic
* operations, you better use a real database as storage.
*/
readonly atomic: true;
readonly atomic: boolean;
}
export interface IFileAtomicOptions {
/**
* The postfix is used to create a temporary file based
* on the original resource. The resulting temporary
* file will be in the same folder as the resource and
* have `postfix` appended to the resource name.
*
* Example: given a file resource `file:///some/path/foo.txt`
* and a postfix `.vsctmp`, the temporary file will be
* created as `file:///some/path/foo.txt.vsctmp`.
*/
readonly postfix: string;
}
export interface IFileAtomicWriteOptions {
/**
* The optional `atomic` flag can be used to make sure
* the `writeFile` method updates the target file atomically
* by first writing to a temporary file in the same folder
* and then renaming it over the target.
*/
readonly atomic: IFileAtomicOptions | false;
}
export interface IFileAtomicDeleteOptions {
/**
* The optional `atomic` flag can be used to make sure
* the `delete` method deletes the target atomically by
* first renaming it to a temporary resource in the same
* folder and then deleting it.
*/
readonly atomic: IFileAtomicOptions | false;
}
export interface IFileReadLimits {
@ -316,7 +353,7 @@ export interface IFileReadStreamOptions {
readonly limits?: IFileReadLimits;
}
export interface IFileWriteOptions extends IFileOverwriteOptions, IFileUnlockOptions {
export interface IFileWriteOptions extends IFileOverwriteOptions, IFileUnlockOptions, IFileAtomicWriteOptions {
/**
* Set to `true` to create a file when it does not exist. Will
@ -358,10 +395,21 @@ export interface IFileDeleteOptions {
/**
* Set to `true` to attempt to move the file to trash
* instead of deleting it permanently from disk. This
* option maybe not be supported on all providers.
* instead of deleting it permanently from disk.
*
* This option maybe not be supported on all providers.
*/
readonly useTrash: boolean;
/**
* The optional `atomic` flag can be used to make sure
* the `delete` method deletes the target atomically by
* first renaming it to a temporary resource in the same
* folder and then deleting it.
*
* This option maybe not be supported on all providers.
*/
readonly atomic: IFileAtomicOptions | false;
}
export enum FileType {
@ -515,10 +563,21 @@ export const enum FileSystemProviderCapabilities {
*/
FileAtomicRead = 1 << 14,
/**
* Provider support to write files atomically. This implies the
* provider provides the `FileReadWrite` capability too.
*/
FileAtomicWrite = 1 << 15,
/**
* Provider support to delete atomically.
*/
FileAtomicDelete = 1 << 16,
/**
* Provider support to clone files atomically.
*/
FileClone = 1 << 15
FileClone = 1 << 17
}
export interface IFileSystemProvider {
@ -607,6 +666,26 @@ export function hasFileAtomicReadCapability(provider: IFileSystemProvider): prov
return !!(provider.capabilities & FileSystemProviderCapabilities.FileAtomicRead);
}
export interface IFileSystemProviderWithFileAtomicWriteCapability extends IFileSystemProvider {
writeFile(resource: URI, contents: Uint8Array, opts?: IFileAtomicWriteOptions): Promise<void>;
}
export function hasFileAtomicWriteCapability(provider: IFileSystemProvider): provider is IFileSystemProviderWithFileAtomicWriteCapability {
if (!hasReadWriteCapability(provider)) {
return false; // we require the `FileReadWrite` capability too
}
return !!(provider.capabilities & FileSystemProviderCapabilities.FileAtomicWrite);
}
export interface IFileSystemProviderWithFileAtomicDeleteCapability extends IFileSystemProvider {
delete(resource: URI, opts: IFileAtomicDeleteOptions): Promise<void>;
}
export function hasFileAtomicDeleteCapability(provider: IFileSystemProvider): provider is IFileSystemProviderWithFileAtomicDeleteCapability {
return !!(provider.capabilities & FileSystemProviderCapabilities.FileAtomicDelete);
}
export enum FileSystemProviderErrorCode {
FileExists = 'EntryExists',
FileNotFound = 'EntryNotFound',
@ -1146,6 +1225,14 @@ export interface IWriteFileOptions {
* Whether to attempt to unlock a file before writing.
*/
readonly unlock?: boolean;
/**
* The optional `atomic` flag can be used to make sure
* the `writeFile` method updates the target file atomically
* by first writing to a temporary file in the same folder
* and then renaming it over the target.
*/
readonly atomic?: IFileAtomicOptions | false;
}
export interface IResolveFileOptions {
@ -1185,7 +1272,7 @@ export class FileOperationError extends Error {
constructor(
message: string,
readonly fileOperationResult: FileOperationResult,
readonly options?: IReadFileOptions & IWriteFileOptions & ICreateFileOptions
readonly options?: IReadFileOptions | IWriteFileOptions | ICreateFileOptions
) {
super(message);
}

View file

@ -12,14 +12,14 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { Event } from 'vs/base/common/event';
import { isEqual } from 'vs/base/common/extpath';
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { basename, dirname } from 'vs/base/common/path';
import { basename, dirname, join } from 'vs/base/common/path';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { extUriBiasedIgnorePathCase, joinPath } from 'vs/base/common/resources';
import { extUriBiasedIgnorePathCase, joinPath, basename as resourcesBasename, dirname as resourcesDirname } from 'vs/base/common/resources';
import { newWriteableStream, ReadableStreamEvents } from 'vs/base/common/stream';
import { URI } from 'vs/base/common/uri';
import { IDirent, Promises, RimRafMode, SymlinkSupport } from 'vs/base/node/pfs';
import { localize } from 'vs/nls';
import { createFileSystemProviderError, IFileAtomicReadOptions, IFileDeleteOptions, IFileOpenOptions, IFileOverwriteOptions, IFileReadStreamOptions, FileSystemProviderCapabilities, FileSystemProviderError, FileSystemProviderErrorCode, FileType, IFileWriteOptions, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileCloneCapability, IFileSystemProviderWithFileFolderCopyCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, isFileOpenForWriteOptions, IStat, FilePermission } from 'vs/platform/files/common/files';
import { createFileSystemProviderError, IFileAtomicReadOptions, IFileDeleteOptions, IFileOpenOptions, IFileOverwriteOptions, IFileReadStreamOptions, FileSystemProviderCapabilities, FileSystemProviderError, FileSystemProviderErrorCode, FileType, IFileWriteOptions, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileCloneCapability, IFileSystemProviderWithFileFolderCopyCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, isFileOpenForWriteOptions, IStat, FilePermission, IFileSystemProviderWithFileAtomicWriteCapability, IFileSystemProviderWithFileAtomicDeleteCapability } from 'vs/platform/files/common/files';
import { readFileIntoStream } from 'vs/platform/files/common/io';
import { AbstractNonRecursiveWatcherClient, AbstractUniversalWatcherClient, IDiskFileChange, ILogMessage } from 'vs/platform/files/common/watcher';
import { ILogService } from 'vs/platform/log/common/log';
@ -46,6 +46,8 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
IFileSystemProviderWithFileReadStreamCapability,
IFileSystemProviderWithFileFolderCopyCapability,
IFileSystemProviderWithFileAtomicReadCapability,
IFileSystemProviderWithFileAtomicWriteCapability,
IFileSystemProviderWithFileAtomicDeleteCapability,
IFileSystemProviderWithFileCloneCapability {
private static TRACE_LOG_RESOURCE_LOCKS = false; // not enabled by default because very spammy
@ -71,6 +73,8 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
FileSystemProviderCapabilities.FileFolderCopy |
FileSystemProviderCapabilities.FileWriteUnlock |
FileSystemProviderCapabilities.FileAtomicRead |
FileSystemProviderCapabilities.FileAtomicWrite |
FileSystemProviderCapabilities.FileAtomicDelete |
FileSystemProviderCapabilities.FileClone;
if (isLinux) {
@ -101,6 +105,14 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
}
}
private async statIgnoreError(resource: URI): Promise<IStat | undefined> {
try {
return await this.stat(resource);
} catch (error) {
return undefined;
}
}
async readdir(resource: URI): Promise<[string, FileType][]> {
try {
const children = await Promises.readdir(this.toFilePath(resource), { withFileTypes: true });
@ -231,6 +243,37 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
}
async writeFile(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise<void> {
if (opts?.atomic !== false && opts?.atomic?.postfix) {
return this.doWriteFileAtomic(resource, joinPath(resourcesDirname(resource), `${resourcesBasename(resource)}${opts.atomic.postfix}`), content, opts);
} else {
return this.doWriteFile(resource, content, opts);
}
}
private async doWriteFileAtomic(resource: URI, tempResource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise<void> {
// Write to temp resource first
await this.doWriteFile(tempResource, content, opts);
try {
// Rename over existing to ensure atomic replace
await this.rename(tempResource, resource, { overwrite: true });
} catch (error) {
// Cleanup in case of rename error
try {
await this.delete(tempResource, { recursive: false, useTrash: false, atomic: false });
} catch (error) {
// ignore - we want the outer error to bubble up
}
throw error;
}
}
private async doWriteFile(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise<void> {
let handle: number | undefined = undefined;
try {
const filePath = this.toFilePath(resource);
@ -296,7 +339,9 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
await Promises.chmod(filePath, stat.mode | 0o200);
}
} catch (error) {
this.logService.trace(error); // ignore any errors here and try to just write
if (error.code !== 'ENOENT') {
this.logService.trace(error); // ignore any errors here and try to just write
}
}
}
@ -542,7 +587,12 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
try {
const filePath = this.toFilePath(resource);
if (opts.recursive) {
await Promises.rm(filePath, RimRafMode.MOVE);
let rmMoveToPath: string | undefined = undefined;
if (opts?.atomic !== false && opts.atomic.postfix) {
rmMoveToPath = join(dirname(filePath), `${basename(filePath)}${opts.atomic.postfix}`);
}
await Promises.rm(filePath, RimRafMode.MOVE, rmMoveToPath);
} else {
try {
await Promises.unlink(filePath);
@ -587,8 +637,8 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
try {
// Ensure target does not exist
await this.validateTargetDeleted(from, to, 'move', opts.overwrite);
// Validate the move operation can perform
await this.validateMoveCopy(from, to, 'move', opts.overwrite);
// Move
await Promises.move(fromFilePath, toFilePath);
@ -614,8 +664,8 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
try {
// Ensure target does not exist
await this.validateTargetDeleted(from, to, 'copy', opts.overwrite);
// Validate the copy operation can perform
await this.validateMoveCopy(from, to, 'copy', opts.overwrite);
// Copy
await Promises.copy(fromFilePath, toFilePath, { preserveSymlinks: true });
@ -631,7 +681,7 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
}
}
private async validateTargetDeleted(from: URI, to: URI, mode: 'move' | 'copy', overwrite?: boolean): Promise<void> {
private async validateMoveCopy(from: URI, to: URI, mode: 'move' | 'copy', overwrite?: boolean): Promise<void> {
const fromFilePath = this.toFilePath(from);
const toFilePath = this.toFilePath(to);
@ -641,18 +691,44 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
isSameResourceWithDifferentPathCase = isEqual(fromFilePath, toFilePath, true /* ignore case */);
}
if (isSameResourceWithDifferentPathCase && mode === 'copy') {
throw createFileSystemProviderError(localize('fileCopyErrorPathCase', "'File cannot be copied to same path with different path case"), FileSystemProviderErrorCode.FileExists);
}
if (isSameResourceWithDifferentPathCase) {
// Handle existing target (unless this is a case change)
if (!isSameResourceWithDifferentPathCase && await Promises.exists(toFilePath)) {
if (!overwrite) {
throw createFileSystemProviderError(localize('fileCopyErrorExists', "File at target already exists"), FileSystemProviderErrorCode.FileExists);
// You cannot copy the same file to the same location with different
// path case unless you are on a case sensitive file system
if (mode === 'copy') {
throw createFileSystemProviderError(localize('fileCopyErrorPathCase', "File cannot be copied to same path with different path case"), FileSystemProviderErrorCode.FileExists);
}
// Delete target
await this.delete(to, { recursive: true, useTrash: false });
// You can move the same file to the same location with different
// path case on case insensitive file systems
else if (mode === 'move') {
return;
}
}
// Here we have to see if the target to move/copy to exists or not.
// We need to respect the `overwrite` option to throw in case the
// target exists.
const fromStat = await this.statIgnoreError(from);
if (!fromStat) {
throw createFileSystemProviderError(localize('fileMoveCopyErrorNotFound', "File to move/copy does not exist"), FileSystemProviderErrorCode.FileNotFound);
}
const toStat = await this.statIgnoreError(to);
if (!toStat) {
return; // target does not exist so we are good
}
if (!overwrite) {
throw createFileSystemProviderError(localize('fileMoveCopyErrorExists', "File at target already exists and thus will not be moved/copied to unless overwrite is specified"), FileSystemProviderErrorCode.FileExists);
}
// Handle existing target for move/copy
if ((fromStat.type & FileType.File) !== 0 && (toStat.type & FileType.File) !== 0) {
return; // node.js can move/copy a file over an existing file without having to delete it first
} else {
await this.delete(to, { recursive: true, useTrash: false, atomic: false });
}
}

View file

@ -82,7 +82,7 @@ flakySuite('IndexedDBFileSystemProvider', function () {
test('root is always present', async () => {
assert.strictEqual((await userdataFileProvider.stat(userdataURIFromPaths([]))).type, FileType.Directory);
await userdataFileProvider.delete(userdataURIFromPaths([]), { recursive: true, useTrash: false });
await userdataFileProvider.delete(userdataURIFromPaths([]), { recursive: true, useTrash: false, atomic: false });
assert.strictEqual((await userdataFileProvider.stat(userdataURIFromPaths([]))).type, FileType.Directory);
});
@ -230,7 +230,7 @@ flakySuite('IndexedDBFileSystemProvider', function () {
let creationPromises: Promise<any> | undefined = undefined;
return {
async create() {
return creationPromises = Promise.all(batch.map(entry => userdataFileProvider.writeFile(entry.resource, VSBuffer.fromString(entry.contents).buffer, { create: true, overwrite: true, unlock: false })));
return creationPromises = Promise.all(batch.map(entry => userdataFileProvider.writeFile(entry.resource, VSBuffer.fromString(entry.contents).buffer, { create: true, overwrite: true, unlock: false, atomic: false })));
},
async assertContentsCorrect() {
if (!creationPromises) { throw Error('read called before create'); }

View file

@ -16,7 +16,7 @@ import { joinPath } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { Promises } from 'vs/base/node/pfs';
import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils';
import { etag, IFileAtomicReadOptions, FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FilePermission, FileSystemProviderCapabilities, hasFileAtomicReadCapability, hasOpenReadWriteCloseCapability, IFileStat, IFileStatWithMetadata, IReadFileOptions, IStat, NotModifiedSinceFileOperationError, TooLargeFileOperationError } from 'vs/platform/files/common/files';
import { etag, IFileAtomicReadOptions, FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FilePermission, FileSystemProviderCapabilities, hasFileAtomicReadCapability, hasOpenReadWriteCloseCapability, IFileStat, IFileStatWithMetadata, IReadFileOptions, IStat, NotModifiedSinceFileOperationError, TooLargeFileOperationError, IFileAtomicOptions } from 'vs/platform/files/common/files';
import { FileService } from 'vs/platform/files/common/fileService';
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
import { NullLogService } from 'vs/platform/log/common/log';
@ -70,6 +70,8 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider {
FileSystemProviderCapabilities.FileFolderCopy |
FileSystemProviderCapabilities.FileWriteUnlock |
FileSystemProviderCapabilities.FileAtomicRead |
FileSystemProviderCapabilities.FileAtomicWrite |
FileSystemProviderCapabilities.FileAtomicDelete |
FileSystemProviderCapabilities.FileClone;
if (isLinux) {
@ -569,22 +571,26 @@ flakySuite('Disk File Service', function () {
});
test('deleteFolder (recursive)', async () => {
return testDeleteFolderRecursive(false);
return testDeleteFolderRecursive(false, false);
});
test('deleteFolder (recursive, atomic)', async () => {
return testDeleteFolderRecursive(false, { postfix: '.vsctmp' });
});
(isLinux /* trash is unreliable on Linux */ ? test.skip : test)('deleteFolder (recursive, useTrash)', async () => {
return testDeleteFolderRecursive(true);
return testDeleteFolderRecursive(true, false);
});
async function testDeleteFolderRecursive(useTrash: boolean): Promise<void> {
async function testDeleteFolderRecursive(useTrash: boolean, atomic: IFileAtomicOptions | false): Promise<void> {
let event: FileOperationEvent;
disposables.add(service.onDidRunOperation(e => event = e));
const resource = URI.file(join(testDir, 'deep'));
const source = await service.resolve(resource);
assert.strictEqual(await service.canDelete(source.resource, { recursive: true, useTrash }), true);
await service.del(source.resource, { recursive: true, useTrash });
assert.strictEqual(await service.canDelete(source.resource, { recursive: true, useTrash, atomic }), true);
await service.del(source.resource, { recursive: true, useTrash, atomic });
assert.strictEqual(existsSync(source.resource.fsPath), false);
assert.ok(event!);
@ -1772,13 +1778,13 @@ flakySuite('Disk File Service', function () {
});
test('writeFile - default', async () => {
return testWriteFile();
return testWriteFile(false);
});
test('writeFile - flush on write', async () => {
DiskFileSystemProvider.configureFlushOnWrite(true);
try {
return await testWriteFile();
return await testWriteFile(false);
} finally {
DiskFileSystemProvider.configureFlushOnWrite(false);
}
@ -1787,16 +1793,41 @@ flakySuite('Disk File Service', function () {
test('writeFile - buffered', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose);
return testWriteFile();
return testWriteFile(false);
});
test('writeFile - unbuffered', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite);
return testWriteFile();
return testWriteFile(false);
});
async function testWriteFile() {
test('writeFile - default (atomic)', async () => {
return testWriteFile(true);
});
test('writeFile - flush on write (atomic)', async () => {
DiskFileSystemProvider.configureFlushOnWrite(true);
try {
return await testWriteFile(true);
} finally {
DiskFileSystemProvider.configureFlushOnWrite(false);
}
});
test('writeFile - buffered (atomic)', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose | FileSystemProviderCapabilities.FileAtomicWrite);
return testWriteFile(true);
});
test('writeFile - unbuffered (atomic)', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.FileAtomicWrite);
return testWriteFile(true);
});
async function testWriteFile(atomic: boolean) {
let event: FileOperationEvent;
disposables.add(service.onDidRunOperation(e => event = e));
@ -1806,7 +1837,7 @@ flakySuite('Disk File Service', function () {
assert.strictEqual(content, 'Small File');
const newContent = 'Updates to the small file';
await service.writeFile(resource, VSBuffer.fromString(newContent));
await service.writeFile(resource, VSBuffer.fromString(newContent), { atomic: atomic ? { postfix: '.vsctmp' } : false });
assert.ok(event!);
assert.strictEqual(event!.resource.fsPath, resource.fsPath);
@ -1816,28 +1847,44 @@ flakySuite('Disk File Service', function () {
}
test('writeFile (large file) - default', async () => {
return testWriteFileLarge();
return testWriteFileLarge(false);
});
test('writeFile (large file) - buffered', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose);
return testWriteFileLarge();
return testWriteFileLarge(false);
});
test('writeFile (large file) - unbuffered', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite);
return testWriteFileLarge();
return testWriteFileLarge(false);
});
async function testWriteFileLarge() {
test('writeFile (large file) - default (atomic)', async () => {
return testWriteFileLarge(true);
});
test('writeFile (large file) - buffered (atomic)', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose | FileSystemProviderCapabilities.FileAtomicWrite);
return testWriteFileLarge(true);
});
test('writeFile (large file) - unbuffered (atomic)', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.FileAtomicWrite);
return testWriteFileLarge(true);
});
async function testWriteFileLarge(atomic: boolean) {
const resource = URI.file(join(testDir, 'lorem.txt'));
const content = readFileSync(resource.fsPath);
const newContent = content.toString() + content.toString();
const fileStat = await service.writeFile(resource, VSBuffer.fromString(newContent));
const fileStat = await service.writeFile(resource, VSBuffer.fromString(newContent), { atomic: atomic ? { postfix: '.vsctmp' } : false });
assert.strictEqual(fileStat.name, 'lorem.txt');
assert.strictEqual(readFileSync(resource.fsPath).toString(), newContent);

View file

@ -139,7 +139,7 @@ export class FileStorage {
// Write to disk
try {
await this.fileService.writeFile(this.storagePath, VSBuffer.fromString(serializedDatabase));
await this.fileService.writeFile(this.storagePath, VSBuffer.fromString(serializedDatabase), { atomic: { postfix: '.vsctmp' } });
this.lastSavedStorageContents = serializedDatabase;
} catch (error) {
this.logService.error(error);

View file

@ -424,6 +424,8 @@ export const diffRemovedOutline = registerColor('diffEditor.removedTextBorder',
export const diffBorder = registerColor('diffEditor.border', { dark: null, light: null, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('diffEditorBorder', 'Border color between the two text editors.'));
export const diffDiagonalFill = registerColor('diffEditor.diagonalFill', { dark: '#cccccc33', light: '#22222233', hcDark: null, hcLight: null }, nls.localize('diffDiagonalFill', "Color of the diff editor's diagonal fill. The diagonal fill is used in side-by-side diff views."));
export const diffUnchangedRegionBackground = registerColor('diffEditor.unchangedRegionBackground', { dark: '#000000', light: '#e4e4e4', hcDark: null, hcLight: null }, nls.localize('diffEditor.unchangedRegionBackground', "The color of unchanged blocks in diff editor."));
/**
* List and tree colors
*/

View file

@ -1292,8 +1292,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
return extHostChat.sendInteractiveRequestToProvider(providerId, message);
},
get onDidPerformUserAction() {
// TODO this needs to be staged with a copilot chat update
// checkProposedApiEnabled(extension, 'interactiveUserActions');
checkProposedApiEnabled(extension, 'interactiveUserActions');
return extHostChat.onDidPerformUserAction;
}
};

View file

@ -107,7 +107,7 @@ export class ExtHostConsumerFileSystem {
await that._proxy.$ensureActivation(uri.scheme);
return await provider.delete(uri, { recursive: false, ...options });
} else {
return await that._proxy.$delete(uri, { recursive: false, useTrash: false, ...options });
return await that._proxy.$delete(uri, { recursive: false, useTrash: false, atomic: false, ...options });
}
} catch (err) {
return ExtHostConsumerFileSystem._handleError(err);

View file

@ -138,10 +138,12 @@ export class ExtHostTesting implements ExtHostTestingShape {
createTestRun: (request, name, persist = true) => {
return this.runTracker.createTestRun(controllerId, collection, request, name, persist);
},
invalidateTestResults: item => {
invalidateTestResults: items => {
checkProposedApiEnabled(extension, 'testInvalidateResults');
const id = item ? TestId.fromExtHostTestItem(item, controllerId).toString() : controllerId;
return this.proxy.$markTestRetired(id);
for (const item of items instanceof Array ? items : [items]) {
const id = item ? TestId.fromExtHostTestItem(item, controllerId).toString() : controllerId;
this.proxy.$markTestRetired(id);
}
},
set resolveHandler(fn) {
collection.resolveHandler = fn;

View file

@ -55,11 +55,11 @@ class DiskFileSystemProviderAdapter implements vscode.FileSystemProvider {
}
writeFile(uri: vscode.Uri, content: Uint8Array, options: { readonly create: boolean; readonly overwrite: boolean }): Promise<void> {
return this.impl.writeFile(uri, content, { ...options, unlock: false });
return this.impl.writeFile(uri, content, { ...options, unlock: false, atomic: false });
}
delete(uri: vscode.Uri, options: { readonly recursive: boolean }): Promise<void> {
return this.impl.delete(uri, { ...options, useTrash: false });
return this.impl.delete(uri, { ...options, useTrash: false, atomic: false });
}
rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { readonly overwrite: boolean }): Promise<void> {

View file

@ -117,9 +117,12 @@ CommandsRegistry.registerCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, async functio
}
const folderPicks: IQuickPickItem[] = folders.map(folder => {
const label = folder.name;
const description = labelService.getUriLabel(dirname(folder.uri), { relative: true });
return {
label: folder.name,
description: labelService.getUriLabel(dirname(folder.uri), { relative: true }),
label,
description: description !== label ? description : undefined, // https://github.com/microsoft/vscode/issues/183418
folder,
iconClasses: getIconClasses(modelService, languageService, folder.uri, FileKind.ROOT_FOLDER)
};

View file

@ -68,6 +68,7 @@ import { UntitledTextEditorInputSerializer, UntitledTextEditorWorkingCopyEditorH
import { DynamicEditorConfigurations } from 'vs/workbench/browser/parts/editor/editorConfiguration';
import { AccessibilityStatus } from 'vs/workbench/browser/parts/editor/accessibilityStatus';
import { ToggleTabsVisibilityAction } from 'vs/workbench/browser/actions/layoutActions';
import 'vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.contribution';
//#region Editor Registrations

View file

@ -385,10 +385,7 @@ function registerDiffEditorCommands(): void {
const activeTextDiffEditor = getActiveTextDiffEditor(accessor);
if (activeTextDiffEditor) {
const navigator = activeTextDiffEditor.getDiffNavigator();
if (navigator) {
next ? navigator.next() : navigator.previous();
}
activeTextDiffEditor.getControl()?.goToDiff(next ? 'next' : 'previous');
}
}

View file

@ -7,13 +7,12 @@ import { localize } from 'vs/nls';
import { deepClone } from 'vs/base/common/objects';
import { isObject, assertIsDefined, withUndefinedAsNull, withNullAsUndefined } from 'vs/base/common/types';
import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser';
import { IDiffEditorOptions, EditorOption, IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IDiffEditorOptions, IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions';
import { AbstractTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor';
import { TEXT_DIFF_EDITOR_ID, IEditorFactoryRegistry, EditorExtensions, ITextDiffEditorPane, IEditorOpenContext, EditorInputCapabilities, isEditorInput, isTextEditorViewState, createTooLargeFileError } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { applyTextEditorOptions } from 'vs/workbench/common/editor/editorOptions';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { DiffNavigator } from 'vs/editor/browser/widget/diffNavigator';
import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget';
import { TextDiffEditorModel } from 'vs/workbench/common/editor/textDiffEditorModel';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@ -37,17 +36,17 @@ import { ByteSize, FileOperationError, FileOperationResult, IFileService, TooLar
import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { StopWatch } from 'vs/base/common/stopwatch';
import { DiffEditorWidget2 } from 'vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2';
/**
* The text editor that leverages the diff text editor for the editing experience.
*/
export class TextDiffEditor extends AbstractTextEditor<IDiffEditorViewState> implements ITextDiffEditorPane {
static readonly ID = TEXT_DIFF_EDITOR_ID;
private static widgetCounter = 0; // Just for debugging
private diffEditorControl: DiffEditorWidget | undefined = undefined;
private diffEditorControl: IDiffEditor | undefined = undefined;
private diffNavigator: DiffNavigator | undefined;
private readonly diffNavigatorDisposables = this._register(new DisposableStore());
private inputLifecycleStopWatch: StopWatch | undefined = undefined;
@ -86,7 +85,18 @@ export class TextDiffEditor extends AbstractTextEditor<IDiffEditorViewState> imp
}
protected override createEditorControl(parent: HTMLElement, configuration: ICodeEditorOptions): void {
this.diffEditorControl = this._register(this.instantiationService.createInstance(DiffEditorWidget, parent, configuration, {}));
TextDiffEditor.widgetCounter++;
let useVersion2 = this.textResourceConfigurationService.getValue(undefined, 'diffEditor.experimental.useVersion2');
if (useVersion2 === 'first') {
// This allows to have both the old and new diff editor next to each other - just for debugging
useVersion2 = TextDiffEditor.widgetCounter === 1;
}
if (useVersion2) {
this.diffEditorControl = this._register(this.instantiationService.createInstance(DiffEditorWidget2, parent, configuration, {}));
} else {
this.diffEditorControl = this._register(this.instantiationService.createInstance(DiffEditorWidget, parent, configuration, {}));
}
}
protected updateEditorControlOptions(options: ICodeEditorOptions): void {
@ -137,12 +147,9 @@ export class TextDiffEditor extends AbstractTextEditor<IDiffEditorViewState> imp
optionsGotApplied = applyTextEditorOptions(options, control, ScrollType.Immediate);
}
// Diff navigator
this.diffNavigator = this.instantiationService.createInstance(DiffNavigator, control, {
alwaysRevealFirst: !optionsGotApplied && !hasPreviousViewState, // only reveal first change if we had no options or viewstate
findResultLoop: this.getMainControl()?.getOption(EditorOption.find).loop
});
this.diffNavigatorDisposables.add(this.diffNavigator);
if (!optionsGotApplied && !hasPreviousViewState) {
control.revealFirstDiff();
}
// Since the resolved model provides information about being readonly
// or not, we apply it here to the editor even though the editor input
@ -335,10 +342,6 @@ export class TextDiffEditor extends AbstractTextEditor<IDiffEditorViewState> imp
});
}
getDiffNavigator(): DiffNavigator | undefined {
return this.diffNavigator;
}
override getControl(): IDiffEditor | undefined {
return this.diffEditorControl;
}

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