mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-13 23:44:09 +01:00
Merge branch 'main' into ai-codefixes
This commit is contained in:
@@ -486,7 +486,7 @@
|
||||
],
|
||||
"action": "comment",
|
||||
"addLabel": "info-needed",
|
||||
"comment": "Please perform the following **three tasks** to diagnose the root cause of the issue:\n\n* [ ] **1.) Disable Extensions**\n * Select `View` and pick `Command Palette...`\n * Run `Developer: Reload With Extensions Disabled`\n * 👉 See if the issue reproduces\n\n* [ ] **2.) Disable Configuration**\n * Select `View` and pick `Command Palette...`\n * Run `Profiles: Create a Temporary Profile`\n * 👉 See if the issue reproduces\n\n* [ ] **3.) Try VS Code Insiders**\n * Download [VS Code Insiders](https://code.visualstudio.com/insiders/)\n * Install and Run it\n * 👉 See if the issue reproduces\n \nThen pick one of the three resolutions depending on which step has helped:\n\n<details>\n <summary>Disabling my Extensions helped</summary>\n\nPlease run the command `Start Extension Bisect` and follow the instructions to find the extension that is causing this issue.\n\n<img width='631' alt='image' src='https://user-images.githubusercontent.com/900690/228760008-d5790ad1-74da-46a2-917f-e1bd26281ca8.png'>\n\nPlease report the issue to the extension causing this.\n</details>\n\n<details>\n <summary>Disabling my configuration helped</summary>\nPlease report back more details about your configuration, including settings.\n</details>\n\n<details>\n <summary>Using VS Code Insiders has helped</summary>\n✅ This likely means that the issue has been addressed already and will be available in an upcoming release. You can safely use VS Code Insiders until the new stable version is available.\n</details>"
|
||||
"comment": "Please diagnose the root cause of the issue by running the command `F1 > Help: Troubleshoot Issue` and following the instructions. Once you have done that, please update the issue with the results.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"__comment__": "Allows folks on the team to label issues by commenting: `\\label My-Label` ",
|
||||
|
||||
@@ -18,3 +18,4 @@ vscode.db
|
||||
/cli/target
|
||||
/cli/openssl
|
||||
product.overrides.json
|
||||
*.snap.actual
|
||||
|
||||
+10
@@ -124,6 +124,16 @@
|
||||
"language": "github-issues",
|
||||
"value": "$REPOS $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:z-author-verified label:unreleased"
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
"language": "markdown",
|
||||
"value": "## All Unverified Fixes"
|
||||
},
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "github-issues",
|
||||
"value": "$REPOS $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:z-author-verified "
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
"language": "markdown",
|
||||
|
||||
+12
-2
@@ -7,7 +7,7 @@
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "github-issues",
|
||||
"value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels\n\n$MILESTONE=milestone:\"August 2023\"\n\n$MINE=assignee:@me"
|
||||
"value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels\r\n\r\n$MILESTONE=milestone:\"August 2023\"\r\n\r\n$MINE=assignee:@me"
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
@@ -169,6 +169,16 @@
|
||||
"language": "github-issues",
|
||||
"value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed -author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found"
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
"language": "markdown",
|
||||
"value": "## Test steps needed from others"
|
||||
},
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "github-issues",
|
||||
"value": "$REPOS $MILESTONE -$MINE is:issue label:bug label:verification-steps-needed"
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
"language": "markdown",
|
||||
@@ -177,6 +187,6 @@
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "github-issues",
|
||||
"value": "repo:microsoft/vscode $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request -label:on-release-notes\nrepo:microsoft/vscode $MILESTONE $MINE is:issue is:closed reason:completed label:engineering -label:on-release-notes\nrepo:microsoft/vscode $MILESTONE $MINE is:issue is:closed reason:completed label:plan-item -label:on-release-notes"
|
||||
"value": "repo:microsoft/vscode $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request -label:on-release-notes\r\nrepo:microsoft/vscode $MILESTONE $MINE is:issue is:closed reason:completed label:engineering -label:on-release-notes\r\nrepo:microsoft/vscode $MILESTONE $MINE is:issue is:closed reason:completed label:plan-item -label:on-release-notes"
|
||||
}
|
||||
]
|
||||
Vendored
+1
-1
@@ -6,7 +6,7 @@
|
||||
// Placeholders with the same ids are connected.
|
||||
// Example:
|
||||
"MSFT Copyright Header": {
|
||||
"scope": "javascript,typescript,css",
|
||||
"scope": "javascript,typescript,css,rust",
|
||||
"prefix": [
|
||||
"header",
|
||||
"stub",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
disturl "https://electronjs.org/headers"
|
||||
target "25.4.0"
|
||||
ms_build_id "22958381"
|
||||
target "25.5.0"
|
||||
ms_build_id "23084831"
|
||||
runtime "electron"
|
||||
build_from_source "true"
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
fc51b54078335a8d687e0dda5e65e0b9916d9678d13d8f84b1dc91bceaac6b94 *electron-v25.4.0-darwin-arm64-symbols.zip
|
||||
8a1f0cc5c8fea926360347f9448a766f56fe4fea8418ce11ca5755be933dcae2 *electron-v25.4.0-darwin-arm64.zip
|
||||
a221e0fb48d178940e77bb96c8bfe26fb5f4eb5b916c93d7b771d0f216e9f310 *electron-v25.4.0-darwin-x64-symbols.zip
|
||||
f6e65c0d27e99121e53eb52335ef42653108bfa5cb8de06c35853c253fafc000 *electron-v25.4.0-darwin-x64.zip
|
||||
e1006e2a327f23fe73c172789a9957c83e1b7563358908c0d1afb6f2a95f305c *electron-v25.4.0-linux-arm64-symbols.zip
|
||||
2225b64fbe7ff602160ad371ede9a4eb79534d865dbac3a6fcc252faf0214240 *electron-v25.4.0-linux-arm64.zip
|
||||
7ff71baa0e0c55728569a3a05fb1aadd079e2dd79bbc7bb4ce460c8fefe1c422 *electron-v25.4.0-linux-armv7l-symbols.zip
|
||||
71469e85d3b3bbf40b698ffb3e86b96eef854c2bc63096900d1c1a606f8b9bb4 *electron-v25.4.0-linux-armv7l.zip
|
||||
28a29971aa65a72ead251bb3be5135b54a1a1c0a858e80959b877aea16975acb *electron-v25.4.0-linux-x64-symbols.zip
|
||||
7cce96b1f33c7f6333d24f6966bd0613e6d95a9fd3e7fba4f20d11869adc22c6 *electron-v25.4.0-linux-x64.zip
|
||||
615e880460216419d9b6dfa8cdf59d067ad3f629497e4419cd723351e544645a *electron-v25.4.0-win32-arm64-pdb.zip
|
||||
548ac3cbfe5a4bd1c8628aa4a73cc424116c0870ab119cf8077993b932baf51b *electron-v25.4.0-win32-arm64-symbols.zip
|
||||
c51677babba862a0b98ceee992b42eaf7d2c9e8e9dfb1496d15d7381b908dc72 *electron-v25.4.0-win32-arm64.zip
|
||||
9a6271c5d9eaf07fb01b38bd743b2cdc0642bcf16f9add2724eccad2aea2b3a8 *electron-v25.4.0-win32-ia32-pdb.zip
|
||||
e961dd15cfde46c9765eebd4f421e17850b85c3f25e0e6891b4f55a9834805f6 *electron-v25.4.0-win32-ia32-symbols.zip
|
||||
14320e1f7db40e885ee101acd39698be58745cdfe6a519e0a5316269837f5bf7 *electron-v25.4.0-win32-ia32.zip
|
||||
b055eb2b0bf037fc54f8af9d76c5894ffaffe6bd24e231da7ce847048ed60c97 *electron-v25.4.0-win32-x64-pdb.zip
|
||||
0d84815a05dd0dd96266808d3cefde52cdea149c5faf2abb2d4d3c028e5f30d0 *electron-v25.4.0-win32-x64-symbols.zip
|
||||
6f0c6da70a4daa50ec46c389896d08009ce14cdac5896c608e5af8956fd359de *electron-v25.4.0-win32-x64.zip
|
||||
0e4fd74705d098d616b313b9078e74ba3b44ead2f3abd083acd8fab68048a563 *ffmpeg-v25.4.0-darwin-arm64.zip
|
||||
a4c1b013448c5b9955d4927be7a72f8836a7af909c795aeecdb43086e76bd0d8 *ffmpeg-v25.4.0-darwin-x64.zip
|
||||
bd52d57ff97fb56ac01a3482af905d04f0d4e9c13c53858c6d9f99957eca82da *ffmpeg-v25.4.0-linux-arm64.zip
|
||||
9b3d09177fa1e63e2a6beecfa70aeec30aeb5c1873ff21128a68051c4e23f95d *ffmpeg-v25.4.0-linux-armv7l.zip
|
||||
edc7b1c9f1a0733f109a2c0375a4e40c5bfe0bf28b7f06dcc76e1ada0aa2f125 *ffmpeg-v25.4.0-linux-x64.zip
|
||||
39d5389ff83fd30bacfd82e3beea5cb95eaa46aaa7d2ddaccc705f2490210f2e *ffmpeg-v25.4.0-win32-arm64.zip
|
||||
059c72026437c4d97ec55ef6fab85526d2fa1de3571c478a55dec3489487fdf4 *ffmpeg-v25.4.0-win32-ia32.zip
|
||||
01871604f273c3206c808954c604afda0294da15dd02dd6360fe736e5b5c82a8 *ffmpeg-v25.4.0-win32-x64.zip
|
||||
c3fb8cb4804143eb25fce55a179a6f2df8c215ed709104ec235c96e357b20f42 *electron-v25.5.0-darwin-arm64-symbols.zip
|
||||
e6d2a09348d4fe7c9fbd92bf796489a95e625642f0f1ce96169212554cfa6841 *electron-v25.5.0-darwin-arm64.zip
|
||||
15c28e613dfee0f7e46a296bb4aed64e17f9644d7ef19129aeb6480b8da230ab *electron-v25.5.0-darwin-x64-symbols.zip
|
||||
a5c5c0b621daf8242258c89edb2387fbdf1c69125c984f8564d89b87927373e3 *electron-v25.5.0-darwin-x64.zip
|
||||
69d60a69b7f692b9069cdf9e518bcbaca9cec561f40199cc87196929936a34ce *electron-v25.5.0-linux-arm64-symbols.zip
|
||||
adec2ba09faf5f8d8af8997c8bed43c7712eba5546db1e7d06f8357bf4613921 *electron-v25.5.0-linux-arm64.zip
|
||||
0fe7a5d152c9c401671e02ebcd9da34ab9bb0c28598ede3657077a79f8b3e70a *electron-v25.5.0-linux-armv7l-symbols.zip
|
||||
eccb66e4a308a0bd2d90474894370e3d687e32f78672e6b5077c1822c7bc526b *electron-v25.5.0-linux-armv7l.zip
|
||||
82bd9bc9e66f8ae802fe48a51b8d7e2fb599403e9715fa4b859190200a7376b1 *electron-v25.5.0-linux-x64-symbols.zip
|
||||
485cbeb206fccfb4ed42f694100eebb80c9db6639b3537a95823c4fcb7f210cd *electron-v25.5.0-linux-x64.zip
|
||||
ed2c2a7da571b53bcb336b9a2a024753a272df82ece45df83df888df51bf1912 *electron-v25.5.0-win32-arm64-pdb.zip
|
||||
c316f6364e9b4cd61e19d4763c96abda7247a2c31ae7d30e81219c9a7754f11a *electron-v25.5.0-win32-arm64-symbols.zip
|
||||
582ccbfc5a85a093f5639ee476bb5fef18c2d25dab4a60e5f5cb47e64c99f7fb *electron-v25.5.0-win32-arm64.zip
|
||||
5776e650c23e3847b0c52d850f61c57a84a4fa30943c3cc82197250112911b5b *electron-v25.5.0-win32-ia32-pdb.zip
|
||||
730b429ad2c4cae0fe3ba9600a6ee86c79dada77976bac1c75ca2404993495bc *electron-v25.5.0-win32-ia32-symbols.zip
|
||||
7e6e68aa33a89c0d647575b06daf415a2401feb170ebb9cd795e221af321f751 *electron-v25.5.0-win32-ia32.zip
|
||||
0792665fda9255b340a829cdd24601887a2ca8f04cc49f9a6d4557db0c0cd2f2 *electron-v25.5.0-win32-x64-pdb.zip
|
||||
dc2459546951f8418e866857e9111dd83a0789d401906b2200c2f8dd59d9146b *electron-v25.5.0-win32-x64-symbols.zip
|
||||
9bf7980fbc024ba77ea8ab3e2d32088a5f69bf32506a7d2db72ede17028abdf4 *electron-v25.5.0-win32-x64.zip
|
||||
5b1ea601b737842eacf88d7456c4d14e697822753e9a08ede889cce9e20bafb2 *ffmpeg-v25.5.0-darwin-arm64.zip
|
||||
37bf5c75edefc0b6735b44d0b5b06fdd427179a8501f6e93df9840283cdb4a95 *ffmpeg-v25.5.0-darwin-x64.zip
|
||||
bd52d57ff97fb56ac01a3482af905d04f0d4e9c13c53858c6d9f99957eca82da *ffmpeg-v25.5.0-linux-arm64.zip
|
||||
9b3d09177fa1e63e2a6beecfa70aeec30aeb5c1873ff21128a68051c4e23f95d *ffmpeg-v25.5.0-linux-armv7l.zip
|
||||
edc7b1c9f1a0733f109a2c0375a4e40c5bfe0bf28b7f06dcc76e1ada0aa2f125 *ffmpeg-v25.5.0-linux-x64.zip
|
||||
2e28767b3570ea247869a20988cddd23af710eb994d6099404f123390cedeba6 *ffmpeg-v25.5.0-win32-arm64.zip
|
||||
715568eefd7267573a30186ade3de901587baeb1f013200d8ae50b35941b613f *ffmpeg-v25.5.0-win32-ia32.zip
|
||||
29876504452aaa505f696642178968e24b8dc8cc4b055071e6f0f3f073088acc *ffmpeg-v25.5.0-win32-x64.zip
|
||||
|
||||
@@ -150,10 +150,12 @@ function hygiene(some, linting = true) {
|
||||
}
|
||||
|
||||
const productJsonFilter = filter('product.json', { restore: true });
|
||||
const snapshotFilter = filter(['**', '!**/*.snap', '!**/*.snap.actual']);
|
||||
const unicodeFilterStream = filter(unicodeFilter, { restore: true });
|
||||
|
||||
const result = input
|
||||
.pipe(filter((f) => !f.stat.isDirectory()))
|
||||
.pipe(snapshotFilter)
|
||||
.pipe(productJsonFilter)
|
||||
.pipe(process.env['BUILD_SOURCEVERSION'] ? es.through() : productJson)
|
||||
.pipe(productJsonFilter.restore)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -93,9 +93,9 @@ function createCompile(src: string, build: boolean, emitError: boolean, transpil
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
export function transpileTask(src: string, out: string, swc: boolean): () => NodeJS.ReadWriteStream {
|
||||
export function transpileTask(src: string, out: string, swc: boolean): task.StreamTask {
|
||||
|
||||
return function () {
|
||||
const task = () => {
|
||||
|
||||
const transpile = createCompile(src, false, true, { swc });
|
||||
const srcPipe = gulp.src(`${src}/**`, { base: `${src}` });
|
||||
@@ -104,11 +104,14 @@ export function transpileTask(src: string, out: string, swc: boolean): () => Nod
|
||||
.pipe(transpile())
|
||||
.pipe(gulp.dest(out));
|
||||
};
|
||||
|
||||
task.taskName = `transpile-${path.basename(src)}`;
|
||||
return task;
|
||||
}
|
||||
|
||||
export function compileTask(src: string, out: string, build: boolean, options: { disableMangle?: boolean } = {}): () => NodeJS.ReadWriteStream {
|
||||
export function compileTask(src: string, out: string, build: boolean, options: { disableMangle?: boolean } = {}): task.StreamTask {
|
||||
|
||||
return function () {
|
||||
const task = () => {
|
||||
|
||||
if (os.totalmem() < 4_000_000_000) {
|
||||
throw new Error('compilation requires 4GB of RAM');
|
||||
@@ -150,11 +153,14 @@ export function compileTask(src: string, out: string, build: boolean, options: {
|
||||
.pipe(compile())
|
||||
.pipe(gulp.dest(out));
|
||||
};
|
||||
|
||||
task.taskName = `compile-${path.basename(src)}`;
|
||||
return task;
|
||||
}
|
||||
|
||||
export function watchTask(out: string, build: boolean): () => NodeJS.ReadWriteStream {
|
||||
export function watchTask(out: string, build: boolean): task.StreamTask {
|
||||
|
||||
return function () {
|
||||
const task = () => {
|
||||
const compile = createCompile('src', build, false, false);
|
||||
|
||||
const src = gulp.src('src/**', { base: 'src' });
|
||||
@@ -168,6 +174,8 @@ export function watchTask(out: string, build: boolean): () => NodeJS.ReadWriteSt
|
||||
.pipe(util.incremental(compile, src, true))
|
||||
.pipe(gulp.dest(out));
|
||||
};
|
||||
task.taskName = `watch-${path.basename(out)}`;
|
||||
return task;
|
||||
}
|
||||
|
||||
const REPO_SRC_FOLDER = path.join(__dirname, '../../src');
|
||||
|
||||
@@ -58,6 +58,10 @@
|
||||
"name": "vs/workbench/contrib/commands",
|
||||
"project": "vscode-workbench"
|
||||
},
|
||||
{
|
||||
"name": "vs/workbench/contrib/mappedEdits",
|
||||
"project": "vscode-workbench"
|
||||
},
|
||||
{
|
||||
"name": "vs/workbench/contrib/comments",
|
||||
"project": "vscode-workbench"
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
"--vscode-diffEditor-insertedTextBackground",
|
||||
"--vscode-diffEditor-insertedTextBorder",
|
||||
"--vscode-diffEditor-move-border",
|
||||
"--vscode-diffEditor-moveActive-border",
|
||||
"--vscode-diffEditor-removedLineBackground",
|
||||
"--vscode-diffEditor-removedTextBackground",
|
||||
"--vscode-diffEditor-removedTextBorder",
|
||||
@@ -545,21 +546,31 @@
|
||||
"--vscode-statusBar-noFolderBackground",
|
||||
"--vscode-statusBar-noFolderBorder",
|
||||
"--vscode-statusBar-noFolderForeground",
|
||||
"--vscode-statusBar-offlineBackground",
|
||||
"--vscode-statusBar-offlineForeground",
|
||||
"--vscode-statusBarItem-offlineBackground",
|
||||
"--vscode-statusBarItem-offlineForeground",
|
||||
"--vscode-statusBarItem-offlineHoverBackground",
|
||||
"--vscode-statusBarItem-offlineHoverForeground",
|
||||
"--vscode-statusBarItem-activeBackground",
|
||||
"--vscode-statusBarItem-compactHoverBackground",
|
||||
"--vscode-statusBarItem-errorBackground",
|
||||
"--vscode-statusBarItem-errorForeground",
|
||||
"--vscode-statusBarItem-errorHoverBackground",
|
||||
"--vscode-statusBarItem-errorHoverForeground",
|
||||
"--vscode-statusBarItem-focusBorder",
|
||||
"--vscode-statusBarItem-hoverBackground",
|
||||
"--vscode-statusBarItem-hoverForeground",
|
||||
"--vscode-statusBarItem-prominentBackground",
|
||||
"--vscode-statusBarItem-prominentForeground",
|
||||
"--vscode-statusBarItem-prominentHoverBackground",
|
||||
"--vscode-statusBarItem-prominentHoverForeground",
|
||||
"--vscode-statusBarItem-remoteBackground",
|
||||
"--vscode-statusBarItem-remoteForeground",
|
||||
"--vscode-statusBarItem-remoteHoverBackground",
|
||||
"--vscode-statusBarItem-remoteHoverForeground",
|
||||
"--vscode-statusBarItem-warningBackground",
|
||||
"--vscode-statusBarItem-warningForeground",
|
||||
"--vscode-statusBarItem-warningHoverBackground",
|
||||
"--vscode-statusBarItem-warningHoverForeground",
|
||||
"--vscode-symbolIcon-arrayForeground",
|
||||
"--vscode-symbolIcon-booleanForeground",
|
||||
"--vscode-symbolIcon-classForeground",
|
||||
@@ -768,4 +779,4 @@
|
||||
"--z-index-notebook-sticky-scroll",
|
||||
"--zoom-factor"
|
||||
]
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -6,7 +6,7 @@
|
||||
"git": {
|
||||
"name": "chromium",
|
||||
"repositoryUrl": "https://chromium.googlesource.com/chromium/src",
|
||||
"commitHash": "8bab63006effd27646be3d5c07eb09d3282c9b4d"
|
||||
"commitHash": "88c0a3606e1a856ad43651130d27bb880e1833d6"
|
||||
}
|
||||
},
|
||||
"licenseDetail": [
|
||||
@@ -40,7 +40,7 @@
|
||||
"SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
],
|
||||
"isOnlyProductionDependency": true,
|
||||
"version": "114.0.5735.248"
|
||||
"version": "114.0.5735.289"
|
||||
},
|
||||
{
|
||||
"component": {
|
||||
@@ -528,12 +528,12 @@
|
||||
"git": {
|
||||
"name": "electron",
|
||||
"repositoryUrl": "https://github.com/electron/electron",
|
||||
"commitHash": "d219fcc8b61dbeb459e92c90a069076f18a9a12a"
|
||||
"commitHash": "34be316c404e84cdd967fa0e10fceeb6424eed90"
|
||||
}
|
||||
},
|
||||
"isOnlyProductionDependency": true,
|
||||
"license": "MIT",
|
||||
"version": "25.4.0"
|
||||
"version": "25.5.0"
|
||||
},
|
||||
{
|
||||
"component": {
|
||||
|
||||
Generated
+2
-2
@@ -983,9 +983,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.8"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
|
||||
checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ libc = "0.2.144"
|
||||
tunnels = { git = "https://github.com/microsoft/dev-tunnels", rev = "2621784a9ad72aa39500372391332a14bad581a3", default-features = false, features = ["connections"] }
|
||||
keyring = { version = "2.0.3", default-features = false, features = ["linux-secret-service-rt-tokio-crypto-openssl"] }
|
||||
dialoguer = "0.10.4"
|
||||
hyper = "0.14.26"
|
||||
hyper = { version = "0.14.26", features = ["server", "http1", "runtime"] }
|
||||
indicatif = "0.17.4"
|
||||
tempfile = "3.5.0"
|
||||
clap_lex = "0.5.0"
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::process::Command;
|
||||
|
||||
use clap::Parser;
|
||||
use cli::{
|
||||
commands::{args, tunnels, update, version, CommandContext},
|
||||
commands::{args, serve_web, tunnels, update, version, CommandContext},
|
||||
constants::get_default_user_agent,
|
||||
desktop, log,
|
||||
state::LauncherPaths,
|
||||
@@ -99,6 +99,10 @@ async fn main() -> Result<(), std::convert::Infallible> {
|
||||
tunnels::command_shell(context!(), cs_args).await
|
||||
}
|
||||
|
||||
Some(args::Commands::ServeWeb(sw_args)) => {
|
||||
serve_web::serve_web(context!(), sw_args).await
|
||||
}
|
||||
|
||||
Some(args::Commands::Tunnel(tunnel_args)) => match tunnel_args.subcommand {
|
||||
Some(args::TunnelSubcommand::Prune) => tunnels::prune(context!()).await,
|
||||
Some(args::TunnelSubcommand::Unregister) => tunnels::unregister(context!()).await,
|
||||
|
||||
@@ -9,4 +9,5 @@ pub mod args;
|
||||
pub mod tunnels;
|
||||
pub mod update;
|
||||
pub mod version;
|
||||
pub mod serve_web;
|
||||
pub use context::CommandContext;
|
||||
|
||||
@@ -172,11 +172,42 @@ pub enum Commands {
|
||||
/// Changes the version of the editor you're using.
|
||||
Version(VersionArgs),
|
||||
|
||||
/// Runs a local web version of VS Code.
|
||||
ServeWeb(ServeWebArgs),
|
||||
|
||||
/// Runs the control server on process stdin/stdout
|
||||
#[clap(hide = true)]
|
||||
CommandShell(CommandShellArgs),
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct ServeWebArgs {
|
||||
/// Host to listen on, defaults to 'localhost'
|
||||
#[clap(long)]
|
||||
pub host: Option<String>,
|
||||
/// Port to listen on. If 0 is passed a random free port is picked.
|
||||
#[clap(long, default_value_t = 8000)]
|
||||
pub port: u16,
|
||||
/// A secret that must be included with all requests.
|
||||
#[clap(long)]
|
||||
pub connection_token: Option<String>,
|
||||
/// Run without a connection token. Only use this if the connection is secured by other means.
|
||||
#[clap(long)]
|
||||
pub without_connection_token: bool,
|
||||
/// If set, the user accepts the server license terms and the server will be started without a user prompt.
|
||||
#[clap(long)]
|
||||
pub accept_server_license_terms: bool,
|
||||
/// Specifies the directory that server data is kept in.
|
||||
#[clap(long)]
|
||||
pub server_data_dir: Option<String>,
|
||||
/// Specifies the directory that user data is kept in. Can be used to open multiple distinct instances of Code.
|
||||
#[clap(long)]
|
||||
pub user_data_dir: Option<String>,
|
||||
/// Set the root path for extensions.
|
||||
#[clap(long)]
|
||||
pub extensions_dir: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct CommandShellArgs {
|
||||
/// Listen on a socket instead of stdin/stdout.
|
||||
|
||||
@@ -0,0 +1,617 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::convert::Infallible;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{Body, Request, Response, Server};
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
use tokio::pin;
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::async_pipe::{get_socket_name, get_socket_rw_stream, AsyncPipe};
|
||||
use crate::constants::VSCODE_CLI_QUALITY;
|
||||
use crate::download_cache::DownloadCache;
|
||||
use crate::log;
|
||||
use crate::options::Quality;
|
||||
use crate::update_service::{
|
||||
unzip_downloaded_release, Platform, Release, TargetKind, UpdateService,
|
||||
};
|
||||
use crate::util::errors::AnyError;
|
||||
use crate::util::http::{self, ReqwestSimpleHttp};
|
||||
use crate::util::io::SilentCopyProgress;
|
||||
use crate::util::sync::{new_barrier, Barrier, BarrierOpener};
|
||||
use crate::{
|
||||
tunnels::legal,
|
||||
util::{errors::CodeError, prereqs::PreReqChecker},
|
||||
};
|
||||
|
||||
use super::{args::ServeWebArgs, CommandContext};
|
||||
|
||||
/// Length of a commit hash, for validation
|
||||
const COMMIT_HASH_LEN: usize = 40;
|
||||
/// Number of seconds where, if there's no connections to a VS Code server,
|
||||
/// the server is shut down.
|
||||
const SERVER_IDLE_TIMEOUT_SECS: u64 = 60 * 60;
|
||||
/// Number of seconds in which the server times out when there is a connection
|
||||
/// (should be large enough to basically never happen)
|
||||
const SERVER_ACTIVE_TIMEOUT_SECS: u64 = SERVER_IDLE_TIMEOUT_SECS * 24 * 30 * 12;
|
||||
/// How long to cache the "latest" version we get from the update service.
|
||||
const RELEASE_CACHE_SECS: u64 = 60 * 60;
|
||||
|
||||
/// Implements the vscode "server of servers". Clients who go to the URI get
|
||||
/// served the latest version of the VS Code server whenever they load the
|
||||
/// page. The VS Code server prefixes all assets and connections it loads with
|
||||
/// its version string, so existing clients can continue to get served even
|
||||
/// while new clients get new VS Code Server versions.
|
||||
pub async fn serve_web(ctx: CommandContext, mut args: ServeWebArgs) -> Result<i32, AnyError> {
|
||||
legal::require_consent(&ctx.paths, args.accept_server_license_terms)?;
|
||||
let mut addr: SocketAddr = match &args.host {
|
||||
Some(h) => h.parse().map_err(CodeError::InvalidHostAddress)?,
|
||||
None => SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
|
||||
};
|
||||
addr.set_port(args.port);
|
||||
|
||||
let platform: crate::update_service::Platform = PreReqChecker::new().verify().await?;
|
||||
|
||||
if !args.without_connection_token {
|
||||
// Ensure there's a defined connection token, since if multiple server versions
|
||||
// are excuted, they will need to have a single shared token.
|
||||
let connection_token = args
|
||||
.connection_token
|
||||
.clone()
|
||||
.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
|
||||
ctx.log.result(format!(
|
||||
"Web UI available at http://{}?tkn={}",
|
||||
addr, connection_token,
|
||||
));
|
||||
args.connection_token = Some(connection_token);
|
||||
} else {
|
||||
ctx.log
|
||||
.result(format!("Web UI available at http://{}", addr));
|
||||
args.connection_token = None;
|
||||
}
|
||||
|
||||
let cm = ConnectionManager::new(&ctx, platform, args);
|
||||
let make_svc = make_service_fn(move |_conn| {
|
||||
let cm = cm.clone();
|
||||
let log = ctx.log.clone();
|
||||
let service = service_fn(move |req| handle(cm.clone(), log.clone(), req));
|
||||
async move { Ok::<_, Infallible>(service) }
|
||||
});
|
||||
|
||||
let server = Server::bind(&addr).serve(make_svc);
|
||||
|
||||
server.await.map_err(CodeError::CouldNotListenOnInterface)?;
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
/// Handler function for an inbound request
|
||||
async fn handle(
|
||||
cm: Arc<ConnectionManager>,
|
||||
log: log::Logger,
|
||||
req: Request<Body>,
|
||||
) -> Result<Response<Body>, Infallible> {
|
||||
let release = if let Some((r, _)) = get_release_from_path(req.uri().path(), cm.platform) {
|
||||
r
|
||||
} else {
|
||||
match cm.get_latest_release().await {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
error!(log, "error getting latest version: {}", e);
|
||||
return Ok(response::code_err(e));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(match cm.get_connection(release).await {
|
||||
Ok(rw) => {
|
||||
if req.headers().contains_key(hyper::header::UPGRADE) {
|
||||
forward_ws_req_to_server(cm.log.clone(), rw, req).await
|
||||
} else {
|
||||
forward_http_req_to_server(rw, req).await
|
||||
}
|
||||
}
|
||||
Err(CodeError::ServerNotYetDownloaded) => response::wait_for_download(),
|
||||
Err(e) => response::code_err(e),
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the release info from the VS Code path prefix, which is in the
|
||||
/// format `/<quality>-<commit>/...`
|
||||
fn get_release_from_path(path: &str, platform: Platform) -> Option<(Release, String)> {
|
||||
if !path.starts_with('/') {
|
||||
return None; // paths must start with '/'
|
||||
}
|
||||
|
||||
let path = &path[1..];
|
||||
let i = path.find('/').unwrap_or(path.len());
|
||||
let quality_commit_sep = path.get(..i).and_then(|p| p.find('-'))?;
|
||||
|
||||
let (quality_commit, remaining) = path.split_at(i);
|
||||
let (quality, commit) = quality_commit.split_at(quality_commit_sep);
|
||||
|
||||
if !is_commit_hash(commit) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((
|
||||
Release {
|
||||
// remember to trim off the leading '/' which is now part of th quality
|
||||
quality: Quality::try_from(quality).ok()?,
|
||||
commit: commit.to_string(),
|
||||
platform,
|
||||
target: TargetKind::Web,
|
||||
name: "".to_string(),
|
||||
},
|
||||
remaining.to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Proxies the standard HTTP request to the async pipe, returning the piped response
|
||||
async fn forward_http_req_to_server(
|
||||
(rw, handle): (AsyncPipe, ConnectionHandle),
|
||||
req: Request<Body>,
|
||||
) -> Response<Body> {
|
||||
let (mut request_sender, connection) =
|
||||
match hyper::client::conn::Builder::new().handshake(rw).await {
|
||||
Ok(r) => r,
|
||||
Err(e) => return response::connection_err(e),
|
||||
};
|
||||
|
||||
tokio::spawn(connection);
|
||||
|
||||
let res = request_sender
|
||||
.send_request(req)
|
||||
.await
|
||||
.unwrap_or_else(response::connection_err);
|
||||
|
||||
// technically, we should buffer the body into memory since it may not be
|
||||
// read at this point, but because the keepalive time is very large
|
||||
// there's not going to be responses that take hours to send and x
|
||||
// cause us to kill the server before the response is sent
|
||||
drop(handle);
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
/// Proxies the websocket request to the async pipe
|
||||
async fn forward_ws_req_to_server(
|
||||
log: log::Logger,
|
||||
(rw, handle): (AsyncPipe, ConnectionHandle),
|
||||
mut req: Request<Body>,
|
||||
) -> Response<Body> {
|
||||
// splicing of client and servers inspired by https://github.com/hyperium/hyper/blob/fece9f7f50431cf9533cfe7106b53a77b48db699/examples/upgrades.rs
|
||||
let (mut request_sender, connection) =
|
||||
match hyper::client::conn::Builder::new().handshake(rw).await {
|
||||
Ok(r) => r,
|
||||
Err(e) => return response::connection_err(e),
|
||||
};
|
||||
|
||||
tokio::spawn(connection);
|
||||
|
||||
let mut proxied_req = Request::builder().uri(req.uri());
|
||||
for (k, v) in req.headers() {
|
||||
proxied_req = proxied_req.header(k, v);
|
||||
}
|
||||
|
||||
let mut res = request_sender
|
||||
.send_request(proxied_req.body(Body::empty()).unwrap())
|
||||
.await
|
||||
.unwrap_or_else(response::connection_err);
|
||||
|
||||
let mut proxied_res = Response::new(Body::empty());
|
||||
*proxied_res.status_mut() = res.status();
|
||||
for (k, v) in res.headers() {
|
||||
proxied_res.headers_mut().insert(k, v.clone());
|
||||
}
|
||||
|
||||
// only start upgrade at this point in case the server decides to deny socket
|
||||
if res.status() == hyper::StatusCode::SWITCHING_PROTOCOLS {
|
||||
tokio::spawn(async move {
|
||||
let (s_req, s_res) =
|
||||
tokio::join!(hyper::upgrade::on(&mut req), hyper::upgrade::on(&mut res));
|
||||
|
||||
match (s_req, s_res) {
|
||||
(Err(e1), Err(e2)) => debug!(
|
||||
log,
|
||||
"client ({}) and server ({}) websocket upgrade failed", e1, e2
|
||||
),
|
||||
(Err(e1), _) => debug!(log, "client ({}) websocket upgrade failed", e1),
|
||||
(_, Err(e2)) => debug!(log, "server ({}) websocket upgrade failed", e2),
|
||||
(Ok(mut s_req), Ok(mut s_res)) => {
|
||||
trace!(log, "websocket upgrade succeeded");
|
||||
let r = tokio::io::copy_bidirectional(&mut s_req, &mut s_res).await;
|
||||
trace!(log, "websocket closed (error: {:?})", r.err());
|
||||
}
|
||||
}
|
||||
|
||||
drop(handle);
|
||||
});
|
||||
}
|
||||
|
||||
proxied_res
|
||||
}
|
||||
|
||||
/// Returns whether the string looks like a commit hash.
|
||||
fn is_commit_hash(s: &str) -> bool {
|
||||
s.len() == COMMIT_HASH_LEN && s.chars().all(|c| c.is_ascii_hexdigit())
|
||||
}
|
||||
|
||||
/// Module holding original responses the CLI's server makes.
|
||||
mod response {
|
||||
use const_format::concatcp;
|
||||
|
||||
use crate::constants::QUALITYLESS_SERVER_NAME;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn connection_err(err: hyper::Error) -> Response<Body> {
|
||||
Response::builder()
|
||||
.status(503)
|
||||
.body(Body::from(format!("Error connecting to server: {:?}", err)))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn code_err(err: CodeError) -> Response<Body> {
|
||||
Response::builder()
|
||||
.status(500)
|
||||
.body(Body::from(format!("Error serving request: {}", err)))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn wait_for_download() -> Response<Body> {
|
||||
Response::builder()
|
||||
.status(202)
|
||||
.header("Content-Type", "text/html") // todo: get latest
|
||||
.body(Body::from(concatcp!("The latest version of the ", QUALITYLESS_SERVER_NAME, " is downloading, please wait a moment...<script>setTimeout(()=>location.reload(),1500)</script>", )))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle returned when getting a stream to the server, used to refcount
|
||||
/// connections to a server so it can be disposed when there are no more clients.
|
||||
struct ConnectionHandle {
|
||||
client_counter: Arc<tokio::sync::watch::Sender<usize>>,
|
||||
}
|
||||
|
||||
impl ConnectionHandle {
|
||||
pub fn new(client_counter: Arc<tokio::sync::watch::Sender<usize>>) -> Self {
|
||||
client_counter.send_modify(|v| {
|
||||
*v += 1;
|
||||
});
|
||||
Self { client_counter }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ConnectionHandle {
|
||||
fn drop(&mut self) {
|
||||
self.client_counter.send_modify(|v| {
|
||||
*v -= 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
type StartData = (PathBuf, Arc<tokio::sync::watch::Sender<usize>>);
|
||||
|
||||
/// State stored in the ConnectionManager for each server version.
|
||||
struct VersionState {
|
||||
downloaded: bool,
|
||||
socket_path: Barrier<Result<StartData, String>>,
|
||||
}
|
||||
|
||||
type ConnectionStateMap = Arc<Mutex<HashMap<(Quality, String), VersionState>>>;
|
||||
|
||||
/// Manages the connections to running web UI instances. Multiple web servers
|
||||
/// can run concurrently, with routing based on the URL path.
|
||||
struct ConnectionManager {
|
||||
pub platform: Platform,
|
||||
pub log: log::Logger,
|
||||
args: ServeWebArgs,
|
||||
/// Cache where servers are stored
|
||||
cache: DownloadCache,
|
||||
/// Mapping of (Quality, Commit) to the state each server is in
|
||||
state: ConnectionStateMap,
|
||||
/// Update service instance
|
||||
update_service: UpdateService,
|
||||
/// Cache of the latest released version, storing the time we checked as well
|
||||
latest_version: tokio::sync::Mutex<Option<(Instant, Release)>>,
|
||||
}
|
||||
|
||||
fn key_for_release(release: &Release) -> (Quality, String) {
|
||||
(release.quality, release.commit.clone())
|
||||
}
|
||||
|
||||
impl ConnectionManager {
|
||||
pub fn new(ctx: &CommandContext, platform: Platform, args: ServeWebArgs) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
platform,
|
||||
args,
|
||||
log: ctx.log.clone(),
|
||||
cache: DownloadCache::new(ctx.paths.web_server_storage()),
|
||||
update_service: UpdateService::new(
|
||||
ctx.log.clone(),
|
||||
Arc::new(ReqwestSimpleHttp::with_client(ctx.http.clone())),
|
||||
),
|
||||
state: ConnectionStateMap::default(),
|
||||
latest_version: tokio::sync::Mutex::default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets a connection to a server version
|
||||
pub async fn get_connection(
|
||||
&self,
|
||||
release: Release,
|
||||
) -> Result<(AsyncPipe, ConnectionHandle), CodeError> {
|
||||
// todo@connor4312: there is likely some performance benefit to
|
||||
// implementing a 'keepalive' for these connections.
|
||||
let (path, counter) = self.get_version_data(release).await?;
|
||||
let handle = ConnectionHandle::new(counter);
|
||||
let rw = get_socket_rw_stream(&path).await?;
|
||||
Ok((rw, handle))
|
||||
}
|
||||
|
||||
/// Gets the latest release for the CLI quality, caching its result for some
|
||||
/// time to allow for fast loads.
|
||||
pub async fn get_latest_release(&self) -> Result<Release, CodeError> {
|
||||
let mut latest = self.latest_version.lock().await;
|
||||
let now = Instant::now();
|
||||
if let Some((checked_at, release)) = &*latest {
|
||||
if checked_at.elapsed() < Duration::from_secs(RELEASE_CACHE_SECS) {
|
||||
return Ok(release.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let quality = VSCODE_CLI_QUALITY
|
||||
.ok_or_else(|| CodeError::UpdatesNotConfigured("no configured quality"))
|
||||
.and_then(|q| {
|
||||
Quality::try_from(q).map_err(|_| CodeError::UpdatesNotConfigured("unknown quality"))
|
||||
})?;
|
||||
|
||||
let release = self
|
||||
.update_service
|
||||
.get_latest_commit(self.platform, TargetKind::Web, quality)
|
||||
.await
|
||||
.map_err(|e| CodeError::UpdateCheckFailed(e.to_string()));
|
||||
|
||||
// If the update service is unavailable and we have stale data, use that
|
||||
if let (Err(e), Some((_, previous))) = (&release, &*latest) {
|
||||
warning!(self.log, "error getting latest release, using stale: {}", e);
|
||||
return Ok(previous.clone());
|
||||
}
|
||||
|
||||
let release = release?;
|
||||
debug!(self.log, "refreshed latest release: {}", release);
|
||||
*latest = Some((now, release.clone()));
|
||||
|
||||
Ok(release)
|
||||
}
|
||||
|
||||
/// Gets the StartData for the a version of the VS Code server, triggering
|
||||
/// download/start if necessary. It returns `CodeError::ServerNotYetDownloaded`
|
||||
/// while the server is downloading, which is used to have a refresh loop on the page.
|
||||
async fn get_version_data(&self, release: Release) -> Result<StartData, CodeError> {
|
||||
self.get_version_data_inner(release)?
|
||||
.wait()
|
||||
.await
|
||||
.unwrap()
|
||||
.map_err(CodeError::ServerDownloadError)
|
||||
}
|
||||
|
||||
fn get_version_data_inner(
|
||||
&self,
|
||||
release: Release,
|
||||
) -> Result<Barrier<Result<StartData, String>>, CodeError> {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
let key = key_for_release(&release);
|
||||
if let Some(s) = state.get_mut(&key) {
|
||||
if !s.downloaded {
|
||||
if s.socket_path.is_open() {
|
||||
s.downloaded = true;
|
||||
} else {
|
||||
return Err(CodeError::ServerNotYetDownloaded);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(s.socket_path.clone());
|
||||
}
|
||||
|
||||
let (socket_path, opener) = new_barrier();
|
||||
let state_map_dup = self.state.clone();
|
||||
let args = StartArgs {
|
||||
args: self.args.clone(),
|
||||
log: self.log.clone(),
|
||||
opener,
|
||||
release,
|
||||
};
|
||||
|
||||
if let Some(p) = self.cache.exists(&args.release.commit) {
|
||||
state.insert(
|
||||
key.clone(),
|
||||
VersionState {
|
||||
socket_path: socket_path.clone(),
|
||||
downloaded: true,
|
||||
},
|
||||
);
|
||||
|
||||
tokio::spawn(async move {
|
||||
Self::start_version(args, p).await;
|
||||
state_map_dup.lock().unwrap().remove(&key);
|
||||
});
|
||||
Ok(socket_path)
|
||||
} else {
|
||||
state.insert(
|
||||
key.clone(),
|
||||
VersionState {
|
||||
socket_path,
|
||||
downloaded: false,
|
||||
},
|
||||
);
|
||||
let update_service = self.update_service.clone();
|
||||
let cache = self.cache.clone();
|
||||
tokio::spawn(async move {
|
||||
Self::download_version(args, update_service.clone(), cache.clone()).await;
|
||||
state_map_dup.lock().unwrap().remove(&key);
|
||||
});
|
||||
Err(CodeError::ServerNotYetDownloaded)
|
||||
}
|
||||
}
|
||||
|
||||
/// Downloads a server version into the cache and starts it.
|
||||
async fn download_version(
|
||||
args: StartArgs,
|
||||
update_service: UpdateService,
|
||||
cache: DownloadCache,
|
||||
) {
|
||||
let release_for_fut = args.release.clone();
|
||||
let log_for_fut = args.log.clone();
|
||||
let dir_fut = cache.create(&args.release.commit, |target_dir| async move {
|
||||
info!(log_for_fut, "Downloading server {}", release_for_fut.commit);
|
||||
let tmpdir = tempfile::tempdir().unwrap();
|
||||
let response = update_service.get_download_stream(&release_for_fut).await?;
|
||||
|
||||
let name = response.url_path_basename().unwrap();
|
||||
let archive_path = tmpdir.path().join(name);
|
||||
http::download_into_file(
|
||||
&archive_path,
|
||||
log_for_fut.get_download_logger("Downloading server:"),
|
||||
response,
|
||||
)
|
||||
.await?;
|
||||
unzip_downloaded_release(&archive_path, &target_dir, SilentCopyProgress())?;
|
||||
Ok(())
|
||||
});
|
||||
|
||||
match dir_fut.await {
|
||||
Err(e) => args.opener.open(Err(e.to_string())),
|
||||
Ok(dir) => Self::start_version(args, dir).await,
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts a downloaded server that can be found in the given `path`.
|
||||
async fn start_version(args: StartArgs, path: PathBuf) {
|
||||
info!(args.log, "Starting server {}", args.release.commit);
|
||||
|
||||
let executable = path
|
||||
.join("bin")
|
||||
.join(args.release.quality.server_entrypoint());
|
||||
let socket_path = get_socket_name();
|
||||
|
||||
#[cfg(not(windows))]
|
||||
let mut cmd = Command::new(&executable);
|
||||
#[cfg(windows)]
|
||||
let mut cmd = {
|
||||
let mut cmd = Command::new("cmd");
|
||||
cmd.arg("/Q");
|
||||
cmd.arg("/C");
|
||||
cmd.arg(&executable);
|
||||
cmd
|
||||
};
|
||||
|
||||
cmd.stdin(std::process::Stdio::null());
|
||||
cmd.stderr(std::process::Stdio::piped());
|
||||
cmd.stdout(std::process::Stdio::piped());
|
||||
cmd.arg("--socket-path");
|
||||
cmd.arg(&socket_path);
|
||||
|
||||
// License agreement already checked by the `server_web` function.
|
||||
cmd.args(["--accept-server-license-terms"]);
|
||||
|
||||
if let Some(a) = &args.args.server_data_dir {
|
||||
cmd.arg("--server-data-dir");
|
||||
cmd.arg(a);
|
||||
}
|
||||
if let Some(a) = &args.args.user_data_dir {
|
||||
cmd.arg("--user-data-dir");
|
||||
cmd.arg(a);
|
||||
}
|
||||
if let Some(a) = &args.args.extensions_dir {
|
||||
cmd.arg("--extensions-dir");
|
||||
cmd.arg(a);
|
||||
}
|
||||
if args.args.without_connection_token {
|
||||
cmd.arg("--without-connection-token");
|
||||
}
|
||||
if let Some(ct) = &args.args.connection_token {
|
||||
cmd.arg("--connection-token");
|
||||
cmd.arg(ct);
|
||||
}
|
||||
|
||||
// removed, otherwise the workbench will not be usable when running the CLI from sources.
|
||||
cmd.env_remove("VSCODE_DEV");
|
||||
|
||||
let mut child = match cmd.spawn() {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
args.opener.open(Err(e.to_string()));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let (mut stdout, mut stderr) = (
|
||||
BufReader::new(child.stdout.take().unwrap()).lines(),
|
||||
BufReader::new(child.stderr.take().unwrap()).lines(),
|
||||
);
|
||||
|
||||
// wrapped option to prove that we only use this once in the loop
|
||||
let (counter_tx, mut counter_rx) = tokio::sync::watch::channel(0);
|
||||
let mut opener = Some((args.opener, socket_path, Arc::new(counter_tx)));
|
||||
let commit_prefix = &args.release.commit[..7];
|
||||
let kill_timer = tokio::time::sleep(Duration::from_secs(SERVER_IDLE_TIMEOUT_SECS));
|
||||
pin!(kill_timer);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
Ok(Some(l)) = stdout.next_line() => {
|
||||
info!(args.log, "[{} stdout]: {}", commit_prefix, l);
|
||||
|
||||
if l.contains("Server bound to") {
|
||||
if let Some((opener, path, counter_tx)) = opener.take() {
|
||||
opener.open(Ok((path, counter_tx)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Some(l)) = stderr.next_line() => {
|
||||
info!(args.log, "[{} stderr]: {}", commit_prefix, l);
|
||||
},
|
||||
n = counter_rx.changed() => {
|
||||
kill_timer.as_mut().reset(match n {
|
||||
// err means that the record was dropped
|
||||
Err(_) => tokio::time::Instant::now(),
|
||||
Ok(_) => {
|
||||
if *counter_rx.borrow() == 0 {
|
||||
tokio::time::Instant::now() + Duration::from_secs(SERVER_IDLE_TIMEOUT_SECS)
|
||||
} else {
|
||||
tokio::time::Instant::now() + Duration::from_secs(SERVER_ACTIVE_TIMEOUT_SECS)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
_ = &mut kill_timer => {
|
||||
info!(args.log, "[{} process]: idle timeout reached, ending", commit_prefix);
|
||||
let _ = child.kill().await;
|
||||
break;
|
||||
}
|
||||
e = child.wait() => {
|
||||
info!(args.log, "[{} process]: exited: {:?}", commit_prefix, e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct StartArgs {
|
||||
log: log::Logger,
|
||||
args: ServeWebArgs,
|
||||
release: Release,
|
||||
opener: BarrierOpener<Result<StartData, String>>,
|
||||
}
|
||||
@@ -11,7 +11,7 @@ use crate::{
|
||||
options::Quality,
|
||||
update_service::{unzip_downloaded_release, Platform, Release, TargetKind, UpdateService},
|
||||
util::{
|
||||
errors::{wrap, AnyError, CorruptDownload, UpdatesNotConfigured},
|
||||
errors::{wrap, AnyError, CodeError, CorruptDownload},
|
||||
http,
|
||||
io::{ReportCopyProgress, SilentCopyProgress},
|
||||
},
|
||||
@@ -27,14 +27,16 @@ pub struct SelfUpdate<'a> {
|
||||
impl<'a> SelfUpdate<'a> {
|
||||
pub fn new(update_service: &'a UpdateService) -> Result<Self, AnyError> {
|
||||
let commit = VSCODE_CLI_COMMIT
|
||||
.ok_or_else(|| UpdatesNotConfigured("unknown build commit".to_string()))?;
|
||||
.ok_or_else(|| CodeError::UpdatesNotConfigured("unknown build commit"))?;
|
||||
|
||||
let quality = VSCODE_CLI_QUALITY
|
||||
.ok_or_else(|| UpdatesNotConfigured("no configured quality".to_string()))
|
||||
.and_then(|q| Quality::try_from(q).map_err(UpdatesNotConfigured))?;
|
||||
.ok_or_else(|| CodeError::UpdatesNotConfigured("no configured quality"))
|
||||
.and_then(|q| {
|
||||
Quality::try_from(q).map_err(|_| CodeError::UpdatesNotConfigured("unknown quality"))
|
||||
})?;
|
||||
|
||||
let platform = Platform::env_default().ok_or_else(|| {
|
||||
UpdatesNotConfigured("Unknown platform, please report this error".to_string())
|
||||
CodeError::UpdatesNotConfigured("Unknown platform, please report this error")
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
|
||||
@@ -212,4 +212,10 @@ impl LauncherPaths {
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Suggested path for web server storage
|
||||
pub fn web_server_storage(&self) -> PathBuf {
|
||||
self.root.join("serve-web")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
use crate::constants::{IS_INTERACTIVE_CLI, PRODUCT_NAME_LONG};
|
||||
use crate::constants::IS_INTERACTIVE_CLI;
|
||||
use crate::state::{LauncherPaths, PersistedState};
|
||||
use crate::util::errors::{AnyError, MissingLegalConsent};
|
||||
use crate::util::errors::{AnyError, CodeError};
|
||||
use crate::util::input::prompt_yn;
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -46,23 +46,14 @@ pub fn require_consent(
|
||||
if accept_server_license_terms {
|
||||
load.consented = Some(true);
|
||||
} else if !*IS_INTERACTIVE_CLI {
|
||||
return Err(MissingLegalConsent(
|
||||
"Run this command again with --accept-server-license-terms to indicate your agreement."
|
||||
.to_string(),
|
||||
)
|
||||
.into());
|
||||
return Err(CodeError::NeedsInteractiveLegalConsent.into());
|
||||
} else {
|
||||
match prompt_yn(prompt) {
|
||||
Ok(true) => {
|
||||
load.consented = Some(true);
|
||||
}
|
||||
Ok(false) => {
|
||||
return Err(AnyError::from(MissingLegalConsent(format!(
|
||||
"Sorry you cannot use {} CLI without accepting the terms.",
|
||||
PRODUCT_NAME_LONG
|
||||
))))
|
||||
}
|
||||
Err(e) => return Err(AnyError::from(MissingLegalConsent(e.to_string()))),
|
||||
Ok(false) => return Err(CodeError::DeniedLegalConset.into()),
|
||||
Err(_) => return Err(CodeError::NeedsInteractiveLegalConsent.into()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::{
|
||||
constants::VSCODE_CLI_UPDATE_ENDPOINT,
|
||||
debug, log, options, spanf,
|
||||
util::{
|
||||
errors::{AnyError, CodeError, UpdatesNotConfigured, WrappedError},
|
||||
errors::{AnyError, CodeError, WrappedError},
|
||||
http::{BoxedHttp, SimpleResponse},
|
||||
io::ReportCopyProgress,
|
||||
tar, zipper,
|
||||
@@ -19,6 +19,7 @@ use crate::{
|
||||
};
|
||||
|
||||
/// Implementation of the VS Code Update service for use in the CLI.
|
||||
#[derive(Clone)]
|
||||
pub struct UpdateService {
|
||||
client: BoxedHttp,
|
||||
log: log::Logger,
|
||||
@@ -54,6 +55,10 @@ fn quality_download_segment(quality: options::Quality) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_update_endpoint() -> Result<&'static str, CodeError> {
|
||||
VSCODE_CLI_UPDATE_ENDPOINT.ok_or_else(|| CodeError::UpdatesNotConfigured("no service url"))
|
||||
}
|
||||
|
||||
impl UpdateService {
|
||||
pub fn new(log: log::Logger, http: BoxedHttp) -> Self {
|
||||
UpdateService { client: http, log }
|
||||
@@ -66,8 +71,7 @@ impl UpdateService {
|
||||
quality: options::Quality,
|
||||
version: &str,
|
||||
) -> Result<Release, AnyError> {
|
||||
let update_endpoint =
|
||||
VSCODE_CLI_UPDATE_ENDPOINT.ok_or_else(UpdatesNotConfigured::no_url)?;
|
||||
let update_endpoint = get_update_endpoint()?;
|
||||
let download_segment = target
|
||||
.download_segment(platform)
|
||||
.ok_or_else(|| CodeError::UnsupportedPlatform(platform.to_string()))?;
|
||||
@@ -108,8 +112,7 @@ impl UpdateService {
|
||||
target: TargetKind,
|
||||
quality: options::Quality,
|
||||
) -> Result<Release, AnyError> {
|
||||
let update_endpoint =
|
||||
VSCODE_CLI_UPDATE_ENDPOINT.ok_or_else(UpdatesNotConfigured::no_url)?;
|
||||
let update_endpoint = get_update_endpoint()?;
|
||||
let download_segment = target
|
||||
.download_segment(platform)
|
||||
.ok_or_else(|| CodeError::UnsupportedPlatform(platform.to_string()))?;
|
||||
@@ -144,8 +147,7 @@ impl UpdateService {
|
||||
|
||||
/// Gets the download stream for the release.
|
||||
pub async fn get_download_stream(&self, release: &Release) -> Result<SimpleResponse, AnyError> {
|
||||
let update_endpoint =
|
||||
VSCODE_CLI_UPDATE_ENDPOINT.ok_or_else(UpdatesNotConfigured::no_url)?;
|
||||
let update_endpoint = get_update_endpoint()?;
|
||||
let download_segment = release
|
||||
.target
|
||||
.download_segment(release.platform)
|
||||
|
||||
+19
-26
@@ -108,16 +108,6 @@ impl StatusError {
|
||||
}
|
||||
}
|
||||
|
||||
// When the user has not consented to the licensing terms in using the Launcher
|
||||
#[derive(Debug)]
|
||||
pub struct MissingLegalConsent(pub String);
|
||||
|
||||
impl std::fmt::Display for MissingLegalConsent {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
// When the provided connection token doesn't match the one used to set up the original VS Code Server
|
||||
// This is most likely due to a new user joining.
|
||||
#[derive(Debug)]
|
||||
@@ -313,20 +303,6 @@ impl std::fmt::Display for ServerHasClosed {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UpdatesNotConfigured(pub String);
|
||||
|
||||
impl UpdatesNotConfigured {
|
||||
pub fn no_url() -> Self {
|
||||
UpdatesNotConfigured("no service url".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for UpdatesNotConfigured {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "Update service is not configured: {}", self.0)
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct ServiceAlreadyRegistered();
|
||||
|
||||
@@ -517,10 +493,28 @@ pub enum CodeError {
|
||||
KeyringTimeout,
|
||||
#[error("no host is connected to the tunnel relay")]
|
||||
NoTunnelEndpoint,
|
||||
#[error("could not parse `host`: {0}")]
|
||||
InvalidHostAddress(std::net::AddrParseError),
|
||||
#[error("could not start server on the given host/port: {0}")]
|
||||
CouldNotListenOnInterface(hyper::Error),
|
||||
#[error(
|
||||
"Run this command again with --accept-server-license-terms to indicate your agreement."
|
||||
)]
|
||||
NeedsInteractiveLegalConsent,
|
||||
#[error("Sorry, you cannot use this CLI without accepting the terms.")]
|
||||
DeniedLegalConset,
|
||||
#[error("The server is not yet downloaded, try again shortly.")]
|
||||
ServerNotYetDownloaded,
|
||||
#[error("An error was encountered downloading the server, please retry: {0}")]
|
||||
ServerDownloadError(String),
|
||||
#[error("Updates are are not available: {0}")]
|
||||
UpdatesNotConfigured(&'static str),
|
||||
// todo: can be specialized when update service is moved to CodeErrors
|
||||
#[error("Could not check for update: {0}")]
|
||||
UpdateCheckFailed(String),
|
||||
}
|
||||
|
||||
makeAnyError!(
|
||||
MissingLegalConsent,
|
||||
MismatchConnectionToken,
|
||||
DevTunnelError,
|
||||
StatusError,
|
||||
@@ -543,7 +537,6 @@ makeAnyError!(
|
||||
ServerHasClosed,
|
||||
ServiceAlreadyRegistered,
|
||||
WindowsNeedsElevation,
|
||||
UpdatesNotConfigured,
|
||||
CorruptDownload,
|
||||
MissingHomeDirectory,
|
||||
OAuthError,
|
||||
|
||||
@@ -63,7 +63,7 @@ impl<T: Clone> BarrierOpener<T> {
|
||||
/// and is thereafter permanently closed. It can contain a value.
|
||||
pub fn new_barrier<T>() -> (Barrier<T>, BarrierOpener<T>)
|
||||
where
|
||||
T: Copy,
|
||||
T: Clone,
|
||||
{
|
||||
let (closed_tx, closed_rx) = watch::channel(None);
|
||||
(Barrier(closed_rx), BarrierOpener(Arc::new(closed_tx)))
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^([a-z0-9-]+):(\\d{1,5})$"
|
||||
"pattern": "^([a-z0-9_-]+):(\\d{1,5})$"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -481,7 +481,7 @@ export class Git {
|
||||
const result = await this.exec(pathInsidePossibleRepository, ['rev-parse', '--show-toplevel']);
|
||||
|
||||
// Keep trailing spaces which are part of the directory name
|
||||
const repositoryRootPath = path.normalize(result.stdout.trimLeft().replace(/[\r\n]+$/, ''));
|
||||
const repositoryRootPath = path.normalize(result.stdout.trimStart().replace(/[\r\n]+$/, ''));
|
||||
|
||||
if (isWindows) {
|
||||
// On Git 2.25+ if you call `rev-parse --show-toplevel` on a mapped drive, instead of getting the mapped
|
||||
@@ -528,7 +528,7 @@ export class Git {
|
||||
!isDescendant(pathInsidePossibleRepository, repositoryRootPath) &&
|
||||
this.compareGitVersionTo('2.31.0') !== -1) {
|
||||
const relativePathResult = await this.exec(pathInsidePossibleRepository, ['rev-parse', '--path-format=relative', '--show-toplevel',]);
|
||||
return path.resolve(pathInsidePossibleRepository, relativePathResult.stdout.trimLeft().replace(/[\r\n]+$/, ''));
|
||||
return path.resolve(pathInsidePossibleRepository, relativePathResult.stdout.trimStart().replace(/[\r\n]+$/, ''));
|
||||
}
|
||||
|
||||
return repositoryRootPath;
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"editor.insertSpaces": false,
|
||||
"prettier.semi": true,
|
||||
"prettier.singleQuote": true,
|
||||
"prettier.printWidth": 120,
|
||||
}
|
||||
"prettier.singleQuote": true,
|
||||
"prettier.printWidth": 120
|
||||
}
|
||||
|
||||
@@ -56,7 +56,11 @@
|
||||
{
|
||||
"command": "ipynb.cleanInvalidImageAttachment",
|
||||
"title": "%cleanInvalidImageAttachment.title%"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "notebook.cellOutput.copyToClipboard",
|
||||
"title": "%copyOutputToClipboard.title%"
|
||||
}
|
||||
],
|
||||
"notebooks": [
|
||||
{
|
||||
@@ -99,7 +103,13 @@
|
||||
"command": "ipynb.cleanInvalidImageAttachment",
|
||||
"when": "false"
|
||||
}
|
||||
]
|
||||
],
|
||||
"webview/context": [
|
||||
{
|
||||
"command": "notebook.cellOutput.copyToClipboard",
|
||||
"when": "webviewId == 'notebook.output' && webviewSection == 'image'"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"newUntitledIpynb.shortTitle": "Jupyter Notebook",
|
||||
"openIpynbInNotebookEditor.title": "Open IPYNB File In Notebook Editor",
|
||||
"cleanInvalidImageAttachment.title": "Clean Invalid Image Attachment Reference",
|
||||
"copyOutputToClipboard.title": "Copy Output to Clipboard",
|
||||
"markdownAttachmentRenderer.displayName": {
|
||||
"message": "Markdown-It ipynb Cell Attachment renderer",
|
||||
"comment": [
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ExtensionContext, OutputChannel, window, workspace, l10n } from 'vscode';
|
||||
import { ExtensionContext, OutputChannel, window, workspace, l10n, env } from 'vscode';
|
||||
import { startClient, LanguageClientConstructor, SchemaRequestService, languageServerDescription } from '../jsonClient';
|
||||
import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient, BaseLanguageClient } from 'vscode-languageclient/node';
|
||||
|
||||
@@ -129,7 +129,10 @@ async function getSchemaRequestService(context: ExtensionContext, log: Log): Pro
|
||||
const isXHRResponse = (error: any): error is XHRResponse => typeof error?.status === 'number';
|
||||
|
||||
const request = async (uri: string, etag?: string): Promise<string> => {
|
||||
const headers: Headers = { 'Accept-Encoding': 'gzip, deflate' };
|
||||
const headers: Headers = {
|
||||
'Accept-Encoding': 'gzip, deflate',
|
||||
'User-Agent': `${env.appName} (${env.appHost})`
|
||||
};
|
||||
if (etag) {
|
||||
headers['If-None-Match'] = etag;
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
"displayName": "JSON Language Features",
|
||||
"description": "Provides rich language support for JSON files.",
|
||||
"json.schemas.desc": "Associate schemas to JSON files in the current project.",
|
||||
"json.schemas.url.desc": "A URL to a schema or a relative path to a schema in the current directory",
|
||||
"json.schemas.fileMatch.desc": "An array of file patterns to match against when resolving JSON files to schemas. `*` can be used as a wildcard. Exclusion patterns can also be defined and start with '!'. A file matches when there is at least one matching pattern and the last matching pattern is not an exclusion pattern.",
|
||||
"json.schemas.fileMatch.item.desc": "A file pattern that can contain '*' to match against when resolving JSON files to schemas.",
|
||||
"json.schemas.url.desc": "A URL or absolute file path to a schema. Can be a relative path in workspace and workspace folder settings.",
|
||||
"json.schemas.fileMatch.desc": "An array of file patterns to match against when resolving JSON files to schemas. `*` and '**' can be used as a wildcard. Exclusion patterns can also be defined and start with '!'. A file matches when there is at least one matching pattern and the last matching pattern is not an exclusion pattern.",
|
||||
"json.schemas.fileMatch.item.desc": "A file pattern that can contain '*' and '**' to match against when resolving JSON files to schemas. When beginning with '!', it defines an exclusion pattern.",
|
||||
"json.schemas.schema.desc": "The schema definition for the given URL. The schema only needs to be provided to avoid accesses to the schema URL.",
|
||||
"json.format.enable.desc": "Enable/disable default JSON formatter",
|
||||
"json.format.keepLines.desc" : "Keep all existing new lines when formatting.",
|
||||
|
||||
@@ -106,10 +106,10 @@ sup {
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
ul ul,
|
||||
ul ol,
|
||||
ol ul,
|
||||
ol ol {
|
||||
ul ul:first-child,
|
||||
ul ol:first-child,
|
||||
ol ul:first-child,
|
||||
ol ol:first-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -138,6 +138,10 @@ p {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
li p {
|
||||
margin-bottom: 0.7em;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
margin-bottom: 0.7em;
|
||||
|
||||
@@ -123,7 +123,8 @@
|
||||
"commands": [
|
||||
{
|
||||
"command": "_markdown.copyImage",
|
||||
"title": "%markdown.copyImage.title%"
|
||||
"title": "%markdown.copyImage.title%",
|
||||
"category": "Markdown"
|
||||
},
|
||||
{
|
||||
"command": "markdown.showPreview",
|
||||
@@ -244,6 +245,10 @@
|
||||
}
|
||||
],
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "_markdown.copyImage",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "markdown.showPreview",
|
||||
"when": "editorLangId == markdown && !notebookEditorFocused",
|
||||
|
||||
@@ -37,6 +37,7 @@ function renderImage(outputInfo: OutputItem, element: HTMLElement): IDisposable
|
||||
if (alt) {
|
||||
image.alt = alt;
|
||||
}
|
||||
image.setAttribute('data-vscode-context', JSON.stringify({ webviewSection: 'image', outputId: outputInfo.id, 'preventDefaultContextMenuItems': true }));
|
||||
const display = document.createElement('div');
|
||||
display.classList.add('display');
|
||||
display.appendChild(image);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"license": "MIT",
|
||||
"description": "Dependencies shared by all extensions",
|
||||
"dependencies": {
|
||||
"typescript": "^5.2.0-dev.20230807"
|
||||
"typescript": "^5.2.1-rc"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "node ./postinstall.mjs"
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"fileSearchProvider",
|
||||
"findTextInFiles",
|
||||
"fsChunks",
|
||||
"mappedEditsProvider",
|
||||
"notebookCellExecutionState",
|
||||
"notebookDeprecated",
|
||||
"notebookLiveShare",
|
||||
@@ -45,7 +46,7 @@
|
||||
"timeline",
|
||||
"tokenInformation",
|
||||
"treeItemCheckbox",
|
||||
"treeViewActiveItem",
|
||||
"treeViewActiveItem",
|
||||
"treeViewReveal",
|
||||
"workspaceTrust",
|
||||
"telemetry",
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import * as assert from 'assert';
|
||||
|
||||
suite('mapped edits provider', () => {
|
||||
|
||||
test('mapped edits does not provide edits for unregistered langs', async function () {
|
||||
|
||||
const uri = vscode.Uri.file(path.join(vscode.workspace.rootPath || '', './myFile.ts'));
|
||||
|
||||
const tsDocFilter = [{ language: 'json' }];
|
||||
|
||||
const r1 = vscode.chat.registerMappedEditsProvider(tsDocFilter, {
|
||||
provideMappedEdits: (_doc: vscode.TextDocument, codeBlocks: string[], context: vscode.MappedEditsContext, _token: vscode.CancellationToken) => {
|
||||
|
||||
assert(context.selections.length === 1);
|
||||
assert(context.related.length === 1);
|
||||
assert('uri' in context.related[0] && 'range' in context.related[0]);
|
||||
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
const text = codeBlocks.join('\n//----\n');
|
||||
edit.replace(uri, context.selections[0], text);
|
||||
return edit;
|
||||
}
|
||||
});
|
||||
await vscode.workspace.openTextDocument(uri);
|
||||
const result = await vscode.commands.executeCommand<vscode.ProviderResult<vscode.WorkspaceEdit | null>>(
|
||||
'vscode.executeMappedEditsProvider',
|
||||
uri,
|
||||
[
|
||||
'// hello',
|
||||
`function foo() {\n\treturn 1;\n}`,
|
||||
],
|
||||
{
|
||||
selections: [new vscode.Selection(0, 0, 1, 0)],
|
||||
related: [
|
||||
{
|
||||
uri,
|
||||
range: new vscode.Range(new vscode.Position(0, 0), new vscode.Position(1, 0))
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
r1.dispose();
|
||||
|
||||
assert(result === null, 'returned null');
|
||||
});
|
||||
|
||||
test('mapped edits provides a single edit replacing the selection', async function () {
|
||||
|
||||
const uri = vscode.Uri.file(path.join(vscode.workspace.rootPath || '', './myFile.ts'));
|
||||
|
||||
const tsDocFilter = [{ language: 'typescript' }];
|
||||
|
||||
const r1 = vscode.chat.registerMappedEditsProvider(tsDocFilter, {
|
||||
provideMappedEdits: (_doc: vscode.TextDocument, codeBlocks: string[], context: vscode.MappedEditsContext, _token: vscode.CancellationToken) => {
|
||||
|
||||
assert(context.selections.length === 1);
|
||||
assert(context.related.length === 1);
|
||||
assert('uri' in context.related[0] && 'range' in context.related[0]);
|
||||
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
const text = codeBlocks.join('\n//----\n');
|
||||
edit.replace(uri, context.selections[0], text);
|
||||
return edit;
|
||||
}
|
||||
});
|
||||
|
||||
await vscode.workspace.openTextDocument(uri);
|
||||
const result = await vscode.commands.executeCommand<vscode.ProviderResult<vscode.WorkspaceEdit | null>>(
|
||||
'vscode.executeMappedEditsProvider',
|
||||
uri,
|
||||
[
|
||||
'// hello',
|
||||
`function foo() {\n\treturn 1;\n}`,
|
||||
],
|
||||
{
|
||||
selections: [new vscode.Selection(0, 0, 1, 0)],
|
||||
related: [
|
||||
{
|
||||
uri,
|
||||
range: new vscode.Range(new vscode.Position(0, 0), new vscode.Position(1, 0))
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
r1.dispose();
|
||||
|
||||
assert(result, 'non null response');
|
||||
const edits = result.get(uri);
|
||||
assert(edits.length === 1);
|
||||
assert(edits[0].range.start.line === 0);
|
||||
assert(edits[0].range.start.character === 0);
|
||||
assert(edits[0].range.end.line === 1);
|
||||
assert(edits[0].range.end.character === 0);
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { deepStrictEqual, doesNotThrow, equal, ok, strictEqual, throws } from 'assert';
|
||||
import { commands, ConfigurationTarget, Disposable, env, EnvironmentVariableCollection, EnvironmentVariableMutator, EnvironmentVariableMutatorOptions, EnvironmentVariableMutatorType, EnvironmentVariableScope, EventEmitter, ExtensionContext, extensions, ExtensionTerminalOptions, Pseudoterminal, Terminal, TerminalDimensions, TerminalExitReason, TerminalOptions, TerminalState, UIKind, Uri, window, workspace } from 'vscode';
|
||||
import { commands, ConfigurationTarget, Disposable, env, EnvironmentVariableMutator, EnvironmentVariableMutatorOptions, EnvironmentVariableMutatorType, EventEmitter, ExtensionContext, extensions, ExtensionTerminalOptions, GlobalEnvironmentVariableCollection, Pseudoterminal, Terminal, TerminalDimensions, TerminalExitReason, TerminalOptions, TerminalState, UIKind, Uri, window, workspace } from 'vscode';
|
||||
import { assertNoRpc, poll } from '../utils';
|
||||
|
||||
// Disable terminal tests:
|
||||
@@ -913,10 +913,10 @@ import { assertNoRpc, poll } from '../utils';
|
||||
|
||||
test('get and forEach should work (scope)', () => {
|
||||
// TODO: Remove cast once `envCollectionWorkspace` API is finalized.
|
||||
const collection = extensionContext.environmentVariableCollection as (EnvironmentVariableCollection & { getScopedEnvironmentVariableCollection(scope: EnvironmentVariableScope): EnvironmentVariableCollection });
|
||||
const collection = extensionContext.environmentVariableCollection as GlobalEnvironmentVariableCollection;
|
||||
disposables.push({ dispose: () => collection.clear() });
|
||||
const scope = { workspaceFolder: { uri: Uri.file('workspace1'), name: 'workspace1', index: 0 } };
|
||||
const scopedCollection = collection.getScopedEnvironmentVariableCollection(scope);
|
||||
const scopedCollection = collection.getScoped(scope);
|
||||
scopedCollection.replace('A', 'scoped~a2~');
|
||||
scopedCollection.append('B', 'scoped~b2~');
|
||||
scopedCollection.prepend('C', 'scoped~c2~');
|
||||
@@ -928,7 +928,7 @@ import { assertNoRpc, poll } from '../utils';
|
||||
applyAtProcessCreation: true,
|
||||
applyAtShellIntegration: false
|
||||
};
|
||||
const expectedScopedCollection = collection.getScopedEnvironmentVariableCollection(scope);
|
||||
const expectedScopedCollection = collection.getScoped(scope);
|
||||
deepStrictEqual(expectedScopedCollection.get('A'), { value: 'scoped~a2~', type: EnvironmentVariableMutatorType.Replace, options: defaultOptions });
|
||||
deepStrictEqual(expectedScopedCollection.get('B'), { value: 'scoped~b2~', type: EnvironmentVariableMutatorType.Append, options: defaultOptions });
|
||||
deepStrictEqual(expectedScopedCollection.get('C'), { value: 'scoped~c2~', type: EnvironmentVariableMutatorType.Prepend, options: defaultOptions });
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
// 1
|
||||
// 2
|
||||
// 3
|
||||
@@ -228,10 +228,10 @@ to-regex-range@^5.0.1:
|
||||
dependencies:
|
||||
is-number "^7.0.0"
|
||||
|
||||
typescript@^5.2.0-dev.20230807:
|
||||
version "5.2.0-dev.20230807"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.0-dev.20230807.tgz#652fa8a3fcc46958035d9b911c815299deb9f58e"
|
||||
integrity sha512-sF8sZl3r/mpAdKAxASaWaoU+mNPF3g8OrZ601HraU2l4WEwAf6nO7sq0Bzl+Y3FV7sWjy+gb6kdM1CtLjVne/g==
|
||||
typescript@^5.2.1-rc:
|
||||
version "5.2.1-rc"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.1-rc.tgz#9cf33ff6bc39ba9e1fa59761124f596ecf5e0c07"
|
||||
integrity sha512-gsOdmedQZEWLrYhNqHuzPmcV+4wX7UujzYqszDC5mVMjcN6Nm7lN2eAtndmjWl24aGdAwJqL2ooywkxpaTx8QQ==
|
||||
|
||||
vscode-grammar-updater@^1.1.0:
|
||||
version "1.1.0"
|
||||
|
||||
+13
-13
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "code-oss-dev",
|
||||
"version": "1.82.0",
|
||||
"distro": "510a8d740286d6895197995d1edc978ffe23cadf",
|
||||
"distro": "1591281180fd2cd18935e6847131d2d4213b7b69",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
},
|
||||
@@ -65,8 +65,8 @@
|
||||
"perf": "node scripts/code-perf.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/1ds-core-js": "^4.0.1",
|
||||
"@microsoft/1ds-post-js": "^4.0.1",
|
||||
"@microsoft/1ds-core-js": "^3.2.13",
|
||||
"@microsoft/1ds-post-js": "^3.2.13",
|
||||
"@parcel/watcher": "2.1.0",
|
||||
"@vscode/iconv-lite-umd": "0.7.0",
|
||||
"@vscode/policy-watcher": "^1.1.4",
|
||||
@@ -95,14 +95,14 @@
|
||||
"vscode-oniguruma": "1.7.0",
|
||||
"vscode-regexpp": "^3.1.0",
|
||||
"vscode-textmate": "9.0.0",
|
||||
"xterm": "5.3.0-beta.39",
|
||||
"xterm-addon-canvas": "0.5.0-beta.9",
|
||||
"xterm-addon-image": "0.6.0-beta.1",
|
||||
"xterm-addon-search": "0.13.0-beta.7",
|
||||
"xterm-addon-serialize": "0.11.0-beta.7",
|
||||
"xterm-addon-unicode11": "0.5.0",
|
||||
"xterm-addon-webgl": "0.16.0-beta.13",
|
||||
"xterm-headless": "5.3.0-beta.39",
|
||||
"xterm": "5.3.0-beta.61",
|
||||
"xterm-addon-canvas": "0.5.0-beta.22",
|
||||
"xterm-addon-image": "0.6.0-beta.14",
|
||||
"xterm-addon-search": "0.13.0-beta.20",
|
||||
"xterm-addon-serialize": "0.11.0-beta.20",
|
||||
"xterm-addon-unicode11": "0.6.0-beta.12",
|
||||
"xterm-addon-webgl": "0.16.0-beta.30",
|
||||
"xterm-headless": "5.3.0-beta.61",
|
||||
"yauzl": "^2.9.2",
|
||||
"yazl": "^2.4.3"
|
||||
},
|
||||
@@ -150,7 +150,7 @@
|
||||
"cssnano": "^4.1.11",
|
||||
"debounce": "^1.0.0",
|
||||
"deemon": "^1.8.0",
|
||||
"electron": "25.4.0",
|
||||
"electron": "25.5.0",
|
||||
"eslint": "8.36.0",
|
||||
"eslint-plugin-header": "3.1.1",
|
||||
"eslint-plugin-jsdoc": "^39.3.2",
|
||||
@@ -212,7 +212,7 @@
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsec": "0.2.7",
|
||||
"typescript": "^5.2.0-dev.20230807",
|
||||
"typescript": "^5.3.0-dev.20230816",
|
||||
"typescript-formatter": "7.1.0",
|
||||
"underscore": "^1.12.1",
|
||||
"util": "^0.12.4",
|
||||
|
||||
+10
-10
@@ -3,8 +3,8 @@
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@microsoft/1ds-core-js": "^4.0.1",
|
||||
"@microsoft/1ds-post-js": "^4.0.1",
|
||||
"@microsoft/1ds-core-js": "^3.2.13",
|
||||
"@microsoft/1ds-post-js": "^3.2.13",
|
||||
"@parcel/watcher": "2.1.0",
|
||||
"@vscode/iconv-lite-umd": "0.7.0",
|
||||
"@vscode/proxy-agent": "^0.17.2",
|
||||
@@ -27,14 +27,14 @@
|
||||
"vscode-oniguruma": "1.7.0",
|
||||
"vscode-regexpp": "^3.1.0",
|
||||
"vscode-textmate": "9.0.0",
|
||||
"xterm": "5.3.0-beta.39",
|
||||
"xterm-addon-canvas": "0.5.0-beta.9",
|
||||
"xterm-addon-image": "0.6.0-beta.1",
|
||||
"xterm-addon-search": "0.13.0-beta.7",
|
||||
"xterm-addon-serialize": "0.11.0-beta.7",
|
||||
"xterm-addon-unicode11": "0.5.0",
|
||||
"xterm-addon-webgl": "0.16.0-beta.13",
|
||||
"xterm-headless": "5.3.0-beta.39",
|
||||
"xterm": "5.3.0-beta.61",
|
||||
"xterm-addon-canvas": "0.5.0-beta.22",
|
||||
"xterm-addon-image": "0.6.0-beta.14",
|
||||
"xterm-addon-search": "0.13.0-beta.20",
|
||||
"xterm-addon-serialize": "0.11.0-beta.20",
|
||||
"xterm-addon-unicode11": "0.6.0-beta.12",
|
||||
"xterm-addon-webgl": "0.16.0-beta.30",
|
||||
"xterm-headless": "5.3.0-beta.61",
|
||||
"yauzl": "^2.9.2",
|
||||
"yazl": "^2.4.3"
|
||||
}
|
||||
|
||||
@@ -3,19 +3,19 @@
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@microsoft/1ds-core-js": "^4.0.1",
|
||||
"@microsoft/1ds-post-js": "^4.0.1",
|
||||
"@microsoft/1ds-core-js": "^3.2.13",
|
||||
"@microsoft/1ds-post-js": "^3.2.13",
|
||||
"@vscode/iconv-lite-umd": "0.7.0",
|
||||
"@vscode/vscode-languagedetection": "1.0.21",
|
||||
"jschardet": "3.0.0",
|
||||
"tas-client-umd": "0.1.8",
|
||||
"vscode-oniguruma": "1.7.0",
|
||||
"vscode-textmate": "9.0.0",
|
||||
"xterm": "5.3.0-beta.39",
|
||||
"xterm-addon-canvas": "0.5.0-beta.9",
|
||||
"xterm-addon-image": "0.6.0-beta.1",
|
||||
"xterm-addon-search": "0.13.0-beta.7",
|
||||
"xterm-addon-unicode11": "0.5.0",
|
||||
"xterm-addon-webgl": "0.16.0-beta.13"
|
||||
"xterm": "5.3.0-beta.61",
|
||||
"xterm-addon-canvas": "0.5.0-beta.22",
|
||||
"xterm-addon-image": "0.6.0-beta.14",
|
||||
"xterm-addon-search": "0.13.0-beta.20",
|
||||
"xterm-addon-unicode11": "0.6.0-beta.12",
|
||||
"xterm-addon-webgl": "0.16.0-beta.30"
|
||||
}
|
||||
}
|
||||
|
||||
+51
-73
@@ -2,63 +2,41 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@microsoft/1ds-core-js@4.0.1", "@microsoft/1ds-core-js@^4.0.1":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-4.0.1.tgz#6fcf129276c933934d618f7fc3de49613259e3ec"
|
||||
integrity sha512-BcTKhcRixCqLYEo1xVlQofvvZpgey9qmkzxQd1j9bBZS4iQl9IOmGBzlf/vGSITC7VXtEk2NZOlHQN0llAIfSw==
|
||||
"@microsoft/1ds-core-js@3.2.13", "@microsoft/1ds-core-js@^3.2.13":
|
||||
version "3.2.13"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.13.tgz#0c105ed75091bae3f1555c0334704fa9911c58fb"
|
||||
integrity sha512-CluYTRWcEk0ObG5EWFNWhs87e2qchJUn0p2D21ZUa3PWojPZfPSBs4//WIE0MYV8Qg1Hdif2ZTwlM7TbYUjfAg==
|
||||
dependencies:
|
||||
"@microsoft/applicationinsights-core-js" "3.0.2"
|
||||
"@microsoft/applicationinsights-shims" "^3.0.1"
|
||||
"@microsoft/dynamicproto-js" "^2.0.2"
|
||||
"@nevware21/ts-async" ">= 0.2.4 < 2.x"
|
||||
"@nevware21/ts-utils" ">= 0.9.6 < 2.x"
|
||||
"@microsoft/applicationinsights-core-js" "2.8.15"
|
||||
"@microsoft/applicationinsights-shims" "^2.0.2"
|
||||
"@microsoft/dynamicproto-js" "^1.1.7"
|
||||
|
||||
"@microsoft/1ds-post-js@^4.0.1":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-4.0.1.tgz#8630927d10518067686104baec98557b74317e4e"
|
||||
integrity sha512-uyYkG9mxzzj/4HgxVJsgcz9abfakW+Rjrsnor/hXS5I5ezE3YUX+54DLvAh/c6mEgoDI7u8wGkGl3HgCtAYbDQ==
|
||||
"@microsoft/1ds-post-js@^3.2.13":
|
||||
version "3.2.13"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.13.tgz#560aacac8a92fdbb79e8c2ebcb293d56e19f51aa"
|
||||
integrity sha512-HgS574fdD19Bo2vPguyznL4eDw7Pcm1cVNpvbvBLWiW3x4e1FCQ3VMXChWnAxCae8Hb0XqlA2sz332ZobBavTA==
|
||||
dependencies:
|
||||
"@microsoft/1ds-core-js" "4.0.1"
|
||||
"@microsoft/applicationinsights-shims" "^3.0.1"
|
||||
"@microsoft/dynamicproto-js" "^2.0.2"
|
||||
"@nevware21/ts-async" ">= 0.2.4 < 2.x"
|
||||
"@nevware21/ts-utils" ">= 0.9.6 < 2.x"
|
||||
"@microsoft/1ds-core-js" "3.2.13"
|
||||
"@microsoft/applicationinsights-shims" "^2.0.2"
|
||||
"@microsoft/dynamicproto-js" "^1.1.7"
|
||||
|
||||
"@microsoft/applicationinsights-core-js@3.0.2":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.2.tgz#108e20df8c162bec92b1f66f9de2530a25d9f51a"
|
||||
integrity sha512-WQhVhzlRlLDrQzn3OShCW/pL3BW5WC57t0oywSknX3q7lMzI3jDg7Ihh0iuIcNTzGCTbDkuqr4d6IjEDWIMtJQ==
|
||||
"@microsoft/applicationinsights-core-js@2.8.15":
|
||||
version "2.8.15"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.15.tgz#8fa466474260e01967fe649f14dd9e5ff91dcdc8"
|
||||
integrity sha512-yYAs9MyjGr2YijQdUSN9mVgT1ijI1FPMgcffpaPmYbHAVbQmF7bXudrBWHxmLzJlwl5rfep+Zgjli2e67lwUqQ==
|
||||
dependencies:
|
||||
"@microsoft/applicationinsights-shims" "3.0.1"
|
||||
"@microsoft/dynamicproto-js" "^2.0.2"
|
||||
"@nevware21/ts-async" ">= 0.2.4 < 2.x"
|
||||
"@nevware21/ts-utils" ">= 0.9.5 < 2.x"
|
||||
"@microsoft/applicationinsights-shims" "2.0.2"
|
||||
"@microsoft/dynamicproto-js" "^1.1.9"
|
||||
|
||||
"@microsoft/applicationinsights-shims@3.0.1", "@microsoft/applicationinsights-shims@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz#3865b73ace8405b9c4618cc5c571f2fe3876f06f"
|
||||
integrity sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==
|
||||
dependencies:
|
||||
"@nevware21/ts-utils" ">= 0.9.4 < 2.x"
|
||||
|
||||
"@microsoft/dynamicproto-js@^2.0.2":
|
||||
"@microsoft/applicationinsights-shims@2.0.2", "@microsoft/applicationinsights-shims@^2.0.2":
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz#e57fbec2e7067d48b7e8e1e1c1d354028ef718a6"
|
||||
integrity sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg==
|
||||
dependencies:
|
||||
"@nevware21/ts-utils" ">= 0.9.4 < 2.x"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.2.tgz#92b36a09375e2d9cb2b4203383b05772be837085"
|
||||
integrity sha512-PoHEgsnmcqruLNHZ/amACqdJ6YYQpED0KSRe6J7gIJTtpZC1FfFU9b1fmDKDKtFoUSrPzEh1qzO3kmRZP0betg==
|
||||
|
||||
"@nevware21/ts-async@>= 0.2.4 < 2.x":
|
||||
version "0.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@nevware21/ts-async/-/ts-async-0.2.6.tgz#9fad1c4a56b01430b407d454817f68bb6ace92b0"
|
||||
integrity sha512-NCUqEZSbsy7LVtKlUScd/eTst6djkWauLlzoIPVKCOxalEBdO8lrgNRIm4Xy68JNudNN5faqa2WA12X8m0BVhA==
|
||||
dependencies:
|
||||
"@nevware21/ts-utils" ">= 0.9.7 < 2.x"
|
||||
|
||||
"@nevware21/ts-utils@>= 0.9.4 < 2.x", "@nevware21/ts-utils@>= 0.9.5 < 2.x", "@nevware21/ts-utils@>= 0.9.6 < 2.x", "@nevware21/ts-utils@>= 0.9.7 < 2.x":
|
||||
version "0.9.8"
|
||||
resolved "https://registry.yarnpkg.com/@nevware21/ts-utils/-/ts-utils-0.9.8.tgz#6db23211a5c7539d3b4362e273087effcfc7d2f8"
|
||||
integrity sha512-kZ8s8hcn9jPVX/M7kSsBYrOGlHjqLahmxrG7QeKTk5paeVwfgKdvVCjj5Acb4UGb/ukU1G34U1Z3eb7bbVanyA==
|
||||
"@microsoft/dynamicproto-js@^1.1.7", "@microsoft/dynamicproto-js@^1.1.9":
|
||||
version "1.1.9"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.9.tgz#7437db7aa061162ee94e4131b69a62b8dad5dea6"
|
||||
integrity sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ==
|
||||
|
||||
"@vscode/iconv-lite-umd@0.7.0":
|
||||
version "0.7.0"
|
||||
@@ -90,32 +68,32 @@ vscode-textmate@9.0.0:
|
||||
resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-9.0.0.tgz#313c6c8792b0507aef35aeb81b6b370b37c44d6c"
|
||||
integrity sha512-Cl65diFGxz7gpwbav10HqiY/eVYTO1sjQpmRmV991Bj7wAoOAjGQ97PpQcXorDE2Uc4hnGWLY17xme+5t6MlSg==
|
||||
|
||||
xterm-addon-canvas@0.5.0-beta.9:
|
||||
version "0.5.0-beta.9"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0-beta.9.tgz#d4a9536cf586f78a54527751e03abf6445613886"
|
||||
integrity sha512-AMyFctHbIWx0aLcACINuZqFFNoz4ndGtIAb4pQguIl9t8r+2otiV11MY4FJmPM0kUzEzoLWzVFJaGxOzykCWuw==
|
||||
xterm-addon-canvas@0.5.0-beta.22:
|
||||
version "0.5.0-beta.22"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0-beta.22.tgz#513f0c2b7cf96073f47627b27e8965c1b1a22431"
|
||||
integrity sha512-9F6ZI0DMRgffVfHkLkDwl5n8VscvCaV10tWI3skXOX7Y7Aws6OEeglkOPoU3IllofCU792kHKM4pPoToUxTltg==
|
||||
|
||||
xterm-addon-image@0.6.0-beta.1:
|
||||
version "0.6.0-beta.1"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.6.0-beta.1.tgz#058c171a4866f1cde7608e3b00050476b55ece4c"
|
||||
integrity sha512-vLxUEo/KAsEaRhMshJ4uxnfc/Si8YfT3kilaK5WuiJflVODzQjytzZrl2iLeZaWKiqQp0NR6AEhuXlYASK675A==
|
||||
xterm-addon-image@0.6.0-beta.14:
|
||||
version "0.6.0-beta.14"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.6.0-beta.14.tgz#75fc3f824123183a4bbb5306e22f8b2c6966b0a6"
|
||||
integrity sha512-D5Gh5JTKhHaPt1KwQNf6diF37KA4eToJw3XId1wy62tWmSqfq+QflhOGTfd+SnSQYCktU05ETzM+0tncIU62pQ==
|
||||
|
||||
xterm-addon-search@0.13.0-beta.7:
|
||||
version "0.13.0-beta.7"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.13.0-beta.7.tgz#2f9bf9d8380c4c40f021c47dc583aa32bea8796e"
|
||||
integrity sha512-Dnuws0ThaqzxRDo8TjkSb5WlMYycONVDSr/irx2pk2I4vC5RuSpOifmD46ALlryNSDkMasjfCLjHPILHsFDCfw==
|
||||
xterm-addon-search@0.13.0-beta.20:
|
||||
version "0.13.0-beta.20"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.13.0-beta.20.tgz#8ddd0513e2a70fcefa325722100d2e1bfaf3b9cb"
|
||||
integrity sha512-wrx6187cJ1UenGL6ZeYv3jFvRPhhENTfbC+Hv1Fnww8LmsKhcj+0+Pm6yInNjX/9hNVsNzdqKyqNeEMoykyoyA==
|
||||
|
||||
xterm-addon-unicode11@0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.5.0.tgz#41c0d96acc1e3bb6c6596eee64e163b6bca74be7"
|
||||
integrity sha512-Jm4/g4QiTxiKiTbYICQgC791ubhIZyoIwxAIgOW8z8HWFNY+lwk+dwaKEaEeGBfM48Vk8fklsUW9u/PlenYEBg==
|
||||
xterm-addon-unicode11@0.6.0-beta.12:
|
||||
version "0.6.0-beta.12"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.6.0-beta.12.tgz#ac6df9d635325dc692e4c602e74a2fc27a09405c"
|
||||
integrity sha512-9wWWf/5nFafYgq0pn9EgAWnXaXGleVxfjNOqavpLRYFv0nw42QbaYyGvnGcxyYHM5Aqx/8rYE/DDVWZBqQZdYA==
|
||||
|
||||
xterm-addon-webgl@0.16.0-beta.13:
|
||||
version "0.16.0-beta.13"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.13.tgz#ffacd22b8d86ca7f9ba903a64bc85654523955fb"
|
||||
integrity sha512-1avJBoAKl+0dOjRK0pvu2im6gaXyP/6oJcEBQXCTz3V8YlgcG/jom8A6FEQSEDGZ/4dnyDvnLbsg3+aLZIr6gw==
|
||||
xterm-addon-webgl@0.16.0-beta.30:
|
||||
version "0.16.0-beta.30"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.30.tgz#820d5c65f868b14ec4177bfb8a294931a53616bf"
|
||||
integrity sha512-39qPHPFmNENxcHf8/CzGHS6wzKMMegoRkHB1+scqtBhSxFaD8tX5Ye33HZIEdQ9nXe9xtr4FWVp77T+n9hdrew==
|
||||
|
||||
xterm@5.3.0-beta.39:
|
||||
version "5.3.0-beta.39"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.39.tgz#b64da3fd1b374fd535db61294a49c16453dbb77e"
|
||||
integrity sha512-Z1r0ijTgad5MjFoz8qZ+2Qc3bgjG6kYY/kZI3DTA1pibArHeOMDYk9+IeCb9adF74zvZurjytRfXKJgX6M9hwg==
|
||||
xterm@5.3.0-beta.61:
|
||||
version "5.3.0-beta.61"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.61.tgz#a6c27d90a5314da51d80deeb32f3bd77f1e1c8f6"
|
||||
integrity sha512-rJHpCc48GSpHnu0SSERynQ80D5ikvFVsqhv6JdmeONTrnAFRr134OglJRIpbi2YK8UPbV6F6Dfqm/AQh+9GZzA==
|
||||
|
||||
+59
-81
@@ -2,63 +2,41 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@microsoft/1ds-core-js@4.0.1", "@microsoft/1ds-core-js@^4.0.1":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-4.0.1.tgz#6fcf129276c933934d618f7fc3de49613259e3ec"
|
||||
integrity sha512-BcTKhcRixCqLYEo1xVlQofvvZpgey9qmkzxQd1j9bBZS4iQl9IOmGBzlf/vGSITC7VXtEk2NZOlHQN0llAIfSw==
|
||||
"@microsoft/1ds-core-js@3.2.13", "@microsoft/1ds-core-js@^3.2.13":
|
||||
version "3.2.13"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.13.tgz#0c105ed75091bae3f1555c0334704fa9911c58fb"
|
||||
integrity sha512-CluYTRWcEk0ObG5EWFNWhs87e2qchJUn0p2D21ZUa3PWojPZfPSBs4//WIE0MYV8Qg1Hdif2ZTwlM7TbYUjfAg==
|
||||
dependencies:
|
||||
"@microsoft/applicationinsights-core-js" "3.0.2"
|
||||
"@microsoft/applicationinsights-shims" "^3.0.1"
|
||||
"@microsoft/dynamicproto-js" "^2.0.2"
|
||||
"@nevware21/ts-async" ">= 0.2.4 < 2.x"
|
||||
"@nevware21/ts-utils" ">= 0.9.6 < 2.x"
|
||||
"@microsoft/applicationinsights-core-js" "2.8.15"
|
||||
"@microsoft/applicationinsights-shims" "^2.0.2"
|
||||
"@microsoft/dynamicproto-js" "^1.1.7"
|
||||
|
||||
"@microsoft/1ds-post-js@^4.0.1":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-4.0.1.tgz#8630927d10518067686104baec98557b74317e4e"
|
||||
integrity sha512-uyYkG9mxzzj/4HgxVJsgcz9abfakW+Rjrsnor/hXS5I5ezE3YUX+54DLvAh/c6mEgoDI7u8wGkGl3HgCtAYbDQ==
|
||||
"@microsoft/1ds-post-js@^3.2.13":
|
||||
version "3.2.13"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.13.tgz#560aacac8a92fdbb79e8c2ebcb293d56e19f51aa"
|
||||
integrity sha512-HgS574fdD19Bo2vPguyznL4eDw7Pcm1cVNpvbvBLWiW3x4e1FCQ3VMXChWnAxCae8Hb0XqlA2sz332ZobBavTA==
|
||||
dependencies:
|
||||
"@microsoft/1ds-core-js" "4.0.1"
|
||||
"@microsoft/applicationinsights-shims" "^3.0.1"
|
||||
"@microsoft/dynamicproto-js" "^2.0.2"
|
||||
"@nevware21/ts-async" ">= 0.2.4 < 2.x"
|
||||
"@nevware21/ts-utils" ">= 0.9.6 < 2.x"
|
||||
"@microsoft/1ds-core-js" "3.2.13"
|
||||
"@microsoft/applicationinsights-shims" "^2.0.2"
|
||||
"@microsoft/dynamicproto-js" "^1.1.7"
|
||||
|
||||
"@microsoft/applicationinsights-core-js@3.0.2":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.2.tgz#108e20df8c162bec92b1f66f9de2530a25d9f51a"
|
||||
integrity sha512-WQhVhzlRlLDrQzn3OShCW/pL3BW5WC57t0oywSknX3q7lMzI3jDg7Ihh0iuIcNTzGCTbDkuqr4d6IjEDWIMtJQ==
|
||||
"@microsoft/applicationinsights-core-js@2.8.15":
|
||||
version "2.8.15"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.15.tgz#8fa466474260e01967fe649f14dd9e5ff91dcdc8"
|
||||
integrity sha512-yYAs9MyjGr2YijQdUSN9mVgT1ijI1FPMgcffpaPmYbHAVbQmF7bXudrBWHxmLzJlwl5rfep+Zgjli2e67lwUqQ==
|
||||
dependencies:
|
||||
"@microsoft/applicationinsights-shims" "3.0.1"
|
||||
"@microsoft/dynamicproto-js" "^2.0.2"
|
||||
"@nevware21/ts-async" ">= 0.2.4 < 2.x"
|
||||
"@nevware21/ts-utils" ">= 0.9.5 < 2.x"
|
||||
"@microsoft/applicationinsights-shims" "2.0.2"
|
||||
"@microsoft/dynamicproto-js" "^1.1.9"
|
||||
|
||||
"@microsoft/applicationinsights-shims@3.0.1", "@microsoft/applicationinsights-shims@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz#3865b73ace8405b9c4618cc5c571f2fe3876f06f"
|
||||
integrity sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==
|
||||
dependencies:
|
||||
"@nevware21/ts-utils" ">= 0.9.4 < 2.x"
|
||||
|
||||
"@microsoft/dynamicproto-js@^2.0.2":
|
||||
"@microsoft/applicationinsights-shims@2.0.2", "@microsoft/applicationinsights-shims@^2.0.2":
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz#e57fbec2e7067d48b7e8e1e1c1d354028ef718a6"
|
||||
integrity sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg==
|
||||
dependencies:
|
||||
"@nevware21/ts-utils" ">= 0.9.4 < 2.x"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.2.tgz#92b36a09375e2d9cb2b4203383b05772be837085"
|
||||
integrity sha512-PoHEgsnmcqruLNHZ/amACqdJ6YYQpED0KSRe6J7gIJTtpZC1FfFU9b1fmDKDKtFoUSrPzEh1qzO3kmRZP0betg==
|
||||
|
||||
"@nevware21/ts-async@>= 0.2.4 < 2.x":
|
||||
version "0.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@nevware21/ts-async/-/ts-async-0.2.6.tgz#9fad1c4a56b01430b407d454817f68bb6ace92b0"
|
||||
integrity sha512-NCUqEZSbsy7LVtKlUScd/eTst6djkWauLlzoIPVKCOxalEBdO8lrgNRIm4Xy68JNudNN5faqa2WA12X8m0BVhA==
|
||||
dependencies:
|
||||
"@nevware21/ts-utils" ">= 0.9.7 < 2.x"
|
||||
|
||||
"@nevware21/ts-utils@>= 0.9.4 < 2.x", "@nevware21/ts-utils@>= 0.9.5 < 2.x", "@nevware21/ts-utils@>= 0.9.6 < 2.x", "@nevware21/ts-utils@>= 0.9.7 < 2.x":
|
||||
version "0.9.8"
|
||||
resolved "https://registry.yarnpkg.com/@nevware21/ts-utils/-/ts-utils-0.9.8.tgz#6db23211a5c7539d3b4362e273087effcfc7d2f8"
|
||||
integrity sha512-kZ8s8hcn9jPVX/M7kSsBYrOGlHjqLahmxrG7QeKTk5paeVwfgKdvVCjj5Acb4UGb/ukU1G34U1Z3eb7bbVanyA==
|
||||
"@microsoft/dynamicproto-js@^1.1.7", "@microsoft/dynamicproto-js@^1.1.9":
|
||||
version "1.1.9"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.9.tgz#7437db7aa061162ee94e4131b69a62b8dad5dea6"
|
||||
integrity sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ==
|
||||
|
||||
"@parcel/watcher@2.1.0":
|
||||
version "2.1.0"
|
||||
@@ -899,45 +877,45 @@ wrappy@1:
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||
|
||||
xterm-addon-canvas@0.5.0-beta.9:
|
||||
version "0.5.0-beta.9"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0-beta.9.tgz#d4a9536cf586f78a54527751e03abf6445613886"
|
||||
integrity sha512-AMyFctHbIWx0aLcACINuZqFFNoz4ndGtIAb4pQguIl9t8r+2otiV11MY4FJmPM0kUzEzoLWzVFJaGxOzykCWuw==
|
||||
xterm-addon-canvas@0.5.0-beta.22:
|
||||
version "0.5.0-beta.22"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0-beta.22.tgz#513f0c2b7cf96073f47627b27e8965c1b1a22431"
|
||||
integrity sha512-9F6ZI0DMRgffVfHkLkDwl5n8VscvCaV10tWI3skXOX7Y7Aws6OEeglkOPoU3IllofCU792kHKM4pPoToUxTltg==
|
||||
|
||||
xterm-addon-image@0.6.0-beta.1:
|
||||
version "0.6.0-beta.1"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.6.0-beta.1.tgz#058c171a4866f1cde7608e3b00050476b55ece4c"
|
||||
integrity sha512-vLxUEo/KAsEaRhMshJ4uxnfc/Si8YfT3kilaK5WuiJflVODzQjytzZrl2iLeZaWKiqQp0NR6AEhuXlYASK675A==
|
||||
xterm-addon-image@0.6.0-beta.14:
|
||||
version "0.6.0-beta.14"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.6.0-beta.14.tgz#75fc3f824123183a4bbb5306e22f8b2c6966b0a6"
|
||||
integrity sha512-D5Gh5JTKhHaPt1KwQNf6diF37KA4eToJw3XId1wy62tWmSqfq+QflhOGTfd+SnSQYCktU05ETzM+0tncIU62pQ==
|
||||
|
||||
xterm-addon-search@0.13.0-beta.7:
|
||||
version "0.13.0-beta.7"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.13.0-beta.7.tgz#2f9bf9d8380c4c40f021c47dc583aa32bea8796e"
|
||||
integrity sha512-Dnuws0ThaqzxRDo8TjkSb5WlMYycONVDSr/irx2pk2I4vC5RuSpOifmD46ALlryNSDkMasjfCLjHPILHsFDCfw==
|
||||
xterm-addon-search@0.13.0-beta.20:
|
||||
version "0.13.0-beta.20"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.13.0-beta.20.tgz#8ddd0513e2a70fcefa325722100d2e1bfaf3b9cb"
|
||||
integrity sha512-wrx6187cJ1UenGL6ZeYv3jFvRPhhENTfbC+Hv1Fnww8LmsKhcj+0+Pm6yInNjX/9hNVsNzdqKyqNeEMoykyoyA==
|
||||
|
||||
xterm-addon-serialize@0.11.0-beta.7:
|
||||
version "0.11.0-beta.7"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.11.0-beta.7.tgz#934a48a4490c2ca94a5cfcdd646fb0813b4e56d0"
|
||||
integrity sha512-TwYsEHq6TflY0vmq3yAI2VKNqzEAZ0gCm3TThDMPrPQiv4LcewQSeGu9RujEqF9RAhujhceaM3GajBxySAWyqQ==
|
||||
xterm-addon-serialize@0.11.0-beta.20:
|
||||
version "0.11.0-beta.20"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.11.0-beta.20.tgz#e879b34d214761403f1081833f9221c6903bf0c3"
|
||||
integrity sha512-OXnC1SATaz7kEFjFWhyv9MJaXi8yHdPjazpGLNi11h33CRTKtCQiqqPBHU87dztnXmpEX6Jw0/jr3zlyXuAmnw==
|
||||
|
||||
xterm-addon-unicode11@0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.5.0.tgz#41c0d96acc1e3bb6c6596eee64e163b6bca74be7"
|
||||
integrity sha512-Jm4/g4QiTxiKiTbYICQgC791ubhIZyoIwxAIgOW8z8HWFNY+lwk+dwaKEaEeGBfM48Vk8fklsUW9u/PlenYEBg==
|
||||
xterm-addon-unicode11@0.6.0-beta.12:
|
||||
version "0.6.0-beta.12"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.6.0-beta.12.tgz#ac6df9d635325dc692e4c602e74a2fc27a09405c"
|
||||
integrity sha512-9wWWf/5nFafYgq0pn9EgAWnXaXGleVxfjNOqavpLRYFv0nw42QbaYyGvnGcxyYHM5Aqx/8rYE/DDVWZBqQZdYA==
|
||||
|
||||
xterm-addon-webgl@0.16.0-beta.13:
|
||||
version "0.16.0-beta.13"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.13.tgz#ffacd22b8d86ca7f9ba903a64bc85654523955fb"
|
||||
integrity sha512-1avJBoAKl+0dOjRK0pvu2im6gaXyP/6oJcEBQXCTz3V8YlgcG/jom8A6FEQSEDGZ/4dnyDvnLbsg3+aLZIr6gw==
|
||||
xterm-addon-webgl@0.16.0-beta.30:
|
||||
version "0.16.0-beta.30"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.30.tgz#820d5c65f868b14ec4177bfb8a294931a53616bf"
|
||||
integrity sha512-39qPHPFmNENxcHf8/CzGHS6wzKMMegoRkHB1+scqtBhSxFaD8tX5Ye33HZIEdQ9nXe9xtr4FWVp77T+n9hdrew==
|
||||
|
||||
xterm-headless@5.3.0-beta.39:
|
||||
version "5.3.0-beta.39"
|
||||
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.3.0-beta.39.tgz#84a509dbe1dc9b2229b5274ae3430ce968986346"
|
||||
integrity sha512-Q4vfqAaYoWh7/T4HearlLlKkn9vjN5EugsNyuPd6hq+Ug1FwJIJYj+I9vY67+Rqxr24XGnrQdXfvvEas29KXsw==
|
||||
xterm-headless@5.3.0-beta.61:
|
||||
version "5.3.0-beta.61"
|
||||
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.3.0-beta.61.tgz#28654550cb572709b99ea3eb8672d4568ae141c9"
|
||||
integrity sha512-yfkbPLUtKjE4K7DsZ204A1BuOKpu6Usqi6rIYWT4XRMi+LjnkTbBjGr2BSjyJ3Gmtm+cSgBD0SvRN+V3xNxbxA==
|
||||
|
||||
xterm@5.3.0-beta.39:
|
||||
version "5.3.0-beta.39"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.39.tgz#b64da3fd1b374fd535db61294a49c16453dbb77e"
|
||||
integrity sha512-Z1r0ijTgad5MjFoz8qZ+2Qc3bgjG6kYY/kZI3DTA1pibArHeOMDYk9+IeCb9adF74zvZurjytRfXKJgX6M9hwg==
|
||||
xterm@5.3.0-beta.61:
|
||||
version "5.3.0-beta.61"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.61.tgz#a6c27d90a5314da51d80deeb32f3bd77f1e1c8f6"
|
||||
integrity sha512-rJHpCc48GSpHnu0SSERynQ80D5ikvFVsqhv6JdmeONTrnAFRr134OglJRIpbi2YK8UPbV6F6Dfqm/AQh+9GZzA==
|
||||
|
||||
yallist@^4.0.0:
|
||||
version "4.0.0"
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
"vs/platform/files/browser/htmlFileSystemProvider.ts",
|
||||
"vs/platform/files/browser/webFileSystemAccess.ts",
|
||||
"vs/platform/telemetry/*",
|
||||
"vs/platform/assignment/*"
|
||||
"vs/platform/assignment/*",
|
||||
"vs/platform/terminal/*",
|
||||
"vs/platform/externalTerminal/*"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -705,6 +705,7 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
this.splitviewSashResetDisposable.dispose();
|
||||
this.childrenSashResetDisposable.dispose();
|
||||
this.childrenChangeDisposable.dispose();
|
||||
this.onDidScrollDisposable.dispose();
|
||||
this.splitview.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { timeout } from 'vs/base/common/async';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { Emitter, Event, EventBufferer } from 'vs/base/common/event';
|
||||
import { matchesPrefix } from 'vs/base/common/filters';
|
||||
import { matchesFuzzy2, matchesPrefix } from 'vs/base/common/filters';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { clamp } from 'vs/base/common/numbers';
|
||||
@@ -527,11 +527,27 @@ class TypeNavigationController<T> implements IDisposable {
|
||||
const label = this.keyboardNavigationLabelProvider.getKeyboardNavigationLabel(this.view.element(index));
|
||||
const labelStr = label && label.toString();
|
||||
|
||||
if (typeof labelStr === 'undefined' || matchesPrefix(word, labelStr)) {
|
||||
this.previouslyFocused = start;
|
||||
this.list.setFocus([index]);
|
||||
this.list.reveal(index);
|
||||
return;
|
||||
if (this.list.options.typeNavigationEnabled) {
|
||||
if (typeof labelStr !== 'undefined') {
|
||||
const prefix = matchesPrefix(word, labelStr);
|
||||
const fuzzy = matchesFuzzy2(word, labelStr);
|
||||
|
||||
// ensures that when fuzzy matching, it doesn't clash with prefix matching (1 input vs 1+ should be prefix and fuzzy respecitvely)
|
||||
const fuzzyScore = fuzzy ? fuzzy[0].end - fuzzy[0].start : 0;
|
||||
if (prefix || fuzzyScore > 1) {
|
||||
this.previouslyFocused = start;
|
||||
this.list.setFocus([index]);
|
||||
this.list.reveal(index);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (typeof labelStr === 'undefined' || matchesPrefix(word, labelStr)) {
|
||||
this.previouslyFocused = start;
|
||||
this.list.setFocus([index]);
|
||||
this.list.reveal(index);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -978,6 +994,7 @@ export interface IListOptions<T> extends IListOptionsUpdate {
|
||||
readonly keyboardNavigationLabelProvider?: IKeyboardNavigationLabelProvider<T>;
|
||||
readonly keyboardNavigationDelegate?: IKeyboardNavigationDelegate;
|
||||
readonly keyboardSupport?: boolean;
|
||||
readonly keyboardNavigationEnabled?: boolean;
|
||||
readonly multipleSelectionController?: IMultipleSelectionController<T>;
|
||||
readonly styleController?: (suffix: string) => IStyleController;
|
||||
readonly accessibilityProvider?: IListAccessibilityProvider<T>;
|
||||
|
||||
@@ -565,11 +565,11 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
|
||||
this.sashContainer = append(this.el, $('.sash-container'));
|
||||
this.viewContainer = $('.split-view-container');
|
||||
|
||||
this.scrollable = new Scrollable({
|
||||
this.scrollable = this._register(new Scrollable({
|
||||
forceIntegerValues: true,
|
||||
smoothScrollDuration: 125,
|
||||
scheduleAtNextAnimationFrame
|
||||
});
|
||||
}));
|
||||
this.scrollableElement = this._register(new SmoothScrollableElement(this.viewContainer, {
|
||||
vertical: this.orientation === Orientation.VERTICAL ? (options.scrollbarVisibility ?? ScrollbarVisibility.Auto) : ScrollbarVisibility.Hidden,
|
||||
horizontal: this.orientation === Orientation.HORIZONTAL ? (options.scrollbarVisibility ?? ScrollbarVisibility.Auto) : ScrollbarVisibility.Hidden
|
||||
|
||||
@@ -52,7 +52,7 @@ export class ToolBar extends Disposable {
|
||||
|
||||
private _onDidChangeDropdownVisibility = this._register(new EventMultiplexer<boolean>());
|
||||
readonly onDidChangeDropdownVisibility = this._onDidChangeDropdownVisibility.event;
|
||||
private disposables = new DisposableStore();
|
||||
private disposables = this._register(new DisposableStore());
|
||||
|
||||
constructor(container: HTMLElement, contextMenuProvider: IContextMenuProvider, options: IToolBarOptions = { orientation: ActionsOrientation.HORIZONTAL }) {
|
||||
super();
|
||||
|
||||
@@ -707,6 +707,8 @@ export function tieBreakComparators<TItem>(...comparators: Comparator<TItem>[]):
|
||||
*/
|
||||
export const numberComparator: Comparator<number> = (a, b) => a - b;
|
||||
|
||||
export const booleanComparator: Comparator<boolean> = (a, b) => numberComparator(a ? 1 : 0, b ? 1 : 0);
|
||||
|
||||
export function reverseOrder<TItem>(comparator: Comparator<TItem>): Comparator<TItem> {
|
||||
return (a, b) => -comparator(a, b);
|
||||
}
|
||||
@@ -754,6 +756,21 @@ export function findMinBy<T>(items: readonly T[], comparator: Comparator<T>): T
|
||||
return findMaxBy(items, (a, b) => -comparator(a, b));
|
||||
}
|
||||
|
||||
export function findMaxIdxBy<T>(items: readonly T[], comparator: Comparator<T>): number {
|
||||
if (items.length === 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
let maxIdx = 0;
|
||||
for (let i = 1; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
if (comparator(item, items[maxIdx]) > 0) {
|
||||
maxIdx = i;
|
||||
}
|
||||
}
|
||||
return maxIdx;
|
||||
}
|
||||
|
||||
export class ArrayQueue<T> {
|
||||
private firstIdx = 0;
|
||||
private lastIdx = this.items.length - 1;
|
||||
|
||||
@@ -47,7 +47,7 @@ export namespace Iterable {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function find<T, R extends T>(iterable: Iterable<T>, predicate: (t: T) => t is R): T | undefined;
|
||||
export function find<T, R extends T>(iterable: Iterable<T>, predicate: (t: T) => t is R): R | undefined;
|
||||
export function find<T>(iterable: Iterable<T>, predicate: (t: T) => boolean): T | undefined;
|
||||
export function find<T>(iterable: Iterable<T>, predicate: (t: T) => boolean): T | undefined {
|
||||
for (const element of iterable) {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { regExpFlags } from 'vs/base/common/strings';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { MarshalledId } from './marshallingIds';
|
||||
|
||||
@@ -28,7 +27,7 @@ function replacer(key: string, value: any): any {
|
||||
return {
|
||||
$mid: MarshalledId.Regexp,
|
||||
source: value.source,
|
||||
flags: regExpFlags(value),
|
||||
flags: value.flags,
|
||||
};
|
||||
}
|
||||
return value;
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
/**
|
||||
* The payload that flows in readable stream events.
|
||||
@@ -567,17 +568,16 @@ export interface IStreamListener<T> {
|
||||
/**
|
||||
* Helper to listen to all events of a T stream in proper order.
|
||||
*/
|
||||
export function listenStream<T>(stream: ReadableStreamEvents<T>, listener: IStreamListener<T>): IDisposable {
|
||||
let destroyed = false;
|
||||
export function listenStream<T>(stream: ReadableStreamEvents<T>, listener: IStreamListener<T>, token?: CancellationToken): void {
|
||||
|
||||
stream.on('error', error => {
|
||||
if (!destroyed) {
|
||||
if (!token?.isCancellationRequested) {
|
||||
listener.onError(error);
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
if (!destroyed) {
|
||||
if (!token?.isCancellationRequested) {
|
||||
listener.onEnd();
|
||||
}
|
||||
});
|
||||
@@ -586,12 +586,10 @@ export function listenStream<T>(stream: ReadableStreamEvents<T>, listener: IStre
|
||||
// into flowing mode. As such it is important to
|
||||
// add this listener last (DO NOT CHANGE!)
|
||||
stream.on('data', data => {
|
||||
if (!destroyed) {
|
||||
if (!token?.isCancellationRequested) {
|
||||
listener.onData(data);
|
||||
}
|
||||
});
|
||||
|
||||
return toDisposable(() => destroyed = true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -217,17 +217,6 @@ export function regExpLeadsToEndlessLoop(regexp: RegExp): boolean {
|
||||
return !!(match && regexp.lastIndex === 0);
|
||||
}
|
||||
|
||||
export function regExpContainsBackreference(regexpValue: string): boolean {
|
||||
return !!regexpValue.match(/([^\\]|^)(\\\\)*\\\d+/);
|
||||
}
|
||||
|
||||
export function regExpFlags(regexp: RegExp): string {
|
||||
return (regexp.global ? 'g' : '')
|
||||
+ (regexp.ignoreCase ? 'i' : '')
|
||||
+ (regexp.multiline ? 'm' : '')
|
||||
+ ((regexp as any /* standalone editor compilation */).unicode ? 'u' : '');
|
||||
}
|
||||
|
||||
export function splitLines(str: string): string[] {
|
||||
return str.split(/\r\n|\r|\n/);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Lazy } from 'vs/base/common/lazy';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
declare const __readFileInTests: (path: string) => Promise<string>;
|
||||
declare const __writeFileInTests: (path: string, contents: string) => Promise<void>;
|
||||
declare const __readDirInTests: (path: string) => Promise<string[]>;
|
||||
declare const __unlinkInTests: (path: string) => Promise<void>;
|
||||
declare const __mkdirPInTests: (path: string) => Promise<void>;
|
||||
|
||||
// setup on import so assertSnapshot has the current context without explicit passing
|
||||
let context: Lazy<SnapshotContext> | undefined;
|
||||
const sanitizeName = (name: string) => name.replace(/[^a-z0-9_-]/gi, '_');
|
||||
const normalizeCrlf = (str: string) => str.replace(/\r\n/g, '\n');
|
||||
|
||||
export interface ISnapshotOptions {
|
||||
/** Name for snapshot file, rather than an incremented number */
|
||||
name?: string;
|
||||
/** Extension name of the snapshot file, defaults to `.snap` */
|
||||
extension?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is exported only for tests against the snapshotting itself! Use
|
||||
* {@link assertSnapshot} as a consumer!
|
||||
*/
|
||||
export class SnapshotContext {
|
||||
private nextIndex = 0;
|
||||
protected snapshotsDir: URI;
|
||||
private readonly namePrefix: string;
|
||||
private readonly usedNames = new Set();
|
||||
|
||||
constructor(private readonly test: Mocha.Test | undefined) {
|
||||
if (!test) {
|
||||
throw new Error('assertSnapshot can only be used in a test');
|
||||
}
|
||||
|
||||
if (!test.file) {
|
||||
throw new Error('currentTest.file is not set, please open an issue with the test you\'re trying to run');
|
||||
}
|
||||
|
||||
const src = FileAccess.asFileUri('');
|
||||
const parts = test.file.split(/[/\\]/g);
|
||||
|
||||
this.namePrefix = sanitizeName(test.fullTitle()) + '_';
|
||||
this.snapshotsDir = URI.joinPath(src, ...[...parts.slice(0, -1), '__snapshots__']);
|
||||
}
|
||||
|
||||
public async assert(value: any, options?: ISnapshotOptions) {
|
||||
const originalStack = new Error().stack!; // save to make the stack nicer on failure
|
||||
const nameOrIndex = (options?.name ? sanitizeName(options.name) : this.nextIndex++);
|
||||
const fileName = this.namePrefix + nameOrIndex + '.' + (options?.extension || 'snap');
|
||||
this.usedNames.add(fileName);
|
||||
|
||||
const fpath = URI.joinPath(this.snapshotsDir, fileName).fsPath;
|
||||
const actual = formatValue(value);
|
||||
let expected: string;
|
||||
try {
|
||||
expected = await __readFileInTests(fpath);
|
||||
} catch {
|
||||
console.info(`Creating new snapshot in: ${fpath}`);
|
||||
await __mkdirPInTests(this.snapshotsDir.fsPath);
|
||||
await __writeFileInTests(fpath, actual);
|
||||
return;
|
||||
}
|
||||
|
||||
if (normalizeCrlf(expected) !== normalizeCrlf(actual)) {
|
||||
await __writeFileInTests(fpath + '.actual', actual);
|
||||
const err: any = new Error(`Snapshot #${nameOrIndex} does not match expected output`);
|
||||
err.expected = expected;
|
||||
err.actual = actual;
|
||||
err.snapshotPath = fpath;
|
||||
err.stack = (err.stack as string)
|
||||
.split('\n')
|
||||
// remove all frames from the async stack and keep the original caller's frame
|
||||
.slice(0, 1)
|
||||
.concat(originalStack.split('\n').slice(3))
|
||||
.join('\n');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
public async removeOldSnapshots() {
|
||||
const contents = await __readDirInTests(this.snapshotsDir.fsPath);
|
||||
const toDelete = contents.filter(f => f.startsWith(this.namePrefix) && !this.usedNames.has(f));
|
||||
if (toDelete.length) {
|
||||
console.info(`Deleting ${toDelete.length} old snapshots for ${this.test?.fullTitle()}`);
|
||||
}
|
||||
|
||||
await Promise.all(toDelete.map(f => __unlinkInTests(URI.joinPath(this.snapshotsDir, f).fsPath)));
|
||||
}
|
||||
}
|
||||
|
||||
const debugDescriptionSymbol = Symbol.for('debug.description');
|
||||
|
||||
function formatValue(value: unknown, level = 0, seen: unknown[] = []): string {
|
||||
switch (typeof value) {
|
||||
case 'bigint':
|
||||
case 'boolean':
|
||||
case 'number':
|
||||
case 'symbol':
|
||||
case 'undefined':
|
||||
return String(value);
|
||||
case 'string':
|
||||
return level === 0 ? value : JSON.stringify(value);
|
||||
case 'function':
|
||||
return `[Function ${value.name}]`;
|
||||
case 'object': {
|
||||
if (value === null) {
|
||||
return 'null';
|
||||
}
|
||||
if (value instanceof RegExp) {
|
||||
return String(value);
|
||||
}
|
||||
if (seen.includes(value)) {
|
||||
return '[Circular]';
|
||||
}
|
||||
if (debugDescriptionSymbol in value && typeof (value as any)[debugDescriptionSymbol] === 'function') {
|
||||
return (value as any)[debugDescriptionSymbol]();
|
||||
}
|
||||
const oi = ' '.repeat(level);
|
||||
const ci = ' '.repeat(level + 1);
|
||||
if (Array.isArray(value)) {
|
||||
const children = value.map(v => formatValue(v, level + 1, [...seen, value]));
|
||||
const multiline = children.some(c => c.includes('\n')) || children.join(', ').length > 80;
|
||||
return multiline ? `[\n${ci}${children.join(`,\n${ci}`)}\n${oi}]` : `[ ${children.join(', ')} ]`;
|
||||
}
|
||||
|
||||
let entries;
|
||||
let prefix = '';
|
||||
if (value instanceof Map) {
|
||||
prefix = 'Map ';
|
||||
entries = [...value.entries()];
|
||||
} else if (value instanceof Set) {
|
||||
prefix = 'Set ';
|
||||
entries = [...value.entries()];
|
||||
} else {
|
||||
entries = Object.entries(value);
|
||||
}
|
||||
|
||||
const lines = entries.map(([k, v]) => `${k}: ${formatValue(v, level + 1, [...seen, value])}`);
|
||||
return prefix + (lines.length > 1
|
||||
? `{\n${ci}${lines.join(`,\n${ci}`)}\n${oi}}`
|
||||
: `{ ${lines.join(',\n')} }`);
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown type ${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
setup(function () {
|
||||
const currentTest = this.currentTest;
|
||||
context = new Lazy(() => new SnapshotContext(currentTest));
|
||||
});
|
||||
teardown(async function () {
|
||||
if (this.currentTest?.state === 'passed') {
|
||||
await context?.rawValue?.removeOldSnapshots();
|
||||
}
|
||||
context = undefined;
|
||||
});
|
||||
|
||||
/**
|
||||
* Implements a snapshot testing utility. ⚠️ This is async! ⚠️
|
||||
*
|
||||
* The first time a snapshot test is run, it'll record the value it's called
|
||||
* with as the expected value. Subsequent runs will fail if the value differs,
|
||||
* but the snapshot can be regenerated by hand or using the Selfhost Test
|
||||
* Provider Extension which'll offer to update it.
|
||||
*
|
||||
* The snapshot will be associated with the currently running test and stored
|
||||
* in a `__snapshots__` directory next to the test file, which is expected to
|
||||
* be the first `.test.js` file in the callstack.
|
||||
*/
|
||||
export function assertSnapshot(value: any, options?: ISnapshotOptions): Promise<void> {
|
||||
if (!context) {
|
||||
throw new Error('assertSnapshot can only be used in a test');
|
||||
}
|
||||
|
||||
return context.value.assert(value, options);
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
import * as assert from 'assert';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { bufferToReadable, VSBuffer } from 'vs/base/common/buffer';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { consumeReadable, consumeStream, isReadable, isReadableBufferedStream, isReadableStream, listenStream, newWriteableStream, peekReadable, peekStream, prefixedReadable, prefixedStream, Readable, ReadableStream, toReadable, toStream, transform } from 'vs/base/common/stream';
|
||||
|
||||
suite('Stream', () => {
|
||||
@@ -351,14 +352,16 @@ suite('Stream', () => {
|
||||
assert.strictEqual(end, true);
|
||||
});
|
||||
|
||||
test('listenStream - dispose', () => {
|
||||
test('listenStream - cancellation', () => {
|
||||
const stream = newWriteableStream<string>(strings => strings.join());
|
||||
|
||||
let error = false;
|
||||
let end = false;
|
||||
let data = '';
|
||||
|
||||
const disposable = listenStream(stream, {
|
||||
const cts = new CancellationTokenSource();
|
||||
|
||||
listenStream(stream, {
|
||||
onData: d => {
|
||||
data = d;
|
||||
},
|
||||
@@ -368,9 +371,9 @@ suite('Stream', () => {
|
||||
onEnd: () => {
|
||||
end = true;
|
||||
}
|
||||
});
|
||||
}, cts.token);
|
||||
|
||||
disposable.dispose();
|
||||
cts.cancel();
|
||||
|
||||
stream.write('Hello');
|
||||
assert.strictEqual(data, '');
|
||||
|
||||
@@ -265,22 +265,6 @@ suite('Strings', () => {
|
||||
assert(regExpWithFlags.multiline);
|
||||
});
|
||||
|
||||
test('regExpContainsBackreference', () => {
|
||||
assert(strings.regExpContainsBackreference('foo \\5 bar'));
|
||||
assert(strings.regExpContainsBackreference('\\2'));
|
||||
assert(strings.regExpContainsBackreference('(\\d)(\\n)(\\1)'));
|
||||
assert(strings.regExpContainsBackreference('(A).*?\\1'));
|
||||
assert(strings.regExpContainsBackreference('\\\\\\1'));
|
||||
assert(strings.regExpContainsBackreference('foo \\\\\\1'));
|
||||
|
||||
assert(!strings.regExpContainsBackreference(''));
|
||||
assert(!strings.regExpContainsBackreference('\\\\1'));
|
||||
assert(!strings.regExpContainsBackreference('foo \\\\1'));
|
||||
assert(!strings.regExpContainsBackreference('(A).*?\\\\1'));
|
||||
assert(!strings.regExpContainsBackreference('foo \\d1 bar'));
|
||||
assert(!strings.regExpContainsBackreference('123'));
|
||||
});
|
||||
|
||||
test('getLeadingWhitespace', () => {
|
||||
assert.strictEqual(strings.getLeadingWhitespace(' foo'), ' ');
|
||||
assert.strictEqual(strings.getLeadingWhitespace(' foo', 2), '');
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
hello_world__0.snap:
|
||||
{ cool: true }
|
||||
hello_world__1.snap:
|
||||
{ nifty: true }
|
||||
hello_world__fourthTest.snap:
|
||||
{ customName: 2 }
|
||||
hello_world__thirdTest.txt:
|
||||
{ customName: 1 }
|
||||
@@ -0,0 +1,4 @@
|
||||
hello_world__0.snap:
|
||||
{ cool: true }
|
||||
hello_world__thirdTest.snap:
|
||||
{ customName: 1 }
|
||||
@@ -0,0 +1,2 @@
|
||||
hello_world__0.snap:
|
||||
{ cool: true }
|
||||
@@ -0,0 +1,35 @@
|
||||
[
|
||||
1,
|
||||
true,
|
||||
undefined,
|
||||
null,
|
||||
123,
|
||||
Symbol(heyo),
|
||||
"hello",
|
||||
{ hello: "world" },
|
||||
{ a: [Circular] },
|
||||
Map {
|
||||
hello: 1,
|
||||
goodbye: 2
|
||||
},
|
||||
Set {
|
||||
1: 1,
|
||||
2: 2,
|
||||
3: 3
|
||||
},
|
||||
[Function helloWorld],
|
||||
/hello/g,
|
||||
[
|
||||
"long stringlong stringlong stringlong stringlong stringlong stringlong stringlong stringlong stringlong string",
|
||||
"long stringlong stringlong stringlong stringlong stringlong stringlong stringlong stringlong stringlong string",
|
||||
"long stringlong stringlong stringlong stringlong stringlong stringlong stringlong stringlong stringlong string",
|
||||
"long stringlong stringlong stringlong stringlong stringlong stringlong stringlong stringlong stringlong string",
|
||||
"long stringlong stringlong stringlong stringlong stringlong stringlong stringlong stringlong stringlong string",
|
||||
"long stringlong stringlong stringlong stringlong stringlong stringlong stringlong stringlong stringlong string",
|
||||
"long stringlong stringlong stringlong stringlong stringlong stringlong stringlong stringlong stringlong string",
|
||||
"long stringlong stringlong stringlong stringlong stringlong stringlong stringlong stringlong stringlong string",
|
||||
"long stringlong stringlong stringlong stringlong stringlong stringlong stringlong stringlong stringlong string",
|
||||
"long stringlong stringlong stringlong stringlong stringlong stringlong stringlong stringlong stringlong string"
|
||||
],
|
||||
Range [1 -> 5]
|
||||
]
|
||||
@@ -0,0 +1,146 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { tmpdir } from 'os';
|
||||
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
|
||||
import { Promises } from 'vs/base/node/pfs';
|
||||
import { SnapshotContext, assertSnapshot } from 'vs/base/test/common/snapshot';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import path = require('path');
|
||||
import { assertThrowsAsync } from 'vs/base/test/common/utils';
|
||||
|
||||
// tests for snapshot are in Node so that we can use native FS operations to
|
||||
// set up and validate things.
|
||||
//
|
||||
// Uses snapshots for testing snapshots. It's snapception!
|
||||
|
||||
suite('snapshot', () => {
|
||||
let testDir: string;
|
||||
|
||||
setup(function () {
|
||||
testDir = getRandomTestPath(tmpdir(), 'vsctests', 'snapshot');
|
||||
return Promises.mkdir(testDir, { recursive: true });
|
||||
});
|
||||
|
||||
teardown(function () {
|
||||
return Promises.rm(testDir);
|
||||
});
|
||||
|
||||
const makeContext = (test: Partial<Mocha.Test> | undefined) => {
|
||||
return new class extends SnapshotContext {
|
||||
constructor() {
|
||||
super(test as Mocha.Test);
|
||||
this.snapshotsDir = URI.file(testDir);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const snapshotFileTree = async () => {
|
||||
let str = '';
|
||||
|
||||
const printDir = async (dir: string, indent: number) => {
|
||||
const children = await Promises.readdir(dir);
|
||||
for (const child of children) {
|
||||
const p = path.join(dir, child);
|
||||
if ((await Promises.stat(p)).isFile()) {
|
||||
const content = await Promises.readFile(p, 'utf-8');
|
||||
str += `${' '.repeat(indent)}${child}:\n`;
|
||||
for (const line of content.split('\n')) {
|
||||
str += `${' '.repeat(indent + 2)}${line}\n`;
|
||||
}
|
||||
} else {
|
||||
str += `${' '.repeat(indent)}${child}/\n`;
|
||||
await printDir(p, indent + 2);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await printDir(testDir, 0);
|
||||
await assertSnapshot(str);
|
||||
};
|
||||
|
||||
test('creates a snapshot', async () => {
|
||||
const ctx = makeContext({
|
||||
file: 'foo/bar',
|
||||
fullTitle: () => 'hello world!'
|
||||
});
|
||||
|
||||
await ctx.assert({ cool: true });
|
||||
await snapshotFileTree();
|
||||
});
|
||||
|
||||
test('validates a snapshot', async () => {
|
||||
const ctx1 = makeContext({
|
||||
file: 'foo/bar',
|
||||
fullTitle: () => 'hello world!'
|
||||
});
|
||||
|
||||
await ctx1.assert({ cool: true });
|
||||
|
||||
const ctx2 = makeContext({
|
||||
file: 'foo/bar',
|
||||
fullTitle: () => 'hello world!'
|
||||
});
|
||||
|
||||
// should pass:
|
||||
await ctx2.assert({ cool: true });
|
||||
|
||||
const ctx3 = makeContext({
|
||||
file: 'foo/bar',
|
||||
fullTitle: () => 'hello world!'
|
||||
});
|
||||
|
||||
// should fail:
|
||||
await assertThrowsAsync(() => ctx3.assert({ cool: false }));
|
||||
});
|
||||
|
||||
test('cleans up old snapshots', async () => {
|
||||
const ctx1 = makeContext({
|
||||
file: 'foo/bar',
|
||||
fullTitle: () => 'hello world!'
|
||||
});
|
||||
|
||||
await ctx1.assert({ cool: true });
|
||||
await ctx1.assert({ nifty: true });
|
||||
await ctx1.assert({ customName: 1 }, { name: 'thirdTest', extension: 'txt' });
|
||||
await ctx1.assert({ customName: 2 }, { name: 'fourthTest' });
|
||||
|
||||
await snapshotFileTree();
|
||||
|
||||
const ctx2 = makeContext({
|
||||
file: 'foo/bar',
|
||||
fullTitle: () => 'hello world!'
|
||||
});
|
||||
|
||||
await ctx2.assert({ cool: true });
|
||||
await ctx2.assert({ customName: 1 }, { name: 'thirdTest' });
|
||||
await ctx2.removeOldSnapshots();
|
||||
|
||||
await snapshotFileTree();
|
||||
});
|
||||
|
||||
test('formats object nicely', async () => {
|
||||
const circular: any = {};
|
||||
circular.a = circular;
|
||||
|
||||
await assertSnapshot([
|
||||
1,
|
||||
true,
|
||||
undefined,
|
||||
null,
|
||||
123n,
|
||||
Symbol('heyo'),
|
||||
'hello',
|
||||
{ hello: 'world' },
|
||||
circular,
|
||||
new Map([['hello', 1], ['goodbye', 2]]),
|
||||
new Set([1, 2, 3]),
|
||||
function helloWorld() { },
|
||||
/hello/g,
|
||||
new Array(10).fill('long string'.repeat(10)),
|
||||
{ [Symbol.for('debug.description')]() { return `Range [1 -> 5]`; } },
|
||||
]);
|
||||
});
|
||||
});
|
||||
+26
-24
@@ -15,7 +15,7 @@ import { whenDeleted, writeFileSync } from 'vs/base/node/pfs';
|
||||
import { findFreePort } from 'vs/base/node/ports';
|
||||
import { watchFileContents } from 'vs/platform/files/node/watcher/nodejs/nodejsWatcherLib';
|
||||
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
|
||||
import { buildHelpMessage, buildVersionMessage, OPTIONS } from 'vs/platform/environment/node/argv';
|
||||
import { buildHelpMessage, buildVersionMessage, NATIVE_CLI_COMMANDS, OPTIONS } from 'vs/platform/environment/node/argv';
|
||||
import { addArg, parseCLIProcessArgv } from 'vs/platform/environment/node/argvHelper';
|
||||
import { getStdinFilePath, hasStdinWithoutTty, readFromStdin, stdinDataListener } from 'vs/platform/environment/node/stdin';
|
||||
import { createWaitMarkerFileSync } from 'vs/platform/environment/node/wait';
|
||||
@@ -51,31 +51,33 @@ export async function main(argv: string[]): Promise<any> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.tunnel) {
|
||||
if (!product.tunnelApplicationName) {
|
||||
console.error(`'tunnel' command not supported in ${product.applicationName}`);
|
||||
return;
|
||||
}
|
||||
const tunnelArgs = argv.slice(argv.indexOf('tunnel') + 1); // all arguments behind `tunnel`
|
||||
return new Promise((resolve, reject) => {
|
||||
let tunnelProcess: ChildProcess;
|
||||
const stdio: StdioOptions = ['ignore', 'pipe', 'pipe'];
|
||||
if (process.env['VSCODE_DEV']) {
|
||||
tunnelProcess = spawn('cargo', ['run', '--', 'tunnel', ...tunnelArgs], { cwd: join(getAppRoot(), 'cli'), stdio });
|
||||
} else {
|
||||
const appPath = process.platform === 'darwin'
|
||||
// ./Contents/MacOS/Electron => ./Contents/Resources/app/bin/code-tunnel-insiders
|
||||
? join(dirname(dirname(process.execPath)), 'Resources', 'app')
|
||||
: dirname(process.execPath);
|
||||
const tunnelCommand = join(appPath, 'bin', `${product.tunnelApplicationName}${isWindows ? '.exe' : ''}`);
|
||||
tunnelProcess = spawn(tunnelCommand, ['tunnel', ...tunnelArgs], { cwd: cwd(), stdio });
|
||||
for (const subcommand of NATIVE_CLI_COMMANDS) {
|
||||
if (args[subcommand]) {
|
||||
if (!product.tunnelApplicationName) {
|
||||
console.error(`'${subcommand}' command not supported in ${product.applicationName}`);
|
||||
return;
|
||||
}
|
||||
const tunnelArgs = argv.slice(argv.indexOf(subcommand) + 1); // all arguments behind `tunnel`
|
||||
return new Promise((resolve, reject) => {
|
||||
let tunnelProcess: ChildProcess;
|
||||
const stdio: StdioOptions = ['ignore', 'pipe', 'pipe'];
|
||||
if (process.env['VSCODE_DEV']) {
|
||||
tunnelProcess = spawn('cargo', ['run', '--', subcommand, ...tunnelArgs], { cwd: join(getAppRoot(), 'cli'), stdio });
|
||||
} else {
|
||||
const appPath = process.platform === 'darwin'
|
||||
// ./Contents/MacOS/Electron => ./Contents/Resources/app/bin/code-tunnel-insiders
|
||||
? join(dirname(dirname(process.execPath)), 'Resources', 'app')
|
||||
: dirname(process.execPath);
|
||||
const tunnelCommand = join(appPath, 'bin', `${product.tunnelApplicationName}${isWindows ? '.exe' : ''}`);
|
||||
tunnelProcess = spawn(tunnelCommand, [subcommand, ...tunnelArgs], { cwd: cwd(), stdio });
|
||||
}
|
||||
|
||||
tunnelProcess.stdout!.pipe(process.stdout);
|
||||
tunnelProcess.stderr!.pipe(process.stderr);
|
||||
tunnelProcess.on('exit', resolve);
|
||||
tunnelProcess.on('error', reject);
|
||||
});
|
||||
tunnelProcess.stdout!.pipe(process.stdout);
|
||||
tunnelProcess.stderr!.pipe(process.stderr);
|
||||
tunnelProcess.on('exit', resolve);
|
||||
tunnelProcess.on('error', reject);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Help
|
||||
|
||||
@@ -17,7 +17,6 @@ import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker
|
||||
import { DiffAlgorithmName, IDiffComputationResult, IEditorWorkerService, ILineChange, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker';
|
||||
import { IModelService } from 'vs/editor/common/services/model';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration';
|
||||
import { regExpFlags } from 'vs/base/common/strings';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
@@ -582,7 +581,7 @@ export class EditorWorkerClient extends Disposable implements IEditorWorkerClien
|
||||
public async textualSuggest(resources: URI[], leadingWord: string | undefined, wordDefRegExp: RegExp): Promise<{ words: string[]; duration: number } | null> {
|
||||
const proxy = await this._withSyncedResources(resources);
|
||||
const wordDef = wordDefRegExp.source;
|
||||
const wordDefFlags = regExpFlags(wordDefRegExp);
|
||||
const wordDefFlags = wordDefRegExp.flags;
|
||||
return proxy.textualSuggest(resources.map(r => r.toString()), leadingWord, wordDef, wordDefFlags);
|
||||
}
|
||||
|
||||
@@ -594,7 +593,7 @@ export class EditorWorkerClient extends Disposable implements IEditorWorkerClien
|
||||
}
|
||||
const wordDefRegExp = this.languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition();
|
||||
const wordDef = wordDefRegExp.source;
|
||||
const wordDefFlags = regExpFlags(wordDefRegExp);
|
||||
const wordDefFlags = wordDefRegExp.flags;
|
||||
return proxy.computeWordRanges(resource.toString(), range, wordDef, wordDefFlags);
|
||||
});
|
||||
}
|
||||
@@ -607,7 +606,7 @@ export class EditorWorkerClient extends Disposable implements IEditorWorkerClien
|
||||
}
|
||||
const wordDefRegExp = this.languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition();
|
||||
const wordDef = wordDefRegExp.source;
|
||||
const wordDefFlags = regExpFlags(wordDefRegExp);
|
||||
const wordDefFlags = wordDefRegExp.flags;
|
||||
return proxy.navigateValueSet(resource.toString(), range, up, wordDef, wordDefFlags);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
}
|
||||
|
||||
.mtkoverflow {
|
||||
background-color: var(--vscode-button-background, --vscode-editor-background);
|
||||
color: var(--vscode-button-foreground, --vscode-editor-foreground);
|
||||
background-color: var(--vscode-button-background, var(--vscode-editor-background));
|
||||
color: var(--vscode-button-foreground, var(--vscode-editor-foreground));
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: var(--vscode-contrastBorder);
|
||||
|
||||
@@ -11,3 +11,9 @@ export const diffMoveBorder = registerColor(
|
||||
{ dark: '#8b8b8b9c', light: '#8b8b8b9c', hcDark: '#8b8b8b9c', hcLight: '#8b8b8b9c', },
|
||||
localize('diffEditor.move.border', 'The border color for text that got moved in the diff editor.')
|
||||
);
|
||||
|
||||
export const diffMoveBorderActive = registerColor(
|
||||
'diffEditor.moveActive.border',
|
||||
{ dark: '#FFA500', light: '#FFA500', hcDark: '#FFA500', hcLight: '#FFA500', },
|
||||
localize('diffEditor.moveActive.border', 'The active border color for text that got moved in the diff editor.')
|
||||
);
|
||||
|
||||
@@ -5,14 +5,12 @@
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IObservable, derived } from 'vs/base/common/observable';
|
||||
import { isDefined } from 'vs/base/common/types';
|
||||
import { arrowRevertChange, diffAddDecoration, diffAddDecorationEmpty, diffDeleteDecoration, diffDeleteDecorationEmpty, diffLineAddDecorationBackground, diffLineAddDecorationBackgroundWithIndicator, diffLineDeleteDecorationBackground, diffLineDeleteDecorationBackgroundWithIndicator, diffWholeLineAddDecoration, diffWholeLineDeleteDecoration } from 'vs/editor/browser/widget/diffEditorWidget2/decorations';
|
||||
import { DiffEditorEditors } from 'vs/editor/browser/widget/diffEditorWidget2/diffEditorEditors';
|
||||
import { DiffEditorOptions } from 'vs/editor/browser/widget/diffEditorWidget2/diffEditorOptions';
|
||||
import { DiffEditorViewModel } from 'vs/editor/browser/widget/diffEditorWidget2/diffEditorViewModel';
|
||||
import { MovedBlocksLinesPart } from 'vs/editor/browser/widget/diffEditorWidget2/movedBlocksLines';
|
||||
import { applyObservableDecorations } from 'vs/editor/browser/widget/diffEditorWidget2/utils';
|
||||
import { LineRange } from 'vs/editor/common/core/lineRange';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IModelDeltaDecoration } from 'vs/editor/common/model';
|
||||
@@ -42,46 +40,37 @@ export class DiffEditorDecorations extends Disposable {
|
||||
|
||||
const originalDecorations: IModelDeltaDecoration[] = [];
|
||||
const modifiedDecorations: IModelDeltaDecoration[] = [];
|
||||
for (const m of diff.mappings) {
|
||||
const fullRangeOriginal = LineRange.subtract(m.lineRangeMapping.originalRange, currentMove?.lineRangeMapping.original)
|
||||
.map(i => i.toInclusiveRange()).filter(isDefined);
|
||||
for (const range of fullRangeOriginal) {
|
||||
originalDecorations.push({ range, options: renderIndicators ? diffLineDeleteDecorationBackgroundWithIndicator : diffLineDeleteDecorationBackground });
|
||||
}
|
||||
|
||||
const fullRangeModified = LineRange.subtract(m.lineRangeMapping.modifiedRange, currentMove?.lineRangeMapping.modified)
|
||||
.map(i => i.toInclusiveRange()).filter(isDefined);
|
||||
for (const range of fullRangeModified) {
|
||||
modifiedDecorations.push({ range, options: renderIndicators ? diffLineAddDecorationBackgroundWithIndicator : diffLineAddDecorationBackground });
|
||||
}
|
||||
|
||||
if (m.lineRangeMapping.modifiedRange.isEmpty || m.lineRangeMapping.originalRange.isEmpty) {
|
||||
for (const range of fullRangeOriginal) {
|
||||
originalDecorations.push({ range, options: diffWholeLineDeleteDecoration });
|
||||
if (!currentMove) {
|
||||
for (const m of diff.mappings) {
|
||||
if (!m.lineRangeMapping.originalRange.isEmpty) {
|
||||
originalDecorations.push({ range: m.lineRangeMapping.originalRange.toInclusiveRange()!, options: renderIndicators ? diffLineDeleteDecorationBackgroundWithIndicator : diffLineDeleteDecorationBackground });
|
||||
}
|
||||
for (const range of fullRangeModified) {
|
||||
modifiedDecorations.push({ range, options: diffWholeLineAddDecoration });
|
||||
if (!m.lineRangeMapping.modifiedRange.isEmpty) {
|
||||
modifiedDecorations.push({ range: m.lineRangeMapping.modifiedRange.toInclusiveRange()!, options: renderIndicators ? diffLineAddDecorationBackgroundWithIndicator : diffLineAddDecorationBackground });
|
||||
}
|
||||
} else {
|
||||
for (const i of m.lineRangeMapping.innerChanges || []) {
|
||||
if (currentMove
|
||||
&& (currentMove.lineRangeMapping.original.intersect(new LineRange(i.originalRange.startLineNumber, i.originalRange.endLineNumber))
|
||||
|| currentMove.lineRangeMapping.modified.intersect(new LineRange(i.modifiedRange.startLineNumber, i.modifiedRange.endLineNumber)))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't show empty markers outside the line range
|
||||
if (m.lineRangeMapping.originalRange.contains(i.originalRange.startLineNumber)) {
|
||||
originalDecorations.push({ range: i.originalRange, options: (i.originalRange.isEmpty() && showEmptyDecorations) ? diffDeleteDecorationEmpty : diffDeleteDecoration });
|
||||
if (m.lineRangeMapping.modifiedRange.isEmpty || m.lineRangeMapping.originalRange.isEmpty) {
|
||||
if (!m.lineRangeMapping.originalRange.isEmpty) {
|
||||
originalDecorations.push({ range: m.lineRangeMapping.originalRange.toInclusiveRange()!, options: diffWholeLineDeleteDecoration });
|
||||
}
|
||||
if (m.lineRangeMapping.modifiedRange.contains(i.modifiedRange.startLineNumber)) {
|
||||
modifiedDecorations.push({ range: i.modifiedRange, options: (i.modifiedRange.isEmpty() && showEmptyDecorations) ? diffAddDecorationEmpty : diffAddDecoration });
|
||||
if (!m.lineRangeMapping.modifiedRange.isEmpty) {
|
||||
modifiedDecorations.push({ range: m.lineRangeMapping.modifiedRange.toInclusiveRange()!, options: diffWholeLineAddDecoration });
|
||||
}
|
||||
} else {
|
||||
for (const i of m.lineRangeMapping.innerChanges || []) {
|
||||
// Don't show empty markers outside the line range
|
||||
if (m.lineRangeMapping.originalRange.contains(i.originalRange.startLineNumber)) {
|
||||
originalDecorations.push({ range: i.originalRange, options: (i.originalRange.isEmpty() && showEmptyDecorations) ? diffDeleteDecorationEmpty : diffDeleteDecoration });
|
||||
}
|
||||
if (m.lineRangeMapping.modifiedRange.contains(i.modifiedRange.startLineNumber)) {
|
||||
modifiedDecorations.push({ range: i.modifiedRange, options: (i.modifiedRange.isEmpty() && showEmptyDecorations) ? diffAddDecorationEmpty : diffAddDecoration });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!m.lineRangeMapping.modifiedRange.isEmpty && this._options.shouldRenderRevertArrows.read(reader) && !currentMove) {
|
||||
modifiedDecorations.push({ range: Range.fromPositions(new Position(m.lineRangeMapping.modifiedRange.startLineNumber, 1)), options: arrowRevertChange });
|
||||
if (!m.lineRangeMapping.modifiedRange.isEmpty && this._options.shouldRenderRevertArrows.read(reader) && !currentMove) {
|
||||
modifiedDecorations.push({ range: Range.fromPositions(new Position(m.lineRangeMapping.modifiedRange.startLineNumber, 1)), options: arrowRevertChange });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +96,7 @@ export class DiffEditorDecorations extends Disposable {
|
||||
originalDecorations.push({
|
||||
range: m.lineRangeMapping.original.toInclusiveRange()!, options: {
|
||||
description: 'moved',
|
||||
blockClassName: 'movedOriginal',
|
||||
blockClassName: 'movedOriginal' + (m === currentMove ? ' currentMove' : ''),
|
||||
blockPadding: [MovedBlocksLinesPart.movedCodeBlockPadding, 0, MovedBlocksLinesPart.movedCodeBlockPadding, MovedBlocksLinesPart.movedCodeBlockPadding],
|
||||
}
|
||||
});
|
||||
@@ -115,7 +104,7 @@ export class DiffEditorDecorations extends Disposable {
|
||||
modifiedDecorations.push({
|
||||
range: m.lineRangeMapping.modified.toInclusiveRange()!, options: {
|
||||
description: 'moved',
|
||||
blockClassName: 'movedModified',
|
||||
blockClassName: 'movedModified' + (m === currentMove ? ' currentMove' : ''),
|
||||
blockPadding: [4, 0, 4, 4],
|
||||
}
|
||||
});
|
||||
|
||||
@@ -35,8 +35,8 @@ export class DiffEditorEditors extends Disposable {
|
||||
) {
|
||||
super();
|
||||
|
||||
this.original = this._createLeftHandSideEditor(_options.editorOptions.get(), codeEditorWidgetOptions.originalEditor || {});
|
||||
this.modified = this._createRightHandSideEditor(_options.editorOptions.get(), codeEditorWidgetOptions.modifiedEditor || {});
|
||||
this.original = this._register(this._createLeftHandSideEditor(_options.editorOptions.get(), codeEditorWidgetOptions.originalEditor || {}));
|
||||
this.modified = this._register(this._createRightHandSideEditor(_options.editorOptions.get(), codeEditorWidgetOptions.modifiedEditor || {}));
|
||||
|
||||
this._register(autorunHandleChanges({
|
||||
createEmptyChangeSummary: () => ({} as IDiffEditorConstructionOptions),
|
||||
|
||||
@@ -11,13 +11,14 @@ import { ISerializedLineRange, 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, MovedText, RangeMapping, SimpleLineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
|
||||
import { lineRangeMappingFromRangeMappings } from 'vs/editor/common/diff/standardLinesDiffComputer';
|
||||
import { StandardLinesDiffComputer, lineRangeMappingFromRangeMappings } from 'vs/editor/common/diff/standardLinesDiffComputer';
|
||||
import { IDiffEditorModel, IDiffEditorViewModel } 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, lengthGetLineCount, lengthOfRange, lengthToPosition, lengthZero, positionToLength } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length';
|
||||
import { DiffEditorOptions } from './diffEditorOptions';
|
||||
import { readHotReloadableExport } from 'vs/editor/browser/widget/diffEditorWidget2/utils';
|
||||
|
||||
export class DiffEditorViewModel extends Disposable implements IDiffEditorViewModel {
|
||||
private readonly _isDiffUpToDate = observableValue<boolean>('isDiffUpToDate', false);
|
||||
@@ -105,40 +106,36 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo
|
||||
|
||||
this._register(model.modified.onDidChangeContent((e) => {
|
||||
const diff = this._diff.get();
|
||||
if (!diff) {
|
||||
return;
|
||||
}
|
||||
|
||||
const textEdits = TextEditInfo.fromModelContentChanges(e.changes);
|
||||
const result = applyModifiedEdits(this._lastDiff!, textEdits, model.original, model.modified);
|
||||
if (result) {
|
||||
this._lastDiff = result;
|
||||
transaction(tx => {
|
||||
this._diff.set(DiffState.fromDiffResult(this._lastDiff!), tx);
|
||||
updateUnchangedRegions(result, tx);
|
||||
const currentSyncedMovedText = this.syncedMovedTexts.get();
|
||||
this.syncedMovedTexts.set(currentSyncedMovedText ? this._lastDiff!.moves.find(m => m.lineRangeMapping.modified.intersect(currentSyncedMovedText.lineRangeMapping.modified)) : undefined, tx);
|
||||
});
|
||||
if (diff) {
|
||||
const textEdits = TextEditInfo.fromModelContentChanges(e.changes);
|
||||
const result = applyModifiedEdits(this._lastDiff!, textEdits, model.original, model.modified);
|
||||
if (result) {
|
||||
this._lastDiff = result;
|
||||
transaction(tx => {
|
||||
this._diff.set(DiffState.fromDiffResult(this._lastDiff!), tx);
|
||||
updateUnchangedRegions(result, tx);
|
||||
const currentSyncedMovedText = this.syncedMovedTexts.get();
|
||||
this.syncedMovedTexts.set(currentSyncedMovedText ? this._lastDiff!.moves.find(m => m.lineRangeMapping.modified.intersect(currentSyncedMovedText.lineRangeMapping.modified)) : undefined, tx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
debouncer.schedule();
|
||||
}));
|
||||
this._register(model.original.onDidChangeContent((e) => {
|
||||
const diff = this._diff.get();
|
||||
if (!diff) {
|
||||
return;
|
||||
}
|
||||
|
||||
const textEdits = TextEditInfo.fromModelContentChanges(e.changes);
|
||||
const result = applyOriginalEdits(this._lastDiff!, textEdits, model.original, model.modified);
|
||||
if (result) {
|
||||
this._lastDiff = result;
|
||||
transaction(tx => {
|
||||
this._diff.set(DiffState.fromDiffResult(this._lastDiff!), tx);
|
||||
updateUnchangedRegions(result, tx);
|
||||
const currentSyncedMovedText = this.syncedMovedTexts.get();
|
||||
this.syncedMovedTexts.set(currentSyncedMovedText ? this._lastDiff!.moves.find(m => m.lineRangeMapping.modified.intersect(currentSyncedMovedText.lineRangeMapping.modified)) : undefined, tx);
|
||||
});
|
||||
if (diff) {
|
||||
const textEdits = TextEditInfo.fromModelContentChanges(e.changes);
|
||||
const result = applyOriginalEdits(this._lastDiff!, textEdits, model.original, model.modified);
|
||||
if (result) {
|
||||
this._lastDiff = result;
|
||||
transaction(tx => {
|
||||
this._diff.set(DiffState.fromDiffResult(this._lastDiff!), tx);
|
||||
updateUnchangedRegions(result, tx);
|
||||
const currentSyncedMovedText = this.syncedMovedTexts.get();
|
||||
this.syncedMovedTexts.set(currentSyncedMovedText ? this._lastDiff!.moves.find(m => m.lineRangeMapping.modified.intersect(currentSyncedMovedText.lineRangeMapping.modified)) : undefined, tx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
debouncer.schedule();
|
||||
@@ -151,6 +148,7 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo
|
||||
debouncer.cancel();
|
||||
contentChangedSignal.read(reader);
|
||||
documentDiffProviderOptionChanged.read(reader);
|
||||
readHotReloadableExport(StandardLinesDiffComputer, reader);
|
||||
|
||||
this._isDiffUpToDate.set(false, undefined);
|
||||
|
||||
@@ -175,7 +173,6 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo
|
||||
result = applyOriginalEdits(result, originalTextEditInfos, model.original, model.modified) ?? result;
|
||||
result = applyModifiedEdits(result, modifiedTextEditInfos, model.original, model.modified) ?? result;
|
||||
|
||||
|
||||
transaction(tx => {
|
||||
updateUnchangedRegions(result, tx);
|
||||
|
||||
|
||||
@@ -91,14 +91,10 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||
)
|
||||
});
|
||||
|
||||
/*
|
||||
TODO@hediet add this back once move detection is more polished.
|
||||
Users can still enable this via settings.json (config.diffEditor.experimental.showMoves).
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||
command: {
|
||||
id: new ToggleShowMovedCodeBlocks().desc.id,
|
||||
title: localize('showMoves', "Show Moves"),
|
||||
title: localize('showMoves', "Show Moved Code Blocks"),
|
||||
icon: Codicon.move,
|
||||
toggled: ContextKeyEqualsExpr.create('config.diffEditor.experimental.showMoves', true),
|
||||
},
|
||||
@@ -106,7 +102,6 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||
group: '1_diff',
|
||||
when: ContextKeyEqualsExpr.create('diffEditorVersion', 2)
|
||||
});
|
||||
*/
|
||||
|
||||
const diffEditorCategory: ILocalizedString = {
|
||||
value: localize('diffEditor', 'Diff Editor'),
|
||||
|
||||
@@ -79,6 +79,8 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
|
||||
private readonly _options: DiffEditorOptions;
|
||||
private readonly _editors: DiffEditorEditors;
|
||||
|
||||
private readonly movedBlocksLinesPart = observableValue<MovedBlocksLinesPart | undefined>('MovedBlocksLinesPart', undefined);
|
||||
|
||||
constructor(
|
||||
private readonly _domElement: HTMLElement,
|
||||
options: Readonly<IDiffEditorConstructionOptions>,
|
||||
@@ -204,13 +206,15 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
|
||||
|
||||
this._register(keepAlive(this._layoutInfo, true));
|
||||
|
||||
this._register(new MovedBlocksLinesPart(
|
||||
this.elements.root,
|
||||
this._diffModel,
|
||||
this._layoutInfo.map(i => i.originalEditor),
|
||||
this._layoutInfo.map(i => i.modifiedEditor),
|
||||
this._editors,
|
||||
));
|
||||
this._register(autorunWithStore((reader, store) => {
|
||||
this.movedBlocksLinesPart.set(store.add(new (readHotReloadableExport(MovedBlocksLinesPart, reader))(
|
||||
this.elements.root,
|
||||
this._diffModel,
|
||||
this._layoutInfo.map(i => i.originalEditor),
|
||||
this._layoutInfo.map(i => i.modifiedEditor),
|
||||
this._editors,
|
||||
)), undefined);
|
||||
}));
|
||||
|
||||
this._register(applyStyle(this.elements.overlay, {
|
||||
width: this._layoutInfo.map((i, r) => i.originalEditor.width + (this._options.renderSideBySide.read(r) ? 0 : i.modifiedEditor.width)),
|
||||
@@ -285,13 +289,15 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
|
||||
const originalWidth = sashLeft ?? Math.max(5, this._editors.original.getLayoutInfo().decorationsLeft);
|
||||
const modifiedWidth = width - originalWidth - (this._options.renderOverviewRuler.read(reader) ? OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH : 0);
|
||||
|
||||
this.elements.original.style.width = originalWidth + 'px';
|
||||
const movedBlocksLinesWidth = this.movedBlocksLinesPart.read(reader)?.width.read(reader) ?? 0;
|
||||
const originalWidthWithoutMovedBlockLines = originalWidth - movedBlocksLinesWidth;
|
||||
this.elements.original.style.width = originalWidthWithoutMovedBlockLines + 'px';
|
||||
this.elements.original.style.left = '0px';
|
||||
|
||||
this.elements.modified.style.width = modifiedWidth + 'px';
|
||||
this.elements.modified.style.left = originalWidth + 'px';
|
||||
|
||||
this._editors.original.layout({ width: originalWidth, height });
|
||||
this._editors.original.layout({ width: originalWidthWithoutMovedBlockLines, height });
|
||||
this._editors.modified.layout({ width: modifiedWidth, height });
|
||||
|
||||
return {
|
||||
|
||||
@@ -265,7 +265,7 @@ export class ViewZoneManager extends Disposable {
|
||||
} else {
|
||||
const delta = a.modifiedHeightInPx - a.originalHeightInPx;
|
||||
if (delta > 0) {
|
||||
if (syncedMovedText?.lineRangeMapping.original.contains(a.originalRange.endLineNumberExclusive - 1)) {
|
||||
if (syncedMovedText?.lineRangeMapping.original.delta(-1).deltaLength(2).contains(a.originalRange.endLineNumberExclusive - 1)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -276,7 +276,7 @@ export class ViewZoneManager extends Disposable {
|
||||
showInHiddenAreas: true,
|
||||
});
|
||||
} else {
|
||||
if (syncedMovedText?.lineRangeMapping.modified.contains(a.modifiedRange.endLineNumberExclusive - 1)) {
|
||||
if (syncedMovedText?.lineRangeMapping.modified.delta(-1).deltaLength(2).contains(a.modifiedRange.endLineNumberExclusive - 1)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,17 +3,26 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IObservable, autorun, observableFromEvent, observableSignalFromEvent } from 'vs/base/common/observable';
|
||||
import { booleanComparator, compareBy, findMaxIdxBy, numberComparator, tieBreakComparators } from 'vs/base/common/arrays';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IObservable, autorun, derived, keepAlive, observableFromEvent, observableSignalFromEvent, observableValue } from 'vs/base/common/observable';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { DiffEditorEditors } from 'vs/editor/browser/widget/diffEditorWidget2/diffEditorEditors';
|
||||
import { DiffEditorViewModel } from 'vs/editor/browser/widget/diffEditorWidget2/diffEditorViewModel';
|
||||
import { EditorLayoutInfo } from 'vs/editor/common/config/editorOptions';
|
||||
import { LineRange } from 'vs/editor/common/core/lineRange';
|
||||
import { OffsetRange, OffsetRangeSet } from 'vs/editor/common/core/offsetRange';
|
||||
|
||||
export class MovedBlocksLinesPart extends Disposable {
|
||||
public static readonly movedCodeBlockPadding = 4;
|
||||
|
||||
private readonly _element: SVGElement;
|
||||
private readonly _originalScrollTop = observableFromEvent(this._editors.original.onDidScrollChange, () => this._editors.original.getScrollTop());
|
||||
private readonly _modifiedScrollTop = observableFromEvent(this._editors.modified.onDidScrollChange, () => this._editors.modified.getScrollTop());
|
||||
private readonly _viewZonesChanged = observableSignalFromEvent('onDidChangeViewZones', this._editors.modified.onDidChangeViewZones);
|
||||
|
||||
public readonly width = observableValue('width', 0);
|
||||
|
||||
constructor(
|
||||
private readonly _rootElement: HTMLElement,
|
||||
private readonly _diffModel: IObservable<DiffEditorViewModel | undefined>,
|
||||
@@ -23,9 +32,10 @@ export class MovedBlocksLinesPart extends Disposable {
|
||||
) {
|
||||
super();
|
||||
|
||||
const element = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
element.setAttribute('class', 'moved-blocks-lines');
|
||||
this._rootElement.appendChild(element);
|
||||
this._element = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
this._element.setAttribute('class', 'moved-blocks-lines');
|
||||
this._rootElement.appendChild(this._element);
|
||||
this._register(toDisposable(() => this._element.remove()));
|
||||
|
||||
this._register(autorun(reader => {
|
||||
/** @description update moved blocks lines positioning */
|
||||
@@ -35,58 +45,142 @@ export class MovedBlocksLinesPart extends Disposable {
|
||||
return;
|
||||
}
|
||||
|
||||
element.style.left = `${info.width - info.verticalScrollbarWidth}px`;
|
||||
element.style.height = `${info.height}px`;
|
||||
element.style.width = `${info.verticalScrollbarWidth + info.contentLeft - MovedBlocksLinesPart.movedCodeBlockPadding}px`;
|
||||
this._element.style.left = `${info.width - info.verticalScrollbarWidth}px`;
|
||||
this._element.style.height = `${info.height}px`;
|
||||
this._element.style.width = `${info.verticalScrollbarWidth + info.contentLeft - MovedBlocksLinesPart.movedCodeBlockPadding + this.width.read(reader)}px`;
|
||||
}));
|
||||
|
||||
const originalScrollTop = observableFromEvent(this._editors.original.onDidScrollChange, () => this._editors.original.getScrollTop());
|
||||
const modifiedScrollTop = observableFromEvent(this._editors.modified.onDidScrollChange, () => this._editors.modified.getScrollTop());
|
||||
const viewZonesChanged = observableSignalFromEvent('onDidChangeViewZones', this._editors.modified.onDidChangeViewZones);
|
||||
this._register(keepAlive(this._state, true));
|
||||
}
|
||||
|
||||
this._register(autorun(reader => {
|
||||
element.replaceChildren();
|
||||
private readonly _state = derived(reader => {
|
||||
/** @description update moved blocks lines */
|
||||
|
||||
/** @description update moved blocks lines */
|
||||
const moves = this._diffModel.read(reader)?.diff.read(reader)?.movedTexts;
|
||||
if (!moves) {
|
||||
return;
|
||||
this._element.replaceChildren();
|
||||
const model = this._diffModel.read(reader);
|
||||
const moves = model?.diff.read(reader)?.movedTexts;
|
||||
if (!moves || moves.length === 0) {
|
||||
this.width.set(0, undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
this._viewZonesChanged.read(reader);
|
||||
|
||||
const infoOrig = this._originalEditorLayoutInfo.read(reader);
|
||||
const infoMod = this._modifiedEditorLayoutInfo.read(reader);
|
||||
if (!infoOrig || !infoMod) {
|
||||
this.width.set(0, undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const lines = moves.map((move) => {
|
||||
function computeLineStart(range: LineRange, editor: ICodeEditor) {
|
||||
const t1 = editor.getTopForLineNumber(range.startLineNumber);
|
||||
const t2 = editor.getTopForLineNumber(range.endLineNumberExclusive);
|
||||
return (t1 + t2) / 2;
|
||||
}
|
||||
|
||||
viewZonesChanged.read(reader);
|
||||
const start = computeLineStart(move.lineRangeMapping.original, this._editors.original);
|
||||
const startOffset = this._originalScrollTop.read(reader);
|
||||
const end = computeLineStart(move.lineRangeMapping.modified, this._editors.modified);
|
||||
const endOffset = this._modifiedScrollTop.read(reader);
|
||||
|
||||
const info = this._originalEditorLayoutInfo.read(reader);
|
||||
const info2 = this._modifiedEditorLayoutInfo.read(reader);
|
||||
if (!info || !info2) {
|
||||
return;
|
||||
const from = start - startOffset;
|
||||
const to = end - endOffset;
|
||||
|
||||
const top = Math.min(start, end);
|
||||
const bottom = Math.max(start, end);
|
||||
|
||||
return { range: new OffsetRange(top, bottom), from, to, fromWithoutScroll: start, toWithoutScroll: end, move };
|
||||
});
|
||||
|
||||
lines.sort(tieBreakComparators(
|
||||
compareBy(l => l.fromWithoutScroll > l.toWithoutScroll, booleanComparator),
|
||||
compareBy(l => -l.fromWithoutScroll, numberComparator)
|
||||
));
|
||||
|
||||
const layout = LinesLayout.compute(lines.map(l => l.range));
|
||||
|
||||
const padding = 10;
|
||||
const lineAreaLeft = infoOrig.verticalScrollbarWidth;
|
||||
const lineAreaWidth = (layout.getTrackCount() - 1) * 10 + padding * 2;
|
||||
const width = lineAreaLeft + lineAreaWidth + (infoMod.contentLeft - MovedBlocksLinesPart.movedCodeBlockPadding);
|
||||
|
||||
let idx = 0;
|
||||
for (const line of lines) {
|
||||
const track = layout.getTrack(idx);
|
||||
const verticalY = lineAreaLeft + padding + track * 10;
|
||||
|
||||
const arrowHeight = 15;
|
||||
const arrowWidth = 15;
|
||||
const right = width;
|
||||
|
||||
const rectWidth = infoMod.glyphMarginWidth + infoMod.lineNumbersWidth;
|
||||
const rectHeight = 18;
|
||||
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
||||
rect.classList.add('arrow-rectangle');
|
||||
rect.setAttribute('x', `${right - rectWidth}`);
|
||||
rect.setAttribute('y', `${line.to - rectHeight / 2}`);
|
||||
rect.setAttribute('width', `${rectWidth}`);
|
||||
rect.setAttribute('height', `${rectHeight}`);
|
||||
this._element.appendChild(rect);
|
||||
|
||||
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
if (line.move === model.syncedMovedTexts.read(reader)) {
|
||||
path.classList.add('currentMove');
|
||||
}
|
||||
const width = info.verticalScrollbarWidth + info.contentLeft - MovedBlocksLinesPart.movedCodeBlockPadding;
|
||||
path.setAttribute('d', `M ${0} ${line.from} L ${verticalY} ${line.from} L ${verticalY} ${line.to} L ${right - arrowWidth} ${line.to}`);
|
||||
path.setAttribute('fill', 'none');
|
||||
this._element.appendChild(path);
|
||||
|
||||
let idx = 0;
|
||||
for (const m of moves) {
|
||||
function computeLineStart(range: LineRange, editor: ICodeEditor) {
|
||||
const t1 = editor.getTopForLineNumber(range.startLineNumber);
|
||||
const t2 = editor.getTopForLineNumber(range.endLineNumberExclusive);
|
||||
return (t1 + t2) / 2;
|
||||
const arrowRight = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
|
||||
arrowRight.classList.add('arrow');
|
||||
if (line.move === model.syncedMovedTexts.read(reader)) {
|
||||
arrowRight.classList.add('currentMove');
|
||||
}
|
||||
arrowRight.setAttribute('points', `${right - arrowWidth},${line.to - arrowHeight / 2} ${right},${line.to} ${right - arrowWidth},${line.to + arrowHeight / 2}`);
|
||||
this._element.appendChild(arrowRight);
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
this.width.set(lineAreaWidth, undefined);
|
||||
});
|
||||
}
|
||||
|
||||
class LinesLayout {
|
||||
public static compute(lines: OffsetRange[]): LinesLayout {
|
||||
const setsPerTrack: OffsetRangeSet[] = [];
|
||||
const trackPerLineIdx: number[] = [];
|
||||
|
||||
for (const line of lines) {
|
||||
let trackIdx = setsPerTrack.findIndex(set => !set.intersectsStrict(line));
|
||||
if (trackIdx === -1) {
|
||||
const maxTrackCount = 6;
|
||||
if (setsPerTrack.length >= maxTrackCount) {
|
||||
trackIdx = findMaxIdxBy(setsPerTrack, compareBy(set => set.intersectWithRangeLength(line), numberComparator));
|
||||
} else {
|
||||
trackIdx = setsPerTrack.length;
|
||||
setsPerTrack.push(new OffsetRangeSet());
|
||||
}
|
||||
|
||||
const start = computeLineStart(m.lineRangeMapping.original, this._editors.original);
|
||||
const startOffset = originalScrollTop.read(reader);
|
||||
const end = computeLineStart(m.lineRangeMapping.modified, this._editors.modified);
|
||||
const endOffset = modifiedScrollTop.read(reader);
|
||||
|
||||
const top = start - startOffset;
|
||||
const bottom = end - endOffset;
|
||||
|
||||
const center = (width / 2) - moves.length * 5 + idx * 10;
|
||||
idx++;
|
||||
|
||||
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
path.setAttribute('d', `M ${0} ${top} L ${center} ${top} L ${center} ${bottom} L ${width} ${bottom}`);
|
||||
|
||||
path.setAttribute('fill', 'none');
|
||||
element.appendChild(path);
|
||||
}
|
||||
}));
|
||||
setsPerTrack[trackIdx].addRange(line);
|
||||
trackPerLineIdx.push(trackIdx);
|
||||
}
|
||||
|
||||
return new LinesLayout(setsPerTrack.length, trackPerLineIdx);
|
||||
}
|
||||
|
||||
private constructor(
|
||||
private readonly _trackCount: number,
|
||||
private readonly trackPerLineIdx: number[]
|
||||
) { }
|
||||
|
||||
getTrack(lineIdx: number): number {
|
||||
return this.trackPerLineIdx[lineIdx];
|
||||
}
|
||||
|
||||
getTrackCount(): number {
|
||||
return this._trackCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,26 @@
|
||||
border: 2px solid var(--vscode-diffEditor-move-border);
|
||||
}
|
||||
|
||||
.monaco-editor .movedOriginal.currentMove, .monaco-editor .movedModified.currentMove {
|
||||
border: 2px solid var(--vscode-diffEditor-moveActive-border);
|
||||
}
|
||||
|
||||
.monaco-diff-editor .moved-blocks-lines path.currentMove {
|
||||
stroke: var(--vscode-diffEditor-moveActive-border);
|
||||
}
|
||||
|
||||
.monaco-diff-editor .moved-blocks-lines .arrow {
|
||||
fill: var(--vscode-diffEditor-move-border);
|
||||
}
|
||||
|
||||
.monaco-diff-editor .moved-blocks-lines .arrow.currentMove {
|
||||
fill: var(--vscode-diffEditor-moveActive-border);
|
||||
}
|
||||
|
||||
.monaco-diff-editor .moved-blocks-lines .arrow-rectangle {
|
||||
fill: var(--vscode-editor-background);
|
||||
}
|
||||
|
||||
.monaco-diff-editor .moved-blocks-lines {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
}
|
||||
|
||||
.monaco-editor .line-insert, .monaco-diff-editor .line-insert {
|
||||
background-color: var(--vscode-diffEditor-insertedLineBackground, --vscode-diffEditor-insertedTextBackground);
|
||||
background-color: var(--vscode-diffEditor-insertedLineBackground, var(--vscode-diffEditor-insertedTextBackground));
|
||||
}
|
||||
|
||||
.monaco-editor .line-insert,
|
||||
@@ -106,7 +106,7 @@
|
||||
|
||||
.monaco-editor .inline-added-margin-view-zone,
|
||||
.monaco-editor .gutter-insert, .monaco-diff-editor .gutter-insert {
|
||||
background-color: var(--vscode-diffEditorGutter-insertedLineBackground, --vscode-diffEditor-insertedLineBackground, --vscode-diffEditor-insertedTextBackground);
|
||||
background-color: var(--vscode-diffEditorGutter-insertedLineBackground, var(--vscode-diffEditor-insertedLineBackground), var(--vscode-diffEditor-insertedTextBackground));
|
||||
}
|
||||
|
||||
.monaco-editor .char-delete, .monaco-diff-editor .char-delete {
|
||||
@@ -114,12 +114,12 @@
|
||||
}
|
||||
|
||||
.monaco-editor .line-delete, .monaco-diff-editor .line-delete {
|
||||
background-color: var(--vscode-diffEditor-removedLineBackground, --vscode-diffEditor-removedTextBackground);
|
||||
background-color: var(--vscode-diffEditor-removedLineBackground, var(--vscode-diffEditor-removedTextBackground));
|
||||
}
|
||||
|
||||
.monaco-editor .inline-deleted-margin-view-zone,
|
||||
.monaco-editor .gutter-delete, .monaco-diff-editor .gutter-delete {
|
||||
background-color: var(--vscode-diffEditorGutter-removedLineBackground, --vscode-diffEditor-removedLineBackground, --vscode-diffEditor-removedTextBackground);
|
||||
background-color: var(--vscode-diffEditorGutter-removedLineBackground, var(--vscode-diffEditor-removedLineBackground), var(--vscode-diffEditor-removedTextBackground));
|
||||
}
|
||||
|
||||
.monaco-diff-editor.side-by-side .editor.modified {
|
||||
|
||||
@@ -730,6 +730,11 @@ export interface IEditorOptions {
|
||||
* Controls whether the editor receives tabs or defers them to the workbench for navigation.
|
||||
*/
|
||||
tabFocusMode?: boolean;
|
||||
|
||||
/**
|
||||
* Controls whether the accessibility hint should be provided to screen reader users when an inline completion is shown.
|
||||
*/
|
||||
inlineCompletionsAccessibilityVerbose?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -5143,7 +5148,8 @@ export const enum EditorOption {
|
||||
layoutInfo,
|
||||
wrappingInfo,
|
||||
defaultColorDecorators,
|
||||
colorDecoratorsActivatedOn
|
||||
colorDecoratorsActivatedOn,
|
||||
inlineCompletionsAccessibilityVerbose
|
||||
}
|
||||
|
||||
export const EditorOptions = {
|
||||
@@ -5734,6 +5740,8 @@ export const EditorOptions = {
|
||||
)),
|
||||
suggest: register(new EditorSuggest()),
|
||||
inlineSuggest: register(new InlineEditorSuggest()),
|
||||
inlineCompletionsAccessibilityVerbose: register(new EditorBooleanOption(EditorOption.inlineCompletionsAccessibilityVerbose, 'inlineCompletionsAccessibilityVerbose', false,
|
||||
{ description: nls.localize('inlineCompletionsAccessibilityVerbose', "Controls whether the accessibility hint should be provided to screen reader users when an inline completion is shown.") })),
|
||||
suggestFontSize: register(new EditorIntOption(
|
||||
EditorOption.suggestFontSize, 'suggestFontSize',
|
||||
0, 0, 1000,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { BugIndicatingError } from 'vs/base/common/errors';
|
||||
import { OffsetRange } from 'vs/editor/common/core/offsetRange';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
|
||||
/**
|
||||
@@ -154,6 +155,10 @@ export class LineRange {
|
||||
return new LineRange(this.startLineNumber + offset, this.endLineNumberExclusive + offset);
|
||||
}
|
||||
|
||||
public deltaLength(offset: number): LineRange {
|
||||
return new LineRange(this.startLineNumber, this.endLineNumberExclusive + offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of lines this line range spans.
|
||||
*/
|
||||
@@ -235,6 +240,14 @@ export class LineRange {
|
||||
public includes(lineNumber: number): boolean {
|
||||
return this.startLineNumber <= lineNumber && lineNumber < this.endLineNumberExclusive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this 1-based line range to a 0-based offset range (subtracts 1!).
|
||||
* @internal
|
||||
*/
|
||||
public toOffsetRange(): OffsetRange {
|
||||
return new OffsetRange(this.startLineNumber - 1, this.endLineNumberExclusive - 1);
|
||||
}
|
||||
}
|
||||
|
||||
export type ISerializedLineRange = [startLineNumber: number, endLineNumberExclusive: number];
|
||||
|
||||
@@ -48,6 +48,14 @@ export class OffsetRange {
|
||||
return new OffsetRange(this.start + offset, this.endExclusive + offset);
|
||||
}
|
||||
|
||||
public deltaStart(offset: number): OffsetRange {
|
||||
return new OffsetRange(this.start + offset, this.endExclusive);
|
||||
}
|
||||
|
||||
public deltaEnd(offset: number): OffsetRange {
|
||||
return new OffsetRange(this.start, this.endExclusive + offset);
|
||||
}
|
||||
|
||||
public get length(): number {
|
||||
return this.endExclusive - this.start;
|
||||
}
|
||||
@@ -90,4 +98,66 @@ export class OffsetRange {
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public slice<T>(arr: T[]): T[] {
|
||||
return arr.slice(this.start, this.endExclusive);
|
||||
}
|
||||
}
|
||||
|
||||
export class OffsetRangeSet {
|
||||
private readonly _sortedRanges: OffsetRange[] = [];
|
||||
|
||||
public addRange(range: OffsetRange): void {
|
||||
let i = 0;
|
||||
while (i < this._sortedRanges.length && this._sortedRanges[i].endExclusive < range.start) {
|
||||
i++;
|
||||
}
|
||||
let j = i;
|
||||
while (j < this._sortedRanges.length && this._sortedRanges[j].start <= range.endExclusive) {
|
||||
j++;
|
||||
}
|
||||
if (i === j) {
|
||||
this._sortedRanges.splice(i, 0, range);
|
||||
} else {
|
||||
const start = Math.min(range.start, this._sortedRanges[i].start);
|
||||
const end = Math.max(range.endExclusive, this._sortedRanges[j - 1].endExclusive);
|
||||
this._sortedRanges.splice(i, j - i, new OffsetRange(start, end));
|
||||
}
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return this._sortedRanges.map(r => r.toString()).join(', ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns of there is a value that is contained in this instance and the given range.
|
||||
*/
|
||||
public intersectsStrict(other: OffsetRange): boolean {
|
||||
// TODO use binary search
|
||||
let i = 0;
|
||||
while (i < this._sortedRanges.length && this._sortedRanges[i].endExclusive <= other.start) {
|
||||
i++;
|
||||
}
|
||||
return i < this._sortedRanges.length && this._sortedRanges[i].start < other.endExclusive;
|
||||
}
|
||||
|
||||
public intersectWithRange(other: OffsetRange): OffsetRangeSet {
|
||||
// TODO use binary search + slice
|
||||
const result = new OffsetRangeSet();
|
||||
for (const range of this._sortedRanges) {
|
||||
const intersection = range.intersect(other);
|
||||
if (intersection) {
|
||||
result.addRange(intersection);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public intersectWithRangeLength(other: OffsetRange): number {
|
||||
return this.intersectWithRange(other).length;
|
||||
}
|
||||
|
||||
public get length(): number {
|
||||
return this._sortedRanges.reduce((prev, cur) => prev + cur.length, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +149,29 @@ export function removeRandomMatches(sequence1: LinesSliceCharSequence, sequence2
|
||||
diffs = result;
|
||||
} while (counter++ < 10 && shouldRepeat);
|
||||
|
||||
// Remove short suffixes/prefixes
|
||||
for (let i = 0; i < diffs.length; i++) {
|
||||
const cur = diffs[i];
|
||||
|
||||
let range1 = cur.seq1Range;
|
||||
let range2 = cur.seq2Range;
|
||||
|
||||
const fullRange1 = sequence1.extendToFullLines(cur.seq1Range);
|
||||
const prefix = sequence1.getText(new OffsetRange(fullRange1.start, cur.seq1Range.start));
|
||||
if (prefix.length > 0 && prefix.trim().length <= 3 && cur.seq1Range.length + cur.seq2Range.length > 100) {
|
||||
range1 = cur.seq1Range.deltaStart(-prefix.length);
|
||||
range2 = cur.seq2Range.deltaStart(-prefix.length);
|
||||
}
|
||||
|
||||
const suffix = sequence1.getText(new OffsetRange(cur.seq1Range.endExclusive, fullRange1.endExclusive));
|
||||
if (suffix.length > 0 && (suffix.trim().length <= 3 && cur.seq1Range.length + cur.seq2Range.length > 150)) {
|
||||
range1 = range1.deltaEnd(suffix.length);
|
||||
range2 = range2.deltaEnd(suffix.length);
|
||||
}
|
||||
|
||||
diffs[i] = new SequenceDiff(range1, range2);
|
||||
}
|
||||
|
||||
return diffs;
|
||||
}
|
||||
|
||||
|
||||
@@ -155,6 +155,13 @@ export class SimpleLineRangeMapping {
|
||||
public flip(): SimpleLineRangeMapping {
|
||||
return new SimpleLineRangeMapping(this.modified, this.original);
|
||||
}
|
||||
|
||||
public join(other: SimpleLineRangeMapping): SimpleLineRangeMapping {
|
||||
return new SimpleLineRangeMapping(
|
||||
this.original.join(other.original),
|
||||
this.modified.join(other.modified),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class MovedText {
|
||||
|
||||
@@ -177,7 +177,7 @@ export class StandardLinesDiffComputer implements ILinesDiffComputer {
|
||||
}
|
||||
|
||||
private computeMoves(changes: LineRangeMapping[], originalLines: string[], modifiedLines: string[], hashedOriginalLines: number[], hashedModifiedLines: number[], timeout: ITimeout, considerWhitespaceChanges: boolean): MovedText[] {
|
||||
const moves: MovedText[] = [];
|
||||
const moves: SimpleLineRangeMapping[] = [];
|
||||
const deletions = changes
|
||||
.filter(c => c.modifiedRange.isEmpty && c.originalRange.length >= 3)
|
||||
.map(d => new LineRangeFragment(d.originalRange, originalLines, d));
|
||||
@@ -199,17 +199,15 @@ export class StandardLinesDiffComputer implements ILinesDiffComputer {
|
||||
}
|
||||
|
||||
if (highestSimilarity > 0.90 && best) {
|
||||
const moveChanges = this.refineDiff(originalLines, modifiedLines, new SequenceDiff(
|
||||
new OffsetRange(deletion.range.startLineNumber - 1, deletion.range.endLineNumberExclusive - 1),
|
||||
new OffsetRange(best.range.startLineNumber - 1, best.range.endLineNumberExclusive - 1)
|
||||
), timeout, considerWhitespaceChanges);
|
||||
const mappings = lineRangeMappingFromRangeMappings(moveChanges.mappings, originalLines, modifiedLines, true);
|
||||
|
||||
insertions.delete(best);
|
||||
moves.push(new MovedText(new SimpleLineRangeMapping(deletion.range, best.range), mappings));
|
||||
moves.push(new SimpleLineRangeMapping(deletion.range, best.range));
|
||||
excludedChanges.add(deletion.source);
|
||||
excludedChanges.add(best.source);
|
||||
}
|
||||
|
||||
if (!timeout.isValid()) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const original3LineHashes = new SetMap<string, { range: LineRange }>();
|
||||
@@ -242,9 +240,6 @@ export class StandardLinesDiffComputer implements ILinesDiffComputer {
|
||||
let lastMappings: PossibleMapping[] = [];
|
||||
for (let i = change.modifiedRange.startLineNumber; i < change.modifiedRange.endLineNumberExclusive - 2; i++) {
|
||||
const key = `${hashedModifiedLines[i - 1]}:${hashedModifiedLines[i + 1 - 1]}:${hashedModifiedLines[i + 2 - 1]}`;
|
||||
|
||||
//const isWeakKey = (originalLines[i].trim().length + originalLines[i + 1].trim().length + originalLines[i + 2].trim().length < 20);
|
||||
|
||||
const currentModifiedRange = new LineRange(i, i + 3);
|
||||
|
||||
const nextMappings: PossibleMapping[] = [];
|
||||
@@ -269,6 +264,10 @@ export class StandardLinesDiffComputer implements ILinesDiffComputer {
|
||||
});
|
||||
lastMappings = nextMappings;
|
||||
}
|
||||
|
||||
if (!timeout.isValid()) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
possibleMappings.sort(reverseOrder(compareBy(m => m.modifiedLineRange.length, numberComparator)));
|
||||
@@ -291,19 +290,49 @@ export class StandardLinesDiffComputer implements ILinesDiffComputer {
|
||||
const modifiedLineRange = s;
|
||||
const originalLineRange = s.delta(-diffOrigToMod);
|
||||
|
||||
const moveChanges = this.refineDiff(originalLines, modifiedLines, new SequenceDiff(
|
||||
new OffsetRange(originalLineRange.startLineNumber - 1, originalLineRange.endLineNumberExclusive - 1),
|
||||
new OffsetRange(modifiedLineRange.startLineNumber - 1, modifiedLineRange.endLineNumberExclusive - 1)
|
||||
), timeout, considerWhitespaceChanges);
|
||||
const mappings = lineRangeMappingFromRangeMappings(moveChanges.mappings, originalLines, modifiedLines, true);
|
||||
moves.push(new MovedText(new SimpleLineRangeMapping(originalLineRange, modifiedLineRange), mappings));
|
||||
moves.push(new SimpleLineRangeMapping(originalLineRange, modifiedLineRange));
|
||||
|
||||
modifiedSet.addRange(modifiedLineRange);
|
||||
originalSet.addRange(originalLineRange);
|
||||
}
|
||||
}
|
||||
|
||||
return moves;
|
||||
// join moves
|
||||
moves.sort(compareBy(m => m.original.startLineNumber, numberComparator));
|
||||
if (moves.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const joinedMoves = [moves[0]];
|
||||
for (let i = 1; i < moves.length; i++) {
|
||||
const last = joinedMoves[joinedMoves.length - 1];
|
||||
const current = moves[i];
|
||||
|
||||
const originalDist = current.original.startLineNumber - last.original.endLineNumberExclusive;
|
||||
const modifiedDist = current.modified.startLineNumber - last.modified.endLineNumberExclusive;
|
||||
const currentMoveAfterLast = originalDist >= 0 && modifiedDist >= 0;
|
||||
|
||||
if (currentMoveAfterLast && originalDist <= 1 && modifiedDist <= 1) {
|
||||
joinedMoves[joinedMoves.length - 1] = last.join(current);
|
||||
continue;
|
||||
}
|
||||
|
||||
const originalText = current.original.toOffsetRange().slice(originalLines).map(l => l.trim()).join('\n');
|
||||
if (originalText.length <= 10) {
|
||||
// Ignore small moves
|
||||
continue;
|
||||
}
|
||||
joinedMoves.push(current);
|
||||
}
|
||||
|
||||
const fullMoves = joinedMoves.map(m => {
|
||||
const moveChanges = this.refineDiff(originalLines, modifiedLines, new SequenceDiff(
|
||||
m.original.toOffsetRange(),
|
||||
m.modified.toOffsetRange(),
|
||||
), timeout, considerWhitespaceChanges);
|
||||
const mappings = lineRangeMappingFromRangeMappings(moveChanges.mappings, originalLines, modifiedLines, true);
|
||||
return new MovedText(m, mappings);
|
||||
});
|
||||
return fullMoves;
|
||||
}
|
||||
|
||||
private refineDiff(originalLines: string[], modifiedLines: string[], diff: SequenceDiff, timeout: ITimeout, considerWhitespaceChanges: boolean): { mappings: RangeMapping[]; hitTimeout: boolean } {
|
||||
@@ -659,7 +688,7 @@ export class LinesSliceCharSequence implements ISequence {
|
||||
private readonly firstCharOffsetByLineMinusOne: number[] = [];
|
||||
public readonly lineRange: OffsetRange;
|
||||
// To account for trimming
|
||||
private readonly offsetByLine: number[] = [];
|
||||
private readonly additionalOffsetByLine: number[] = [];
|
||||
|
||||
constructor(public readonly lines: string[], lineRange: OffsetRange, public readonly considerWhitespaceChanges: boolean) {
|
||||
// This slice has to have lineRange.length many \n! (otherwise diffing against an empty slice will be problematic)
|
||||
@@ -687,7 +716,7 @@ export class LinesSliceCharSequence implements ISequence {
|
||||
line = trimmedStartLine.trimEnd();
|
||||
}
|
||||
|
||||
this.offsetByLine.push(offset);
|
||||
this.additionalOffsetByLine.push(offset);
|
||||
|
||||
for (let i = 0; i < line.length; i++) {
|
||||
this.elements.push(line.charCodeAt(i));
|
||||
@@ -700,7 +729,7 @@ export class LinesSliceCharSequence implements ISequence {
|
||||
}
|
||||
}
|
||||
// To account for the last line
|
||||
this.offsetByLine.push(0);
|
||||
this.additionalOffsetByLine.push(0);
|
||||
}
|
||||
|
||||
toString() {
|
||||
@@ -767,7 +796,7 @@ export class LinesSliceCharSequence implements ISequence {
|
||||
}
|
||||
|
||||
const offsetOfFirstCharInLine = i === 0 ? 0 : this.firstCharOffsetByLineMinusOne[i - 1];
|
||||
return new Position(this.lineRange.start + i + 1, offset - offsetOfFirstCharInLine + 1 + this.offsetByLine[i]);
|
||||
return new Position(this.lineRange.start + i + 1, offset - offsetOfFirstCharInLine + 1 + this.additionalOffsetByLine[i]);
|
||||
}
|
||||
|
||||
public translateRange(range: OffsetRange): Range {
|
||||
@@ -805,9 +834,52 @@ export class LinesSliceCharSequence implements ISequence {
|
||||
return this.translateOffset(range.endExclusive).lineNumber - this.translateOffset(range.start).lineNumber;
|
||||
}
|
||||
|
||||
isStronglyEqual(offset1: number, offset2: number): boolean {
|
||||
public isStronglyEqual(offset1: number, offset2: number): boolean {
|
||||
return this.elements[offset1] === this.elements[offset2];
|
||||
}
|
||||
|
||||
public extendToFullLines(range: OffsetRange): OffsetRange {
|
||||
const firstIdx = findLastIdxMonotonous(this.firstCharOffsetByLineMinusOne, x => x <= range.start);
|
||||
const lastIdx = findFirstIdxMonotonous(this.firstCharOffsetByLineMinusOne, x => range.endExclusive <= x);
|
||||
|
||||
const start = firstIdx === -1 ? 0 : this.firstCharOffsetByLineMinusOne[firstIdx];
|
||||
const end = lastIdx === this.firstCharOffsetByLineMinusOne.length ? this.elements.length : this.firstCharOffsetByLineMinusOne[lastIdx];
|
||||
return new OffsetRange(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns -1 if predicate is false for all items
|
||||
*/
|
||||
function findLastIdxMonotonous<T>(arr: T[], predicate: (item: T) => boolean): number {
|
||||
let i = 0;
|
||||
let j = arr.length;
|
||||
while (i < j) {
|
||||
const k = Math.floor((i + j) / 2);
|
||||
if (predicate(arr[k])) {
|
||||
i = k + 1;
|
||||
} else {
|
||||
j = k;
|
||||
}
|
||||
}
|
||||
return i - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns arr.length if predicate is false for all items
|
||||
*/
|
||||
function findFirstIdxMonotonous<T>(arr: T[], predicate: (item: T) => boolean): number {
|
||||
let i = 0;
|
||||
let j = arr.length;
|
||||
while (i < j) {
|
||||
const k = Math.floor((i + j) / 2);
|
||||
if (predicate(arr[k])) {
|
||||
j = k;
|
||||
} else {
|
||||
i = k + 1;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
function isWordChar(charCode: number): boolean {
|
||||
|
||||
@@ -16,7 +16,7 @@ import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { EditOperation, ISingleEditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { IPosition, Position } from 'vs/editor/common/core/position';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { ISelection, Selection } from 'vs/editor/common/core/selection';
|
||||
import { LanguageId } from 'vs/editor/common/encodedTokenAttributes';
|
||||
import * as model from 'vs/editor/common/model';
|
||||
import { TokenizationRegistry as TokenizationRegistryImpl } from 'vs/editor/common/tokenizationRegistry';
|
||||
@@ -2033,3 +2033,33 @@ export interface DocumentOnDropEditProvider {
|
||||
|
||||
provideDocumentOnDropEdits(model: model.ITextModel, position: IPosition, dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): ProviderResult<DocumentOnDropEdit>;
|
||||
}
|
||||
|
||||
export interface RelatedContextItem {
|
||||
readonly uri: URI;
|
||||
readonly range: IRange;
|
||||
}
|
||||
|
||||
export interface MappedEditsContext {
|
||||
selections: ISelection[];
|
||||
related: RelatedContextItem[];
|
||||
}
|
||||
|
||||
export interface MappedEditsProvider {
|
||||
|
||||
/**
|
||||
* Provider maps code blocks from the chat into a workspace edit.
|
||||
*
|
||||
* @param document The document to provide mapped edits for.
|
||||
* @param codeBlocks Code blocks that come from an LLM's reply.
|
||||
* "Insert at cursor" in the panel chat only sends one edit that the user clicks on, but inline chat can send multiple blocks and let the lang server decide what to do with them.
|
||||
* @param context The context for providing mapped edits.
|
||||
* @param token A cancellation token.
|
||||
* @returns A provider result of text edits.
|
||||
*/
|
||||
provideMappedEdits(
|
||||
document: model.ITextModel,
|
||||
codeBlocks: string[],
|
||||
context: MappedEditsContext,
|
||||
token: CancellationToken
|
||||
): Promise<WorkspaceEdit | null>;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { LanguageFeatureRegistry, NotebookInfoResolver } from 'vs/editor/common/languageFeatureRegistry';
|
||||
import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentPasteEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages';
|
||||
import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentPasteEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, MappedEditsProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export const ILanguageFeaturesService = createDecorator<ILanguageFeaturesService>('ILanguageFeaturesService');
|
||||
@@ -71,6 +71,8 @@ export interface ILanguageFeaturesService {
|
||||
|
||||
readonly documentOnDropEditProvider: LanguageFeatureRegistry<DocumentOnDropEditProvider>;
|
||||
|
||||
readonly mappedEditsProvider: LanguageFeatureRegistry<MappedEditsProvider>;
|
||||
|
||||
// --
|
||||
|
||||
setNotebookTypeResolver(resolver: NotebookInfoResolver | undefined): void;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { LanguageFeatureRegistry, NotebookInfo, NotebookInfoResolver } from 'vs/editor/common/languageFeatureRegistry';
|
||||
import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DocumentPasteEditProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages';
|
||||
import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DocumentPasteEditProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider, MappedEditsProvider } from 'vs/editor/common/languages';
|
||||
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
|
||||
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
|
||||
@@ -42,6 +42,7 @@ export class LanguageFeaturesService implements ILanguageFeaturesService {
|
||||
readonly documentSemanticTokensProvider = new LanguageFeatureRegistry<DocumentSemanticTokensProvider>(this._score.bind(this));
|
||||
readonly documentOnDropEditProvider = new LanguageFeatureRegistry<DocumentOnDropEditProvider>(this._score.bind(this));
|
||||
readonly documentPasteEditProvider = new LanguageFeatureRegistry<DocumentPasteEditProvider>(this._score.bind(this));
|
||||
readonly mappedEditsProvider: LanguageFeatureRegistry<MappedEditsProvider> = new LanguageFeatureRegistry<MappedEditsProvider>(this._score.bind(this));
|
||||
|
||||
private _notebookTypeResolver?: NotebookInfoResolver;
|
||||
|
||||
|
||||
@@ -318,7 +318,8 @@ export enum EditorOption {
|
||||
layoutInfo = 142,
|
||||
wrappingInfo = 143,
|
||||
defaultColorDecorators = 144,
|
||||
colorDecoratorsActivatedOn = 145
|
||||
colorDecoratorsActivatedOn = 145,
|
||||
inlineCompletionsAccessibilityVerbose = 146
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,9 +12,9 @@ import { instantiateTextModel } from 'vs/editor/test/common/testTextModel';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
|
||||
import { ILanguageService } from 'vs/editor/common/languages/language';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
|
||||
|
||||
suite('bracket matching', () => {
|
||||
|
||||
let disposables: DisposableStore;
|
||||
let instantiationService: TestInstantiationService;
|
||||
let languageConfigurationService: ILanguageConfigurationService;
|
||||
@@ -31,6 +31,8 @@ suite('bracket matching', () => {
|
||||
disposables.dispose();
|
||||
});
|
||||
|
||||
ensureNoDisposablesAreLeakedInTestSuite();
|
||||
|
||||
function createTextModelWithBrackets(text: string) {
|
||||
const languageId = 'bracketMode';
|
||||
disposables.add(languageService.registerLanguage({ id: languageId }));
|
||||
|
||||
@@ -6,11 +6,14 @@
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorCommand, EditorContributionInstantiation, ServicesAccessor, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { editorConfigurationBaseNode } from 'vs/editor/common/config/editorConfigurationSchema';
|
||||
import { registerEditorFeature } from 'vs/editor/common/editorFeatures';
|
||||
import { DefaultDropProvidersFeature } from 'vs/editor/contrib/dropOrPasteInto/browser/defaultProviders';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { DropIntoEditorController, changeDropTypeCommandId, dropWidgetVisibleCtx } from './dropIntoEditorController';
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { DropIntoEditorController, changeDropTypeCommandId, defaultProviderConfig, dropWidgetVisibleCtx } from './dropIntoEditorController';
|
||||
|
||||
registerEditorContribution(DropIntoEditorController.ID, DropIntoEditorController, EditorContributionInstantiation.BeforeFirstInteraction);
|
||||
|
||||
@@ -32,3 +35,18 @@ registerEditorCommand(new class extends EditorCommand {
|
||||
});
|
||||
|
||||
registerEditorFeature(DefaultDropProvidersFeature);
|
||||
|
||||
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration({
|
||||
...editorConfigurationBaseNode,
|
||||
properties: {
|
||||
[defaultProviderConfig]: {
|
||||
type: 'object',
|
||||
scope: ConfigurationScope.LANGUAGE_OVERRIDABLE,
|
||||
description: nls.localize('defaultProviderDescription', "Configures the default drop provider to use for content of a given mime type."),
|
||||
default: {},
|
||||
additionalProperties: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { CancelablePromise, createCancelablePromise, raceCancellation } from 'vs/base/common/async';
|
||||
import { VSDataTransfer } from 'vs/base/common/dataTransfer';
|
||||
import { VSDataTransfer, matchesMimeType } from 'vs/base/common/dataTransfer';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { toExternalVSDataTransfer } from 'vs/editor/browser/dnd';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
@@ -13,7 +13,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { DocumentOnDropEditProvider } from 'vs/editor/common/languages';
|
||||
import { DocumentOnDropEdit, DocumentOnDropEditProvider } from 'vs/editor/common/languages';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
|
||||
import { DraggedTreeItemsIdentifier } from 'vs/editor/common/services/treeViewsDnd';
|
||||
@@ -21,11 +21,14 @@ import { ITreeViewsDnDService } from 'vs/editor/common/services/treeViewsDndServ
|
||||
import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/editor/contrib/editorState/browser/editorState';
|
||||
import { InlineProgressManager } from 'vs/editor/contrib/inlineProgress/browser/inlineProgress';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { LocalSelectionTransfer } from 'vs/platform/dnd/browser/dnd';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { sortEditsByYieldTo } from './edit';
|
||||
import { PostEditWidgetManager } from './postEditWidget';
|
||||
import { sortEditsByYieldTo as sortEditsByYieldTo } from './edit';
|
||||
|
||||
export const defaultProviderConfig = 'editor.experimental.dropIntoEditor.defaultProvider';
|
||||
|
||||
export const changeDropTypeCommandId = 'editor.changeDropType';
|
||||
|
||||
@@ -49,6 +52,7 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IConfigurationService private readonly _configService: IConfigurationService,
|
||||
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
|
||||
@ITreeViewsDnDService private readonly _treeViewsDragAndDropService: ITreeViewsDnDService
|
||||
) {
|
||||
@@ -108,9 +112,10 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
|
||||
}
|
||||
|
||||
if (edits.length) {
|
||||
const activeEditIndex = this.getInitialActiveEditIndex(model, edits);
|
||||
const canShowWidget = editor.getOption(EditorOption.dropIntoEditor).showDropSelector === 'afterDrop';
|
||||
// Pass in the parent token here as it tracks cancelling the entire drop operation
|
||||
await this._postDropWidgetManager.applyEditAndShowIfNeeded([Range.fromPositions(position)], { activeEditIndex: 0, allEdits: edits }, canShowWidget, token);
|
||||
await this._postDropWidgetManager.applyEditAndShowIfNeeded([Range.fromPositions(position)], { activeEditIndex, allEdits: edits }, canShowWidget, token);
|
||||
}
|
||||
} finally {
|
||||
tokenSource.dispose();
|
||||
@@ -124,7 +129,7 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
|
||||
this._currentOperation = p;
|
||||
}
|
||||
|
||||
private async getDropEdits(providers: DocumentOnDropEditProvider[], model: ITextModel, position: IPosition, dataTransfer: VSDataTransfer, tokenSource: EditorStateCancellationTokenSource) {
|
||||
private async getDropEdits(providers: readonly DocumentOnDropEditProvider[], model: ITextModel, position: IPosition, dataTransfer: VSDataTransfer, tokenSource: EditorStateCancellationTokenSource) {
|
||||
const results = await raceCancellation(Promise.all(providers.map(async provider => {
|
||||
try {
|
||||
const edit = await provider.provideDocumentOnDropEdits(model, position, dataTransfer, tokenSource.token);
|
||||
@@ -136,9 +141,22 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
|
||||
}
|
||||
return undefined;
|
||||
})), tokenSource.token);
|
||||
|
||||
const edits = coalesce(results ?? []);
|
||||
sortEditsByYieldTo(edits);
|
||||
return edits;
|
||||
return sortEditsByYieldTo(edits);
|
||||
}
|
||||
|
||||
private getInitialActiveEditIndex(model: ITextModel, edits: ReadonlyArray<DocumentOnDropEdit & { readonly providerId?: string }>) {
|
||||
const preferredProviders = this._configService.getValue<Record<string, string>>(defaultProviderConfig, { resource: model.uri });
|
||||
for (const [configMime, desiredId] of Object.entries(preferredProviders)) {
|
||||
const editIndex = edits.findIndex(edit =>
|
||||
desiredId === edit.providerId
|
||||
&& edit.handledMimeType && matchesMimeType(configMime, [edit.handledMimeType]));
|
||||
if (editIndex >= 0) {
|
||||
return editIndex;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private async extractDataTransferData(dragEvent: DragEvent): Promise<VSDataTransfer> {
|
||||
|
||||
@@ -32,21 +32,68 @@ export function sortEditsByYieldTo<T extends {
|
||||
readonly providerId: string | undefined;
|
||||
readonly handledMimeType?: string;
|
||||
readonly yieldTo?: readonly DropYieldTo[];
|
||||
}>(edits: T[]): void {
|
||||
}>(edits: readonly T[]): T[] {
|
||||
function yieldsTo(yTo: DropYieldTo, other: T): boolean {
|
||||
return ('providerId' in yTo && yTo.providerId === other.providerId)
|
||||
|| ('mimeType' in yTo && yTo.mimeType === other.handledMimeType);
|
||||
}
|
||||
|
||||
edits.sort((a, b) => {
|
||||
if (a.yieldTo?.some(yTo => yieldsTo(yTo, b))) {
|
||||
return 1;
|
||||
// Build list of nodes each node yields to
|
||||
const yieldsToMap = new Map<T, T[]>();
|
||||
for (const edit of edits) {
|
||||
for (const yTo of edit.yieldTo ?? []) {
|
||||
for (const other of edits) {
|
||||
if (other === edit) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (yieldsTo(yTo, other)) {
|
||||
let arr = yieldsToMap.get(edit);
|
||||
if (!arr) {
|
||||
arr = [];
|
||||
yieldsToMap.set(edit, arr);
|
||||
}
|
||||
arr.push(other);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!yieldsToMap.size) {
|
||||
return Array.from(edits);
|
||||
}
|
||||
|
||||
// Topological sort
|
||||
const visited = new Set<T>();
|
||||
const tempStack: T[] = [];
|
||||
|
||||
function visit(nodes: T[]): T[] {
|
||||
if (!nodes.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (b.yieldTo?.some(yTo => yieldsTo(yTo, a))) {
|
||||
return -1;
|
||||
const node = nodes[0];
|
||||
if (tempStack.includes(node)) {
|
||||
console.warn(`Yield to cycle detected for ${node.providerId}`);
|
||||
return nodes;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
if (visited.has(node)) {
|
||||
return visit(nodes.slice(1));
|
||||
}
|
||||
|
||||
let pre: T[] = [];
|
||||
const yTo = yieldsToMap.get(node);
|
||||
if (yTo) {
|
||||
tempStack.push(node);
|
||||
pre = visit(yTo);
|
||||
tempStack.pop();
|
||||
}
|
||||
|
||||
visited.add(node);
|
||||
|
||||
return [...pre, node, ...visit(nodes.slice(1))];
|
||||
}
|
||||
|
||||
return visit(Array.from(edits));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as assert from 'assert';
|
||||
import { DocumentOnDropEdit } from 'vs/editor/common/languages';
|
||||
import { sortEditsByYieldTo } from 'vs/editor/contrib/dropOrPasteInto/browser/edit';
|
||||
|
||||
type DropEdit = DocumentOnDropEdit & { providerId: string | undefined };
|
||||
|
||||
function createTestEdit(providerId: string, args?: Partial<DropEdit>): DropEdit {
|
||||
return {
|
||||
label: '',
|
||||
insertText: '',
|
||||
providerId,
|
||||
...args,
|
||||
};
|
||||
}
|
||||
|
||||
suite('sortEditsByYieldTo', () => {
|
||||
test('Should noop for empty edits', () => {
|
||||
const edits: DropEdit[] = [];
|
||||
|
||||
assert.deepStrictEqual(sortEditsByYieldTo(edits), []);
|
||||
});
|
||||
|
||||
test('Yielded to edit should get sorted after target', () => {
|
||||
const edits: DropEdit[] = [
|
||||
createTestEdit('a', { yieldTo: [{ providerId: 'b' }] }),
|
||||
createTestEdit('b'),
|
||||
];
|
||||
assert.deepStrictEqual(sortEditsByYieldTo(edits).map(x => x.providerId), ['b', 'a']);
|
||||
});
|
||||
|
||||
test('Should handle chain of yield to', () => {
|
||||
{
|
||||
const edits: DropEdit[] = [
|
||||
createTestEdit('c', { yieldTo: [{ providerId: 'a' }] }),
|
||||
createTestEdit('a', { yieldTo: [{ providerId: 'b' }] }),
|
||||
createTestEdit('b'),
|
||||
];
|
||||
|
||||
assert.deepStrictEqual(sortEditsByYieldTo(edits).map(x => x.providerId), ['b', 'a', 'c']);
|
||||
}
|
||||
{
|
||||
const edits: DropEdit[] = [
|
||||
createTestEdit('a', { yieldTo: [{ providerId: 'b' }] }),
|
||||
createTestEdit('c', { yieldTo: [{ providerId: 'a' }] }),
|
||||
createTestEdit('b'),
|
||||
];
|
||||
|
||||
assert.deepStrictEqual(sortEditsByYieldTo(edits).map(x => x.providerId), ['b', 'a', 'c']);
|
||||
}
|
||||
});
|
||||
|
||||
test(`Should not reorder when yield to isn't used`, () => {
|
||||
const edits: DropEdit[] = [
|
||||
createTestEdit('c', { yieldTo: [{ providerId: 'x' }] }),
|
||||
createTestEdit('a', { yieldTo: [{ providerId: 'y' }] }),
|
||||
createTestEdit('b'),
|
||||
];
|
||||
|
||||
assert.deepStrictEqual(sortEditsByYieldTo(edits).map(x => x.providerId), ['c', 'a', 'b']);
|
||||
});
|
||||
});
|
||||
@@ -908,13 +908,13 @@ class UnfoldAllRegionsAction extends FoldingAction<void> {
|
||||
}
|
||||
}
|
||||
|
||||
class FoldAllRegionsExceptAction extends FoldingAction<void> {
|
||||
class FoldAllExceptAction extends FoldingAction<void> {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.foldAllExcept',
|
||||
label: nls.localize('foldAllExcept.label', "Fold All Regions Except Selected"),
|
||||
alias: 'Fold All Regions Except Selected',
|
||||
label: nls.localize('foldAllExcept.label', "Fold All Except Selected"),
|
||||
alias: 'Fold All Except Selected',
|
||||
precondition: CONTEXT_FOLDING_ENABLED,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
@@ -931,13 +931,13 @@ class FoldAllRegionsExceptAction extends FoldingAction<void> {
|
||||
|
||||
}
|
||||
|
||||
class UnfoldAllRegionsExceptAction extends FoldingAction<void> {
|
||||
class UnfoldAllExceptAction extends FoldingAction<void> {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.unfoldAllExcept',
|
||||
label: nls.localize('unfoldAllExcept.label', "Unfold All Regions Except Selected"),
|
||||
alias: 'Unfold All Regions Except Selected',
|
||||
label: nls.localize('unfoldAllExcept.label', "Unfold All Except Selected"),
|
||||
alias: 'Unfold All Except Selected',
|
||||
precondition: CONTEXT_FOLDING_ENABLED,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
@@ -1194,8 +1194,8 @@ registerEditorAction(UnfoldAllAction);
|
||||
registerEditorAction(FoldAllBlockCommentsAction);
|
||||
registerEditorAction(FoldAllRegionsAction);
|
||||
registerEditorAction(UnfoldAllRegionsAction);
|
||||
registerEditorAction(FoldAllRegionsExceptAction);
|
||||
registerEditorAction(UnfoldAllRegionsExceptAction);
|
||||
registerEditorAction(FoldAllExceptAction);
|
||||
registerEditorAction(UnfoldAllExceptAction);
|
||||
registerEditorAction(ToggleFoldAction);
|
||||
registerEditorAction(GotoParentFoldAction);
|
||||
registerEditorAction(GotoPreviousFoldAction);
|
||||
|
||||
@@ -854,7 +854,6 @@ export class ContentHoverWidget extends ResizableContentWidget {
|
||||
width = dom.getTotalWidth(containerDomNode);
|
||||
this._contentWidth = width;
|
||||
this._updateMinimumWidth();
|
||||
this._updateResizableNodeMaxDimensions();
|
||||
this._resizableNode.layout(height, width);
|
||||
|
||||
if (this._hasHorizontalScrollbar()) {
|
||||
|
||||
@@ -22,11 +22,13 @@ import { InlineCompletionContextKeys } from 'vs/editor/contrib/inlineCompletions
|
||||
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 { localize } from 'vs/nls';
|
||||
import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
|
||||
export class InlineCompletionsController extends Disposable {
|
||||
static ID = 'editor.contrib.inlineCompletionsController';
|
||||
@@ -73,11 +75,11 @@ export class InlineCompletionsController extends Disposable {
|
||||
@ILanguageFeatureDebounceService private readonly debounceService: ILanguageFeatureDebounceService,
|
||||
@ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService,
|
||||
@IAudioCueService private readonly audioCueService: IAudioCueService,
|
||||
@IKeybindingService private readonly _keybindingService: IKeybindingService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(new InlineCompletionContextKeys(this.contextKeyService, this.model));
|
||||
|
||||
this._register(Event.runAndSubscribe(editor.onDidChangeModel, () => transaction(tx => {
|
||||
/** @description onDidChangeModel */
|
||||
this.model.set(undefined, tx);
|
||||
@@ -147,7 +149,7 @@ export class InlineCompletionsController extends Disposable {
|
||||
|
||||
this._register(this.editor.onDidBlurEditorWidget(() => {
|
||||
// This is a hidden setting very useful for debugging
|
||||
if (this.configurationService.getValue('editor.inlineSuggest.keepOnBlur') ||
|
||||
if (this.contextKeyService.getContextKeyValue<boolean>('accessibleViewIsShown') || this.configurationService.getValue('editor.inlineSuggest.keepOnBlur') ||
|
||||
editor.getOption(EditorOption.inlineSuggest).keepOnBlur) {
|
||||
return;
|
||||
}
|
||||
@@ -190,13 +192,29 @@ export class InlineCompletionsController extends Disposable {
|
||||
const lineText = model.textModel.getLineContent(state.ghostText.lineNumber);
|
||||
this.audioCueService.playAudioCue(AudioCue.inlineSuggestion).then(() => {
|
||||
if (this.editor.getOption(EditorOption.screenReaderAnnounceInlineSuggestion)) {
|
||||
alert(state.ghostText.renderForScreenReader(lineText));
|
||||
this.provideScreenReaderUpdate(state.ghostText.renderForScreenReader(lineText));
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(new InlineCompletionsHintsWidget(this.editor, this.model, this.instantiationService));
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('accessibility.verbosity.inlineCompletions')) {
|
||||
this.editor.updateOptions({ inlineCompletionsAccessibilityVerbose: this.configurationService.getValue('accessibility.verbosity.inlineCompletions') });
|
||||
}
|
||||
}));
|
||||
this.editor.updateOptions({ inlineCompletionsAccessibilityVerbose: this.configurationService.getValue('accessibility.verbosity.inlineCompletions') });
|
||||
}
|
||||
|
||||
private provideScreenReaderUpdate(content: string): void {
|
||||
const accessibleViewShowing = this.contextKeyService.getContextKeyValue<boolean>('accessibleViewIsShown');
|
||||
const accessibleViewKeybinding = this._keybindingService.lookupKeybinding('editor.action.accessibleView');
|
||||
let hint: string | undefined;
|
||||
if (!accessibleViewShowing && accessibleViewKeybinding && this.editor.getOption(EditorOption.inlineCompletionsAccessibilityVerbose)) {
|
||||
hint = localize('showAccessibleViewHint', "Inspect this in the accessible view ({0})", accessibleViewKeybinding.getAriaLabel());
|
||||
}
|
||||
hint ? alert(content + ', ' + hint) : alert(content);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -953,8 +953,8 @@ export class TransposeAction extends EditorAction {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.action.transpose',
|
||||
label: nls.localize('editor.transpose', "Transpose characters around the cursor"),
|
||||
alias: 'Transpose characters around the cursor',
|
||||
label: nls.localize('editor.transpose', "Transpose Characters around the Cursor"),
|
||||
alias: 'Transpose Characters around the Cursor',
|
||||
precondition: EditorContextKeys.writable
|
||||
});
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ export class MoveLinesCommand implements ICommand {
|
||||
const oldIndentation = strings.getLeadingWhitespace(model.getLineContent(movingLineNumber));
|
||||
const newSpaceCnt = movingLineMatchResult + indentUtils.getSpaceCnt(oldIndentation, tabSize);
|
||||
const newIndentation = indentUtils.generateIndent(newSpaceCnt, tabSize, insertSpaces);
|
||||
insertingText = newIndentation + this.trimLeft(movingLineText);
|
||||
insertingText = newIndentation + this.trimStart(movingLineText);
|
||||
} else {
|
||||
// no enter rule matches, let's check indentatin rules then.
|
||||
virtualModel.getLineContent = (lineNumber: number) => {
|
||||
@@ -141,7 +141,7 @@ export class MoveLinesCommand implements ICommand {
|
||||
const oldSpaceCnt = indentUtils.getSpaceCnt(oldIndentation, tabSize);
|
||||
if (newSpaceCnt !== oldSpaceCnt) {
|
||||
const newIndentation = indentUtils.generateIndent(newSpaceCnt, tabSize, insertSpaces);
|
||||
insertingText = newIndentation + this.trimLeft(movingLineText);
|
||||
insertingText = newIndentation + this.trimStart(movingLineText);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -272,7 +272,7 @@ export class MoveLinesCommand implements ICommand {
|
||||
enterPrefix = indentConverter.unshiftIndent(enter.indentation) + enter.appendText;
|
||||
}
|
||||
const movingLineText = model.getLineContent(line);
|
||||
if (this.trimLeft(movingLineText).indexOf(this.trimLeft(enterPrefix)) >= 0) {
|
||||
if (this.trimStart(movingLineText).indexOf(this.trimStart(enterPrefix)) >= 0) {
|
||||
const oldIndentation = strings.getLeadingWhitespace(model.getLineContent(line));
|
||||
let newIndentation = strings.getLeadingWhitespace(enterPrefix);
|
||||
const indentMetadataOfMovelingLine = getIndentMetadata(model, line, this._languageConfigurationService);
|
||||
@@ -354,7 +354,7 @@ export class MoveLinesCommand implements ICommand {
|
||||
return this.parseEnterResult(model, indentConverter, tabSize, line, enter);
|
||||
}
|
||||
|
||||
private trimLeft(str: string) {
|
||||
private trimStart(str: string) {
|
||||
return str.replace(/^\s+/, '');
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ export class ToggleStickyScroll extends Action2 {
|
||||
},
|
||||
menu: [
|
||||
{ id: MenuId.CommandPalette },
|
||||
{ id: MenuId.MenubarViewMenu, group: '5_editor', order: 2 },
|
||||
{ id: MenuId.MenubarAppearanceMenu, group: '4_editor', order: 3 },
|
||||
{ id: MenuId.StickyScrollContext }
|
||||
]
|
||||
});
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IActiveCodeEditor, ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { IActiveCodeEditor, ICodeEditor, MouseTargetType } from 'vs/editor/browser/editorBrowser';
|
||||
import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
|
||||
import { EditorOption, RenderLineNumbersType } from 'vs/editor/common/config/editorOptions';
|
||||
import { StickyScrollWidget, StickyScrollWidgetState } from './stickyScrollWidget';
|
||||
@@ -26,7 +26,7 @@ import { ILanguageConfigurationService } from 'vs/editor/common/languages/langua
|
||||
import { ILanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { StickyRange } from 'vs/editor/contrib/stickyScroll/browser/stickyScrollElement';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { IMouseEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
|
||||
export interface IStickyScrollController {
|
||||
get stickyScrollCandidateProvider(): IStickyLineCandidateProvider;
|
||||
@@ -63,6 +63,8 @@ export class StickyScrollController extends Disposable implements IEditorContrib
|
||||
private _focused = false;
|
||||
private _positionRevealed = false;
|
||||
private _onMouseDown = false;
|
||||
private _endLineNumbers: number[] = [];
|
||||
private _showEndForLine: number | null = null;
|
||||
|
||||
constructor(
|
||||
private readonly _editor: ICodeEditor,
|
||||
@@ -79,7 +81,7 @@ export class StickyScrollController extends Disposable implements IEditorContrib
|
||||
this._register(this._stickyScrollWidget);
|
||||
this._register(this._stickyLineCandidateProvider);
|
||||
|
||||
this._widgetState = new StickyScrollWidgetState([], 0);
|
||||
this._widgetState = new StickyScrollWidgetState([], [], 0);
|
||||
this._readConfiguration();
|
||||
const stickyScrollDomNode = this._stickyScrollWidget.getDomNode();
|
||||
this._register(this._editor.onDidChangeConfiguration(e => {
|
||||
@@ -183,8 +185,19 @@ export class StickyScrollController extends Disposable implements IEditorContrib
|
||||
}
|
||||
|
||||
private _revealPosition(position: IPosition): void {
|
||||
this._reveaInEditor(position, () => this._editor.revealPosition(position));
|
||||
}
|
||||
|
||||
private _revealLineInCenterIfOutsideViewport(position: IPosition): void {
|
||||
this._reveaInEditor(position, () => this._editor.revealLineInCenterIfOutsideViewport(position.lineNumber, ScrollType.Smooth));
|
||||
}
|
||||
|
||||
private _reveaInEditor(position: IPosition, revealFunction: () => void): void {
|
||||
if (this._focused) {
|
||||
this._disposeFocusStickyScrollStore();
|
||||
}
|
||||
this._positionRevealed = true;
|
||||
this._editor.revealPosition(position);
|
||||
revealFunction();
|
||||
this._editor.setSelection(Range.fromPositions(position));
|
||||
this._editor.focus();
|
||||
}
|
||||
@@ -223,35 +236,59 @@ export class StickyScrollController extends Disposable implements IEditorContrib
|
||||
};
|
||||
};
|
||||
|
||||
this._register(this._editor.onMouseUp((mouseEvent: IEditorMouseEvent) => {
|
||||
if (mouseEvent.target.type !== MouseTargetType.OVERLAY_WIDGET || mouseEvent.target.detail !== this._stickyScrollWidget.getId()) {
|
||||
// not hovering over our widget
|
||||
return;
|
||||
}
|
||||
if (mouseEvent.event.ctrlKey || mouseEvent.event.shiftKey || mouseEvent.event.altKey || mouseEvent.event.metaKey) {
|
||||
const stickyScrollWidgetDomNode = this._stickyScrollWidget.getDomNode();
|
||||
this._register(dom.addStandardDisposableListener(stickyScrollWidgetDomNode, dom.EventType.CLICK, (mouseEvent: IMouseEvent) => {
|
||||
if (mouseEvent.ctrlKey || mouseEvent.altKey || mouseEvent.metaKey) {
|
||||
// modifier pressed
|
||||
return;
|
||||
}
|
||||
if (!mouseEvent.event.leftButton) {
|
||||
if (!mouseEvent.leftButton) {
|
||||
// not left click
|
||||
return;
|
||||
}
|
||||
|
||||
let position = this._stickyScrollWidget.getEditorPositionFromNode(mouseEvent.target.element);
|
||||
if (mouseEvent.shiftKey) {
|
||||
// shift click
|
||||
const lineIndex = this._stickyScrollWidget.getStickyLineIndexFromChildDomNode(mouseEvent.target);
|
||||
if (lineIndex === null) {
|
||||
return;
|
||||
}
|
||||
const position = new Position(this._endLineNumbers[lineIndex], 1);
|
||||
this._revealLineInCenterIfOutsideViewport(position);
|
||||
return;
|
||||
}
|
||||
// normal click
|
||||
let position = this._stickyScrollWidget.getEditorPositionFromNode(mouseEvent.target);
|
||||
if (!position) {
|
||||
const lineNumber = this._stickyScrollWidget.getLineNumberFromChildDomNode(mouseEvent.target.element);
|
||||
const lineNumber = this._stickyScrollWidget.getLineNumberFromChildDomNode(mouseEvent.target);
|
||||
if (lineNumber === null) {
|
||||
// not hovering a sticky scroll line
|
||||
return;
|
||||
}
|
||||
position = new Position(lineNumber, 1);
|
||||
}
|
||||
|
||||
if (this._focused) {
|
||||
this._disposeFocusStickyScrollStore();
|
||||
}
|
||||
this._revealPosition(position);
|
||||
}));
|
||||
this._register(dom.addStandardDisposableListener(stickyScrollWidgetDomNode, dom.EventType.MOUSE_MOVE, (mouseEvent: IMouseEvent) => {
|
||||
if (mouseEvent.shiftKey) {
|
||||
const currentEndForLineIndex = this._stickyScrollWidget.getStickyLineIndexFromChildDomNode(mouseEvent.target);
|
||||
if (currentEndForLineIndex === null || this._showEndForLine !== null && this._showEndForLine === currentEndForLineIndex) {
|
||||
return;
|
||||
}
|
||||
this._showEndForLine = currentEndForLineIndex;
|
||||
this._renderStickyScroll();
|
||||
return;
|
||||
}
|
||||
if (this._showEndForLine !== null) {
|
||||
this._showEndForLine = null;
|
||||
this._renderStickyScroll();
|
||||
}
|
||||
}));
|
||||
this._register(dom.addDisposableListener(stickyScrollWidgetDomNode, dom.EventType.MOUSE_LEAVE, (e) => {
|
||||
if (this._showEndForLine !== null) {
|
||||
this._showEndForLine = null;
|
||||
this._renderStickyScroll();
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(gesture.onMouseMoveOrRelevantKeyDown(([mouseEvent, _keyboardEvent]) => {
|
||||
const mouseTarget = getMouseEventTarget(mouseEvent);
|
||||
@@ -344,18 +381,25 @@ export class StickyScrollController extends Disposable implements IEditorContrib
|
||||
this._editor.addOverlayWidget(this._stickyScrollWidget);
|
||||
this._sessionStore.add(this._editor.onDidScrollChange((e) => {
|
||||
if (e.scrollTopChanged) {
|
||||
this._showEndForLine = null;
|
||||
this._renderStickyScroll();
|
||||
}
|
||||
}));
|
||||
this._sessionStore.add(this._editor.onDidLayoutChange(() => this._onDidResize()));
|
||||
this._sessionStore.add(this._editor.onDidChangeModelTokens((e) => this._onTokensChange(e)));
|
||||
this._sessionStore.add(this._stickyLineCandidateProvider.onDidChangeStickyScroll(() => this._renderStickyScroll()));
|
||||
this._sessionStore.add(this._stickyLineCandidateProvider.onDidChangeStickyScroll(() => {
|
||||
this._showEndForLine = null;
|
||||
this._renderStickyScroll();
|
||||
}));
|
||||
this._enabled = true;
|
||||
}
|
||||
|
||||
const lineNumberOption = this._editor.getOption(EditorOption.lineNumbers);
|
||||
if (lineNumberOption.renderType === RenderLineNumbersType.Relative) {
|
||||
this._sessionStore.add(this._editor.onDidChangeCursorPosition(() => this._renderStickyScroll()));
|
||||
this._sessionStore.add(this._editor.onDidChangeCursorPosition(() => {
|
||||
this._showEndForLine = null;
|
||||
this._renderStickyScroll();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,7 +436,7 @@ export class StickyScrollController extends Disposable implements IEditorContrib
|
||||
const stickyLineVersion = this._stickyLineCandidateProvider.getVersionId();
|
||||
if (stickyLineVersion === undefined || stickyLineVersion === model.getVersionId()) {
|
||||
this._widgetState = this.findScrollWidgetState();
|
||||
this._stickyScrollVisibleContextKey.set(!(this._widgetState.lineNumbers.length === 0));
|
||||
this._stickyScrollVisibleContextKey.set(!(this._widgetState.startLineNumbers.length === 0));
|
||||
|
||||
if (!this._focused) {
|
||||
this._stickyScrollWidget.setState(this._widgetState);
|
||||
@@ -430,7 +474,8 @@ export class StickyScrollController extends Disposable implements IEditorContrib
|
||||
const maxNumberStickyLines = Math.min(this._maxStickyLines, this._editor.getOption(EditorOption.stickyScroll).maxLineCount);
|
||||
const scrollTop: number = this._editor.getScrollTop();
|
||||
let lastLineRelativePosition: number = 0;
|
||||
const lineNumbers: number[] = [];
|
||||
const startLineNumbers: number[] = [];
|
||||
const endLineNumbers: number[] = [];
|
||||
const arrayVisibleRanges = this._editor.getVisibleRanges();
|
||||
if (arrayVisibleRanges.length !== 0) {
|
||||
const fullVisibleRange = new StickyRange(arrayVisibleRanges[0].startLineNumber, arrayVisibleRanges[arrayVisibleRanges.length - 1].endLineNumber);
|
||||
@@ -448,20 +493,23 @@ export class StickyScrollController extends Disposable implements IEditorContrib
|
||||
const bottomOfEndLine = this._editor.getBottomForLineNumber(end) - scrollTop;
|
||||
|
||||
if (topOfElementAtDepth > topOfEndLine && topOfElementAtDepth <= bottomOfEndLine) {
|
||||
lineNumbers.push(start);
|
||||
startLineNumbers.push(start);
|
||||
endLineNumbers.push(end + 1);
|
||||
lastLineRelativePosition = bottomOfEndLine - bottomOfElementAtDepth;
|
||||
break;
|
||||
}
|
||||
else if (bottomOfElementAtDepth > bottomOfBeginningLine && bottomOfElementAtDepth <= bottomOfEndLine) {
|
||||
lineNumbers.push(start);
|
||||
startLineNumbers.push(start);
|
||||
endLineNumbers.push(end + 1);
|
||||
}
|
||||
if (lineNumbers.length === maxNumberStickyLines) {
|
||||
if (startLineNumbers.length === maxNumberStickyLines) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new StickyScrollWidgetState(lineNumbers, lastLineRelativePosition);
|
||||
this._endLineNumbers = endLineNumbers;
|
||||
return new StickyScrollWidgetState(startLineNumbers, endLineNumbers, lastLineRelativePosition, this._showEndForLine);
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
|
||||
import { OutlineElement, OutlineGroup, OutlineModel } from 'vs/editor/contrib/documentSymbols/browser/outlineModel';
|
||||
@@ -45,18 +45,20 @@ export interface IStickyModelProvider {
|
||||
update(textModel: ITextModel, textModelVersionId: number, token: CancellationToken): Promise<StickyModel | null>;
|
||||
}
|
||||
|
||||
export class StickyModelProvider implements IStickyModelProvider {
|
||||
export class StickyModelProvider extends Disposable implements IStickyModelProvider {
|
||||
|
||||
private _modelProviders: IStickyModelCandidateProvider<any>[] = [];
|
||||
private _modelPromise: CancelablePromise<any | null> | null = null;
|
||||
private _updateScheduler: Delayer<StickyModel | null> = new Delayer<StickyModel | null>(300);
|
||||
private readonly _store: DisposableStore;
|
||||
private _updateScheduler: Delayer<StickyModel | null> = this._register(new Delayer<StickyModel | null>(300));
|
||||
private readonly _updateOperation: DisposableStore = this._register(new DisposableStore());
|
||||
|
||||
constructor(
|
||||
private readonly _editor: ICodeEditor,
|
||||
@ILanguageConfigurationService readonly _languageConfigurationService: ILanguageConfigurationService,
|
||||
@ILanguageFeaturesService readonly _languageFeaturesService: ILanguageFeaturesService,
|
||||
defaultModel: string) {
|
||||
defaultModel: string
|
||||
) {
|
||||
super();
|
||||
|
||||
const stickyModelFromCandidateOutlineProvider = new StickyModelFromCandidateOutlineProvider(_languageFeaturesService);
|
||||
const stickyModelFromSyntaxFoldingProvider = new StickyModelFromCandidateSyntaxFoldingProvider(this._editor, _languageFeaturesService);
|
||||
@@ -76,8 +78,6 @@ export class StickyModelProvider implements IStickyModelProvider {
|
||||
this._modelProviders.push(stickyModelFromIndentationFoldingProvider);
|
||||
break;
|
||||
}
|
||||
|
||||
this._store = new DisposableStore();
|
||||
}
|
||||
|
||||
private _cancelModelPromise(): void {
|
||||
@@ -89,11 +89,11 @@ export class StickyModelProvider implements IStickyModelProvider {
|
||||
|
||||
public async update(textModel: ITextModel, textModelVersionId: number, token: CancellationToken): Promise<StickyModel | null> {
|
||||
|
||||
this._store.clear();
|
||||
this._store.add({
|
||||
this._updateOperation.clear();
|
||||
this._updateOperation.add({
|
||||
dispose: () => {
|
||||
this._cancelModelPromise();
|
||||
this._updateScheduler?.cancel();
|
||||
this._updateScheduler.cancel();
|
||||
}
|
||||
});
|
||||
this._cancelModelPromise();
|
||||
@@ -113,7 +113,7 @@ export class StickyModelProvider implements IStickyModelProvider {
|
||||
}
|
||||
switch (status) {
|
||||
case Status.CANCELED:
|
||||
this._store.clear();
|
||||
this._updateOperation.clear();
|
||||
return null;
|
||||
case Status.VALID:
|
||||
return modelProvider.stickyModel;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user