mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 21:09:43 +00:00
Merge remote-tracking branch 'origin/main' into merogge/notebook-verbosity
This commit is contained in:
commit
39f618bb38
67
.configurations/configuration.dsc.yaml
Normal file
67
.configurations/configuration.dsc.yaml
Normal 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
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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."
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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.
|
||||
|
|
108
cli/src/rpc.rs
108
cli/src/rpc.rs
|
@ -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";
|
||||
|
|
|
@ -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(¶ms.env);
|
||||
p.stdin(Stdio::piped());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
["`", "`"]
|
||||
],
|
||||
"folding": {
|
||||
"offSide": true,
|
||||
"markers": {
|
||||
"start": "^\\s*--\\s*#region\\b",
|
||||
"end": "^\\s*--\\s*#endregion\\b"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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..."),
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 });
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "code-oss-dev",
|
||||
"version": "1.79.0",
|
||||
"distro": "7272f69cc607298dc986708a73bd978aa7c3af3d",
|
||||
"distro": "b28dee681d8f2ede89c55ce436c03ed09560565b",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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',
|
||||
});
|
|
@ -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
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
)
|
||||
});
|
|
@ -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!),
|
||||
},
|
||||
};
|
||||
}
|
344
src/vs/editor/browser/widget/diffEditorWidget2/diffModel.ts
Normal file
344
src/vs/editor/browser/widget/diffEditorWidget2/diffModel.ts
Normal 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,
|
||||
};
|
||||
}
|
237
src/vs/editor/browser/widget/diffEditorWidget2/lineAlignment.ts
Normal file
237
src/vs/editor/browser/widget/diffEditorWidget2/lineAlignment.ts
Normal 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;
|
||||
}
|
|
@ -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);
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
}
|
||||
}
|
61
src/vs/editor/browser/widget/diffEditorWidget2/style.css
Normal file
61
src/vs/editor/browser/widget/diffEditorWidget2/style.css
Normal 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;
|
||||
}
|
|
@ -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>;
|
||||
}
|
121
src/vs/editor/browser/widget/diffEditorWidget2/utils.ts
Normal file
121
src/vs/editor/browser/widget/diffEditorWidget2/utils.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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."),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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']
|
||||
}
|
||||
)),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -104,7 +104,7 @@ interface NodePosition {
|
|||
*/
|
||||
node: TreeNode;
|
||||
/**
|
||||
* remainer in current piece.
|
||||
* remainder in current piece.
|
||||
*/
|
||||
remainder: number;
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -198,7 +198,7 @@ export class ToggleAlwaysShowInlineSuggestionToolbar extends Action2 {
|
|||
group: 'secondary',
|
||||
order: 10,
|
||||
}],
|
||||
toggled: InlineCompletionContextKeys.alwaysShowInlineSuggestionToolbar,
|
||||
toggled: ContextKeyExpr.equals('config.editor.inlineSuggest.showToolbar', 'always')
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,10 +89,10 @@ export class InlineCompletionsSource extends Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
this._updateOperation.clear();
|
||||
transaction(tx => {
|
||||
target.set(completions, tx);
|
||||
});
|
||||
this._updateOperation.clear();
|
||||
|
||||
return true;
|
||||
})();
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
15
src/vs/monaco.d.ts
vendored
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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'); }
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue