mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-11 17:18:13 +01:00
Merge branch 'main' into aiday/differentLightBulbDependingOnCodeAction
This commit is contained in:
@@ -31,6 +31,16 @@ const extensions = [
|
||||
workspaceFolder: path.join(os.tmpdir(), `nbout-${Math.floor(Math.random() * 100000)}`),
|
||||
mocha: { timeout: 60_000 }
|
||||
},
|
||||
{
|
||||
label: 'vscode-colorize-tests',
|
||||
workspaceFolder: `extensions/vscode-colorize-tests/test`,
|
||||
mocha: { timeout: 60_000 }
|
||||
},
|
||||
{
|
||||
label: 'configuration-editing',
|
||||
workspaceFolder: path.join(os.tmpdir(), `confeditout-${Math.floor(Math.random() * 100000)}`),
|
||||
mocha: { timeout: 60_000 }
|
||||
},
|
||||
{
|
||||
label: 'github-authentication',
|
||||
workspaceFolder: path.join(os.tmpdir(), `msft-auth-${Math.floor(Math.random() * 100000)}`),
|
||||
|
||||
Vendored
+1
-4
@@ -166,8 +166,5 @@
|
||||
"src/vs/workbench/api/common/extHostTypes.ts"
|
||||
],
|
||||
// Temporarily enabled for self-hosting
|
||||
"terminal.integrated.stickyScroll.enabled": true,
|
||||
// Temporarily enabled for self-hosting
|
||||
"scm.showIncomingChanges": "always",
|
||||
"scm.showOutgoingChanges": "always",
|
||||
"terminal.integrated.stickyScroll.enabled": true
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -11,12 +11,13 @@ import { pipeline } from 'node:stream/promises';
|
||||
import * as yauzl from 'yauzl';
|
||||
import * as crypto from 'crypto';
|
||||
import { retry } from './retry';
|
||||
import { BlobServiceClient, BlockBlobParallelUploadOptions, StoragePipelineOptions, StorageRetryPolicyType } from '@azure/storage-blob';
|
||||
import { BlobServiceClient, BlockBlobParallelUploadOptions, StorageRetryPolicyType } from '@azure/storage-blob';
|
||||
import * as mime from 'mime';
|
||||
import { CosmosClient } from '@azure/cosmos';
|
||||
import { ClientSecretCredential } from '@azure/identity';
|
||||
import * as cp from 'child_process';
|
||||
import * as os from 'os';
|
||||
import { Worker, isMainThread, workerData } from 'node:worker_threads';
|
||||
|
||||
function e(name: string): string {
|
||||
const result = process.env[name];
|
||||
@@ -48,49 +49,6 @@ class Temp {
|
||||
}
|
||||
}
|
||||
|
||||
export class Limiter {
|
||||
|
||||
private _size = 0;
|
||||
private runningPromises: number;
|
||||
private readonly maxDegreeOfParalellism: number;
|
||||
private readonly outstandingPromises: { factory: () => Promise<any>; c: (v: any) => void; e: (err: Error) => void }[];
|
||||
|
||||
constructor(maxDegreeOfParalellism: number) {
|
||||
this.maxDegreeOfParalellism = maxDegreeOfParalellism;
|
||||
this.outstandingPromises = [];
|
||||
this.runningPromises = 0;
|
||||
}
|
||||
|
||||
queue<T>(factory: () => Promise<T>): Promise<T> {
|
||||
this._size++;
|
||||
|
||||
return new Promise<T>((c, e) => {
|
||||
this.outstandingPromises.push({ factory, c, e });
|
||||
this.consume();
|
||||
});
|
||||
}
|
||||
|
||||
private consume(): void {
|
||||
while (this.outstandingPromises.length && this.runningPromises < this.maxDegreeOfParalellism) {
|
||||
const iLimitedTask = this.outstandingPromises.shift()!;
|
||||
this.runningPromises++;
|
||||
|
||||
const promise = iLimitedTask.factory();
|
||||
promise.then(iLimitedTask.c, iLimitedTask.e);
|
||||
promise.then(() => this.consumed(), () => this.consumed());
|
||||
}
|
||||
}
|
||||
|
||||
private consumed(): void {
|
||||
this._size--;
|
||||
this.runningPromises--;
|
||||
|
||||
if (this.outstandingPromises.length > 0) {
|
||||
this.consume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface RequestOptions {
|
||||
readonly body?: string;
|
||||
}
|
||||
@@ -196,8 +154,6 @@ interface ReleaseDetailsResult {
|
||||
|
||||
class ESRPClient {
|
||||
|
||||
private static Limiter = new Limiter(1);
|
||||
|
||||
private readonly authPath: string;
|
||||
|
||||
constructor(
|
||||
@@ -232,10 +188,8 @@ class ESRPClient {
|
||||
version: string,
|
||||
filePath: string
|
||||
): Promise<Release> {
|
||||
const submitReleaseResult = await ESRPClient.Limiter.queue(async () => {
|
||||
this.log(`Submitting release for ${version}: ${filePath}`);
|
||||
return await this.SubmitRelease(version, filePath);
|
||||
});
|
||||
this.log(`Submitting release for ${version}: ${filePath}`);
|
||||
const submitReleaseResult = await this.SubmitRelease(version, filePath);
|
||||
|
||||
if (submitReleaseResult.submissionResponse.statusCode !== 'pass') {
|
||||
throw new Error(`Unexpected status code: ${submitReleaseResult.submissionResponse.statusCode}`);
|
||||
@@ -399,7 +353,6 @@ async function releaseAndProvision(
|
||||
const credential = new ClientSecretCredential(provisionTenantId, provisionAADUsername, provisionAADPassword);
|
||||
const accessToken = await credential.getToken(['https://microsoft.onmicrosoft.com/DS.Provisioning.WebApi/.default']);
|
||||
const service = new ProvisionService(log, accessToken.token);
|
||||
|
||||
await service.provision(release.releaseId, release.fileId, fileName);
|
||||
|
||||
return result;
|
||||
@@ -674,17 +627,12 @@ function getRealType(type: string) {
|
||||
}
|
||||
}
|
||||
|
||||
const azureLimiter = new Limiter(1);
|
||||
const mooncakeLimiter = new Limiter(1);
|
||||
|
||||
async function uploadAssetLegacy(log: (...args: any[]) => void, quality: string, commit: string, filePath: string): Promise<{ assetUrl: string; mooncakeUrl: string }> {
|
||||
async function uploadAssetLegacy(log: (...args: any[]) => void, quality: string, commit: string, filePath: string): Promise<string> {
|
||||
const fileName = path.basename(filePath);
|
||||
const blobName = commit + '/' + fileName;
|
||||
|
||||
const storagePipelineOptions: StoragePipelineOptions = { retryOptions: { retryPolicyType: StorageRetryPolicyType.EXPONENTIAL, maxTries: 6, tryTimeoutInMs: 10 * 60 * 1000 } };
|
||||
|
||||
const credential = new ClientSecretCredential(e('AZURE_TENANT_ID'), e('AZURE_CLIENT_ID'), e('AZURE_CLIENT_SECRET'));
|
||||
const blobServiceClient = new BlobServiceClient(`https://vscode.blob.core.windows.net`, credential, storagePipelineOptions);
|
||||
const blobServiceClient = new BlobServiceClient(`https://vscode.blob.core.windows.net`, credential, { retryOptions: { retryPolicyType: StorageRetryPolicyType.FIXED, tryTimeoutInMs: 2 * 60 * 1000 } });
|
||||
const containerClient = blobServiceClient.getContainerClient(quality);
|
||||
const blobClient = containerClient.getBlockBlobClient(blobName);
|
||||
|
||||
@@ -696,121 +644,39 @@ async function uploadAssetLegacy(log: (...args: any[]) => void, quality: string,
|
||||
}
|
||||
};
|
||||
|
||||
const uploadPromises: Promise<void>[] = [];
|
||||
log(`Checking for blob in Azure...`);
|
||||
|
||||
uploadPromises.push((async (): Promise<void> => {
|
||||
log(`Checking for blob in Azure...`);
|
||||
|
||||
if (await retry(() => blobClient.exists())) {
|
||||
throw new Error(`Blob ${quality}, ${blobName} already exists, not publishing again.`);
|
||||
} else {
|
||||
await retry(attempt => azureLimiter.queue(async () => {
|
||||
log(`Uploading blobs to Azure storage (attempt ${attempt})...`);
|
||||
await blobClient.uploadFile(filePath, blobOptions);
|
||||
log('Blob successfully uploaded to Azure storage.');
|
||||
}));
|
||||
}
|
||||
})());
|
||||
|
||||
const shouldUploadToMooncake = /true/i.test(e('VSCODE_PUBLISH_TO_MOONCAKE'));
|
||||
|
||||
if (shouldUploadToMooncake) {
|
||||
const mooncakeCredential = new ClientSecretCredential(e('AZURE_MOONCAKE_TENANT_ID'), e('AZURE_MOONCAKE_CLIENT_ID'), e('AZURE_MOONCAKE_CLIENT_SECRET'));
|
||||
const mooncakeBlobServiceClient = new BlobServiceClient(`https://vscode.blob.core.chinacloudapi.cn`, mooncakeCredential, storagePipelineOptions);
|
||||
const mooncakeContainerClient = mooncakeBlobServiceClient.getContainerClient(quality);
|
||||
const mooncakeBlobClient = mooncakeContainerClient.getBlockBlobClient(blobName);
|
||||
|
||||
uploadPromises.push((async (): Promise<void> => {
|
||||
log(`Checking for blob in Mooncake Azure...`);
|
||||
|
||||
if (await retry(() => mooncakeBlobClient.exists())) {
|
||||
throw new Error(`Mooncake Blob ${quality}, ${blobName} already exists, not publishing again.`);
|
||||
} else {
|
||||
await retry(attempt => mooncakeLimiter.queue(async () => {
|
||||
log(`Uploading blobs to Mooncake Azure storage (attempt ${attempt})...`);
|
||||
await mooncakeBlobClient.uploadFile(filePath, blobOptions);
|
||||
log('Blob successfully uploaded to Mooncake Azure storage.');
|
||||
}));
|
||||
}
|
||||
})());
|
||||
}
|
||||
|
||||
const promiseResults = await Promise.allSettled(uploadPromises);
|
||||
const rejectedPromiseResults = promiseResults.filter(result => result.status === 'rejected') as PromiseRejectedResult[];
|
||||
|
||||
if (rejectedPromiseResults.length === 0) {
|
||||
log('All blobs successfully uploaded.');
|
||||
} else if (rejectedPromiseResults[0]?.reason?.message?.includes('already exists')) {
|
||||
log(rejectedPromiseResults[0].reason.message);
|
||||
log('Some blobs successfully uploaded.');
|
||||
if (await blobClient.exists()) {
|
||||
log(`Blob ${quality}, ${blobName} already exists, not publishing again.`);
|
||||
} else {
|
||||
// eslint-disable-next-line no-throw-literal
|
||||
throw rejectedPromiseResults[0]?.reason;
|
||||
log(`Uploading blobs to Azure storage...`);
|
||||
await blobClient.uploadFile(filePath, blobOptions);
|
||||
log('Blob successfully uploaded to Azure storage.');
|
||||
}
|
||||
|
||||
const assetUrl = `${e('AZURE_CDN_URL')}/${quality}/${blobName}`;
|
||||
const blobPath = new URL(assetUrl).pathname;
|
||||
const mooncakeUrl = `${e('MOONCAKE_CDN_URL')}${blobPath}`;
|
||||
|
||||
return { assetUrl, mooncakeUrl };
|
||||
return `${e('AZURE_CDN_URL')}/${quality}/${blobName}`;
|
||||
}
|
||||
|
||||
const downloadLimiter = new Limiter(5);
|
||||
const cosmosLimiter = new Limiter(1);
|
||||
|
||||
async function processArtifact(artifact: Artifact): Promise<void> {
|
||||
async function processArtifact(artifact: Artifact, artifactFilePath: string): Promise<void> {
|
||||
const log = (...args: any[]) => console.log(`[${artifact.name}]`, ...args);
|
||||
const match = /^vscode_(?<product>[^_]+)_(?<os>[^_]+)_(?<arch>[^_]+)_(?<unprocessedType>[^_]+)$/.exec(artifact.name);
|
||||
|
||||
if (!match) {
|
||||
throw new Error(`Invalid artifact name: ${artifact.name}`);
|
||||
}
|
||||
|
||||
const { product, os, arch, unprocessedType } = match.groups!;
|
||||
const log = (...args: any[]) => console.log(`[${product} ${os} ${arch} ${unprocessedType}]`, ...args);
|
||||
const start = Date.now();
|
||||
|
||||
const filePath = await retry(async attempt => {
|
||||
const artifactZipPath = path.join(e('AGENT_TEMPDIRECTORY'), `${artifact.name}.zip`);
|
||||
|
||||
const start = Date.now();
|
||||
log(`Downloading ${artifact.resource.downloadUrl} (attempt ${attempt})...`);
|
||||
|
||||
try {
|
||||
await downloadLimiter.queue(() => downloadArtifact(artifact, artifactZipPath));
|
||||
} catch (err) {
|
||||
log(`Download failed: ${err.message}`);
|
||||
throw err;
|
||||
}
|
||||
|
||||
const archiveSize = fs.statSync(artifactZipPath).size;
|
||||
const downloadDurationS = (Date.now() - start) / 1000;
|
||||
const downloadSpeedKBS = Math.round((archiveSize / 1024) / downloadDurationS);
|
||||
log(`Successfully downloaded ${artifact.resource.downloadUrl} after ${Math.floor(downloadDurationS)} seconds (${downloadSpeedKBS} KB/s).`);
|
||||
|
||||
const filePath = await unzip(artifactZipPath, e('AGENT_TEMPDIRECTORY'));
|
||||
const artifactSize = fs.statSync(filePath).size;
|
||||
|
||||
if (artifactSize !== Number(artifact.resource.properties.artifactsize)) {
|
||||
log(`Artifact size mismatch. Expected ${artifact.resource.properties.artifactsize}. Actual ${artifactSize}`);
|
||||
throw new Error(`Artifact size mismatch.`);
|
||||
}
|
||||
|
||||
return filePath;
|
||||
});
|
||||
|
||||
log(`Successfully downloaded and extracted after ${(Date.now() - start) / 1000} seconds.`);
|
||||
|
||||
// getPlatform needs the unprocessedType
|
||||
const quality = e('VSCODE_QUALITY');
|
||||
const commit = e('BUILD_SOURCEVERSION');
|
||||
const { product, os, arch, unprocessedType } = match.groups!;
|
||||
const platform = getPlatform(product, os, arch, unprocessedType);
|
||||
const type = getRealType(unprocessedType);
|
||||
const size = fs.statSync(filePath).size;
|
||||
const stream = fs.createReadStream(filePath);
|
||||
const size = fs.statSync(artifactFilePath).size;
|
||||
const stream = fs.createReadStream(artifactFilePath);
|
||||
const [sha1hash, sha256hash] = await Promise.all([hashStream('sha1', stream), hashStream('sha256', stream)]);
|
||||
|
||||
const [{ assetUrl, mooncakeUrl }, prssUrl] = await Promise.all([
|
||||
uploadAssetLegacy(log, quality, commit, filePath),
|
||||
const [cdnSettledResult, prssSettledResult] = await Promise.allSettled([
|
||||
uploadAssetLegacy(log, quality, commit, artifactFilePath),
|
||||
releaseAndProvision(
|
||||
log,
|
||||
e('RELEASE_TENANT_ID'),
|
||||
@@ -822,27 +688,46 @@ async function processArtifact(artifact: Artifact): Promise<void> {
|
||||
e('PROVISION_AAD_PASSWORD'),
|
||||
commit,
|
||||
quality,
|
||||
filePath
|
||||
artifactFilePath
|
||||
)
|
||||
]);
|
||||
|
||||
const asset: Asset = { platform, type, url: assetUrl, hash: sha1hash, mooncakeUrl, prssUrl, sha256hash, size, supportsFastUpdate: true };
|
||||
if (cdnSettledResult.status === 'rejected') {
|
||||
throw cdnSettledResult.reason;
|
||||
} else if (prssSettledResult.status === 'rejected') { // TODO@joaomoreno, let's temporarily ignore these errors
|
||||
console.error(prssSettledResult.reason);
|
||||
}
|
||||
|
||||
const assetUrl = cdnSettledResult.value;
|
||||
const prssUrl = prssSettledResult.status === 'fulfilled' ? prssSettledResult.value : undefined;
|
||||
|
||||
const asset: Asset = { platform, type, url: assetUrl, hash: sha1hash, mooncakeUrl: prssUrl, prssUrl, sha256hash, size, supportsFastUpdate: true };
|
||||
log('Creating asset...', JSON.stringify(asset));
|
||||
|
||||
await retry(async (attempt) => {
|
||||
await cosmosLimiter.queue(async () => {
|
||||
log(`Creating asset in Cosmos DB (attempt ${attempt})...`);
|
||||
const aadCredentials = new ClientSecretCredential(e('AZURE_TENANT_ID'), e('AZURE_CLIENT_ID'), e('AZURE_CLIENT_SECRET'));
|
||||
const client = new CosmosClient({ endpoint: e('AZURE_DOCUMENTDB_ENDPOINT'), aadCredentials });
|
||||
const scripts = client.database('builds').container(quality).scripts;
|
||||
await scripts.storedProcedure('createAsset').execute('', [commit, asset, true]);
|
||||
});
|
||||
log(`Creating asset in Cosmos DB (attempt ${attempt})...`);
|
||||
const aadCredentials = new ClientSecretCredential(e('AZURE_TENANT_ID'), e('AZURE_CLIENT_ID'), e('AZURE_CLIENT_SECRET'));
|
||||
const client = new CosmosClient({ endpoint: e('AZURE_DOCUMENTDB_ENDPOINT'), aadCredentials });
|
||||
const scripts = client.database('builds').container(quality).scripts;
|
||||
await scripts.storedProcedure('createAsset').execute('', [commit, asset, true]);
|
||||
});
|
||||
|
||||
log('Asset successfully created');
|
||||
}
|
||||
|
||||
// It is VERY important that we don't download artifacts too much too fast from AZDO.
|
||||
// AZDO throttles us SEVERELY if we do. Not just that, but they also close open
|
||||
// sockets, so the whole things turns to a grinding halt. So, downloading and extracting
|
||||
// happens serially in the main thread, making the downloads are spaced out
|
||||
// properly. For each extracted artifact, we spawn a worker thread to upload it to
|
||||
// the CDN and finally update the build in Cosmos DB.
|
||||
async function main() {
|
||||
if (!isMainThread) {
|
||||
const { artifact, artifactFilePath } = workerData;
|
||||
await processArtifact(artifact, artifactFilePath);
|
||||
return;
|
||||
}
|
||||
|
||||
const done = new State();
|
||||
const processing = new Set<string>();
|
||||
|
||||
@@ -857,6 +742,7 @@ async function main() {
|
||||
if (e('VSCODE_BUILD_STAGE_MACOS') === 'True') { stages.add('macOS'); }
|
||||
if (e('VSCODE_BUILD_STAGE_WEB') === 'True') { stages.add('Web'); }
|
||||
|
||||
let resultPromise = Promise.resolve<PromiseSettledResult<void>[]>([]);
|
||||
const operations: { name: string; operation: Promise<void> }[] = [];
|
||||
|
||||
while (true) {
|
||||
@@ -880,15 +766,49 @@ async function main() {
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`Found new artifact: ${artifact.name}`);
|
||||
console.log(`[${artifact.name}] Found new artifact`);
|
||||
|
||||
const artifactZipPath = path.join(e('AGENT_TEMPDIRECTORY'), `${artifact.name}.zip`);
|
||||
|
||||
await retry(async (attempt) => {
|
||||
const start = Date.now();
|
||||
console.log(`[${artifact.name}] Downloading (attempt ${attempt})...`);
|
||||
await downloadArtifact(artifact, artifactZipPath);
|
||||
const archiveSize = fs.statSync(artifactZipPath).size;
|
||||
const downloadDurationS = (Date.now() - start) / 1000;
|
||||
const downloadSpeedKBS = Math.round((archiveSize / 1024) / downloadDurationS);
|
||||
console.log(`[${artifact.name}] Successfully downloaded after ${Math.floor(downloadDurationS)} seconds(${downloadSpeedKBS} KB/s).`);
|
||||
});
|
||||
|
||||
const artifactFilePath = await unzip(artifactZipPath, e('AGENT_TEMPDIRECTORY'));
|
||||
const artifactSize = fs.statSync(artifactFilePath).size;
|
||||
|
||||
if (artifactSize !== Number(artifact.resource.properties.artifactsize)) {
|
||||
console.log(`[${artifact.name}] Artifact size mismatch.Expected ${artifact.resource.properties.artifactsize}. Actual ${artifactSize} `);
|
||||
throw new Error(`Artifact size mismatch.`);
|
||||
}
|
||||
|
||||
processing.add(artifact.name);
|
||||
const operation = processArtifact(artifact).then(() => {
|
||||
const promise = new Promise<void>((resolve, reject) => {
|
||||
const worker = new Worker(__filename, { workerData: { artifact, artifactFilePath } });
|
||||
worker.on('error', reject);
|
||||
worker.on('exit', code => {
|
||||
if (code === 0) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`[${artifact.name}] Worker stopped with exit code ${code}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const operation = promise.then(() => {
|
||||
processing.delete(artifact.name);
|
||||
done.add(artifact.name);
|
||||
console.log(`\u2705 ${artifact.name}`);
|
||||
console.log(`\u2705 ${artifact.name} `);
|
||||
});
|
||||
|
||||
operations.push({ name: artifact.name, operation });
|
||||
resultPromise = Promise.allSettled(operations.map(o => o.operation));
|
||||
}
|
||||
|
||||
await new Promise(c => setTimeout(c, 10_000));
|
||||
@@ -902,7 +822,7 @@ async function main() {
|
||||
console.log('Artifacts in progress:', artifactsInProgress.map(a => a.name).join(', '));
|
||||
}
|
||||
|
||||
const results = await Promise.allSettled(operations.map(o => o.operation));
|
||||
const results = await resultPromise;
|
||||
|
||||
for (let i = 0; i < operations.length; i++) {
|
||||
const result = results[i];
|
||||
|
||||
@@ -76,10 +76,6 @@ parameters:
|
||||
displayName: "Publish to builds.code.visualstudio.com"
|
||||
type: boolean
|
||||
default: true
|
||||
- name: VSCODE_PUBLISH_TO_MOONCAKE
|
||||
displayName: "Publish to Azure China"
|
||||
type: boolean
|
||||
default: true
|
||||
- name: VSCODE_RELEASE
|
||||
displayName: "Release build if successful"
|
||||
type: boolean
|
||||
@@ -116,8 +112,6 @@ variables:
|
||||
value: ${{ in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI') }}
|
||||
- name: VSCODE_PUBLISH
|
||||
value: ${{ and(eq(parameters.VSCODE_PUBLISH, true), eq(variables.VSCODE_CIBUILD, false), eq(parameters.VSCODE_COMPILE_ONLY, false)) }}
|
||||
- name: VSCODE_PUBLISH_TO_MOONCAKE
|
||||
value: ${{ eq(parameters.VSCODE_PUBLISH_TO_MOONCAKE, true) }}
|
||||
- name: VSCODE_SCHEDULEDBUILD
|
||||
value: ${{ eq(variables['Build.Reason'], 'Schedule') }}
|
||||
- name: VSCODE_7PM_BUILD
|
||||
@@ -138,8 +132,6 @@ variables:
|
||||
value: https://az764295.vo.msecnd.net
|
||||
- name: AZURE_DOCUMENTDB_ENDPOINT
|
||||
value: https://vscode.documents.azure.com:443/
|
||||
- name: MOONCAKE_CDN_URL
|
||||
value: https://vscode.cdn.azure.cn
|
||||
- name: VSCODE_MIXIN_REPO
|
||||
value: microsoft/vscode-distro
|
||||
- name: skipComponentGovernanceDetection
|
||||
|
||||
@@ -49,18 +49,6 @@ steps:
|
||||
Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId"
|
||||
Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_SECRET;issecret=true]$env:servicePrincipalKey"
|
||||
|
||||
- task: AzureCLI@2
|
||||
displayName: Fetch Mooncake secrets
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-mooncake-subscription"
|
||||
scriptType: pscore
|
||||
scriptLocation: inlineScript
|
||||
addSpnToEnvironment: true
|
||||
inlineScript: |
|
||||
Write-Host "##vso[task.setvariable variable=AZURE_MOONCAKE_TENANT_ID]$env:tenantId"
|
||||
Write-Host "##vso[task.setvariable variable=AZURE_MOONCAKE_CLIENT_ID]$env:servicePrincipalId"
|
||||
Write-Host "##vso[task.setvariable variable=AZURE_MOONCAKE_CLIENT_SECRET;issecret=true]$env:servicePrincipalKey"
|
||||
|
||||
- pwsh: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
|
||||
@@ -102,9 +90,6 @@ steps:
|
||||
AZURE_TENANT_ID: "$(AZURE_TENANT_ID)"
|
||||
AZURE_CLIENT_ID: "$(AZURE_CLIENT_ID)"
|
||||
AZURE_CLIENT_SECRET: "$(AZURE_CLIENT_SECRET)"
|
||||
AZURE_MOONCAKE_TENANT_ID: "$(AZURE_MOONCAKE_TENANT_ID)"
|
||||
AZURE_MOONCAKE_CLIENT_ID: "$(AZURE_MOONCAKE_CLIENT_ID)"
|
||||
AZURE_MOONCAKE_CLIENT_SECRET: "$(AZURE_MOONCAKE_CLIENT_SECRET)"
|
||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||
RELEASE_TENANT_ID: "$(PRSS_RELEASE_TENANT_ID)"
|
||||
RELEASE_CLIENT_ID: "$(PRSS_RELEASE_CLIENT_ID)"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"publisher": "vscode",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"icon": "media/icon.png",
|
||||
"icon": "media/icon.png",
|
||||
"engines": {
|
||||
"vscode": "^1.57.0"
|
||||
},
|
||||
@@ -58,10 +58,10 @@
|
||||
"command": "ipynb.cleanInvalidImageAttachment",
|
||||
"title": "%cleanInvalidImageAttachment.title%"
|
||||
},
|
||||
{
|
||||
"command": "notebook.cellOutput.copy",
|
||||
"title": "%copyCellOutput.title%"
|
||||
}
|
||||
{
|
||||
"command": "notebook.cellOutput.copy",
|
||||
"title": "%copyCellOutput.title%"
|
||||
}
|
||||
],
|
||||
"notebooks": [
|
||||
{
|
||||
@@ -105,12 +105,12 @@
|
||||
"when": "false"
|
||||
}
|
||||
],
|
||||
"webview/context": [
|
||||
{
|
||||
"command": "notebook.cellOutput.copy",
|
||||
"when": "webviewId == 'notebook.output' && webviewSection == 'image'"
|
||||
}
|
||||
]
|
||||
"webview/context": [
|
||||
{
|
||||
"command": "notebook.cellOutput.copy",
|
||||
"when": "webviewId == 'notebook.output' && webviewSection == 'image'"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
@@ -120,13 +120,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@enonic/fnv-plus": "^1.3.0",
|
||||
"detect-indent": "^6.0.0",
|
||||
"uuid": "^8.3.2"
|
||||
"detect-indent": "^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jupyterlab/nbformat": "^3.2.9",
|
||||
"@types/markdown-it": "12.2.3",
|
||||
"@types/uuid": "^8.3.1"
|
||||
"@types/markdown-it": "12.2.3"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ExtensionContext, NotebookDocument, NotebookDocumentChangeEvent, NotebookEdit, workspace, WorkspaceEdit } from 'vscode';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { getCellMetadata } from './serializers';
|
||||
import { CellMetadata } from './common';
|
||||
import { getNotebookMetadata } from './notebookSerializer';
|
||||
@@ -57,7 +56,7 @@ function generateCellId(notebook: NotebookDocument) {
|
||||
while (true) {
|
||||
// Details of the id can be found here https://jupyter.org/enhancement-proposals/62-cell-id/cell-id.html#adding-an-id-field,
|
||||
// & here https://jupyter.org/enhancement-proposals/62-cell-id/cell-id.html#updating-older-formats
|
||||
const id = uuid().replace(/-/g, '').substring(0, 8);
|
||||
const id = generateUuid().replace(/-/g, '').substring(0, 8);
|
||||
let duplicate = false;
|
||||
for (let index = 0; index < notebook.cellCount; index++) {
|
||||
const cell = notebook.cellAt(index);
|
||||
@@ -75,3 +74,56 @@ function generateCellId(notebook: NotebookDocument) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copied from src/vs/base/common/uuid.ts
|
||||
*/
|
||||
function generateUuid() {
|
||||
// use `randomValues` if possible
|
||||
function getRandomValues(bucket: Uint8Array): Uint8Array {
|
||||
for (let i = 0; i < bucket.length; i++) {
|
||||
bucket[i] = Math.floor(Math.random() * 256);
|
||||
}
|
||||
return bucket;
|
||||
}
|
||||
|
||||
// prep-work
|
||||
const _data = new Uint8Array(16);
|
||||
const _hex: string[] = [];
|
||||
for (let i = 0; i < 256; i++) {
|
||||
_hex.push(i.toString(16).padStart(2, '0'));
|
||||
}
|
||||
|
||||
// get data
|
||||
getRandomValues(_data);
|
||||
|
||||
// set version bits
|
||||
_data[6] = (_data[6] & 0x0f) | 0x40;
|
||||
_data[8] = (_data[8] & 0x3f) | 0x80;
|
||||
|
||||
// print as string
|
||||
let i = 0;
|
||||
let result = '';
|
||||
result += _hex[_data[i++]];
|
||||
result += _hex[_data[i++]];
|
||||
result += _hex[_data[i++]];
|
||||
result += _hex[_data[i++]];
|
||||
result += '-';
|
||||
result += _hex[_data[i++]];
|
||||
result += _hex[_data[i++]];
|
||||
result += '-';
|
||||
result += _hex[_data[i++]];
|
||||
result += _hex[_data[i++]];
|
||||
result += '-';
|
||||
result += _hex[_data[i++]];
|
||||
result += _hex[_data[i++]];
|
||||
result += '-';
|
||||
result += _hex[_data[i++]];
|
||||
result += _hex[_data[i++]];
|
||||
result += _hex[_data[i++]];
|
||||
result += _hex[_data[i++]];
|
||||
result += _hex[_data[i++]];
|
||||
result += _hex[_data[i++]];
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -37,17 +37,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9"
|
||||
integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==
|
||||
|
||||
"@types/uuid@^8.3.1":
|
||||
version "8.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
|
||||
integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==
|
||||
|
||||
detect-indent@^6.0.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6"
|
||||
integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==
|
||||
|
||||
uuid@^8.3.2:
|
||||
version "8.3.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||
|
||||
@@ -282,6 +282,15 @@ export const activate: ActivationFunction<void> = (ctx) => {
|
||||
line-height: 1.357em;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
li p {
|
||||
margin-bottom: 0.7em;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
margin-bottom: 0.7em;
|
||||
}
|
||||
`;
|
||||
const template = document.createElement('template');
|
||||
template.classList.add('markdown-style');
|
||||
|
||||
@@ -507,7 +507,7 @@
|
||||
"type": "string",
|
||||
"scope": "resource",
|
||||
"markdownDescription": "%configuration.markdown.editor.pasteUrlAsFormattedLink.enabled%",
|
||||
"default":"never",
|
||||
"default": "never",
|
||||
"enum": [
|
||||
"always",
|
||||
"smart",
|
||||
@@ -734,6 +734,7 @@
|
||||
"morphdom": "^2.6.1",
|
||||
"picomatch": "^2.3.1",
|
||||
"vscode-languageclient": "^8.0.2",
|
||||
"vscode-languageserver-textdocument": "^1.0.11",
|
||||
"vscode-uri": "^3.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -3,18 +3,28 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TextDocument } from 'vscode-languageserver-textdocument';
|
||||
import * as vscode from 'vscode';
|
||||
import { ITextDocument } from '../types/textDocument';
|
||||
|
||||
export class InMemoryDocument implements ITextDocument {
|
||||
|
||||
constructor(
|
||||
public readonly uri: vscode.Uri,
|
||||
private readonly _contents: string,
|
||||
public readonly version = 0,
|
||||
) { }
|
||||
private readonly _doc: TextDocument;
|
||||
|
||||
getText(): string {
|
||||
return this._contents;
|
||||
public readonly uri: vscode.Uri;
|
||||
public readonly version: number;
|
||||
|
||||
constructor(
|
||||
uri: vscode.Uri,
|
||||
contents: string,
|
||||
version: number = 0,
|
||||
) {
|
||||
this.uri = uri;
|
||||
this.version = version;
|
||||
this._doc = TextDocument.create(this.uri.toString(), 'markdown', 0, contents);
|
||||
}
|
||||
|
||||
getText(range?: vscode.Range): string {
|
||||
return this._doc.getText(range);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ async function insertLink(activeEditor: vscode.TextEditor, selectedFiles: vscode
|
||||
function createInsertLinkEdit(activeEditor: vscode.TextEditor, selectedFiles: vscode.Uri[], insertAsMedia: boolean, title = '', placeholderValue = 0, pasteAsMarkdownLink = true, isExternalLink = false) {
|
||||
const snippetEdits = coalesce(activeEditor.selections.map((selection, i): vscode.SnippetTextEdit | undefined => {
|
||||
const selectionText = activeEditor.document.getText(selection);
|
||||
const snippet = createUriListSnippet(activeEditor.document, selectedFiles, [], title, placeholderValue, pasteAsMarkdownLink, isExternalLink, {
|
||||
const snippet = createUriListSnippet(activeEditor.document, selectedFiles.map(uri => ({ uri })), title, placeholderValue, pasteAsMarkdownLink, isExternalLink, {
|
||||
insertAsMedia,
|
||||
placeholderText: selectionText,
|
||||
placeholderStartIndex: (i + 1) * selectedFiles.length,
|
||||
|
||||
+3
-3
@@ -44,12 +44,12 @@ class ResourceDropProvider implements vscode.DocumentDropEditProvider {
|
||||
private async _getUriListEdit(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.DocumentDropEdit | undefined> {
|
||||
const urlList = await dataTransfer.get(Mime.textUriList)?.asString();
|
||||
if (!urlList || token.isCancellationRequested) {
|
||||
return undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
const snippet = await tryGetUriListSnippet(document, urlList, token);
|
||||
const snippet = tryGetUriListSnippet(document, urlList);
|
||||
if (!snippet) {
|
||||
return undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
const edit = new vscode.DocumentDropEdit(snippet.snippet);
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ class PasteResourceEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
}
|
||||
|
||||
const pasteUrlSetting = getPasteUrlAsFormattedLinkSetting(document);
|
||||
const pasteEdit = await createEditAddingLinksForUriList(document, ranges, uriList, false, pasteUrlSetting === PasteUrlAsFormattedLink.Smart, token);
|
||||
const pasteEdit = createEditAddingLinksForUriList(document, ranges, uriList, false, pasteUrlSetting === PasteUrlAsFormattedLink.Smart);
|
||||
if (!pasteEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
+8
-3
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { Mime } from '../../util/mimes';
|
||||
import { createEditAddingLinksForUriList, getPasteUrlAsFormattedLinkSetting, PasteUrlAsFormattedLink, validateLink } from './shared';
|
||||
import { createEditAddingLinksForUriList, findValidUriInText, getPasteUrlAsFormattedLinkSetting, PasteUrlAsFormattedLink } from './shared';
|
||||
|
||||
class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
|
||||
@@ -28,11 +28,16 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
|
||||
const item = dataTransfer.get(Mime.textPlain);
|
||||
const urlList = await item?.asString();
|
||||
if (token.isCancellationRequested || !urlList || !validateLink(urlList).isValid) {
|
||||
if (token.isCancellationRequested || !urlList) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pasteEdit = await createEditAddingLinksForUriList(document, ranges, validateLink(urlList).cleanedUrlList, true, pasteUrlSetting === PasteUrlAsFormattedLink.Smart, token);
|
||||
const uriText = findValidUriInText(urlList);
|
||||
if (!uriText) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pasteEdit = createEditAddingLinksForUriList(document, ranges, uriText, true, pasteUrlSetting === PasteUrlAsFormattedLink.Smart);
|
||||
if (!pasteEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import * as URI from 'vscode-uri';
|
||||
import { ITextDocument } from '../../types/textDocument';
|
||||
import { coalesce } from '../../util/arrays';
|
||||
import { getDocumentDir } from '../../util/document';
|
||||
import { mediaMimes } from '../../util/mimes';
|
||||
@@ -18,7 +19,7 @@ enum MediaKind {
|
||||
Audio,
|
||||
}
|
||||
|
||||
export const externalUriSchemes = [
|
||||
const externalUriSchemes = [
|
||||
'http',
|
||||
'https',
|
||||
'mailto',
|
||||
@@ -50,21 +51,6 @@ export const mediaFileExtensions = new Map<string, MediaKind>([
|
||||
['wav', MediaKind.Audio],
|
||||
]);
|
||||
|
||||
const smartPasteRegexes = [
|
||||
{ regex: /(\[[^\[\]]*](?:\([^\(\)]*\)|\[[^\[\]]*]))/g }, // In a Markdown link
|
||||
{ regex: /^```[\s\S]*?```$/gm }, // In a backtick fenced code block
|
||||
{ regex: /^~~~[\s\S]*?~~~$/gm }, // In a tildefenced code block
|
||||
{ regex: /^\$\$[\s\S]*?\$\$$/gm }, // In a fenced math block
|
||||
{ regex: /`[^`]*`/g }, // In inline code
|
||||
{ regex: /\$[^$]*\$/g }, // In inline math
|
||||
];
|
||||
|
||||
export interface SkinnyTextDocument {
|
||||
offsetAt(position: vscode.Position): number;
|
||||
getText(range?: vscode.Range): string;
|
||||
readonly uri: vscode.Uri;
|
||||
}
|
||||
|
||||
export enum PasteUrlAsFormattedLink {
|
||||
Always = 'always',
|
||||
Smart = 'smart',
|
||||
@@ -75,18 +61,17 @@ export function getPasteUrlAsFormattedLinkSetting(document: vscode.TextDocument)
|
||||
return vscode.workspace.getConfiguration('markdown', document).get<PasteUrlAsFormattedLink>('editor.pasteUrlAsFormattedLink.enabled', PasteUrlAsFormattedLink.Smart);
|
||||
}
|
||||
|
||||
export async function createEditAddingLinksForUriList(
|
||||
document: SkinnyTextDocument,
|
||||
export function createEditAddingLinksForUriList(
|
||||
document: ITextDocument,
|
||||
ranges: readonly vscode.Range[],
|
||||
urlList: string,
|
||||
isExternalLink: boolean,
|
||||
useSmartPaste: boolean,
|
||||
token: vscode.CancellationToken,
|
||||
): Promise<{ additionalEdits: vscode.WorkspaceEdit; label: string; markdownLink: boolean } | undefined> {
|
||||
|
||||
if (ranges.length === 0) {
|
||||
): { additionalEdits: vscode.WorkspaceEdit; label: string; markdownLink: boolean } | undefined {
|
||||
if (!ranges.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const edits: vscode.SnippetTextEdit[] = [];
|
||||
let placeHolderValue: number = ranges.length;
|
||||
let label: string = '';
|
||||
@@ -94,17 +79,12 @@ export async function createEditAddingLinksForUriList(
|
||||
let markdownLink: boolean = true;
|
||||
|
||||
for (const range of ranges) {
|
||||
const selectedRange: vscode.Range = new vscode.Range(
|
||||
new vscode.Position(range.start.line, document.offsetAt(range.start)),
|
||||
new vscode.Position(range.end.line, document.offsetAt(range.end))
|
||||
);
|
||||
|
||||
if (useSmartPaste) {
|
||||
pasteAsMarkdownLink = checkSmartPaste(document, selectedRange, range);
|
||||
pasteAsMarkdownLink = shouldSmartPaste(document, range);
|
||||
markdownLink = pasteAsMarkdownLink; // FIX: this will only match the last range
|
||||
}
|
||||
|
||||
const snippet = await tryGetUriListSnippet(document, urlList, token, document.getText(range), placeHolderValue, pasteAsMarkdownLink, isExternalLink);
|
||||
const snippet = tryGetUriListSnippet(document, urlList, document.getText(range), placeHolderValue, pasteAsMarkdownLink, isExternalLink);
|
||||
if (!snippet) {
|
||||
return;
|
||||
}
|
||||
@@ -121,13 +101,47 @@ export async function createEditAddingLinksForUriList(
|
||||
return { additionalEdits, label, markdownLink };
|
||||
}
|
||||
|
||||
export function checkSmartPaste(document: SkinnyTextDocument, selectedRange: vscode.Range, range: vscode.Range): boolean {
|
||||
if (selectedRange.isEmpty || /^[\s\n]*$/.test(document.getText(range)) || validateLink(document.getText(range)).isValid) {
|
||||
export function findValidUriInText(text: string): string | undefined {
|
||||
const trimmedUrlList = text.trim();
|
||||
|
||||
// Uri must consist of a single sequence of characters without spaces
|
||||
if (!/^\S+$/.test(trimmedUrlList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let uri: vscode.Uri;
|
||||
try {
|
||||
uri = vscode.Uri.parse(trimmedUrlList);
|
||||
} catch {
|
||||
// Could not parse
|
||||
return;
|
||||
}
|
||||
|
||||
if (!externalUriSchemes.includes(uri.scheme.toLowerCase()) || uri.authority.length <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
return trimmedUrlList;
|
||||
}
|
||||
|
||||
const smartPasteRegexes = [
|
||||
{ regex: /(\[[^\[\]]*](?:\([^\(\)]*\)|\[[^\[\]]*]))/g }, // In a Markdown link
|
||||
{ regex: /^```[\s\S]*?```$/gm }, // In a backtick fenced code block
|
||||
{ regex: /^~~~[\s\S]*?~~~$/gm }, // In a tildefenced code block
|
||||
{ regex: /^\$\$[\s\S]*?\$\$$/gm }, // In a fenced math block
|
||||
{ regex: /`[^`]*`/g }, // In inline code
|
||||
{ regex: /\$[^$]*\$/g }, // In inline math
|
||||
];
|
||||
|
||||
export function shouldSmartPaste(document: ITextDocument, selectedRange: vscode.Range): boolean {
|
||||
if (selectedRange.isEmpty || /^[\s\n]*$/.test(document.getText(selectedRange)) || findValidUriInText(document.getText(selectedRange))) {
|
||||
return false;
|
||||
}
|
||||
if (/\[.*\]\(.*\)/.test(document.getText(range)) || /!\[.*\]\(.*\)/.test(document.getText(range))) {
|
||||
|
||||
if (/\[.*\]\(.*\)/.test(document.getText(selectedRange)) || /!\[.*\]\(.*\)/.test(document.getText(selectedRange))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const regex of smartPasteRegexes) {
|
||||
const matches = [...document.getText().matchAll(regex.regex)];
|
||||
for (const match of matches) {
|
||||
@@ -139,40 +153,20 @@ export function checkSmartPaste(document: SkinnyTextDocument, selectedRange: vsc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function validateLink(urlList: string): { isValid: boolean; cleanedUrlList: string } {
|
||||
let isValid = false;
|
||||
let uri = undefined;
|
||||
const trimmedUrlList = urlList?.trim(); //remove leading and trailing whitespace and new lines
|
||||
try {
|
||||
uri = vscode.Uri.parse(trimmedUrlList);
|
||||
} catch (error) {
|
||||
return { isValid: false, cleanedUrlList: urlList };
|
||||
}
|
||||
const splitUrlList = trimmedUrlList.split(' ').filter(item => item !== ''); //split on spaces and remove empty strings
|
||||
if (uri) {
|
||||
isValid = splitUrlList.length === 1 && !splitUrlList[0].includes('\n') && externalUriSchemes.includes(vscode.Uri.parse(splitUrlList[0]).scheme) && !!vscode.Uri.parse(splitUrlList[0]).authority;
|
||||
}
|
||||
return { isValid, cleanedUrlList: splitUrlList[0] };
|
||||
}
|
||||
|
||||
export async function tryGetUriListSnippet(document: SkinnyTextDocument, urlList: String, token: vscode.CancellationToken, title = '', placeHolderValue = 0, pasteAsMarkdownLink = true, isExternalLink = false): Promise<{ snippet: vscode.SnippetString; label: string } | undefined> {
|
||||
if (token.isCancellationRequested) {
|
||||
return undefined;
|
||||
}
|
||||
const uriStrings: string[] = [];
|
||||
const uris: vscode.Uri[] = [];
|
||||
for (const resource of urlList.split(/\r?\n/g)) {
|
||||
export function tryGetUriListSnippet(document: ITextDocument, urlList: String, title = '', placeHolderValue = 0, pasteAsMarkdownLink = true, isExternalLink = false): { snippet: vscode.SnippetString; label: string } | undefined {
|
||||
const entries = coalesce(urlList.split(/\r?\n/g).map(line => {
|
||||
try {
|
||||
uris.push(vscode.Uri.parse(resource));
|
||||
uriStrings.push(resource);
|
||||
return { uri: vscode.Uri.parse(line), str: line };
|
||||
} catch {
|
||||
// noop
|
||||
// Uri parse failure
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return createUriListSnippet(document, uris, uriStrings, title, placeHolderValue, pasteAsMarkdownLink, isExternalLink);
|
||||
}));
|
||||
return createUriListSnippet(document, entries, title, placeHolderValue, pasteAsMarkdownLink, isExternalLink);
|
||||
}
|
||||
|
||||
interface UriListSnippetOptions {
|
||||
@@ -193,20 +187,21 @@ interface UriListSnippetOptions {
|
||||
export function appendToLinkSnippet(
|
||||
snippet: vscode.SnippetString,
|
||||
title: string,
|
||||
uriString: string,
|
||||
link: string,
|
||||
placeholderValue: number,
|
||||
isExternalLink: boolean,
|
||||
): vscode.SnippetString {
|
||||
): void {
|
||||
snippet.appendText('[');
|
||||
snippet.appendPlaceholder(escapeBrackets(title) || 'Title', placeholderValue);
|
||||
snippet.appendText(`](${escapeMarkdownLinkPath(uriString, isExternalLink)})`);
|
||||
return snippet;
|
||||
snippet.appendText(`](${escapeMarkdownLinkPath(link, isExternalLink)})`);
|
||||
}
|
||||
|
||||
export function createUriListSnippet(
|
||||
document: SkinnyTextDocument,
|
||||
uris: readonly vscode.Uri[],
|
||||
uriStrings?: readonly string[],
|
||||
document: ITextDocument,
|
||||
uris: ReadonlyArray<{
|
||||
readonly uri: vscode.Uri;
|
||||
readonly str?: string;
|
||||
}>,
|
||||
title = '',
|
||||
placeholderValue = 0,
|
||||
pasteAsMarkdownLink = true,
|
||||
@@ -219,15 +214,15 @@ export function createUriListSnippet(
|
||||
|
||||
const documentDir = getDocumentDir(document.uri);
|
||||
|
||||
let snippet = new vscode.SnippetString();
|
||||
const snippet = new vscode.SnippetString();
|
||||
let insertedLinkCount = 0;
|
||||
let insertedImageCount = 0;
|
||||
let insertedAudioVideoCount = 0;
|
||||
|
||||
uris.forEach((uri, i) => {
|
||||
const mdPath = getMdPath(documentDir, uri);
|
||||
const mdPath = getRelativeMdPath(documentDir, uri.uri) ?? uri.str ?? uri.uri.toString();
|
||||
|
||||
const ext = URI.Utils.extname(uri).toLowerCase().replace('.', '');
|
||||
const ext = URI.Utils.extname(uri.uri).toLowerCase().replace('.', '');
|
||||
const insertAsMedia = typeof options?.insertAsMedia === 'undefined' ? mediaFileExtensions.has(ext) : !!options.insertAsMedia;
|
||||
const insertAsVideo = mediaFileExtensions.get(ext) === MediaKind.Video;
|
||||
const insertAsAudio = mediaFileExtensions.get(ext) === MediaKind.Audio;
|
||||
@@ -257,11 +252,7 @@ export function createUriListSnippet(
|
||||
}
|
||||
} else {
|
||||
insertedLinkCount++;
|
||||
if (uriStrings && isExternalLink) {
|
||||
snippet = appendToLinkSnippet(snippet, title, uriStrings[i], placeholderValue, isExternalLink);
|
||||
} else {
|
||||
snippet.appendText(escapeMarkdownLinkPath(mdPath, isExternalLink));
|
||||
}
|
||||
appendToLinkSnippet(snippet, title, mdPath, placeholderValue, isExternalLink);
|
||||
}
|
||||
|
||||
if (i < uris.length - 1 && uris.length > 1) {
|
||||
@@ -349,7 +340,7 @@ export async function createEditForMediaFiles(
|
||||
}
|
||||
}
|
||||
|
||||
const snippet = createUriListSnippet(document, fileEntries.map(entry => entry.uri));
|
||||
const snippet = createUriListSnippet(document, fileEntries);
|
||||
if (!snippet) {
|
||||
return;
|
||||
}
|
||||
@@ -361,7 +352,7 @@ export async function createEditForMediaFiles(
|
||||
};
|
||||
}
|
||||
|
||||
function getMdPath(dir: vscode.Uri | undefined, file: vscode.Uri) {
|
||||
function getRelativeMdPath(dir: vscode.Uri | undefined, file: vscode.Uri): string | undefined {
|
||||
if (dir && dir.scheme === file.scheme && dir.authority === file.authority) {
|
||||
if (file.scheme === Schemes.file) {
|
||||
// On windows, we must use the native `path.relative` to generate the relative path
|
||||
@@ -373,8 +364,7 @@ function getMdPath(dir: vscode.Uri | undefined, file: vscode.Uri) {
|
||||
|
||||
return path.posix.relative(dir.path, file.path);
|
||||
}
|
||||
|
||||
return file.toString(false);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function escapeHtmlAttribute(attr: string): string {
|
||||
@@ -422,4 +412,3 @@ function needsBracketLink(mdPath: string) {
|
||||
|
||||
return nestingCount > 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,92 +2,101 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as vscode from 'vscode';
|
||||
import * as assert from 'assert';
|
||||
import 'mocha';
|
||||
import { SkinnyTextDocument, checkSmartPaste, createEditAddingLinksForUriList, appendToLinkSnippet, validateLink } from '../languageFeatures/copyFiles/shared';
|
||||
import * as vscode from 'vscode';
|
||||
import { InMemoryDocument } from '../client/inMemoryDocument';
|
||||
import { appendToLinkSnippet, createEditAddingLinksForUriList, findValidUriInText, shouldSmartPaste } from '../languageFeatures/copyFiles/shared';
|
||||
|
||||
suite('createEditAddingLinksForUriList', () => {
|
||||
|
||||
test('Markdown Link Pasting should occur for a valid link (end to end)', async () => {
|
||||
// createEditAddingLinksForUriList -> checkSmartPaste -> tryGetUriListSnippet -> createUriListSnippet -> createLinkSnippet
|
||||
|
||||
const skinnyDocument: SkinnyTextDocument = {
|
||||
uri: vscode.Uri.parse('file:///path/to/your/file'),
|
||||
offsetAt: function () { return 0; },
|
||||
getText: function () { return 'hello world!'; },
|
||||
};
|
||||
|
||||
const result = await createEditAddingLinksForUriList(skinnyDocument, [new vscode.Range(0, 0, 0, 12)], 'https://www.microsoft.com/', true, true, new vscode.CancellationTokenSource().token);
|
||||
const result = createEditAddingLinksForUriList(
|
||||
new InMemoryDocument(vscode.Uri.file('test.md'), 'hello world!'), [new vscode.Range(0, 0, 0, 12)], 'https://www.microsoft.com/', true, true);
|
||||
// need to check the actual result -> snippet value
|
||||
assert.strictEqual(result?.label, 'Insert Markdown Link');
|
||||
});
|
||||
|
||||
suite('validateLink', () => {
|
||||
|
||||
test('Markdown pasting should occur for a valid link.', () => {
|
||||
const isLink = validateLink('https://www.microsoft.com/').isValid;
|
||||
assert.strictEqual(isLink, true);
|
||||
test('Markdown pasting should occur for a valid link', () => {
|
||||
assert.strictEqual(
|
||||
findValidUriInText('https://www.microsoft.com/'),
|
||||
'https://www.microsoft.com/');
|
||||
});
|
||||
|
||||
test('Markdown pasting should occur for a valid link preceded by a new line.', () => {
|
||||
const isLink = validateLink('\r\nhttps://www.microsoft.com/').isValid;
|
||||
assert.strictEqual(isLink, true);
|
||||
test('Markdown pasting should occur for a valid link preceded by a new line', () => {
|
||||
assert.strictEqual(
|
||||
findValidUriInText('\r\nhttps://www.microsoft.com/'),
|
||||
'https://www.microsoft.com/');
|
||||
});
|
||||
|
||||
test('Markdown pasting should occur for a valid link followed by a new line.', () => {
|
||||
const isLink = validateLink('https://www.microsoft.com/\r\n').isValid;
|
||||
assert.strictEqual(isLink, true);
|
||||
test('Markdown pasting should occur for a valid link followed by a new line', () => {
|
||||
assert.strictEqual(
|
||||
findValidUriInText('https://www.microsoft.com/\r\n'),
|
||||
'https://www.microsoft.com/');
|
||||
});
|
||||
|
||||
test('Markdown pasting should not occur for a valid hostname and invalid protool.', () => {
|
||||
const isLink = validateLink('invalid:www.microsoft.com').isValid;
|
||||
assert.strictEqual(isLink, false);
|
||||
test('Markdown pasting should not occur for a valid hostname and invalid protool', () => {
|
||||
assert.strictEqual(
|
||||
findValidUriInText('invalid:www.microsoft.com'),
|
||||
undefined);
|
||||
});
|
||||
|
||||
test('Markdown pasting should not occur for plain text.', () => {
|
||||
const isLink = validateLink('hello world!').isValid;
|
||||
assert.strictEqual(isLink, false);
|
||||
test('Markdown pasting should not occur for plain text', () => {
|
||||
assert.strictEqual(
|
||||
findValidUriInText('hello world!'),
|
||||
undefined);
|
||||
});
|
||||
|
||||
test('Markdown pasting should not occur for plain text including a colon.', () => {
|
||||
const isLink = validateLink('hello: world!').isValid;
|
||||
assert.strictEqual(isLink, false);
|
||||
test('Markdown pasting should not occur for plain text including a colon', () => {
|
||||
assert.strictEqual(
|
||||
findValidUriInText('hello: world!'),
|
||||
undefined);
|
||||
});
|
||||
|
||||
test('Markdown pasting should not occur for plain text including a slashes.', () => {
|
||||
const isLink = validateLink('helloworld!').isValid;
|
||||
assert.strictEqual(isLink, false);
|
||||
test('Markdown pasting should not occur for plain text including a slashes', () => {
|
||||
assert.strictEqual(
|
||||
findValidUriInText('helloworld!'),
|
||||
undefined);
|
||||
});
|
||||
|
||||
test('Markdown pasting should not occur for a link followed by text.', () => {
|
||||
const isLink = validateLink('https://www.microsoft.com/ hello world!').isValid;
|
||||
assert.strictEqual(isLink, false);
|
||||
test('Markdown pasting should not occur for a link followed by text', () => {
|
||||
assert.strictEqual(
|
||||
findValidUriInText('https://www.microsoft.com/ hello world!'),
|
||||
undefined);
|
||||
});
|
||||
|
||||
test('Markdown pasting should occur for a link preceded or followed by spaces.', () => {
|
||||
const isLink = validateLink(' https://www.microsoft.com/ ').isValid;
|
||||
assert.strictEqual(isLink, true);
|
||||
test('Markdown pasting should occur for a link preceded or followed by spaces', () => {
|
||||
assert.strictEqual(
|
||||
findValidUriInText(' https://www.microsoft.com/ '),
|
||||
'https://www.microsoft.com/');
|
||||
});
|
||||
|
||||
test('Markdown pasting should not occur for a link with an invalid scheme.', () => {
|
||||
const isLink = validateLink('hello:www.microsoft.com').isValid;
|
||||
assert.strictEqual(isLink, false);
|
||||
test('Markdown pasting should not occur for a link with an invalid scheme', () => {
|
||||
assert.strictEqual(
|
||||
findValidUriInText('hello:www.microsoft.com'),
|
||||
undefined);
|
||||
});
|
||||
|
||||
test('Markdown pasting should not occur for multiple links being pasted.', () => {
|
||||
const isLink = validateLink('https://www.microsoft.com/\r\nhttps://www.microsoft.com/\r\nhttps://www.microsoft.com/\r\nhttps://www.microsoft.com/').isValid;
|
||||
assert.strictEqual(isLink, false);
|
||||
test('Markdown pasting should not occur for multiple links being pasted', () => {
|
||||
assert.strictEqual(
|
||||
findValidUriInText('https://www.microsoft.com/\r\nhttps://www.microsoft.com/\r\nhttps://www.microsoft.com/\r\nhttps://www.microsoft.com/'),
|
||||
undefined);
|
||||
});
|
||||
|
||||
test('Markdown pasting should not occur for multiple links with spaces being pasted.', () => {
|
||||
const isLink = validateLink('https://www.microsoft.com/ \r\nhttps://www.microsoft.com/\r\nhttps://www.microsoft.com/\r\n hello \r\nhttps://www.microsoft.com/').isValid;
|
||||
assert.strictEqual(isLink, false);
|
||||
test('Markdown pasting should not occur for multiple links with spaces being pasted', () => {
|
||||
assert.strictEqual(
|
||||
findValidUriInText('https://www.microsoft.com/ \r\nhttps://www.microsoft.com/\r\nhttps://www.microsoft.com/\r\n hello \r\nhttps://www.microsoft.com/'),
|
||||
undefined);
|
||||
});
|
||||
|
||||
test('Markdown pasting should not occur for just a valid uri scheme', () => {
|
||||
const isLink = validateLink('https://').isValid;
|
||||
assert.strictEqual(isLink, false);
|
||||
assert.strictEqual(
|
||||
findValidUriInText('https://'),
|
||||
undefined);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -95,31 +104,36 @@ suite('createEditAddingLinksForUriList', () => {
|
||||
|
||||
test('Should create snippet with < > when pasted link has an mismatched parentheses', () => {
|
||||
const uriString = 'https://www.mic(rosoft.com';
|
||||
const snippet = appendToLinkSnippet(new vscode.SnippetString(''), 'abc', uriString, 0, true);
|
||||
const snippet = new vscode.SnippetString('');
|
||||
appendToLinkSnippet(snippet, 'abc', uriString, 0, true);
|
||||
assert.strictEqual(snippet?.value, '[${0:abc}](<https://www.mic(rosoft.com>)');
|
||||
});
|
||||
|
||||
test('Should create Markdown link snippet when pasteAsMarkdownLink is true', () => {
|
||||
const uriString = 'https://www.microsoft.com';
|
||||
const snippet = appendToLinkSnippet(new vscode.SnippetString(''), '', uriString, 0, true);
|
||||
const snippet = new vscode.SnippetString('');
|
||||
appendToLinkSnippet(snippet, '', uriString, 0, true);
|
||||
assert.strictEqual(snippet?.value, '[${0:Title}](https://www.microsoft.com)');
|
||||
});
|
||||
|
||||
test('Should use an unencoded URI string in Markdown link when passing in an external browser link', () => {
|
||||
const uriString = 'https://www.microsoft.com';
|
||||
const snippet = appendToLinkSnippet(new vscode.SnippetString(''), '', uriString, 0, true);
|
||||
const snippet = new vscode.SnippetString('');
|
||||
appendToLinkSnippet(snippet, '', uriString, 0, true);
|
||||
assert.strictEqual(snippet?.value, '[${0:Title}](https://www.microsoft.com)');
|
||||
});
|
||||
|
||||
test('Should not decode an encoded URI string when passing in an external browser link', () => {
|
||||
const uriString = 'https://www.microsoft.com/%20';
|
||||
const snippet = appendToLinkSnippet(new vscode.SnippetString(''), '', uriString, 0, true);
|
||||
const snippet = new vscode.SnippetString('');
|
||||
appendToLinkSnippet(snippet, '', uriString, 0, true);
|
||||
assert.strictEqual(snippet?.value, '[${0:Title}](https://www.microsoft.com/%20)');
|
||||
});
|
||||
|
||||
test('Should not encode an unencoded URI string when passing in an external browser link', () => {
|
||||
const uriString = 'https://www.example.com/path?query=value&another=value#fragment';
|
||||
const snippet = appendToLinkSnippet(new vscode.SnippetString(''), '', uriString, 0, true);
|
||||
const snippet = new vscode.SnippetString('');
|
||||
appendToLinkSnippet(snippet, '', uriString, 0, true);
|
||||
assert.strictEqual(snippet?.value, '[${0:Title}](https://www.example.com/path?query=value&another=value#fragment)');
|
||||
});
|
||||
});
|
||||
@@ -127,107 +141,86 @@ suite('createEditAddingLinksForUriList', () => {
|
||||
|
||||
suite('checkSmartPaste', () => {
|
||||
|
||||
const skinnyDocument: SkinnyTextDocument = {
|
||||
uri: vscode.Uri.file('/path/to/your/file'),
|
||||
offsetAt: function () { return 0; },
|
||||
getText: function () { return 'hello world!'; },
|
||||
};
|
||||
|
||||
test('Should evaluate pasteAsMarkdownLink as true for selected plain text', () => {
|
||||
const pasteAsMarkdownLink = checkSmartPaste(skinnyDocument, new vscode.Range(0, 0, 0, 12), new vscode.Range(0, 0, 0, 12));
|
||||
assert.strictEqual(pasteAsMarkdownLink, true);
|
||||
assert.strictEqual(
|
||||
shouldSmartPaste(makeTestDoc('hello world'), new vscode.Range(0, 0, 0, 12)),
|
||||
true);
|
||||
});
|
||||
|
||||
test('Should evaluate pasteAsMarkdownLink as false for a valid selected link', () => {
|
||||
skinnyDocument.getText = function () { return 'https://www.microsoft.com'; };
|
||||
const pasteAsMarkdownLink = checkSmartPaste(skinnyDocument, new vscode.Range(0, 0, 0, 25), new vscode.Range(0, 0, 0, 25));
|
||||
assert.strictEqual(pasteAsMarkdownLink, false);
|
||||
assert.strictEqual(
|
||||
shouldSmartPaste(makeTestDoc('https://www.microsoft.com'), new vscode.Range(0, 0, 0, 25)),
|
||||
false);
|
||||
});
|
||||
|
||||
test('Should evaluate pasteAsMarkdownLink as false for a valid selected link with trailing whitespace', () => {
|
||||
skinnyDocument.getText = function () { return ' https://www.microsoft.com '; };
|
||||
const pasteAsMarkdownLink = checkSmartPaste(skinnyDocument, new vscode.Range(0, 0, 0, 30), new vscode.Range(0, 0, 0, 30));
|
||||
assert.strictEqual(pasteAsMarkdownLink, false);
|
||||
assert.strictEqual(
|
||||
shouldSmartPaste(makeTestDoc(' https://www.microsoft.com '), new vscode.Range(0, 0, 0, 30)),
|
||||
false);
|
||||
});
|
||||
|
||||
test('Should evaluate pasteAsMarkdownLink as true for a link pasted in square brackets', () => {
|
||||
skinnyDocument.getText = function () { return '[abc]'; };
|
||||
const pasteAsMarkdownLink = checkSmartPaste(skinnyDocument, new vscode.Range(0, 1, 0, 4), new vscode.Range(0, 1, 0, 4));
|
||||
assert.strictEqual(pasteAsMarkdownLink, true);
|
||||
assert.strictEqual(
|
||||
shouldSmartPaste(makeTestDoc('[abc]'), new vscode.Range(0, 1, 0, 4)),
|
||||
true);
|
||||
});
|
||||
|
||||
test('Should evaluate pasteAsMarkdownLink as false for no selection', () => {
|
||||
const pasteAsMarkdownLink = checkSmartPaste(skinnyDocument, new vscode.Range(0, 0, 0, 0), new vscode.Range(0, 0, 0, 0));
|
||||
assert.strictEqual(pasteAsMarkdownLink, false);
|
||||
assert.strictEqual(
|
||||
shouldSmartPaste(makeTestDoc('xyz'), new vscode.Range(0, 0, 0, 0)),
|
||||
false);
|
||||
});
|
||||
|
||||
test('Should evaluate pasteAsMarkdownLink as false for selected whitespace and new lines', () => {
|
||||
skinnyDocument.getText = function () { return ' \r\n\r\n'; };
|
||||
const pasteAsMarkdownLink = checkSmartPaste(skinnyDocument, new vscode.Range(0, 0, 0, 7), new vscode.Range(0, 0, 0, 7));
|
||||
assert.strictEqual(pasteAsMarkdownLink, false);
|
||||
assert.strictEqual(
|
||||
shouldSmartPaste(makeTestDoc(' \r\n\r\n'), new vscode.Range(0, 0, 0, 7)),
|
||||
false);
|
||||
});
|
||||
|
||||
test('Should evaluate pasteAsMarkdownLink as false for pasting within a backtick code block', () => {
|
||||
skinnyDocument.getText = function () { return '```\r\n\r\n```'; };
|
||||
const pasteAsMarkdownLink = checkSmartPaste(skinnyDocument, new vscode.Range(0, 5, 0, 5), new vscode.Range(0, 5, 0, 5));
|
||||
assert.strictEqual(pasteAsMarkdownLink, false);
|
||||
assert.strictEqual(
|
||||
shouldSmartPaste(makeTestDoc('```\r\n\r\n```'), new vscode.Range(0, 5, 0, 5)),
|
||||
false);
|
||||
});
|
||||
|
||||
test('Should evaluate pasteAsMarkdownLink as false for pasting within a tilde code block', () => {
|
||||
skinnyDocument.getText = function () { return '~~~\r\n\r\n~~~'; };
|
||||
const pasteAsMarkdownLink = checkSmartPaste(skinnyDocument, new vscode.Range(0, 5, 0, 5), new vscode.Range(0, 5, 0, 5));
|
||||
assert.strictEqual(pasteAsMarkdownLink, false);
|
||||
assert.strictEqual(
|
||||
shouldSmartPaste(makeTestDoc('~~~\r\n\r\n~~~'), new vscode.Range(0, 5, 0, 5)),
|
||||
false);
|
||||
});
|
||||
|
||||
test('Should evaluate pasteAsMarkdownLink as false for pasting within a math block', () => {
|
||||
skinnyDocument.getText = function () { return '$$$\r\n\r\n$$$'; };
|
||||
const pasteAsMarkdownLink = checkSmartPaste(skinnyDocument, new vscode.Range(0, 5, 0, 5), new vscode.Range(0, 5, 0, 5));
|
||||
assert.strictEqual(pasteAsMarkdownLink, false);
|
||||
assert.strictEqual(
|
||||
shouldSmartPaste(makeTestDoc('$$$\r\n\r\n$$$'), new vscode.Range(0, 5, 0, 5)),
|
||||
false);
|
||||
});
|
||||
|
||||
const linkSkinnyDoc: SkinnyTextDocument = {
|
||||
uri: vscode.Uri.file('/path/to/your/file'),
|
||||
offsetAt: function () { return 0; },
|
||||
getText: function () { return '[a](bcdef)'; },
|
||||
};
|
||||
|
||||
test('Should evaluate pasteAsMarkdownLink as false for pasting within a Markdown link', () => {
|
||||
const pasteAsMarkdownLink = checkSmartPaste(linkSkinnyDoc, new vscode.Range(0, 4, 0, 6), new vscode.Range(0, 4, 0, 6));
|
||||
assert.strictEqual(pasteAsMarkdownLink, false);
|
||||
assert.strictEqual(
|
||||
shouldSmartPaste(makeTestDoc('[a](bcdef)'), new vscode.Range(0, 4, 0, 6)),
|
||||
false);
|
||||
});
|
||||
|
||||
|
||||
const imageLinkSkinnyDoc: SkinnyTextDocument = {
|
||||
uri: vscode.Uri.file('/path/to/your/file'),
|
||||
offsetAt: function () { return 0; },
|
||||
getText: function () { return ''; },
|
||||
};
|
||||
|
||||
test('Should evaluate pasteAsMarkdownLink as false for pasting within a Markdown image link', () => {
|
||||
const pasteAsMarkdownLink = checkSmartPaste(imageLinkSkinnyDoc, new vscode.Range(0, 5, 0, 10), new vscode.Range(0, 5, 0, 10));
|
||||
assert.strictEqual(pasteAsMarkdownLink, false);
|
||||
assert.strictEqual(
|
||||
shouldSmartPaste(makeTestDoc(''), new vscode.Range(0, 5, 0, 10)),
|
||||
false);
|
||||
});
|
||||
|
||||
const inlineCodeSkinnyCode: SkinnyTextDocument = {
|
||||
uri: vscode.Uri.file('/path/to/your/file'),
|
||||
offsetAt: function () { return 0; },
|
||||
getText: function () { return '``'; },
|
||||
};
|
||||
|
||||
test('Should evaluate pasteAsMarkdownLink as false for pasting within inline code', () => {
|
||||
const pasteAsMarkdownLink = checkSmartPaste(inlineCodeSkinnyCode, new vscode.Range(0, 1, 0, 1), new vscode.Range(0, 1, 0, 1));
|
||||
assert.strictEqual(pasteAsMarkdownLink, false);
|
||||
assert.strictEqual(
|
||||
shouldSmartPaste(makeTestDoc('``'), new vscode.Range(0, 1, 0, 1)),
|
||||
false);
|
||||
});
|
||||
|
||||
const inlineMathSkinnyDoc: SkinnyTextDocument = {
|
||||
uri: vscode.Uri.file('/path/to/your/file'),
|
||||
offsetAt: function () { return 0; },
|
||||
getText: function () { return '$$'; },
|
||||
};
|
||||
|
||||
test('Should evaluate pasteAsMarkdownLink as false for pasting within inline math', () => {
|
||||
const pasteAsMarkdownLink = checkSmartPaste(inlineMathSkinnyDoc, new vscode.Range(0, 1, 0, 1), new vscode.Range(0, 1, 0, 1));
|
||||
assert.strictEqual(pasteAsMarkdownLink, false);
|
||||
assert.strictEqual(
|
||||
shouldSmartPaste(makeTestDoc('$$'), new vscode.Range(0, 1, 0, 1)),
|
||||
false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function makeTestDoc(contents: string) {
|
||||
return new InMemoryDocument(vscode.Uri.file('test.md'), contents);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,6 @@ export interface ITextDocument {
|
||||
readonly uri: vscode.Uri;
|
||||
readonly version: number;
|
||||
|
||||
getText(): string;
|
||||
getText(range?: vscode.Range): string;
|
||||
}
|
||||
|
||||
|
||||
@@ -295,6 +295,11 @@ vscode-languageserver-protocol@3.17.2:
|
||||
vscode-jsonrpc "8.0.2"
|
||||
vscode-languageserver-types "3.17.2"
|
||||
|
||||
vscode-languageserver-textdocument@^1.0.11:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz#0822a000e7d4dc083312580d7575fe9e3ba2e2bf"
|
||||
integrity sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==
|
||||
|
||||
vscode-languageserver-textdocument@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.5.tgz#838769940ece626176ec5d5a2aa2d0aa69f5095c"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"license": "MIT",
|
||||
"description": "Dependencies shared by all extensions",
|
||||
"dependencies": {
|
||||
"typescript": "^5.3.1-rc"
|
||||
"typescript": "5.3.2"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "node ./postinstall.mjs"
|
||||
|
||||
@@ -62,7 +62,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<Api> {
|
||||
new TypeScriptVersion(
|
||||
TypeScriptVersionSource.Bundled,
|
||||
vscode.Uri.joinPath(context.extensionUri, 'dist/browser/typescript/tsserver.web.js').toString(),
|
||||
API.fromSimpleString('5.1.3')));
|
||||
API.fromSimpleString('5.3.2')));
|
||||
|
||||
let experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined;
|
||||
const packageInfo = getPackageInfo(context);
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ export class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLens
|
||||
switch (item.kind) {
|
||||
case PConst.Kind.function: {
|
||||
const showOnAllFunctions = vscode.workspace.getConfiguration(this.language.id).get<boolean>('referencesCodeLens.showOnAllFunctions');
|
||||
if (showOnAllFunctions) {
|
||||
if (showOnAllFunctions && item.nameSpan) {
|
||||
return getSymbolRange(document, item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider<VsCodeCode
|
||||
commandManager.register(new CompositeCommand());
|
||||
commandManager.register(new ApplyCodeActionCommand(client, diagnosticsManager, telemetryReporter));
|
||||
commandManager.register(new ApplyFixAllCodeAction(client, telemetryReporter));
|
||||
commandManager.register(new EditorChatFollowUp(client));
|
||||
commandManager.register(new EditorChatFollowUp(client, telemetryReporter));
|
||||
|
||||
this.supportedCodeActionProvider = new SupportedCodeActionProvider(client);
|
||||
}
|
||||
@@ -349,7 +349,8 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider<VsCodeCode
|
||||
arguments: [<EditorChatFollowUp_Args>{
|
||||
message: 'Add types to this code. Add separate interfaces when possible. Do not change the code except for adding types.',
|
||||
expand: { kind: 'navtree-function', pos: diagnostic.range.start },
|
||||
document
|
||||
document,
|
||||
action: { type: 'quickfix', quickfix: action }
|
||||
}],
|
||||
title: ''
|
||||
};
|
||||
|
||||
@@ -506,7 +506,7 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider<TsCodeActi
|
||||
commandManager.register(new CompositeCommand());
|
||||
commandManager.register(new SelectRefactorCommand(this.client));
|
||||
commandManager.register(new MoveToFileRefactorCommand(this.client, didApplyRefactoringCommand));
|
||||
commandManager.register(new EditorChatFollowUp(this.client));
|
||||
commandManager.register(new EditorChatFollowUp(this.client, telemetryReporter));
|
||||
}
|
||||
|
||||
public static readonly metadata: vscode.CodeActionProviderMetadata = {
|
||||
@@ -634,7 +634,8 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider<TsCodeActi
|
||||
if (Extract_Constant.matches(action) && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.extractConstant')
|
||||
|| Extract_Function.matches(action) && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.extractFunction')
|
||||
|| Extract_Type.matches(action) && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.extractType')
|
||||
|| Extract_Interface.matches(action) && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.extractInterface')) {
|
||||
|| Extract_Interface.matches(action) && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.extractInterface')
|
||||
) {
|
||||
const newName = Extract_Constant.matches(action) ? 'newLocal'
|
||||
: Extract_Function.matches(action) ? 'newFunction'
|
||||
: Extract_Type.matches(action) ? 'NewType'
|
||||
@@ -652,6 +653,7 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider<TsCodeActi
|
||||
kind: 'refactor-info',
|
||||
refactor: info,
|
||||
},
|
||||
action: { type: 'refactor', refactor: action },
|
||||
document,
|
||||
}]
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import { nulToken } from '../../utils/cancellation';
|
||||
import type * as Proto from '../../tsServer/protocol/protocol';
|
||||
import * as typeConverters from '../../typeConverters';
|
||||
import { ITypeScriptServiceClient } from '../../typescriptService';
|
||||
import { TelemetryReporter } from '../../logging/telemetry';
|
||||
|
||||
export class EditorChatFollowUp implements Command {
|
||||
public static readonly ID = '_typescript.quickFix.editorChatReplacement2';
|
||||
@@ -16,9 +17,38 @@ export class EditorChatFollowUp implements Command {
|
||||
|
||||
constructor(
|
||||
private readonly client: ITypeScriptServiceClient,
|
||||
private readonly telemetryReporter: TelemetryReporter,
|
||||
) { }
|
||||
|
||||
async execute({ message, document, expand }: EditorChatFollowUp_Args) {
|
||||
async execute({ message, document, expand, action }: EditorChatFollowUp_Args) {
|
||||
if (action.type === 'quickfix') {
|
||||
/* __GDPR__
|
||||
"aiQuickfix.execute" : {
|
||||
"owner": "mjbvz",
|
||||
"action" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
|
||||
"${include}": [
|
||||
"${TypeScriptCommonProperties}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.logTelemetry('aiQuickfix.execute', {
|
||||
action: action.quickfix.fixName,
|
||||
});
|
||||
} else {
|
||||
/* __GDPR__
|
||||
"aiRefactor.execute" : {
|
||||
"owner": "mjbvz",
|
||||
"action" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
|
||||
"${include}": [
|
||||
"${TypeScriptCommonProperties}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.logTelemetry('aiRefactor.execute', {
|
||||
action: action.refactor.name,
|
||||
});
|
||||
}
|
||||
|
||||
const initialRange =
|
||||
expand.kind === 'navtree-function'
|
||||
? await findScopeEndLineFromNavTree(
|
||||
@@ -50,6 +80,13 @@ export interface EditorChatFollowUp_Args {
|
||||
readonly message: string;
|
||||
readonly document: vscode.TextDocument;
|
||||
readonly expand: Expand;
|
||||
readonly action: {
|
||||
readonly type: 'refactor';
|
||||
readonly refactor: Proto.RefactorActionInfo;
|
||||
} | {
|
||||
readonly type: 'quickfix';
|
||||
readonly quickfix: Proto.CodeFixAction;
|
||||
};
|
||||
}
|
||||
|
||||
export class CompositeCommand implements Command {
|
||||
|
||||
@@ -228,10 +228,10 @@ to-regex-range@^5.0.1:
|
||||
dependencies:
|
||||
is-number "^7.0.0"
|
||||
|
||||
typescript@^5.3.1-rc:
|
||||
version "5.3.1-rc"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.1-rc.tgz#c307d4b69ea0c1c2cd17d4dd7700d30f7197f829"
|
||||
integrity sha512-NVq/AufFc6KVjmVPcuVwdCkhTQlTcMEyRYJPvaGhPvj+X80MYUF+90qf0//uvINPb2ULg9m91/gbdIOhN2cZqA==
|
||||
typescript@5.3.2:
|
||||
version "5.3.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.2.tgz#00d1c7c1c46928c5845c1ee8d0cc2791031d4c43"
|
||||
integrity sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==
|
||||
|
||||
vscode-grammar-updater@^1.1.0:
|
||||
version "1.1.0"
|
||||
|
||||
+2
-2
@@ -99,7 +99,6 @@
|
||||
"native-watchdog": "^1.4.1",
|
||||
"node-pty": "1.1.0-beta5",
|
||||
"tas-client-umd": "0.1.8",
|
||||
"util": "^0.12.4",
|
||||
"v8-inspect-profiler": "^0.1.0",
|
||||
"vscode-oniguruma": "1.7.0",
|
||||
"vscode-regexpp": "^3.1.0",
|
||||
@@ -211,6 +210,7 @@
|
||||
"tsec": "0.2.7",
|
||||
"typescript": "^5.4.0-dev.20231116",
|
||||
"typescript-formatter": "7.1.0",
|
||||
"util": "^0.12.4",
|
||||
"vscode-nls-dev": "^3.3.1",
|
||||
"webpack": "^5.77.0",
|
||||
"webpack-cli": "^5.0.1",
|
||||
@@ -228,4 +228,4 @@
|
||||
"optionalDependencies": {
|
||||
"windows-foreground-love": "0.5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
echo.
|
||||
echo ### Colorize tests
|
||||
call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-colorize-tests\test --extensionDevelopmentPath=%~dp0\..\extensions\vscode-colorize-tests --extensionTestsPath=%~dp0\..\extensions\vscode-colorize-tests\out %API_TESTS_EXTRA_ARGS%
|
||||
call yarn test-extension -l vscode-colorize-tests
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
echo.
|
||||
@@ -89,7 +89,7 @@ echo.
|
||||
echo ### Configuration editing tests
|
||||
set CFWORKSPACE=%TEMPDIR%\cf-%RANDOM%
|
||||
mkdir %CFWORKSPACE%
|
||||
call "%INTEGRATION_TEST_ELECTRON_PATH%" %CFWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\configuration-editing --extensionTestsPath=%~dp0\..\extensions\configuration-editing\out\test %API_TESTS_EXTRA_ARGS%
|
||||
call yarn test-extension -l configuration-editing
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
echo.
|
||||
|
||||
@@ -67,7 +67,7 @@ kill_app
|
||||
echo
|
||||
echo "### Colorize tests"
|
||||
echo
|
||||
"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out $API_TESTS_EXTRA_ARGS
|
||||
yarn test-extension -l vscode-colorize-tests
|
||||
kill_app
|
||||
|
||||
echo
|
||||
@@ -109,7 +109,7 @@ kill_app
|
||||
echo
|
||||
echo "### Configuration editing tests"
|
||||
echo
|
||||
"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $(mktemp -d 2>/dev/null) --extensionDevelopmentPath=$ROOT/extensions/configuration-editing --extensionTestsPath=$ROOT/extensions/configuration-editing/out/test $API_TESTS_EXTRA_ARGS
|
||||
yarn test-extension -l configuration-editing
|
||||
kill_app
|
||||
|
||||
echo
|
||||
|
||||
Binary file not shown.
@@ -580,6 +580,7 @@ export const Codicon = {
|
||||
micFilled: register('mic-filled', 0xec1c),
|
||||
gitFetch: register('git-fetch', 0xec1d),
|
||||
copilot: register('copilot', 0xec1e),
|
||||
lightbulbSparkle: register('lightbulb-sparkle', 0xec1f),
|
||||
|
||||
// derived icons, that could become separate icons
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { equalsIgnoreCase, startsWithIgnoreCase } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export namespace Schemas {
|
||||
@@ -109,6 +110,18 @@ export namespace Schemas {
|
||||
export const vscodeSourceControl = 'vscode-scm';
|
||||
}
|
||||
|
||||
export function matchesScheme(target: URI | string, scheme: string): boolean {
|
||||
if (URI.isUri(target)) {
|
||||
return equalsIgnoreCase(target.scheme, scheme);
|
||||
} else {
|
||||
return startsWithIgnoreCase(target, scheme + ':');
|
||||
}
|
||||
}
|
||||
|
||||
export function matchesSomeScheme(target: URI | string, ...schemes: string[]): boolean {
|
||||
return schemes.some(scheme => matchesScheme(target, scheme));
|
||||
}
|
||||
|
||||
export const connectionTokenCookieName = 'vscode-tkn';
|
||||
export const connectionTokenQueryName = 'tkn';
|
||||
|
||||
|
||||
@@ -11,8 +11,7 @@ import * as path from 'vs/base/common/path';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
import { Promises } from 'vs/base/node/pfs';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Entry, open as _openZip, ZipFile } from 'yauzl';
|
||||
import * as yazl from 'yazl';
|
||||
import type { Entry, ZipFile } from 'yauzl';
|
||||
|
||||
export const CorruptZipMessage: string = 'end of central directory record signature not found';
|
||||
const CORRUPT_ZIP_PATTERN = new RegExp(CorruptZipMessage);
|
||||
@@ -161,9 +160,11 @@ function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions, tok
|
||||
}).finally(() => listener.dispose());
|
||||
}
|
||||
|
||||
function openZip(zipFile: string, lazy: boolean = false): Promise<ZipFile> {
|
||||
async function openZip(zipFile: string, lazy: boolean = false): Promise<ZipFile> {
|
||||
const { open } = await import('yauzl');
|
||||
|
||||
return new Promise<ZipFile>((resolve, reject) => {
|
||||
_openZip(zipFile, lazy ? { lazyEntries: true } : undefined!, (error?: Error, zipfile?: ZipFile) => {
|
||||
open(zipFile, lazy ? { lazyEntries: true } : undefined!, (error?: Error, zipfile?: ZipFile) => {
|
||||
if (error) {
|
||||
reject(toExtractError(error));
|
||||
} else {
|
||||
@@ -191,9 +192,11 @@ export interface IFile {
|
||||
localPath?: string;
|
||||
}
|
||||
|
||||
export function zip(zipPath: string, files: IFile[]): Promise<string> {
|
||||
export async function zip(zipPath: string, files: IFile[]): Promise<string> {
|
||||
const { ZipFile } = await import('yazl');
|
||||
|
||||
return new Promise<string>((c, e) => {
|
||||
const zip = new yazl.ZipFile();
|
||||
const zip = new ZipFile();
|
||||
files.forEach(f => {
|
||||
if (f.contents) {
|
||||
zip.addBuffer(typeof f.contents === 'string' ? Buffer.from(f.contents, 'utf8') : f.contents, f.path);
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
'self'
|
||||
vscode-remote-resource:
|
||||
vscode-managed-remote-resource:
|
||||
https://*.vscode-unpkg.net
|
||||
;
|
||||
require-trusted-types-for
|
||||
'script'
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
'self'
|
||||
vscode-remote-resource:
|
||||
vscode-managed-remote-resource:
|
||||
https://*.vscode-unpkg.net
|
||||
;
|
||||
require-trusted-types-for
|
||||
'script'
|
||||
|
||||
@@ -10,13 +10,13 @@ import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { LinkedList } from 'vs/base/common/linkedList';
|
||||
import { ResourceMap } from 'vs/base/common/map';
|
||||
import { parse } from 'vs/base/common/marshalling';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { matchesScheme, matchesSomeScheme, Schemas } from 'vs/base/common/network';
|
||||
import { normalizePath } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { EditorOpenSource } from 'vs/platform/editor/common/editor';
|
||||
import { extractSelection, IExternalOpener, IExternalUriResolver, IOpener, IOpenerService, IResolvedExternalUri, IValidator, matchesScheme, matchesSomeScheme, OpenOptions, ResolveExternalUriOptions } from 'vs/platform/opener/common/opener';
|
||||
import { extractSelection, IExternalOpener, IExternalUriResolver, IOpener, IOpenerService, IResolvedExternalUri, IValidator, OpenOptions, ResolveExternalUriOptions } from 'vs/platform/opener/common/opener';
|
||||
|
||||
class CommandOpener implements IOpener {
|
||||
|
||||
|
||||
@@ -275,13 +275,14 @@ export class GlyphMarginWidgets extends ViewPart {
|
||||
|
||||
for (const widget of Object.values(this._widgets)) {
|
||||
const range = widget.preference.range;
|
||||
if (range.endLineNumber < visibleStartLineNumber || range.startLineNumber > visibleEndLineNumber) {
|
||||
const { startLineNumber, endLineNumber } = this._context.viewModel.coordinatesConverter.convertModelRangeToViewRange(Range.lift(range));
|
||||
if (!startLineNumber || !endLineNumber || endLineNumber < visibleStartLineNumber || startLineNumber > visibleEndLineNumber) {
|
||||
// The widget is not in the viewport
|
||||
continue;
|
||||
}
|
||||
|
||||
// The widget is in the viewport, find a good line for it
|
||||
const widgetLineNumber = Math.max(range.startLineNumber, visibleStartLineNumber);
|
||||
const widgetLineNumber = Math.max(startLineNumber, visibleStartLineNumber);
|
||||
const lane = Math.min(widget.preference.lane, this._glyphMarginDecorationLaneCount);
|
||||
requests.push(new WidgetBasedGlyphRenderRequest(widgetLineNumber, lane, widget.preference.zIndex, widget));
|
||||
}
|
||||
|
||||
@@ -62,3 +62,7 @@
|
||||
height: 16px;
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
.monaco-diff-editor .revertButton {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { ThemeIcon } from 'vs/base/common/themables';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { localize } from 'vs/nls';
|
||||
@@ -76,12 +75,3 @@ export const diffDeleteDecorationEmpty = ModelDecorationOptions.register({
|
||||
className: 'char-delete diff-range-empty',
|
||||
description: 'char-delete diff-range-empty',
|
||||
});
|
||||
|
||||
|
||||
export const arrowRevertChange = ModelDecorationOptions.register({
|
||||
description: 'diff-editor-arrow-revert-change',
|
||||
glyphMarginHoverMessage: new MarkdownString(undefined, { isTrusted: true, supportThemeIcons: true })
|
||||
.appendMarkdown(localize('revertChangeHoverMessage', 'Click to revert change')),
|
||||
glyphMarginClassName: 'arrow-revert-change ' + ThemeIcon.asClassName(Codicon.arrowRight),
|
||||
zIndex: 10001,
|
||||
});
|
||||
|
||||
@@ -3,26 +3,36 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IObservable, derived } from 'vs/base/common/observable';
|
||||
import { arrowRevertChange, diffAddDecoration, diffAddDecorationEmpty, diffDeleteDecoration, diffDeleteDecorationEmpty, diffLineAddDecorationBackground, diffLineAddDecorationBackgroundWithIndicator, diffLineDeleteDecorationBackground, diffLineDeleteDecorationBackgroundWithIndicator, diffWholeLineAddDecoration, diffWholeLineDeleteDecoration } from 'vs/editor/browser/widget/diffEditor/decorations';
|
||||
import { h } from 'vs/base/browser/dom';
|
||||
import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IObservable, autorunWithStore, derived } from 'vs/base/common/observable';
|
||||
import { IGlyphMarginWidget, IGlyphMarginWidgetPosition } from 'vs/editor/browser/editorBrowser';
|
||||
import { diffAddDecoration, diffAddDecorationEmpty, diffDeleteDecoration, diffDeleteDecorationEmpty, diffLineAddDecorationBackground, diffLineAddDecorationBackgroundWithIndicator, diffLineDeleteDecorationBackground, diffLineDeleteDecorationBackgroundWithIndicator, diffWholeLineAddDecoration, diffWholeLineDeleteDecoration } from 'vs/editor/browser/widget/diffEditor/decorations';
|
||||
import { DiffEditorEditors } from 'vs/editor/browser/widget/diffEditor/diffEditorEditors';
|
||||
import { DiffEditorOptions } from 'vs/editor/browser/widget/diffEditor/diffEditorOptions';
|
||||
import { DiffEditorViewModel } from 'vs/editor/browser/widget/diffEditor/diffEditorViewModel';
|
||||
import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget';
|
||||
import { MovedBlocksLinesPart } from 'vs/editor/browser/widget/diffEditor/movedBlocksLines';
|
||||
import { applyObservableDecorations } from 'vs/editor/browser/widget/diffEditor/utils';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { LineRange, LineRangeSet } from 'vs/editor/common/core/lineRange';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IModelDeltaDecoration } from 'vs/editor/common/model';
|
||||
import { RangeMapping } from 'vs/editor/common/diff/rangeMapping';
|
||||
import { GlyphMarginLane, IModelDeltaDecoration } from 'vs/editor/common/model';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export class DiffEditorDecorations extends Disposable {
|
||||
constructor(
|
||||
private readonly _editors: DiffEditorEditors,
|
||||
private readonly _diffModel: IObservable<DiffEditorViewModel | undefined>,
|
||||
private readonly _options: DiffEditorOptions,
|
||||
widget: DiffEditorWidget,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(new RevertButtonsFeature(_editors, _diffModel, _options, widget));
|
||||
|
||||
this._register(applyObservableDecorations(this._editors.original, this._decorations.map(d => d?.originalDecorations || [])));
|
||||
this._register(applyObservableDecorations(this._editors.modified, this._decorations.map(d => d?.modifiedDecorations || [])));
|
||||
}
|
||||
@@ -66,10 +76,6 @@ export class DiffEditorDecorations extends Disposable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!m.lineRangeMapping.modified.isEmpty && this._options.shouldRenderRevertArrows.read(reader) && !movedTextToCompare) {
|
||||
modifiedDecorations.push({ range: Range.fromPositions(new Position(m.lineRangeMapping.modified.startLineNumber, 1)), options: arrowRevertChange });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,3 +119,139 @@ export class DiffEditorDecorations extends Disposable {
|
||||
return { originalDecorations, modifiedDecorations };
|
||||
});
|
||||
}
|
||||
|
||||
class RevertButtonsFeature extends Disposable {
|
||||
constructor(
|
||||
private readonly _editors: DiffEditorEditors,
|
||||
private readonly _diffModel: IObservable<DiffEditorViewModel | undefined>,
|
||||
private readonly _options: DiffEditorOptions,
|
||||
private readonly _widget: DiffEditorWidget,
|
||||
) {
|
||||
super();
|
||||
|
||||
const emptyArr: never[] = [];
|
||||
const selectedDiffs = derived(this, (reader) => {
|
||||
/** @description selectedDiffs */
|
||||
|
||||
const model = this._diffModel.read(reader);
|
||||
const diff = model?.diff.read(reader);
|
||||
if (!diff) { return emptyArr; }
|
||||
|
||||
const selections = this._editors.modifiedSelections.read(reader);
|
||||
|
||||
if (selections.every(s => s.isEmpty())) {
|
||||
return emptyArr;
|
||||
}
|
||||
|
||||
const lineRanges = new LineRangeSet(selections.map(s => LineRange.fromRangeInclusive(s)));
|
||||
|
||||
const mappings = diff.mappings.filter(m => m.lineRangeMapping.innerChanges && lineRanges.intersects(m.lineRangeMapping.modified));
|
||||
|
||||
const result = mappings.map(mapping => ({
|
||||
mapping,
|
||||
rangeMappings: mapping.lineRangeMapping.innerChanges!.filter(c => selections.some(s => Range.areIntersecting(c.modifiedRange, s)))
|
||||
}));
|
||||
if (result.length === 0 || result.every(r => r.rangeMappings.length === 0)) { return emptyArr; }
|
||||
return result;
|
||||
});
|
||||
|
||||
this._register(autorunWithStore((reader, store) => {
|
||||
const model = this._diffModel.read(reader);
|
||||
const diff = model?.diff.read(reader);
|
||||
if (!model || !diff) { return; }
|
||||
const movedTextToCompare = this._diffModel.read(reader)!.movedTextToCompare.read(reader);
|
||||
if (movedTextToCompare) { return; }
|
||||
if (!this._options.shouldRenderRevertArrows.read(reader)) { return; }
|
||||
|
||||
const glyphWidgetsModified: IGlyphMarginWidget[] = [];
|
||||
|
||||
const selectedDiffs_ = selectedDiffs.read(reader);
|
||||
const diffsSet = new Set(selectedDiffs_.map(d => d.mapping));
|
||||
|
||||
if (selectedDiffs_.length > 0) {
|
||||
const selections = this._editors.modifiedSelections.read(reader);
|
||||
|
||||
const btn = new RevertButton(selections[selections.length - 1].positionLineNumber, this._widget, selectedDiffs_.flatMap(d => d.rangeMappings), true);
|
||||
this._editors.modified.addGlyphMarginWidget(btn);
|
||||
glyphWidgetsModified.push(btn);
|
||||
}
|
||||
|
||||
for (const m of diff.mappings) {
|
||||
if (diffsSet.has(m)) {
|
||||
continue;
|
||||
}
|
||||
if (!m.lineRangeMapping.modified.isEmpty && m.lineRangeMapping.innerChanges) {
|
||||
const btn = new RevertButton(m.lineRangeMapping.modified.startLineNumber, this._widget, m.lineRangeMapping.innerChanges, false);
|
||||
this._editors.modified.addGlyphMarginWidget(btn);
|
||||
glyphWidgetsModified.push(btn);
|
||||
}
|
||||
}
|
||||
|
||||
store.add(toDisposable(() => {
|
||||
for (const w of glyphWidgetsModified) {
|
||||
this._editors.modified.removeGlyphMarginWidget(w);
|
||||
}
|
||||
}));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class RevertButton implements IGlyphMarginWidget {
|
||||
public static counter = 0;
|
||||
|
||||
private readonly _id: string = `revertButton${RevertButton.counter++}`;
|
||||
|
||||
getId(): string { return this._id; }
|
||||
|
||||
private readonly _domNode = h('div.revertButton', {
|
||||
title: this._selection
|
||||
? localize('revertSelectedChanges', 'Revert Selected Changes')
|
||||
: localize('revertChange', 'Revert Change')
|
||||
},
|
||||
[renderIcon(Codicon.arrowRight)]
|
||||
).root;
|
||||
|
||||
constructor(
|
||||
private readonly _lineNumber: number,
|
||||
private readonly _widget: DiffEditorWidget,
|
||||
private readonly _diffs: RangeMapping[],
|
||||
private readonly _selection: boolean,
|
||||
) {
|
||||
this._domNode.onmousedown = e => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
this._domNode.onmouseup = e => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
this._domNode.onclick = (e) => {
|
||||
this._widget.revertRangeMappings(this._diffs);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the dom node of the glyph widget.
|
||||
*/
|
||||
getDomNode(): HTMLElement {
|
||||
return this._domNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the placement of the glyph widget.
|
||||
*/
|
||||
getPosition(): IGlyphMarginWidgetPosition {
|
||||
return {
|
||||
lane: GlyphMarginLane.Right,
|
||||
range: {
|
||||
startColumn: 1,
|
||||
startLineNumber: this._lineNumber,
|
||||
endColumn: 1,
|
||||
endLineNumber: this._lineNumber,
|
||||
},
|
||||
zIndex: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { DiffEditorOptions } from './diffEditorOptions';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { IDiffCodeEditorWidgetOptions } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
|
||||
export class DiffEditorEditors extends Disposable {
|
||||
public readonly modified: CodeEditorWidget;
|
||||
@@ -30,6 +32,9 @@ export class DiffEditorEditors extends Disposable {
|
||||
|
||||
public readonly modifiedModel: IObservable<ITextModel | null>;
|
||||
|
||||
public readonly modifiedSelections: IObservable<Selection[]>;
|
||||
public readonly modifiedCursor: IObservable<Position>;
|
||||
|
||||
constructor(
|
||||
private readonly originalEditorElement: HTMLElement,
|
||||
private readonly modifiedEditorElement: HTMLElement,
|
||||
@@ -49,6 +54,9 @@ export class DiffEditorEditors extends Disposable {
|
||||
this.modifiedScrollTop = observableFromEvent(this.modified.onDidScrollChange, () => /** @description modified.getScrollTop */ this.modified.getScrollTop());
|
||||
this.modifiedScrollHeight = observableFromEvent(this.modified.onDidScrollChange, () => /** @description modified.getScrollHeight */ this.modified.getScrollHeight());
|
||||
|
||||
this.modifiedSelections = observableFromEvent(this.modified.onDidChangeCursorSelection, () => this.modified.getSelections() ?? []);
|
||||
this.modifiedCursor = observableFromEvent(this.modified.onDidChangeCursorPosition, () => this.modified.getPosition() ?? new Position(1, 1));
|
||||
|
||||
this._register(autorunHandleChanges({
|
||||
createEmptyChangeSummary: () => ({} as IDiffEditorConstructionOptions),
|
||||
handleChange: (ctx, changeSummary) => {
|
||||
|
||||
@@ -104,12 +104,10 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo
|
||||
const lastUnchangedRegions = this._unchangedRegions.get();
|
||||
const lastUnchangedRegionsOrigRanges = lastUnchangedRegions.originalDecorationIds
|
||||
.map(id => model.original.getDecorationRange(id))
|
||||
.filter(r => !!r)
|
||||
.map(r => LineRange.fromRange(r!));
|
||||
.map(r => r ? LineRange.fromRange(r) : undefined);
|
||||
const lastUnchangedRegionsModRanges = lastUnchangedRegions.modifiedDecorationIds
|
||||
.map(id => model.modified.getDecorationRange(id))
|
||||
.filter(r => !!r)
|
||||
.map(r => LineRange.fromRange(r!));
|
||||
.map(r => r ? LineRange.fromRange(r) : undefined);
|
||||
|
||||
const originalDecorationIds = model.original.deltaDecorations(
|
||||
lastUnchangedRegions.originalDecorationIds,
|
||||
@@ -123,8 +121,8 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo
|
||||
|
||||
for (const r of newUnchangedRegions) {
|
||||
for (let i = 0; i < lastUnchangedRegions.regions.length; i++) {
|
||||
if (r.originalUnchangedRange.intersectsStrict(lastUnchangedRegionsOrigRanges[i])
|
||||
&& r.modifiedUnchangedRange.intersectsStrict(lastUnchangedRegionsModRanges[i])) {
|
||||
if (lastUnchangedRegionsOrigRanges[i] && r.originalUnchangedRange.intersectsStrict(lastUnchangedRegionsOrigRanges[i]!)
|
||||
&& lastUnchangedRegionsModRanges[i] && r.modifiedUnchangedRange.intersectsStrict(lastUnchangedRegionsModRanges[i]!)) {
|
||||
r.setHiddenModifiedRange(lastUnchangedRegions.regions[i].getHiddenModifiedRange(undefined), tx);
|
||||
break;
|
||||
}
|
||||
@@ -156,6 +154,7 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo
|
||||
}
|
||||
}
|
||||
|
||||
this._isDiffUpToDate.set(false, undefined);
|
||||
debouncer.schedule();
|
||||
}));
|
||||
this._register(model.original.onDidChangeContent((e) => {
|
||||
@@ -174,6 +173,7 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo
|
||||
}
|
||||
}
|
||||
|
||||
this._isDiffUpToDate.set(false, undefined);
|
||||
debouncer.schedule();
|
||||
}));
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { IObservable, ITransaction, autorunWithStore, derived, observableFromEve
|
||||
import { derivedDisposable } from 'vs/base/common/observableInternal/derived';
|
||||
import 'vs/css!./style';
|
||||
import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration';
|
||||
import { ICodeEditor, IDiffEditor, IDiffEditorConstructionOptions, IMouseTargetViewZone } from 'vs/editor/browser/editorBrowser';
|
||||
import { ICodeEditor, IDiffEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorExtensionsRegistry, IDiffEditorContributionDescription } from 'vs/editor/browser/editorExtensions';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll';
|
||||
@@ -31,7 +31,7 @@ import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { CursorChangeReason } from 'vs/editor/common/cursorEvents';
|
||||
import { IDiffComputationResult, ILineChange } from 'vs/editor/common/diff/legacyLinesDiffComputer';
|
||||
import { DetailedLineRangeMapping } from 'vs/editor/common/diff/rangeMapping';
|
||||
import { DetailedLineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping';
|
||||
import { EditorType, IDiffEditorModel, IDiffEditorViewModel, IDiffEditorViewState } from 'vs/editor/common/editorCommon';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
|
||||
@@ -171,7 +171,7 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor {
|
||||
derivedDisposable(this, reader => /** @description DiffEditorDecorations */
|
||||
this._instantiationService.createInstance(
|
||||
readHotReloadableExport(DiffEditorDecorations, reader),
|
||||
this._editors, this._diffModel, this._options,
|
||||
this._editors, this._diffModel, this._options, this,
|
||||
)
|
||||
).recomputeInitiallyAndOnChange(this._store);
|
||||
|
||||
@@ -260,27 +260,6 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor {
|
||||
),
|
||||
}));
|
||||
|
||||
// Revert change when an arrow is clicked.
|
||||
this._register(this._editors.modified.onMouseDown(event => {
|
||||
if (!event.event.rightButton && event.target.position && event.target.element?.className.includes('arrow-revert-change')) {
|
||||
const lineNumber = event.target.position.lineNumber;
|
||||
const viewZone = event.target as IMouseTargetViewZone | undefined;
|
||||
|
||||
const model = this._diffModel.get();
|
||||
if (!model) { return; }
|
||||
const diffs = model.diff.get()?.mappings;
|
||||
if (!diffs) { return; }
|
||||
const diff = diffs.find(d =>
|
||||
viewZone?.detail.afterLineNumber === d.lineRangeMapping.modified.startLineNumber - 1 ||
|
||||
d.lineRangeMapping.modified.startLineNumber === lineNumber
|
||||
);
|
||||
if (!diff) { return; }
|
||||
this.revert(diff.lineRangeMapping);
|
||||
|
||||
event.event.stopPropagation();
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(Event.runAndSubscribe(this._editors.modified.onDidChangeCursorPosition, (e) => {
|
||||
if (e?.reason === CursorChangeReason.Explicit) {
|
||||
const diff = this._diffModel.get()?.diff.get()?.mappings.find(m => m.lineRangeMapping.modified.contains(e.position.lineNumber));
|
||||
@@ -486,20 +465,29 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor {
|
||||
}
|
||||
|
||||
revert(diff: DetailedLineRangeMapping): void {
|
||||
if (diff.innerChanges) {
|
||||
this.revertRangeMappings(diff.innerChanges);
|
||||
}
|
||||
|
||||
const model = this._diffModel.get()?.model;
|
||||
if (!model) { return; }
|
||||
|
||||
const changes: IIdentifiedSingleEditOperation[] = diff.innerChanges
|
||||
? diff.innerChanges.map<IIdentifiedSingleEditOperation>(c => ({
|
||||
range: c.modifiedRange,
|
||||
text: model.original.getValueInRange(c.originalRange)
|
||||
}))
|
||||
: [
|
||||
{
|
||||
range: diff.modified.toExclusiveRange(),
|
||||
text: model.original.getValueInRange(diff.original.toExclusiveRange())
|
||||
}
|
||||
];
|
||||
this._editors.modified.executeEdits('diffEditor', [
|
||||
{
|
||||
range: diff.modified.toExclusiveRange(),
|
||||
text: model.original.getValueInRange(diff.original.toExclusiveRange())
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
revertRangeMappings(diffs: RangeMapping[]): void {
|
||||
const model = this._diffModel.get();
|
||||
if (!model || !model.isDiffUpToDate.get()) { return; }
|
||||
|
||||
const changes: IIdentifiedSingleEditOperation[] = diffs.map<IIdentifiedSingleEditOperation>(c => ({
|
||||
range: c.modifiedRange,
|
||||
text: model.model.original.getValueInRange(c.originalRange)
|
||||
}));
|
||||
|
||||
this._editors.modified.executeEdits('diffEditor', changes);
|
||||
}
|
||||
|
||||
@@ -6,30 +6,33 @@ import { h } from 'vs/base/browser/dom';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { autorun, derived } from 'vs/base/common/observable';
|
||||
import { globalTransaction, observableValue } from 'vs/base/common/observableInternal/base';
|
||||
import { autorun, derived, observableFromEvent } from 'vs/base/common/observable';
|
||||
import { IObservable, globalTransaction, observableValue } from 'vs/base/common/observableInternal/base';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget';
|
||||
import { IDocumentDiffItem } from 'vs/editor/browser/widget/multiDiffEditorWidget/model';
|
||||
import { DocumentDiffItemViewModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel';
|
||||
import { IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory';
|
||||
import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { OffsetRange } from 'vs/editor/common/core/offsetRange';
|
||||
import { IDiffEditorViewModel } from 'vs/editor/common/editorCommon';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IObjectData, IPooledObject } from './objectPool';
|
||||
import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
|
||||
export class TemplateData implements IObjectData {
|
||||
constructor(
|
||||
public readonly viewModel: IDiffEditorViewModel,
|
||||
public readonly entry: IDocumentDiffItem,
|
||||
public readonly viewModel: DocumentDiffItemViewModel,
|
||||
) { }
|
||||
|
||||
|
||||
getId(): unknown {
|
||||
return this.entry;
|
||||
return this.viewModel;
|
||||
}
|
||||
}
|
||||
|
||||
export class DiffEditorItemTemplate extends Disposable implements IPooledObject<TemplateData> {
|
||||
private readonly _viewModel = observableValue<DocumentDiffItemViewModel | undefined>(this, undefined);
|
||||
|
||||
private readonly _collapsed = derived(this, reader => this._viewModel.read(reader)?.collapsed.read(reader));
|
||||
|
||||
private readonly _contentHeight = observableValue<number>(this, 500);
|
||||
public readonly height = derived(this, reader => {
|
||||
const h = this._collapsed.read(reader) ? 0 : this._contentHeight.read(reader);
|
||||
@@ -51,29 +54,21 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject<
|
||||
}
|
||||
});
|
||||
|
||||
private readonly _collapsed = observableValue<boolean>(this, false);
|
||||
|
||||
private readonly _elements = h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}
|
||||
}, [
|
||||
h('div', {
|
||||
private readonly _elements = h('div.multiDiffEntry', [
|
||||
h('div.content', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
|
||||
flex: '1',
|
||||
overflow: 'hidden',
|
||||
}
|
||||
}, [
|
||||
h('div@header', { style: { display: 'flex', alignItems: 'center', padding: '8px 5px', color: 'var(--vscode-foreground)', background: 'var(--vscode-editor-background)', zIndex: '10000' } }, [
|
||||
h('div.expand-button@collapseButton', { style: { margin: '0 5px' } }),
|
||||
h('div.show-file-icons@title', { style: { fontSize: '14px', lineHeight: '22px' } }, [] as any),
|
||||
h('div.header@header', [
|
||||
h('div.collapse-button@collapseButton'),
|
||||
h('div.title.show-file-icons@title', [] as any),
|
||||
]),
|
||||
|
||||
h('div', {
|
||||
h('div.editorParent', {
|
||||
style: {
|
||||
flex: '1',
|
||||
display: 'flex',
|
||||
@@ -85,10 +80,14 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject<
|
||||
])
|
||||
]);
|
||||
|
||||
private readonly _editor = this._register(this._instantiationService.createInstance(DiffEditorWidget, this._elements.editor, {
|
||||
public readonly editor = this._register(this._instantiationService.createInstance(DiffEditorWidget, this._elements.editor, {
|
||||
overflowWidgetsDomNode: this._overflowWidgetsDomNode,
|
||||
}, {}));
|
||||
|
||||
private readonly isModifedFocused = isFocused(this.editor.getModifiedEditor());
|
||||
private readonly isOriginalFocused = isFocused(this.editor.getOriginalEditor());
|
||||
public readonly isFocused = derived(this, reader => this.isModifedFocused.read(reader) || this.isOriginalFocused.read(reader));
|
||||
|
||||
private readonly _resourceLabel = this._workbenchUIElementFactory.createResourceLabel
|
||||
? this._register(this._workbenchUIElementFactory.createResourceLabel(this._elements.title))
|
||||
: undefined;
|
||||
@@ -110,25 +109,28 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject<
|
||||
btn.icon = this._collapsed.read(reader) ? Codicon.chevronRight : Codicon.chevronDown;
|
||||
}));
|
||||
this._register(btn.onDidClick(() => {
|
||||
this._collapsed.set(!this._collapsed.get(), undefined);
|
||||
this._elements.editor.style.display = this._collapsed.get() ? 'none' : 'block';
|
||||
this._viewModel.get()?.collapsed.set(!this._collapsed.get(), undefined);
|
||||
}));
|
||||
|
||||
this._editor.getModifiedEditor().onDidLayoutChange(e => {
|
||||
const width = this._editor.getModifiedEditor().getLayoutInfo().contentWidth;
|
||||
this._register(autorun(reader => {
|
||||
this._elements.editor.style.display = this._collapsed.read(reader) ? 'none' : 'block';
|
||||
}));
|
||||
|
||||
this.editor.getModifiedEditor().onDidLayoutChange(e => {
|
||||
const width = this.editor.getModifiedEditor().getLayoutInfo().contentWidth;
|
||||
this._modifiedWidth.set(width, undefined);
|
||||
});
|
||||
|
||||
this._editor.getOriginalEditor().onDidLayoutChange(e => {
|
||||
const width = this._editor.getOriginalEditor().getLayoutInfo().contentWidth;
|
||||
this.editor.getOriginalEditor().onDidLayoutChange(e => {
|
||||
const width = this.editor.getOriginalEditor().getLayoutInfo().contentWidth;
|
||||
this._originalWidth.set(width, undefined);
|
||||
});
|
||||
|
||||
this._register(this._editor.onDidContentSizeChange(e => {
|
||||
this._register(this.editor.onDidContentSizeChange(e => {
|
||||
globalTransaction(tx => {
|
||||
this._contentHeight.set(e.contentHeight, tx);
|
||||
this._modifiedContentWidth.set(this._editor.getModifiedEditor().getContentWidth(), tx);
|
||||
this._originalContentWidth.set(this._editor.getOriginalEditor().getContentWidth(), tx);
|
||||
this._modifiedContentWidth.set(this.editor.getModifiedEditor().getContentWidth(), tx);
|
||||
this._originalContentWidth.set(this.editor.getOriginalEditor().getContentWidth(), tx);
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -138,18 +140,15 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject<
|
||||
|
||||
public setScrollLeft(left: number): void {
|
||||
if (this._modifiedContentWidth.get() - this._modifiedWidth.get() > this._originalContentWidth.get() - this._originalWidth.get()) {
|
||||
this._editor.getModifiedEditor().setScrollLeft(left);
|
||||
this.editor.getModifiedEditor().setScrollLeft(left);
|
||||
} else {
|
||||
this._editor.getOriginalEditor().setScrollLeft(left);
|
||||
this.editor.getOriginalEditor().setScrollLeft(left);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly _dataStore = new DisposableStore();
|
||||
|
||||
public setData(data: TemplateData): void {
|
||||
this._resourceLabel?.setUri(data.viewModel.model.modified.uri);
|
||||
this._dataStore.clear();
|
||||
|
||||
function updateOptions(options: IDiffEditorOptions): IDiffEditorOptions {
|
||||
return {
|
||||
...options,
|
||||
@@ -161,20 +160,26 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject<
|
||||
vertical: 'hidden',
|
||||
horizontal: 'hidden',
|
||||
handleMouseWheel: false,
|
||||
useShadows: false,
|
||||
},
|
||||
renderOverviewRuler: false,
|
||||
fixedOverflowWidgets: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (data.entry.onOptionsDidChange) {
|
||||
this._dataStore.add(data.entry.onOptionsDidChange(() => {
|
||||
this._editor.updateOptions(updateOptions(data.entry.options ?? {}));
|
||||
const value = data.viewModel.entry.value!; // TODO
|
||||
|
||||
if (value.onOptionsDidChange) {
|
||||
this._dataStore.add(value.onOptionsDidChange(() => {
|
||||
this.editor.updateOptions(updateOptions(value.options ?? {}));
|
||||
}));
|
||||
}
|
||||
globalTransaction(tx => {
|
||||
this._editor.setModel(data.viewModel, tx);
|
||||
this._editor.updateOptions(updateOptions(data.entry.options ?? {}));
|
||||
this._resourceLabel?.setUri(data.viewModel.diffEditorViewModel.model.modified.uri);
|
||||
this._dataStore.clear();
|
||||
this._viewModel.set(data.viewModel, tx);
|
||||
this.editor.setModel(data.viewModel.diffEditorViewModel, tx);
|
||||
this.editor.updateOptions(updateOptions(value.options ?? {}));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -186,15 +191,18 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject<
|
||||
this._elements.root.style.position = 'absolute';
|
||||
|
||||
// For sticky scroll
|
||||
this._elements.header.style.transform = `translateY(${Math.max(0, Math.min(verticalRange.length - this._elements.header.clientHeight, viewPort.start - verticalRange.start))}px)`;
|
||||
const delta = Math.max(0, Math.min(verticalRange.length - this._elements.header.clientHeight, viewPort.start - verticalRange.start));
|
||||
this._elements.header.style.transform = `translateY(${delta}px)`;
|
||||
|
||||
globalTransaction(tx => {
|
||||
this._editor.layout({
|
||||
this.editor.layout({
|
||||
width: width,
|
||||
height: verticalRange.length - this._outerEditorHeight,
|
||||
});
|
||||
});
|
||||
this._editor.getOriginalEditor().setScrollTop(editorScroll);
|
||||
this.editor.getOriginalEditor().setScrollTop(editorScroll);
|
||||
|
||||
this._elements.header.classList.toggle('shadow', delta > 0 || editorScroll > 0);
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
@@ -202,3 +210,15 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject<
|
||||
this._elements.root.style.visibility = 'hidden'; // Some editor parts are still visible
|
||||
}
|
||||
}
|
||||
|
||||
function isFocused(editor: ICodeEditor): IObservable<boolean> {
|
||||
return observableFromEvent(
|
||||
h => {
|
||||
const store = new DisposableStore();
|
||||
store.add(editor.onDidFocusEditorWidget(() => h(true)));
|
||||
store.add(editor.onDidBlurEditorWidget(() => h(false)));
|
||||
return store;
|
||||
},
|
||||
() => editor.hasWidgetFocus()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { derivedWithStore, observableFromEvent, observableValue } from 'vs/base/common/observable';
|
||||
import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget';
|
||||
import { IDocumentDiffItem, IMultiDiffEditorModel, LazyPromise } from 'vs/editor/browser/widget/multiDiffEditorWidget/model';
|
||||
import { IDiffEditorViewModel } from 'vs/editor/common/editorCommon';
|
||||
|
||||
export class MultiDiffEditorViewModel extends Disposable {
|
||||
private readonly _documents = observableFromEvent(this._model.onDidChange, /** @description MultiDiffEditorViewModel.documents */() => this._model.documents);
|
||||
|
||||
public readonly items = derivedWithStore<readonly DocumentDiffItemViewModel[]>(this,
|
||||
(reader, store) => this._documents.read(reader).map(d => store.add(new DocumentDiffItemViewModel(d, this._diffEditorViewModelFactory)))
|
||||
).recomputeInitiallyAndOnChange(this._store);
|
||||
|
||||
public readonly activeDiffItem = observableValue<DocumentDiffItemViewModel | undefined>(this, undefined);
|
||||
|
||||
constructor(
|
||||
private readonly _model: IMultiDiffEditorModel,
|
||||
private readonly _diffEditorViewModelFactory: DiffEditorWidget,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
export class DocumentDiffItemViewModel extends Disposable {
|
||||
public readonly diffEditorViewModel: IDiffEditorViewModel;
|
||||
|
||||
public readonly collapsed = observableValue<boolean>(this, false);
|
||||
|
||||
constructor(
|
||||
public readonly entry: LazyPromise<IDocumentDiffItem>,
|
||||
diffEditorViewModelFactory: DiffEditorWidget
|
||||
) {
|
||||
super();
|
||||
|
||||
this.diffEditorViewModel = this._register(diffEditorViewModelFactory.createViewModel({
|
||||
original: entry.value!.original!,
|
||||
modified: entry.value!.modified!,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,16 @@
|
||||
|
||||
import { Dimension } from 'vs/base/browser/dom';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { derivedWithStore, observableValue, recomputeInitiallyAndOnChange } from 'vs/base/common/observable';
|
||||
import { derived, derivedWithStore, observableValue, recomputeInitiallyAndOnChange } from 'vs/base/common/observable';
|
||||
import { readHotReloadableExport } from 'vs/editor/browser/widget/diffEditor/utils';
|
||||
import { IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/model';
|
||||
import { MultiDiffEditorViewModel, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl';
|
||||
import { MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl';
|
||||
import { MultiDiffEditorViewModel } from './multiDiffEditorViewModel';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import './colors';
|
||||
import { DiffEditorItemTemplate } from 'vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate';
|
||||
import { IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export class MultiDiffEditorWidget extends Disposable {
|
||||
private readonly _dimension = observableValue<Dimension | undefined>(this, undefined);
|
||||
@@ -50,4 +52,12 @@ export class MultiDiffEditorWidget extends Disposable {
|
||||
public layout(dimension: Dimension): void {
|
||||
this._dimension.set(dimension, undefined);
|
||||
}
|
||||
|
||||
private readonly _activeControl = derived(this, (reader) => this._widgetImpl.read(reader).activeControl.read(reader));
|
||||
|
||||
public getActiveControl(): any | undefined {
|
||||
return this._activeControl.get();
|
||||
}
|
||||
|
||||
public readonly onDidChangeActiveControl = Event.fromObservableLight(this._activeControl);
|
||||
}
|
||||
|
||||
@@ -6,20 +6,20 @@
|
||||
import { Dimension, getWindow, h, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
|
||||
import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { Disposable, IReference, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IObservable, IReader, autorun, derived, derivedWithStore, observableFromEvent, observableValue } from 'vs/base/common/observable';
|
||||
import { IObservable, IReader, autorun, derived, derivedObservableWithCache, derivedWithStore, observableFromEvent, observableValue } from 'vs/base/common/observable';
|
||||
import { Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import 'vs/css!./style';
|
||||
import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget';
|
||||
import { ObservableElementSizeObserver } from 'vs/editor/browser/widget/diffEditor/utils';
|
||||
import { IDocumentDiffItem, IMultiDiffEditorModel, LazyPromise } from 'vs/editor/browser/widget/multiDiffEditorWidget/model';
|
||||
import { IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/model';
|
||||
import { OffsetRange } from 'vs/editor/common/core/offsetRange';
|
||||
import { IDiffEditorViewModel } from 'vs/editor/common/editorCommon';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { DiffEditorItemTemplate, TemplateData } from './diffEditorItemTemplate';
|
||||
import { ObjectPool } from './objectPool';
|
||||
import { disposableObservableValue, globalTransaction, transaction } from 'vs/base/common/observableInternal/base';
|
||||
import { IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory';
|
||||
import { findFirstMaxBy } from 'vs/base/common/arraysFind';
|
||||
import { MultiDiffEditorViewModel, DocumentDiffItemViewModel } from './multiDiffEditorViewModel';
|
||||
|
||||
export class MultiDiffEditorWidgetImpl extends Disposable {
|
||||
private readonly _elements = h('div', {
|
||||
@@ -85,6 +85,9 @@ export class MultiDiffEditorWidgetImpl extends Disposable {
|
||||
);
|
||||
|
||||
private readonly _totalHeight = this._viewItems.map(this, (items, reader) => items.reduce((r, i) => r + i.contentHeight.read(reader), 0));
|
||||
public readonly activeDiffItem = derived(this, reader => this._viewItems.read(reader).find(i => i.template.read(reader)?.isFocused.read(reader)));
|
||||
public readonly lastActiveDiffItem = derivedObservableWithCache<VirtualizedViewItem | undefined>((reader, lastValue) => this.activeDiffItem.read(reader) ?? lastValue);
|
||||
public readonly activeControl = derived(this, reader => this.lastActiveDiffItem.read(reader)?.template.read(reader)?.editor);
|
||||
|
||||
constructor(
|
||||
private readonly _element: HTMLElement,
|
||||
@@ -95,6 +98,13 @@ export class MultiDiffEditorWidgetImpl extends Disposable {
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(autorun((reader) => {
|
||||
const lastActiveDiffItem = this.lastActiveDiffItem.read(reader);
|
||||
transaction(tx => {
|
||||
this._viewModel.read(reader)?.activeDiffItem.set(lastActiveDiffItem?.viewModel, tx);
|
||||
});
|
||||
}));
|
||||
|
||||
this._register(autorun((reader) => {
|
||||
/** @description Update widget dimension */
|
||||
const dimension = this._dimension.read(reader);
|
||||
@@ -179,37 +189,6 @@ export class MultiDiffEditorWidgetImpl extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
export class MultiDiffEditorViewModel extends Disposable {
|
||||
private readonly _documents = observableFromEvent(this._model.onDidChange, /** @description MultiDiffEditorViewModel.documents */() => this._model.documents);
|
||||
|
||||
public readonly items = derivedWithStore<readonly DocumentDiffItemViewModel[]>(this,
|
||||
(reader, store) => this._documents.read(reader).map(d => store.add(new DocumentDiffItemViewModel(d, this._diffEditorViewModelFactory)))
|
||||
).recomputeInitiallyAndOnChange(this._store);
|
||||
|
||||
constructor(
|
||||
private readonly _model: IMultiDiffEditorModel,
|
||||
private readonly _diffEditorViewModelFactory: DiffEditorWidget,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentDiffItemViewModel extends Disposable {
|
||||
public readonly diffEditorViewModel: IDiffEditorViewModel;
|
||||
|
||||
constructor(
|
||||
public readonly entry: LazyPromise<IDocumentDiffItem>,
|
||||
diffEditorViewModelFactory: DiffEditorWidget,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.diffEditorViewModel = this._register(diffEditorViewModelFactory.createViewModel({
|
||||
original: entry.value!.original!,
|
||||
modified: entry.value!.modified!,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class VirtualizedViewItem extends Disposable {
|
||||
// TODO this should be in the view model
|
||||
private readonly _lastTemplateData = observableValue<{ contentHeight: number; maxScroll: { maxScroll: number; width: number } }>(
|
||||
@@ -224,8 +203,10 @@ class VirtualizedViewItem extends Disposable {
|
||||
|
||||
public readonly maxScroll = derived(this, reader => this._templateRef.read(reader)?.object.maxScroll.read(reader) ?? this._lastTemplateData.read(reader).maxScroll);
|
||||
|
||||
public readonly template = derived(this, reader => this._templateRef.read(reader)?.object);
|
||||
|
||||
constructor(
|
||||
private readonly _viewModel: DocumentDiffItemViewModel,
|
||||
public readonly viewModel: DocumentDiffItemViewModel,
|
||||
private readonly _objectPool: ObjectPool<TemplateData, DiffEditorItemTemplate>,
|
||||
private readonly _scrollLeft: IObservable<number>,
|
||||
) {
|
||||
@@ -243,7 +224,7 @@ class VirtualizedViewItem extends Disposable {
|
||||
}
|
||||
|
||||
public override toString(): string {
|
||||
return `VirtualViewItem(${this._viewModel.entry.value!.title})`;
|
||||
return `VirtualViewItem(${this.viewModel.entry.value!.title})`;
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
@@ -263,7 +244,7 @@ class VirtualizedViewItem extends Disposable {
|
||||
public render(verticalSpace: OffsetRange, offset: number, width: number, viewPort: OffsetRange): void {
|
||||
let ref = this._templateRef.get();
|
||||
if (!ref) {
|
||||
ref = this._objectPool.getUnusedObj(new TemplateData(this._viewModel.diffEditorViewModel, this._viewModel.entry.value!));
|
||||
ref = this._objectPool.getUnusedObj(new TemplateData(this.viewModel));
|
||||
this._templateRef.set(ref, undefined);
|
||||
}
|
||||
ref.object.render(verticalSpace, width, offset, viewPort);
|
||||
|
||||
@@ -3,6 +3,33 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-component .expand-button a {
|
||||
.monaco-component .multiDiffEntry {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.monaco-component .multiDiffEntry .collapse-button {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.monaco-component .multiDiffEntry .collapse-button a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.monaco-component .multiDiffEntry .header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 5px;
|
||||
color: var(--vscode-foreground);
|
||||
background: var(--vscode-editor-background);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.monaco-component .multiDiffEntry .header.shadow {
|
||||
box-shadow: var(--vscode-scrollbar-shadow) 0px 6px 6px -6px;
|
||||
}
|
||||
|
||||
.monaco-component .multiDiffEntry .header .title {
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@ export class LineRange {
|
||||
return new LineRange(range.startLineNumber, range.endLineNumber);
|
||||
}
|
||||
|
||||
public static fromRangeInclusive(range: Range): LineRange {
|
||||
return new LineRange(range.startLineNumber, range.endLineNumber + 1);
|
||||
}
|
||||
|
||||
public static subtract(a: LineRange, b: LineRange | undefined): LineRange[] {
|
||||
if (!b) {
|
||||
return [a];
|
||||
|
||||
@@ -12,7 +12,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
|
||||
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { matchesScheme } from 'vs/platform/opener/common/opener';
|
||||
import { matchesScheme } from 'vs/base/common/network';
|
||||
|
||||
|
||||
export const ILanguageFeatureDebounceService = createDecorator<ILanguageFeatureDebounceService>('ILanguageFeatureDebounceService');
|
||||
|
||||
@@ -1308,6 +1308,19 @@ class IdentityCoordinatesConverter implements ICoordinatesConverter {
|
||||
return true;
|
||||
}
|
||||
|
||||
public modelRangeIsVisible(modelRange: Range): boolean {
|
||||
const lineCount = this._lines.model.getLineCount();
|
||||
if (modelRange.startLineNumber < 1 || modelRange.startLineNumber > lineCount) {
|
||||
// invalid arguments
|
||||
return false;
|
||||
}
|
||||
if (modelRange.endLineNumber < 1 || modelRange.endLineNumber > lineCount) {
|
||||
// invalid arguments
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public getModelLineViewLineCount(modelLineNumber: number): number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -759,6 +759,7 @@ class StandaloneTelemetryService implements ITelemetryService {
|
||||
readonly telemetryLevel = TelemetryLevel.NONE;
|
||||
readonly sessionId = 'someValue.sessionId';
|
||||
readonly machineId = 'someValue.machineId';
|
||||
readonly sqmId = 'someValue.sqmId';
|
||||
readonly firstSessionDate = 'someValue.firstSessionDate';
|
||||
readonly sendErrorTelemetry = false;
|
||||
setEnabled(): void { }
|
||||
|
||||
@@ -11,7 +11,7 @@ import { TestCodeEditorService } from 'vs/editor/test/browser/editorTestServices
|
||||
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { NullCommandService } from 'vs/platform/commands/test/common/nullCommandService';
|
||||
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { matchesScheme, matchesSomeScheme } from 'vs/platform/opener/common/opener';
|
||||
import { matchesScheme, matchesSomeScheme } from 'vs/base/common/network';
|
||||
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
|
||||
|
||||
suite('OpenerService', function () {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nativeKeymap from 'native-keymap';
|
||||
import type * as nativeKeymap from 'native-keymap';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as spdlog from '@vscode/spdlog';
|
||||
import type * as spdlog from '@vscode/spdlog';
|
||||
import { ByteSize } from 'vs/platform/files/common/files';
|
||||
import { AbstractMessageLogger, ILogger, LogLevel } from 'vs/platform/log/common/log';
|
||||
|
||||
|
||||
@@ -42,7 +42,6 @@ import { WindowProfiler } from 'vs/platform/profiling/electron-main/windowProfil
|
||||
import { IV8Profile } from 'vs/platform/profiling/common/profiling';
|
||||
import { IAuxiliaryWindowsMainService, isAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows';
|
||||
import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow';
|
||||
import { loadSystemCertificates } from '@vscode/proxy-agent';
|
||||
|
||||
export interface INativeHostMainService extends AddFirstParameterToFunctions<ICommonNativeHostService, Promise<unknown> /* only methods, not events */, number | undefined /* window ID */> { }
|
||||
|
||||
@@ -766,7 +765,8 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
|
||||
}
|
||||
|
||||
async loadCertificates(_windowId: number | undefined): Promise<string[]> {
|
||||
return loadSystemCertificates({ log: this.logService });
|
||||
const proxyAgent = await import('@vscode/proxy-agent');
|
||||
return proxyAgent.loadSystemCertificates({ log: this.logService });
|
||||
}
|
||||
|
||||
findFreePort(windowId: number | undefined, startPort: number, giveUpAfter: number, timeout: number, stride = 1): Promise<number> {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { equalsIgnoreCase, startsWithIgnoreCase } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IEditorOptions, ITextEditorSelection } from 'vs/platform/editor/common/editor';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -117,18 +116,6 @@ export interface IOpenerService {
|
||||
resolveExternalUri(resource: URI, options?: ResolveExternalUriOptions): Promise<IResolvedExternalUri>;
|
||||
}
|
||||
|
||||
export function matchesScheme(target: URI | string, scheme: string): boolean {
|
||||
if (URI.isUri(target)) {
|
||||
return equalsIgnoreCase(target.scheme, scheme);
|
||||
} else {
|
||||
return startsWithIgnoreCase(target, scheme + ':');
|
||||
}
|
||||
}
|
||||
|
||||
export function matchesSomeScheme(target: URI | string, ...schemes: string[]): boolean {
|
||||
return schemes.some(scheme => matchesScheme(target, scheme));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes selection into the `URI`.
|
||||
*
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { AbstractPolicyService, IPolicyService, PolicyDefinition } from 'vs/platform/policy/common/policy';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { Throttler } from 'vs/base/common/async';
|
||||
import { createWatcher, PolicyUpdate, Watcher } from '@vscode/policy-watcher';
|
||||
import type { PolicyUpdate, Watcher } from '@vscode/policy-watcher';
|
||||
import { MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
@@ -25,6 +25,8 @@ export class NativePolicyService extends AbstractPolicyService implements IPolic
|
||||
protected async _updatePolicyDefinitions(policyDefinitions: IStringDictionary<PolicyDefinition>): Promise<void> {
|
||||
this.logService.trace(`NativePolicyService#_updatePolicyDefinitions - Found ${Object.keys(policyDefinitions).length} policy definitions`);
|
||||
|
||||
const { createWatcher } = await import('@vscode/policy-watcher');
|
||||
|
||||
await this.throttler.queue(() => new Promise<void>((c, e) => {
|
||||
try {
|
||||
this.watcher.value = createWatcher(this.productName, policyDefinitions, update => {
|
||||
|
||||
@@ -20,7 +20,6 @@ import { ILogService, ILoggerService } from 'vs/platform/log/common/log';
|
||||
import { AbstractRequestService, IRequestService } from 'vs/platform/request/common/request';
|
||||
import { Agent, getProxyAgent } from 'vs/platform/request/node/proxy';
|
||||
import { createGunzip } from 'zlib';
|
||||
import { loadSystemCertificates } from '@vscode/proxy-agent';
|
||||
|
||||
interface IHTTPConfiguration {
|
||||
proxy?: string;
|
||||
@@ -112,7 +111,8 @@ export class RequestService extends AbstractRequestService implements IRequestSe
|
||||
}
|
||||
|
||||
async loadCertificates(): Promise<string[]> {
|
||||
return loadSystemCertificates({ log: this.logService });
|
||||
const proxyAgent = await import('@vscode/proxy-agent');
|
||||
return proxyAgent.loadSystemCertificates({ log: this.logService });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface ITelemetryService {
|
||||
|
||||
readonly sessionId: string;
|
||||
readonly machineId: string;
|
||||
readonly sqmId: string;
|
||||
readonly firstSessionDate: string;
|
||||
readonly msftInternal?: boolean;
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ export class TelemetryService implements ITelemetryService {
|
||||
|
||||
readonly sessionId: string;
|
||||
readonly machineId: string;
|
||||
readonly sqmId: string;
|
||||
readonly firstSessionDate: string;
|
||||
readonly msftInternal: boolean | undefined;
|
||||
|
||||
@@ -56,6 +57,7 @@ export class TelemetryService implements ITelemetryService {
|
||||
|
||||
this.sessionId = this._commonProperties['sessionID'] as string;
|
||||
this.machineId = this._commonProperties['common.machineId'] as string;
|
||||
this.sqmId = this._commonProperties['common.sqmId'] as string;
|
||||
this.firstSessionDate = this._commonProperties['common.firstSessionDate'] as string;
|
||||
this.msftInternal = this._commonProperties['common.msftInternal'] as boolean | undefined;
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ export class NullTelemetryServiceShape implements ITelemetryService {
|
||||
readonly telemetryLevel = TelemetryLevel.NONE;
|
||||
readonly sessionId = 'someValue.sessionId';
|
||||
readonly machineId = 'someValue.machineId';
|
||||
readonly sqmId = 'someValue.sqmId';
|
||||
readonly firstSessionDate = 'someValue.firstSessionDate';
|
||||
readonly sendErrorTelemetry = false;
|
||||
publicLog() { }
|
||||
|
||||
@@ -32,7 +32,6 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe
|
||||
private __isCommandStorageDisabled: boolean = false;
|
||||
private _handleCommandStartOptions?: IHandleCommandOptions;
|
||||
|
||||
|
||||
private _commitCommandFinished?: RunOnceScheduler;
|
||||
|
||||
private _ptyHeuristicsHooks: ICommandDetectionHeuristicsHooks;
|
||||
@@ -99,6 +98,10 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe
|
||||
get commandMarkers() { return that._commandMarkers; }
|
||||
set commandMarkers(value) { that._commandMarkers = value; }
|
||||
get clearCommandsInViewport() { return that._clearCommandsInViewport.bind(that); }
|
||||
commitCommandFinished() {
|
||||
that._commitCommandFinished?.flush();
|
||||
that._commitCommandFinished = undefined;
|
||||
}
|
||||
};
|
||||
this._ptyHeuristics = this._register(new MandatoryMutableDisposable(new UnixPtyHeuristics(this._terminal, this, this._ptyHeuristicsHooks, this._logService)));
|
||||
|
||||
@@ -172,6 +175,10 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe
|
||||
get commandMarkers() { return that._commandMarkers; }
|
||||
set commandMarkers(value) { that._commandMarkers = value; }
|
||||
get clearCommandsInViewport() { return that._clearCommandsInViewport.bind(that); }
|
||||
commitCommandFinished() {
|
||||
that._commitCommandFinished?.flush();
|
||||
that._commitCommandFinished = undefined;
|
||||
}
|
||||
},
|
||||
this._logService
|
||||
);
|
||||
@@ -235,8 +242,6 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe
|
||||
this._logService.debug('CommandDetectionCapability#handlePromptStart adjusted commandFinished', `${lastCommand.endMarker.line} -> ${lastCommand.executedMarker.line + 1}`);
|
||||
lastCommand.endMarker = cloneMarker(this._terminal, lastCommand.executedMarker, 1);
|
||||
}
|
||||
this._commitCommandFinished?.flush();
|
||||
this._commitCommandFinished = undefined;
|
||||
|
||||
this._currentCommand.promptStartMarker = options?.marker || (lastCommand?.endMarker ? cloneMarker(this._terminal, lastCommand.endMarker) : this._terminal.registerMarker(0));
|
||||
|
||||
@@ -408,6 +413,7 @@ interface ICommandDetectionHeuristicsHooks {
|
||||
commandMarkers: IMarker[];
|
||||
|
||||
clearCommandsInViewport(): void;
|
||||
commitCommandFinished(): void;
|
||||
}
|
||||
|
||||
type IPtyHeuristics = (
|
||||
@@ -439,6 +445,8 @@ class UnixPtyHeuristics extends Disposable {
|
||||
}
|
||||
|
||||
async handleCommandStart(options?: IHandleCommandOptions) {
|
||||
this._hooks.commitCommandFinished();
|
||||
|
||||
const currentCommand = this._capability.currentCommand;
|
||||
currentCommand.commandStartX = this._terminal.buffer.active.cursorX;
|
||||
currentCommand.commandStartMarker = options?.marker || this._terminal.registerMarker(0);
|
||||
@@ -507,6 +515,10 @@ class WindowsPtyHeuristics extends Disposable {
|
||||
|
||||
private _recentlyPerformedCsiJ = false;
|
||||
|
||||
private _tryAdjustCommandStartMarkerScheduler?: RunOnceScheduler;
|
||||
private _tryAdjustCommandStartMarkerScannedLineCount: number = 0;
|
||||
private _tryAdjustCommandStartMarkerPollCount: number = 0;
|
||||
|
||||
constructor(
|
||||
private readonly _terminal: Terminal,
|
||||
private readonly _capability: CommandDetectionCapability,
|
||||
@@ -592,10 +604,6 @@ class WindowsPtyHeuristics extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private _tryAdjustCommandStartMarkerScheduler?: RunOnceScheduler;
|
||||
private _tryAdjustCommandStartMarkerScannedLineCount: number = 0;
|
||||
private _tryAdjustCommandStartMarkerPollCount: number = 0;
|
||||
|
||||
async handleCommandStart() {
|
||||
this._capability.currentCommand.commandStartX = this._terminal.buffer.active.cursorX;
|
||||
|
||||
@@ -661,6 +669,13 @@ class WindowsPtyHeuristics extends Disposable {
|
||||
this._logService.debug('CommandDetectionCapability#_tryAdjustCommandStartMarker adjusted promptStart', `${this._capability.currentCommand.promptStartMarker?.line} -> ${this._capability.currentCommand.commandStartMarker.line}`);
|
||||
this._capability.currentCommand.promptStartMarker?.dispose();
|
||||
this._capability.currentCommand.promptStartMarker = cloneMarker(this._terminal, this._capability.currentCommand.commandStartMarker);
|
||||
// Adjust the last command if it's not in the same position as the following
|
||||
// prompt start marker
|
||||
const lastCommand = this._capability.commands.at(-1);
|
||||
if (lastCommand && this._capability.currentCommand.commandStartMarker.line !== lastCommand.endMarker?.line) {
|
||||
lastCommand.endMarker?.dispose();
|
||||
lastCommand.endMarker = cloneMarker(this._terminal, this._capability.currentCommand.commandStartMarker);
|
||||
}
|
||||
}
|
||||
// use the regex to set the position as it's possible input has occurred
|
||||
this._capability.currentCommand.commandStartX = adjustedPrompt.length;
|
||||
@@ -692,6 +707,8 @@ class WindowsPtyHeuristics extends Disposable {
|
||||
this._tryAdjustCommandStartMarkerScheduler = undefined;
|
||||
}
|
||||
|
||||
this._hooks.commitCommandFinished();
|
||||
|
||||
if (!this._capability.currentCommand.commandExecutedMarker) {
|
||||
this._onCursorMoveListener.value = this._terminal.onCursorMove(() => {
|
||||
if (this._hooks.commandMarkers.length === 0 || this._hooks.commandMarkers[this._hooks.commandMarkers.length - 1].line !== this._terminal.buffer.active.cursorY) {
|
||||
|
||||
@@ -16,10 +16,11 @@ import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/ext
|
||||
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||
import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService';
|
||||
import { INotebookCellExecution, INotebookExecution, INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
|
||||
import { IKernelSourceActionProvider, INotebookKernel, INotebookKernelChangeEvent, INotebookKernelDetectionTask, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
|
||||
import { IKernelSourceActionProvider, INotebookKernel, INotebookKernelChangeEvent, INotebookKernelDetectionTask, INotebookKernelService, VariablesResult } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
|
||||
import { SerializableObjectWithBuffers } from 'vs/workbench/services/extensions/common/proxyIdentifier';
|
||||
import { ExtHostContext, ExtHostNotebookKernelsShape, ICellExecuteUpdateDto, ICellExecutionCompleteDto, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape } from '../common/extHost.protocol';
|
||||
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
|
||||
import { AsyncIterableObject, AsyncIterableSource } from 'vs/base/common/async';
|
||||
|
||||
abstract class MainThreadKernel implements INotebookKernel {
|
||||
private readonly _onDidChange = new Emitter<INotebookKernelChangeEvent>();
|
||||
@@ -36,6 +37,7 @@ abstract class MainThreadKernel implements INotebookKernel {
|
||||
detail?: string;
|
||||
supportedLanguages: string[];
|
||||
implementsExecutionOrder: boolean;
|
||||
hasVariableProvider: boolean;
|
||||
localResourceRoot: URI;
|
||||
|
||||
public get preloadUris() {
|
||||
@@ -57,6 +59,7 @@ abstract class MainThreadKernel implements INotebookKernel {
|
||||
this.detail = data.detail;
|
||||
this.supportedLanguages = isNonEmptyArray(data.supportedLanguages) ? data.supportedLanguages : _languageService.getRegisteredLanguageIds();
|
||||
this.implementsExecutionOrder = data.supportsExecutionOrder ?? false;
|
||||
this.hasVariableProvider = data.hasVariableProvider ?? false;
|
||||
this.localResourceRoot = URI.revive(data.extensionLocation);
|
||||
this.preloads = data.preloads?.map(u => ({ uri: URI.revive(u.uri), provides: u.provides })) ?? [];
|
||||
}
|
||||
@@ -89,11 +92,16 @@ abstract class MainThreadKernel implements INotebookKernel {
|
||||
this.implementsInterrupt = data.supportsInterrupt;
|
||||
event.hasInterruptHandler = true;
|
||||
}
|
||||
if (data.hasVariableProvider !== undefined) {
|
||||
this.hasVariableProvider = data.hasVariableProvider;
|
||||
event.hasVariableProvider = true;
|
||||
}
|
||||
this._onDidChange.fire(event);
|
||||
}
|
||||
|
||||
abstract executeNotebookCellsRequest(uri: URI, cellHandles: number[]): Promise<void>;
|
||||
abstract cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise<void>;
|
||||
abstract provideVariables(notebookUri: URI, variableName: string | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject<VariablesResult>;
|
||||
}
|
||||
|
||||
class MainThreadKernelDetectionTask implements INotebookKernelDetectionTask {
|
||||
@@ -214,6 +222,15 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape
|
||||
return didSend;
|
||||
}
|
||||
|
||||
private variableRequestIndex = 0;
|
||||
private variableRequestMap = new Map<string, AsyncIterableSource<VariablesResult>>();
|
||||
$receiveVariable(requestId: string, variable: VariablesResult) {
|
||||
const source = this.variableRequestMap.get(requestId);
|
||||
if (source) {
|
||||
source.emitOne(variable);
|
||||
}
|
||||
}
|
||||
|
||||
// --- kernel adding/updating/removal
|
||||
|
||||
async $addKernel(handle: number, data: INotebookKernelDto2): Promise<void> {
|
||||
@@ -225,6 +242,24 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape
|
||||
async cancelNotebookCellExecution(uri: URI, handles: number[]): Promise<void> {
|
||||
await that._proxy.$cancelCells(handle, uri, handles);
|
||||
}
|
||||
provideVariables(notebookUri: URI, parentName: string | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject<VariablesResult> {
|
||||
const requestId = `${handle}variables${that.variableRequestIndex++}`;
|
||||
if (that.variableRequestMap.has(requestId)) {
|
||||
return that.variableRequestMap.get(requestId)!.asyncIterable;
|
||||
}
|
||||
|
||||
const source = new AsyncIterableSource<VariablesResult>();
|
||||
that.variableRequestMap.set(requestId, source);
|
||||
that._proxy.$provideVariables(handle, requestId, notebookUri, parentName, kind, start, token).then(() => {
|
||||
source.resolve();
|
||||
that.variableRequestMap.delete(requestId);
|
||||
}).catch((err) => {
|
||||
source.reject(err);
|
||||
that.variableRequestMap.delete(requestId);
|
||||
});
|
||||
|
||||
return source.asyncIterable;
|
||||
}
|
||||
}(data, this._languageService);
|
||||
|
||||
const listener = this._notebookKernelService.onDidChangeSelectedNotebooks(e => {
|
||||
@@ -405,4 +440,8 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape
|
||||
emitter.fire(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
$variablesUpdated(notebookUri: UriComponents): void {
|
||||
this._notebookKernelService.notifyVariablesChange(URI.revive(notebookUri));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable, DisposableStore, combinedDisposable, dispose, DisposableMap } from 'vs/base/common/lifecycle';
|
||||
import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource, ISCMResourceGroup, ISCMResourceDecorations, IInputValidation, ISCMViewService, InputValidationType, ISCMActionButtonDescriptor, ISCMInputValueProvider, ISCMInputValueProviderContext } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { ExtHostContext, MainThreadSCMShape, ExtHostSCMShape, SCMProviderFeatures, SCMRawResourceSplices, SCMGroupFeatures, MainContext, SCMHistoryItemDto, SCMActionButtonDto, SCMHistoryItemGroupDto, SCMInputActionButtonDto } from '../common/extHost.protocol';
|
||||
import { ExtHostContext, MainThreadSCMShape, ExtHostSCMShape, SCMProviderFeatures, SCMRawResourceSplices, SCMGroupFeatures, MainContext, SCMActionButtonDto, SCMHistoryItemGroupDto, SCMInputActionButtonDto } from '../common/extHost.protocol';
|
||||
import { Command } from 'vs/editor/common/languages';
|
||||
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
@@ -18,6 +18,7 @@ import { IQuickDiffService, QuickDiffProvider } from 'vs/workbench/contrib/scm/c
|
||||
import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryItemGroup, ISCMHistoryItemGroupDetails, ISCMHistoryItemGroupEntry, ISCMHistoryOptions, ISCMHistoryProvider } from 'vs/workbench/contrib/scm/common/history';
|
||||
import { ResourceTree } from 'vs/base/common/resourceTree';
|
||||
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
|
||||
function getSCMInputBoxActionButtonIcon(actionButton: SCMInputActionButtonDto): URI | { light: URI; dark: URI } | ThemeIcon | undefined {
|
||||
if (!actionButton.icon) {
|
||||
@@ -32,19 +33,6 @@ function getSCMInputBoxActionButtonIcon(actionButton: SCMInputActionButtonDto):
|
||||
}
|
||||
}
|
||||
|
||||
function getSCMHistoryItemIcon(historyItem: SCMHistoryItemDto): URI | { light: URI; dark: URI } | ThemeIcon | undefined {
|
||||
if (!historyItem.icon) {
|
||||
return undefined;
|
||||
} else if (URI.isUri(historyItem.icon)) {
|
||||
return URI.revive(historyItem.icon);
|
||||
} else if (ThemeIcon.isThemeIcon(historyItem.icon)) {
|
||||
return historyItem.icon;
|
||||
} else {
|
||||
const icon = historyItem.icon as { light: UriComponents; dark: UriComponents };
|
||||
return { light: URI.revive(icon.light), dark: URI.revive(icon.dark) };
|
||||
}
|
||||
}
|
||||
|
||||
function getIconFromIconDto(iconDto?: UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon): URI | { light: URI; dark: URI } | ThemeIcon | undefined {
|
||||
if (iconDto === undefined) {
|
||||
return undefined;
|
||||
@@ -184,8 +172,8 @@ class MainThreadSCMHistoryProvider implements ISCMHistoryProvider {
|
||||
if (historyItemGroupBase) {
|
||||
incoming = {
|
||||
id: historyItemGroupBase.id,
|
||||
label: `$(cloud-download) ${historyItemGroupBase.label}`,
|
||||
// description: localize('incoming', "Incoming Changes"),
|
||||
label: historyItemGroupBase.label,
|
||||
icon: Codicon.cloudDownload,
|
||||
ancestor: ancestor?.id,
|
||||
count: ancestor?.behind ?? 0,
|
||||
};
|
||||
@@ -194,8 +182,8 @@ class MainThreadSCMHistoryProvider implements ISCMHistoryProvider {
|
||||
// Outgoing
|
||||
const outgoing: ISCMHistoryItemGroupEntry = {
|
||||
id: historyItemGroup.id,
|
||||
label: `$(cloud-upload) ${historyItemGroup.label}`,
|
||||
// description: localize('outgoing', "Outgoing Changes"),
|
||||
label: historyItemGroup.label,
|
||||
icon: Codicon.cloudUpload,
|
||||
ancestor: ancestor?.id,
|
||||
count: ancestor?.ahead ?? 0,
|
||||
};
|
||||
@@ -213,7 +201,7 @@ class MainThreadSCMHistoryProvider implements ISCMHistoryProvider {
|
||||
|
||||
async provideHistoryItems(historyItemGroupId: string, options: ISCMHistoryOptions): Promise<ISCMHistoryItem[] | undefined> {
|
||||
const historyItems = await this.proxy.$provideHistoryItems(this.handle, historyItemGroupId, options, CancellationToken.None);
|
||||
return historyItems?.map(historyItem => ({ ...historyItem, icon: getSCMHistoryItemIcon(historyItem), }));
|
||||
return historyItems?.map(historyItem => ({ ...historyItem, icon: getIconFromIconDto(historyItem.icon) }));
|
||||
}
|
||||
|
||||
async provideHistoryItemChanges(historyItemId: string): Promise<ISCMHistoryItemChange[] | undefined> {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { Schemas, matchesScheme } from 'vs/base/common/network';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions';
|
||||
@@ -18,7 +18,6 @@ import { ExtensionIdentifierSet, IExtensionDescription } from 'vs/platform/exten
|
||||
import * as files from 'vs/platform/files/common/files';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILogService, ILoggerService, LogLevel } from 'vs/platform/log/common/log';
|
||||
import { matchesScheme } from 'vs/platform/opener/common/opener';
|
||||
import { getRemoteName } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { EditSessionIdentityMatch } from 'vs/platform/workspace/common/editSessions';
|
||||
@@ -1574,6 +1573,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
NotebookControllerAffinity2: extHostTypes.NotebookControllerAffinity2,
|
||||
NotebookEdit: extHostTypes.NotebookEdit,
|
||||
NotebookKernelSourceAction: extHostTypes.NotebookKernelSourceAction,
|
||||
NotebookVariablesRequestKind: extHostTypes.NotebookVariablesRequestKind,
|
||||
PortAttributes: extHostTypes.PortAttributes,
|
||||
LinkedEditingRanges: extHostTypes.LinkedEditingRanges,
|
||||
TestResultState: extHostTypes.TestResultState,
|
||||
|
||||
@@ -80,6 +80,7 @@ import { CandidatePort } from 'vs/workbench/services/remote/common/tunnelModel';
|
||||
import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder';
|
||||
import * as search from 'vs/workbench/services/search/common/search';
|
||||
import { ISaveProfileResult } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
import { VariablesResult } from 'vscode';
|
||||
|
||||
export interface IWorkspaceData extends IStaticWorkspaceData {
|
||||
folders: { uri: UriComponents; name: string; index: number }[];
|
||||
@@ -1075,6 +1076,7 @@ export interface INotebookKernelDto2 {
|
||||
supportsInterrupt?: boolean;
|
||||
supportsExecutionOrder?: boolean;
|
||||
preloads?: { uri: UriComponents; provides: readonly string[] }[];
|
||||
hasVariableProvider?: boolean;
|
||||
}
|
||||
|
||||
export interface INotebookProxyKernelDto {
|
||||
@@ -1131,6 +1133,8 @@ export interface MainThreadNotebookKernelsShape extends IDisposable {
|
||||
$addKernelSourceActionProvider(handle: number, eventHandle: number, notebookType: string): Promise<void>;
|
||||
$removeKernelSourceActionProvider(handle: number, eventHandle: number): void;
|
||||
$emitNotebookKernelSourceActionsChangeEvent(eventHandle: number): void;
|
||||
$receiveVariable(requestId: string, variable: VariablesResult): void;
|
||||
$variablesUpdated(notebookUri: UriComponents): void;
|
||||
}
|
||||
|
||||
export interface MainThreadNotebookRenderersShape extends IDisposable {
|
||||
@@ -2538,6 +2542,7 @@ export interface ExtHostNotebookKernelsShape {
|
||||
$acceptKernelMessageFromRenderer(handle: number, editorId: string, message: any): void;
|
||||
$cellExecutionChanged(uri: UriComponents, cellHandle: number, state: notebookCommon.NotebookCellExecutionState | undefined): void;
|
||||
$provideKernelSourceActions(handle: number, token: CancellationToken): Promise<notebookCommon.INotebookKernelSourceAction[]>;
|
||||
$provideVariables(handle: number, requestId: string, notebookUri: UriComponents, variableName: string | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ExtHostInteractiveShape {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { Schemas, matchesSomeScheme } from 'vs/base/common/network';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
@@ -13,7 +13,6 @@ import * as languages from 'vs/editor/common/languages';
|
||||
import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto';
|
||||
import { validateWhenClauses } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { matchesSomeScheme } from 'vs/platform/opener/common/opener';
|
||||
import { ICallHierarchyItemDto, IIncomingCallDto, IInlineValueContextDto, IOutgoingCallDto, IRawColorInfo, ITypeHierarchyItemDto, IWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
|
||||
import { CustomCodeAction } from 'vs/workbench/api/common/extHostLanguageFeatures';
|
||||
|
||||
@@ -96,6 +96,11 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 {
|
||||
}
|
||||
|
||||
const convertedProgress = typeConvert.ChatResponseProgress.from(agent.extension, progress);
|
||||
if (!convertedProgress) {
|
||||
this._logService.error('Unknown progress type: ' + JSON.stringify(progress));
|
||||
return;
|
||||
}
|
||||
|
||||
if ('placeholder' in progress && 'resolvedContent' in progress) {
|
||||
const resolvedContent = Promise.all([this._proxy.$handleProgressChunk(requestId, convertedProgress), progress.resolvedContent]);
|
||||
raceCancellation(resolvedContent, token).then(res => {
|
||||
@@ -104,6 +109,11 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 {
|
||||
}
|
||||
const [progressHandle, progressContent] = res;
|
||||
const convertedContent = typeConvert.ChatResponseProgress.from(agent.extension, progressContent);
|
||||
if (!convertedContent) {
|
||||
this._logService.error('Unknown progress type: ' + JSON.stringify(progressContent));
|
||||
return;
|
||||
}
|
||||
|
||||
this._proxy.$handleProgressChunk(requestId, convertedContent, progressHandle ?? undefined);
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -18,7 +18,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData
|
||||
import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook';
|
||||
import { ExtHostCell, ExtHostNotebookDocument } from 'vs/workbench/api/common/extHostNotebookDocument';
|
||||
import * as extHostTypeConverters from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { NotebookCellExecutionState as ExtHostNotebookCellExecutionState, NotebookCellOutput, NotebookControllerAffinity2 } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { NotebookCellExecutionState as ExtHostNotebookCellExecutionState, NotebookCellOutput, NotebookControllerAffinity2, NotebookVariablesRequestKind } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { asWebviewUri } from 'vs/workbench/contrib/webview/common/webview';
|
||||
import { INotebookKernelSourceAction, NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService';
|
||||
@@ -125,6 +125,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape {
|
||||
//
|
||||
let _executeHandler = handler ?? _defaultExecutHandler;
|
||||
let _interruptHandler: ((this: vscode.NotebookController, notebook: vscode.NotebookDocument) => void | Thenable<void>) | undefined;
|
||||
let _variableProvider: vscode.NotebookVariableProvider | undefined;
|
||||
|
||||
this._proxy.$addKernel(handle, data).catch(err => {
|
||||
// this can happen when a kernel with that ID is already registered
|
||||
@@ -207,6 +208,16 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape {
|
||||
data.supportsInterrupt = Boolean(value);
|
||||
_update();
|
||||
},
|
||||
set variableProvider(value) {
|
||||
checkProposedApiEnabled(extension, 'notebookVariableProvider');
|
||||
_variableProvider = value;
|
||||
data.hasVariableProvider = !!value;
|
||||
value?.onDidChangeVariables(e => that._proxy.$variablesUpdated(e.uri));
|
||||
_update();
|
||||
},
|
||||
get variableProvider() {
|
||||
return _variableProvider;
|
||||
},
|
||||
createNotebookCellExecution(cell) {
|
||||
if (isDisposed) {
|
||||
throw new Error('notebook controller is DISPOSED');
|
||||
@@ -404,6 +415,29 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape {
|
||||
}
|
||||
}
|
||||
|
||||
async $provideVariables(handle: number, requestId: string, notebookUri: UriComponents, parentName: string | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): Promise<void> {
|
||||
const obj = this._kernelData.get(handle);
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
|
||||
const document = this._extHostNotebook.getNotebookDocument(URI.revive(notebookUri));
|
||||
const variableProvider = obj.controller.variableProvider;
|
||||
if (!variableProvider) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parent = parentName ? { name: parentName, value: '' } : undefined;
|
||||
const requestKind = kind === 'named' ? NotebookVariablesRequestKind.Named : NotebookVariablesRequestKind.Indexed;
|
||||
const variables = variableProvider.provideVariables(document.apiNotebook, parent, requestKind, start, token);
|
||||
for await (const variable of variables) {
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
this._proxy.$receiveVariable(requestId, variable);
|
||||
}
|
||||
}
|
||||
|
||||
$acceptKernelMessageFromRenderer(handle: number, editorId: string, message: any): void {
|
||||
const obj = this._kernelData.get(handle);
|
||||
if (!obj) {
|
||||
|
||||
@@ -103,6 +103,7 @@ export class ExtHostTelemetry extends Disposable implements ExtHostTelemetryShap
|
||||
commonProperties['common.extversion'] = extension.version;
|
||||
commonProperties['common.vscodemachineid'] = this.initData.telemetryInfo.machineId;
|
||||
commonProperties['common.vscodesessionid'] = this.initData.telemetryInfo.sessionId;
|
||||
commonProperties['common.sqmid'] = this.initData.telemetryInfo.sqmId;
|
||||
commonProperties['common.vscodeversion'] = this.initData.version;
|
||||
commonProperties['common.isnewappinstall'] = isNewAppInstall(this.initData.telemetryInfo.firstSessionDate);
|
||||
commonProperties['common.product'] = this.initData.environment.appHost;
|
||||
|
||||
@@ -2295,13 +2295,18 @@ export namespace InteractiveEditorResponseFeedbackKind {
|
||||
}
|
||||
|
||||
export namespace ChatResponseProgress {
|
||||
export function from(extension: IExtensionDescription, progress: vscode.ChatAgentExtendedProgress): extHostProtocol.IChatProgressDto {
|
||||
export function from(extension: IExtensionDescription, progress: vscode.ChatAgentExtendedProgress): extHostProtocol.IChatProgressDto | undefined {
|
||||
if ('placeholder' in progress && 'resolvedContent' in progress) {
|
||||
return { content: progress.placeholder, kind: 'asyncContent' } satisfies extHostProtocol.IChatAsyncContentDto;
|
||||
} else if ('markdownContent' in progress) {
|
||||
checkProposedApiEnabled(extension, 'chatAgents2Additions');
|
||||
return { content: MarkdownString.from(progress.markdownContent), kind: 'markdownContent' };
|
||||
} else if ('content' in progress) {
|
||||
if ('vulnerability' in progress && progress.vulnerability) {
|
||||
checkProposedApiEnabled(extension, 'chatAgents2Additions');
|
||||
return { content: progress.content, title: progress.vulnerability.title, description: progress.vulnerability!.description, kind: 'vulnerability' };
|
||||
}
|
||||
|
||||
if (typeof progress.content === 'string') {
|
||||
return { content: progress.content, kind: 'content' };
|
||||
}
|
||||
@@ -2344,7 +2349,7 @@ export namespace ChatResponseProgress {
|
||||
} else if ('message' in progress) {
|
||||
return { content: progress.message, kind: 'progressMessage' };
|
||||
} else {
|
||||
throw new Error('Invalid progress type: ' + JSON.stringify(progress));
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3798,6 +3798,11 @@ export class NotebookKernelSourceAction {
|
||||
) { }
|
||||
}
|
||||
|
||||
export enum NotebookVariablesRequestKind {
|
||||
Named = 1,
|
||||
Indexed = 2
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Timeline
|
||||
|
||||
@@ -42,6 +42,7 @@ suite('ExtHostTelemetry', function () {
|
||||
firstSessionDate: '2020-01-01T00:00:00.000Z',
|
||||
sessionId: 'test',
|
||||
machineId: 'test',
|
||||
sqmId: 'test'
|
||||
};
|
||||
|
||||
const mockRemote = {
|
||||
|
||||
@@ -7,16 +7,15 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { isEqual, dirname } from 'vs/base/common/resources';
|
||||
import { Schemas, matchesSomeScheme } from 'vs/base/common/network';
|
||||
import { dirname, isEqual } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs';
|
||||
import { FileKind } from 'vs/platform/files/common/files';
|
||||
import { IOutline, IOutlineService, OutlineTarget } from 'vs/workbench/services/outline/browser/outline';
|
||||
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs';
|
||||
import { IEditorPane } from 'vs/workbench/common/editor';
|
||||
import { matchesSomeScheme } from 'vs/platform/opener/common/opener';
|
||||
import { IOutline, IOutlineService, OutlineTarget } from 'vs/workbench/services/outline/browser/outline';
|
||||
|
||||
export class FileElement {
|
||||
constructor(
|
||||
|
||||
@@ -41,7 +41,7 @@ import {
|
||||
ReOpenInTextEditorAction, DuplicateGroupDownAction, DuplicateGroupLeftAction, DuplicateGroupRightAction, DuplicateGroupUpAction, ToggleEditorTypeAction, SplitEditorToAboveGroupAction, SplitEditorToBelowGroupAction,
|
||||
SplitEditorToFirstGroupAction, SplitEditorToLastGroupAction, SplitEditorToLeftGroupAction, SplitEditorToNextGroupAction, SplitEditorToPreviousGroupAction, SplitEditorToRightGroupAction, NavigateForwardInEditsAction,
|
||||
NavigateBackwardsInEditsAction, NavigateForwardInNavigationsAction, NavigateBackwardsInNavigationsAction, NavigatePreviousInNavigationsAction, NavigatePreviousInEditsAction, NavigateToLastNavigationLocationAction,
|
||||
MaximizeGroupHideSidebarAction, ExperimentalMoveEditorIntoNewWindowAction, ToggleMaximizeEditorGroupAction, MinimizeOtherGroupsHideSidebarAction
|
||||
MaximizeGroupHideSidebarAction, MoveEditorToNewDetachedWindowAction, CopyEditorToNewDetachedWindowAction, RestoreEditorsToMainWindowAction, ToggleMaximizeEditorGroupAction, MinimizeOtherGroupsHideSidebarAction, CopyEditorGroupToNewDetachedWindowAction, MoveEditorGroupToNewDetachedWindowAction
|
||||
} from 'vs/workbench/browser/parts/editor/editorActions';
|
||||
import {
|
||||
CLOSE_EDITORS_AND_GROUP_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_EDITOR_GROUP_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID,
|
||||
@@ -292,7 +292,12 @@ registerAction2(QuickAccessLeastRecentlyUsedEditorAction);
|
||||
registerAction2(QuickAccessPreviousRecentlyUsedEditorInGroupAction);
|
||||
registerAction2(QuickAccessLeastRecentlyUsedEditorInGroupAction);
|
||||
registerAction2(QuickAccessPreviousEditorFromHistoryAction);
|
||||
registerAction2(ExperimentalMoveEditorIntoNewWindowAction);
|
||||
|
||||
registerAction2(MoveEditorToNewDetachedWindowAction);
|
||||
registerAction2(CopyEditorToNewDetachedWindowAction);
|
||||
registerAction2(MoveEditorGroupToNewDetachedWindowAction);
|
||||
registerAction2(CopyEditorGroupToNewDetachedWindowAction);
|
||||
registerAction2(RestoreEditorsToMainWindowAction);
|
||||
|
||||
const quickAccessNavigateNextInEditorPickerId = 'workbench.action.quickOpenNavigateNextInEditorPicker';
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
|
||||
@@ -14,7 +14,7 @@ import { GoFilter, IHistoryService } from 'vs/workbench/services/history/common/
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveCopyArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, COPY_ACTIVE_EDITOR_COMMAND_ID, SPLIT_EDITOR, resolveCommandsContext, getCommandsContext, TOGGLE_MAXIMIZE_EDITOR_GROUP } from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { IEditorGroupsService, IEditorGroup, GroupsArrangement, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IEditorGroupsService, IEditorGroup, GroupsArrangement, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder, MergeGroupMode } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
|
||||
@@ -33,9 +33,10 @@ import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { Categories } from 'vs/platform/action/common/actionCommonCategories';
|
||||
import { ActiveEditorAvailableEditorIdsContext, ActiveEditorContext, ActiveEditorGroupEmptyContext, AuxiliaryBarVisibleContext, EditorPartMaximizedEditorGroupContext, EditorPartMultipleEditorGroupsContext, MultipleEditorGroupsContext, SideBarVisibleContext } from 'vs/workbench/common/contextkeys';
|
||||
import { ActiveEditorAvailableEditorIdsContext, ActiveEditorContext, ActiveEditorGroupEmptyContext, AuxiliaryBarVisibleContext, EditorPartMaximizedEditorGroupContext, EditorPartMultipleEditorGroupsContext, IsAuxiliaryWindowFocusedContext, MultipleEditorGroupsContext, SideBarVisibleContext } from 'vs/workbench/common/contextkeys';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { getActiveDocument } from 'vs/base/browser/dom';
|
||||
import { ICommandActionTitle } from 'vs/platform/action/common/action';
|
||||
|
||||
class ExecuteCommandAction extends Action2 {
|
||||
|
||||
@@ -225,7 +226,7 @@ export class JoinAllGroupsAction extends Action2 {
|
||||
override async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
|
||||
editorGroupService.mergeAllGroups();
|
||||
editorGroupService.mergeAllGroups(editorGroupService.activeGroup);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2485,16 +2486,17 @@ export class ReOpenInTextEditorAction extends Action2 {
|
||||
}
|
||||
}
|
||||
|
||||
export class ExperimentalMoveEditorIntoNewWindowAction extends Action2 {
|
||||
|
||||
constructor() {
|
||||
abstract class BaseMoveCopyEditorToNewDetachedWindowAction extends Action2 {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
title: ICommandActionTitle,
|
||||
private readonly move: boolean
|
||||
) {
|
||||
super({
|
||||
id: 'workbench.action.experimentalMoveEditorIntoNewWindowAction',
|
||||
title: {
|
||||
value: localize('popEditorOut', "Move Active Editor into a New Window (Experimental)"),
|
||||
mnemonicTitle: localize({ key: 'miPopEditorOut', comment: ['&& denotes a mnemonic'] }, "&&Move Active Editor into a New Window (Experimental)"),
|
||||
original: 'Move Active Editor into a New Window (Experimental)'
|
||||
},
|
||||
id,
|
||||
title,
|
||||
category: Categories.View,
|
||||
precondition: ActiveEditorContext,
|
||||
f1: true
|
||||
@@ -2512,6 +2514,124 @@ export class ExperimentalMoveEditorIntoNewWindowAction extends Action2 {
|
||||
|
||||
const auxiliaryEditorPart = await editorGroupService.createAuxiliaryEditorPart();
|
||||
|
||||
activeEditorPane.group.moveEditor(activeEditorPane.input, auxiliaryEditorPart.activeGroup);
|
||||
if (this.move) {
|
||||
activeEditorPane.group.moveEditor(activeEditorPane.input, auxiliaryEditorPart.activeGroup);
|
||||
} else {
|
||||
activeEditorPane.group.copyEditor(activeEditorPane.input, auxiliaryEditorPart.activeGroup);
|
||||
}
|
||||
|
||||
auxiliaryEditorPart.activeGroup.focus();
|
||||
}
|
||||
}
|
||||
|
||||
export class MoveEditorToNewDetachedWindowAction extends BaseMoveCopyEditorToNewDetachedWindowAction {
|
||||
|
||||
constructor() {
|
||||
super(
|
||||
'workbench.action.moveEditorToNewDetachedWindow',
|
||||
{
|
||||
value: localize('moveEditorToNewDetachedWindow', "Move Editor into a New Detached Window"),
|
||||
mnemonicTitle: localize({ key: 'miMoveEditorToNewDetachedWindow', comment: ['&& denotes a mnemonic'] }, "&&Move Editor into a New Detached Window"),
|
||||
original: 'Move Editor into a New Detached Window'
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class CopyEditorToNewDetachedWindowAction extends BaseMoveCopyEditorToNewDetachedWindowAction {
|
||||
|
||||
constructor() {
|
||||
super(
|
||||
'workbench.action.copyEditorToNewDetachedWindow',
|
||||
{
|
||||
value: localize('copyEditorToNewDetachedWindow', "Copy Editor into a New Detached Window"),
|
||||
mnemonicTitle: localize({ key: 'miCopyEditorToNewDetachedWindow', comment: ['&& denotes a mnemonic'] }, "&&Copy Editor into a New Detached Window"),
|
||||
original: 'Copy Editor into a New Detached Window'
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class BaseMoveCopyEditorGroupToNewDetachedWindowAction extends Action2 {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
title: ICommandActionTitle,
|
||||
private readonly move: boolean
|
||||
) {
|
||||
super({
|
||||
id,
|
||||
title,
|
||||
category: Categories.View,
|
||||
f1: true
|
||||
});
|
||||
}
|
||||
|
||||
override async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
const activeGroup = editorGroupService.activeGroup;
|
||||
|
||||
const auxiliaryEditorPart = await editorGroupService.createAuxiliaryEditorPart();
|
||||
|
||||
editorGroupService.mergeGroup(activeGroup, auxiliaryEditorPart.activeGroup, {
|
||||
mode: this.move ? MergeGroupMode.MOVE_EDITORS : MergeGroupMode.COPY_EDITORS
|
||||
});
|
||||
|
||||
auxiliaryEditorPart.activeGroup.focus();
|
||||
}
|
||||
}
|
||||
|
||||
export class MoveEditorGroupToNewDetachedWindowAction extends BaseMoveCopyEditorGroupToNewDetachedWindowAction {
|
||||
|
||||
constructor() {
|
||||
super(
|
||||
'workbench.action.moveEditorGroupToNewDetachedWindow',
|
||||
{
|
||||
value: localize('moveEditorGroupToNewDetachedWindow', "Move Editor Group into a New Detached Window"),
|
||||
mnemonicTitle: localize({ key: 'miMoveEditorGroupToNewDetachedWindow', comment: ['&& denotes a mnemonic'] }, "&&Move Editor Group into a New Detached Window"),
|
||||
original: 'Move Editor Group into a New Detached Window'
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class CopyEditorGroupToNewDetachedWindowAction extends BaseMoveCopyEditorGroupToNewDetachedWindowAction {
|
||||
|
||||
constructor() {
|
||||
super(
|
||||
'workbench.action.copyEditorGroupToNewDetachedWindow',
|
||||
{
|
||||
value: localize('copyEditorGroupToNewDetachedWindow', "Copy Editor Group into a New Detached Window"),
|
||||
mnemonicTitle: localize({ key: 'miCopyEditorGroupToNewDetachedWindow', comment: ['&& denotes a mnemonic'] }, "&&Copy Editor Group into a New Detached Window"),
|
||||
original: 'Copy Editor Group into a New Detached Window'
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class RestoreEditorsToMainWindowAction extends Action2 {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.action.restoreDetachedEditorsToMainWindow',
|
||||
title: {
|
||||
value: localize('restoreDetachedEditorsToMainWindow', "Restore Detached Editors into Main Window"),
|
||||
mnemonicTitle: localize({ key: 'miRestoreDetachedEditorsToMainWindow', comment: ['&& denotes a mnemonic'] }, "&&Restore Detached Editors into Main Window"),
|
||||
original: 'Restore Detached Editors into Main Window'
|
||||
},
|
||||
f1: true,
|
||||
precondition: IsAuxiliaryWindowFocusedContext,
|
||||
category: Categories.View
|
||||
});
|
||||
}
|
||||
|
||||
override async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
|
||||
editorGroupService.mergeAllGroups(editorGroupService.mainPart.activeGroup);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,44 +3,44 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { isObject, isString, isUndefined, isNumber } from 'vs/base/common/types';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IEditorIdentifier, IEditorCommandsContext, CloseDirection, IVisibleEditorPane, EditorsOrder, EditorInputCapabilities, isEditorIdentifier, isEditorInputWithOptionsAndGroup, IUntitledTextResourceEditorInput, IResourceDiffEditorInput } from 'vs/workbench/common/editor';
|
||||
import { TextCompareEditorVisibleContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, ActiveEditorStickyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, TextCompareEditorActiveContext, SideBySideEditorActiveContext } from 'vs/workbench/common/contextkeys';
|
||||
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
|
||||
import { EditorGroupColumn, columnToEditorGroup } from 'vs/workbench/services/editor/common/editorGroupColumn';
|
||||
import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor';
|
||||
import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IListService, IOpenEvent } from 'vs/platform/list/browser/listService';
|
||||
import { List } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { distinct, coalesce } from 'vs/base/common/arrays';
|
||||
import { IEditorGroupsService, IEditorGroup, GroupDirection, GroupLocation, GroupsOrder, preferredSideBySideGroupDirection, EditorGroupLayout, isEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { CommandsRegistry, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { MenuRegistry, MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
|
||||
import { Categories } from 'vs/platform/action/common/actionCommonCategories';
|
||||
import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess';
|
||||
import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener';
|
||||
import { EditorResolution, IEditorOptions, IResourceEditorInput, ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput';
|
||||
import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService';
|
||||
import { IPathService } from 'vs/workbench/services/path/common/pathService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { extname } from 'vs/base/common/resources';
|
||||
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
|
||||
import { isDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
|
||||
import { getActiveElement } from 'vs/base/browser/dom';
|
||||
import { List } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { coalesce, distinct } from 'vs/base/common/arrays';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { Schemas, matchesScheme } from 'vs/base/common/network';
|
||||
import { extname } from 'vs/base/common/resources';
|
||||
import { isNumber, isObject, isString, isUndefined } from 'vs/base/common/types';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { isDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Categories } from 'vs/platform/action/common/actionCommonCategories';
|
||||
import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { CommandsRegistry, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { EditorResolution, IEditorOptions, IResourceEditorInput, ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IListService, IOpenEvent } from 'vs/platform/list/browser/listService';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess';
|
||||
import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor';
|
||||
import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor';
|
||||
import { ActiveEditorCanSplitInGroupContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupLockedContext, ActiveEditorStickyContext, MultipleEditorGroupsContext, SideBySideEditorActiveContext, TextCompareEditorActiveContext, TextCompareEditorVisibleContext } from 'vs/workbench/common/contextkeys';
|
||||
import { CloseDirection, EditorInputCapabilities, EditorsOrder, IEditorCommandsContext, IEditorIdentifier, IResourceDiffEditorInput, IUntitledTextResourceEditorInput, IVisibleEditorPane, isEditorIdentifier, isEditorInputWithOptionsAndGroup } from 'vs/workbench/common/editor';
|
||||
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
|
||||
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
|
||||
import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput';
|
||||
import { EditorGroupColumn, columnToEditorGroup } from 'vs/workbench/services/editor/common/editorGroupColumn';
|
||||
import { EditorGroupLayout, GroupDirection, GroupLocation, GroupsOrder, IEditorGroup, IEditorGroupsService, isEditorGroup, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService';
|
||||
import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IPathService } from 'vs/workbench/services/path/common/pathService';
|
||||
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
|
||||
|
||||
export const CLOSE_SAVED_EDITORS_COMMAND_ID = 'workbench.action.closeUnmodifiedEditors';
|
||||
export const CLOSE_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeEditorsInGroup';
|
||||
|
||||
@@ -132,7 +132,7 @@ export class EditorPanes extends Disposable {
|
||||
|
||||
// Assert the `EditorInputCapabilities.AuxWindowUnsupported` condition
|
||||
if (getWindow(this.editorPanesParent) !== mainWindow && editor.hasCapability(EditorInputCapabilities.AuxWindowUnsupported)) {
|
||||
return await this.doShowError(createEditorOpenError(localize('editorUnsupportedInAuxWindow', "This type of editor cannot be opened in floating windows yet."), [
|
||||
return await this.doShowError(createEditorOpenError(localize('editorUnsupportedInAuxWindow', "This type of editor cannot be opened in detached windows yet."), [
|
||||
toAction({
|
||||
id: 'workbench.editor.action.closeEditor', label: localize('openFolder', "Close Editor"), run: async () => {
|
||||
return this.groupView.closeEditor(editor);
|
||||
|
||||
@@ -910,16 +910,18 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView {
|
||||
return targetView;
|
||||
}
|
||||
|
||||
mergeAllGroups(target = this.activeGroup): IEditorGroupView {
|
||||
mergeAllGroups(target: IEditorGroupView | GroupIdentifier): IEditorGroupView {
|
||||
const targetView = this.assertGroupView(target);
|
||||
|
||||
for (const group of this.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) {
|
||||
if (group === target) {
|
||||
if (group === targetView) {
|
||||
continue; // keep target
|
||||
}
|
||||
|
||||
this.mergeGroup(group, target);
|
||||
this.mergeGroup(group, targetView);
|
||||
}
|
||||
|
||||
return target;
|
||||
return targetView;
|
||||
}
|
||||
|
||||
protected assertGroupView(group: IEditorGroupView | GroupIdentifier): IEditorGroupView {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { EditorGroupLayout, GroupDirection, GroupOrientation, GroupsArrangement, GroupsOrder, IAuxiliaryEditorPart, IEditorDropTargetDelegate, IEditorGroupsService, IEditorSideGroup, IFindGroupScope, IMergeGroupOptions } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { EditorGroupLayout, GroupDirection, GroupLocation, GroupOrientation, GroupsArrangement, GroupsOrder, IAuxiliaryEditorPart, IEditorDropTargetDelegate, IEditorGroupsService, IEditorSideGroup, IFindGroupScope, IMergeGroupOptions } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { getActiveDocument } from 'vs/base/browser/dom';
|
||||
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
@@ -261,6 +261,21 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd
|
||||
return this.mainPart.getGroup(identifier);
|
||||
}
|
||||
|
||||
private assertGroupView(group: IEditorGroupView | GroupIdentifier): IEditorGroupView {
|
||||
let groupView: IEditorGroupView | undefined;
|
||||
if (typeof group === 'number') {
|
||||
groupView = this.getGroup(group);
|
||||
} else {
|
||||
groupView = group;
|
||||
}
|
||||
|
||||
if (!groupView) {
|
||||
throw new Error('Invalid editor group provided!');
|
||||
}
|
||||
|
||||
return groupView;
|
||||
}
|
||||
|
||||
activateGroup(group: IEditorGroupView | GroupIdentifier): IEditorGroupView {
|
||||
return this.getPart(group).activateGroup(group);
|
||||
}
|
||||
@@ -305,12 +320,46 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd
|
||||
this.activePart.setGroupOrientation(orientation);
|
||||
}
|
||||
|
||||
findGroup(scope: IFindGroupScope, source?: IEditorGroupView | GroupIdentifier, wrap?: boolean): IEditorGroupView | undefined {
|
||||
if (source) {
|
||||
return this.getPart(source).findGroup(scope, source, wrap);
|
||||
findGroup(scope: IFindGroupScope, source: IEditorGroupView | GroupIdentifier = this.activeGroup, wrap?: boolean): IEditorGroupView | undefined {
|
||||
const sourcePart = this.getPart(source);
|
||||
if (this._parts.size > 1) {
|
||||
const groups = this.getGroups(GroupsOrder.GRID_APPEARANCE);
|
||||
|
||||
// Ensure that FIRST/LAST dispatches globally over all parts
|
||||
if (scope.location === GroupLocation.FIRST || scope.location === GroupLocation.LAST) {
|
||||
return scope.location === GroupLocation.FIRST ? groups[0] : groups[groups.length - 1];
|
||||
}
|
||||
|
||||
// Try to find in target part first without wrapping
|
||||
const group = sourcePart.findGroup(scope, source, false);
|
||||
if (group) {
|
||||
return group;
|
||||
}
|
||||
|
||||
// Ensure that NEXT/PREVIOUS dispatches globally over all parts
|
||||
if (scope.location === GroupLocation.NEXT || scope.location === GroupLocation.PREVIOUS) {
|
||||
const sourceGroup = this.assertGroupView(source);
|
||||
const index = groups.indexOf(sourceGroup);
|
||||
|
||||
if (scope.location === GroupLocation.NEXT) {
|
||||
let nextGroup: IEditorGroupView | undefined = groups[index + 1];
|
||||
if (!nextGroup && wrap) {
|
||||
nextGroup = groups[0];
|
||||
}
|
||||
|
||||
return nextGroup;
|
||||
} else {
|
||||
let previousGroup: IEditorGroupView | undefined = groups[index - 1];
|
||||
if (!previousGroup && wrap) {
|
||||
previousGroup = groups[groups.length - 1];
|
||||
}
|
||||
|
||||
return previousGroup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.activePart.findGroup(scope, source, wrap);
|
||||
return sourcePart.findGroup(scope, source, wrap);
|
||||
}
|
||||
|
||||
addGroup(location: IEditorGroupView | GroupIdentifier, direction: GroupDirection): IEditorGroupView {
|
||||
@@ -329,8 +378,8 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd
|
||||
return this.getPart(group).mergeGroup(group, target, options);
|
||||
}
|
||||
|
||||
mergeAllGroups(): IEditorGroupView {
|
||||
return this.activePart.mergeAllGroups();
|
||||
mergeAllGroups(target: IEditorGroupView | GroupIdentifier): IEditorGroupView {
|
||||
return this.activePart.mergeAllGroups(target);
|
||||
}
|
||||
|
||||
copyGroup(group: IEditorGroupView | GroupIdentifier, location: IEditorGroupView | GroupIdentifier, direction: GroupDirection): IEditorGroupView {
|
||||
|
||||
@@ -140,7 +140,7 @@ class ViewPaneDropOverlay extends Themable {
|
||||
this.overlay.style.outlineWidth = activeContrastBorderColor ? '2px' : '';
|
||||
|
||||
this.overlay.style.borderColor = activeContrastBorderColor || '';
|
||||
this.overlay.style.borderStyle = 'solid' || '';
|
||||
this.overlay.style.borderStyle = 'solid';
|
||||
this.overlay.style.borderWidth = '0px';
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { HidDeviceData, requestHidDevice, requestSerialPort, requestUsbDevice, S
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { matchesScheme, Schemas } from 'vs/base/common/network';
|
||||
import { isIOS, isMacintosh } from 'vs/base/common/platform';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
@@ -19,7 +19,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { IDialogService, IPromptButton } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
|
||||
@@ -247,7 +247,7 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
|
||||
'workbench.editor.dragToOpenWindow': {
|
||||
'type': 'boolean',
|
||||
'default': product.quality !== 'stable',
|
||||
'markdownDescription': localize('dragToOpenWindow', "Controls if editors can be dragged out of the window to open them in a new floating window. Press and hold `Alt`-key while dragging to toggle this dynamically.")
|
||||
'markdownDescription': localize('dragToOpenWindow', "Controls if editors can be dragged out of the window to open them in a new detached window. Press and hold `Alt`-key while dragging to toggle this dynamically.")
|
||||
},
|
||||
'workbench.editor.focusRecentEditorAfterClose': {
|
||||
'type': 'boolean',
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/act
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
|
||||
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
|
||||
import { CONTEXT_RESPONSE_FILTERED } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
||||
import { IChatRequestViewModel, IChatResponseViewModel, isRequestVM, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
|
||||
|
||||
export function registerChatCopyActions() {
|
||||
@@ -24,6 +25,7 @@ export function registerChatCopyActions() {
|
||||
category: CHAT_CATEGORY,
|
||||
menu: {
|
||||
id: MenuId.ChatContext,
|
||||
when: CONTEXT_RESPONSE_FILTERED.toNegated(),
|
||||
group: 'copy',
|
||||
}
|
||||
});
|
||||
@@ -36,8 +38,8 @@ export function registerChatCopyActions() {
|
||||
if (widget) {
|
||||
const viewModel = widget.viewModel;
|
||||
const sessionAsText = viewModel?.getItems()
|
||||
.filter((item): item is (IChatRequestViewModel | IChatResponseViewModel) => isRequestVM(item) || isResponseVM(item))
|
||||
.map(stringifyItem)
|
||||
.filter((item): item is (IChatRequestViewModel | IChatResponseViewModel) => isRequestVM(item) || (isResponseVM(item) && !item.errorDetails?.responseIsFiltered))
|
||||
.map(item => stringifyItem(item))
|
||||
.join('\n\n');
|
||||
if (sessionAsText) {
|
||||
clipboardService.writeText(sessionAsText);
|
||||
@@ -58,6 +60,7 @@ export function registerChatCopyActions() {
|
||||
category: CHAT_CATEGORY,
|
||||
menu: {
|
||||
id: MenuId.ChatContext,
|
||||
when: CONTEXT_RESPONSE_FILTERED.toNegated(),
|
||||
group: 'copy',
|
||||
}
|
||||
});
|
||||
@@ -70,13 +73,16 @@ export function registerChatCopyActions() {
|
||||
}
|
||||
|
||||
const clipboardService = accessor.get(IClipboardService);
|
||||
const text = stringifyItem(item);
|
||||
const text = stringifyItem(item, false);
|
||||
clipboardService.writeText(text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function stringifyItem(item: IChatRequestViewModel | IChatResponseViewModel): string {
|
||||
return isRequestVM(item) ?
|
||||
`${item.username}: ${item.messageText}` : `${item.username}: ${item.response.asString()}`;
|
||||
function stringifyItem(item: IChatRequestViewModel | IChatResponseViewModel, includeName = true): string {
|
||||
if (isRequestVM(item)) {
|
||||
return (includeName ? `${item.username}: ` : '') + item.messageText;
|
||||
} else {
|
||||
return (includeName ? `${item.username}: ` : '') + item.response.asString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,14 +50,14 @@ import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibil
|
||||
import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
|
||||
import { ChatTreeItem, IChatCodeBlockInfo, IChatFileTreeInfo } from 'vs/workbench/contrib/chat/browser/chat';
|
||||
import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups';
|
||||
import { convertParsedRequestToMarkdown, reduceInlineContentReferences, walkTreeAndAnnotateReferenceLinks } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer';
|
||||
import { annotateSpecialMarkdownContent, convertParsedRequestToMarkdown, extractVulnerabilitiesFromText, walkTreeAndAnnotateReferenceLinks } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer';
|
||||
import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions';
|
||||
import { CodeBlockPart, ICodeBlockData, ICodeBlockPart } from 'vs/workbench/contrib/chat/browser/codeBlockPart';
|
||||
import { IChatAgentMetadata } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||
import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
||||
import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||
import { IChatProgressRenderableResponseContent } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||
import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||
import { IChatContentInlineReference, IChatContentReference, IChatReplyFollowup, IChatResponseProgressFileTreeData, IChatService, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
import { IChatContentReference, IChatReplyFollowup, IChatResponseProgressFileTreeData, IChatService, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
import { IChatResponseMarkdownRenderData, IChatResponseRenderData, IChatResponseViewModel, IChatWelcomeMessageViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
|
||||
import { IWordCountResult, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter';
|
||||
import { createFileIconThemableTreeContainerScope } from 'vs/workbench/contrib/files/browser/views/explorerView';
|
||||
@@ -68,7 +68,7 @@ const $ = dom.$;
|
||||
|
||||
interface IChatListItemTemplate {
|
||||
readonly rowContainer: HTMLElement;
|
||||
readonly titleToolbar: MenuWorkbenchToolBar;
|
||||
readonly titleToolbar?: MenuWorkbenchToolBar;
|
||||
readonly avatarContainer: HTMLElement;
|
||||
readonly agentAvatarContainer: HTMLElement;
|
||||
readonly username: HTMLElement;
|
||||
@@ -94,6 +94,8 @@ export interface IChatRendererDelegate {
|
||||
|
||||
export interface IChatListItemRendererOptions {
|
||||
readonly renderStyle?: 'default' | 'compact';
|
||||
readonly noHeader?: boolean;
|
||||
readonly noPadding?: boolean;
|
||||
}
|
||||
|
||||
export class ChatListItemRenderer extends Disposable implements ITreeRenderer<ChatTreeItem, FuzzyScore, IChatListItemTemplate> {
|
||||
@@ -211,7 +213,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
|
||||
}
|
||||
|
||||
layout(width: number): void {
|
||||
this._currentLayoutWidth = width - 40; // TODO Padding
|
||||
this._currentLayoutWidth = width - (this.rendererOptions.noPadding ? 0 : 40); // padding
|
||||
this._editorPool.inUse.forEach(editor => {
|
||||
editor.layout(this._currentLayoutWidth);
|
||||
});
|
||||
@@ -223,6 +225,9 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
|
||||
if (this.rendererOptions.renderStyle === 'compact') {
|
||||
rowContainer.classList.add('interactive-item-compact');
|
||||
}
|
||||
if (this.rendererOptions.noPadding) {
|
||||
rowContainer.classList.add('no-padding');
|
||||
}
|
||||
const header = dom.append(rowContainer, $('.header'));
|
||||
const user = dom.append(header, $('.user'));
|
||||
const avatarContainer = dom.append(user, $('.avatar-container'));
|
||||
@@ -238,26 +243,32 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
|
||||
|
||||
const contextKeyService = templateDisposables.add(this.contextKeyService.createScoped(rowContainer));
|
||||
const scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyService]));
|
||||
const titleToolbar = templateDisposables.add(scopedInstantiationService.createInstance(MenuWorkbenchToolBar, header, MenuId.ChatMessageTitle, {
|
||||
menuOptions: {
|
||||
shouldForwardArgs: true
|
||||
},
|
||||
actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => {
|
||||
if (action instanceof MenuItemAction && (action.item.id === 'workbench.action.chat.voteDown' || action.item.id === 'workbench.action.chat.voteUp')) {
|
||||
return scopedInstantiationService.createInstance(ChatVoteButton, action, options as IMenuEntryActionViewItemOptions);
|
||||
let titleToolbar: MenuWorkbenchToolBar | undefined;
|
||||
if (this.rendererOptions.noHeader) {
|
||||
header.classList.add('hidden');
|
||||
} else {
|
||||
titleToolbar = templateDisposables.add(scopedInstantiationService.createInstance(MenuWorkbenchToolBar, header, MenuId.ChatMessageTitle, {
|
||||
menuOptions: {
|
||||
shouldForwardArgs: true
|
||||
},
|
||||
actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => {
|
||||
if (action instanceof MenuItemAction && (action.item.id === 'workbench.action.chat.voteDown' || action.item.id === 'workbench.action.chat.voteUp')) {
|
||||
return scopedInstantiationService.createInstance(ChatVoteButton, action, options as IMenuEntryActionViewItemOptions);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
}));
|
||||
}
|
||||
const template: IChatListItemTemplate = { avatarContainer, agentAvatarContainer, username, detail, progressSteps, referencesListContainer, value, rowContainer, elementDisposables, titleToolbar, templateDisposables, contextKeyService };
|
||||
return template;
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<ChatTreeItem, FuzzyScore>, index: number, templateData: IChatListItemTemplate): void {
|
||||
const { element } = node;
|
||||
this.renderChatTreeItem(node.element, index, templateData);
|
||||
}
|
||||
|
||||
renderChatTreeItem(element: ChatTreeItem, index: number, templateData: IChatListItemTemplate): void {
|
||||
const kind = isRequestVM(element) ? 'request' :
|
||||
isResponseVM(element) ? 'response' :
|
||||
'welcome';
|
||||
@@ -272,7 +283,9 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
|
||||
CONTEXT_RESPONSE_VOTE.bindTo(templateData.contextKeyService).set('');
|
||||
}
|
||||
|
||||
templateData.titleToolbar.context = element;
|
||||
if (templateData.titleToolbar) {
|
||||
templateData.titleToolbar.context = element;
|
||||
}
|
||||
|
||||
const isFiltered = !!(isResponseVM(element) && element.errorDetails?.responseIsFiltered);
|
||||
CONTEXT_RESPONSE_FILTERED.bindTo(templateData.contextKeyService).set(isFiltered);
|
||||
@@ -283,7 +296,9 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
|
||||
templateData.rowContainer.classList.toggle('filtered-response', isFiltered);
|
||||
templateData.rowContainer.classList.toggle('show-progress', isResponseVM(element) && !element.isComplete);
|
||||
templateData.username.textContent = element.username;
|
||||
this.renderAvatar(element, templateData);
|
||||
if (!this.rendererOptions.noHeader) {
|
||||
this.renderAvatar(element, templateData);
|
||||
}
|
||||
|
||||
dom.clearNode(templateData.detail);
|
||||
if (isResponseVM(element)) {
|
||||
@@ -316,7 +331,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
|
||||
timer.cancelAndSet(runProgressiveRender, 50, dom.getWindow(templateData.rowContainer));
|
||||
runProgressiveRender(true);
|
||||
} else if (isResponseVM(element)) {
|
||||
const renderableResponse = reduceInlineContentReferences(element.response.value);
|
||||
const renderableResponse = annotateSpecialMarkdownContent(element.response.value);
|
||||
this.basicRenderElement(renderableResponse, element, index, templateData);
|
||||
} else if (isRequestVM(element)) {
|
||||
const markdown = 'kind' in element.message ?
|
||||
@@ -417,7 +432,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
|
||||
}
|
||||
}
|
||||
|
||||
private basicRenderElement(value: ReadonlyArray<Exclude<IChatProgressResponseContent, IChatContentInlineReference>>, element: ChatTreeItem, index: number, templateData: IChatListItemTemplate) {
|
||||
private basicRenderElement(value: ReadonlyArray<IChatProgressRenderableResponseContent>, element: ChatTreeItem, index: number, templateData: IChatListItemTemplate) {
|
||||
const fillInIncompleteTokens = isResponseVM(element) && (!element.isComplete || element.isCanceled || element.errorDetails?.responseIsFiltered || element.errorDetails?.responseIsIncomplete);
|
||||
|
||||
dom.clearNode(templateData.value);
|
||||
@@ -520,7 +535,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
|
||||
|
||||
disposables.clear();
|
||||
|
||||
const renderableResponse = reduceInlineContentReferences(element.response.value);
|
||||
const annotatedResult = annotateSpecialMarkdownContent(element.response.value);
|
||||
const renderableResponse = annotatedResult;
|
||||
let isFullyRendered = false;
|
||||
if (element.isCanceled) {
|
||||
this.traceLayout('runProgressiveRender', `canceled, index=${index}`);
|
||||
@@ -801,8 +817,10 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
|
||||
const result = this.renderer.render(markdown, {
|
||||
fillInIncompleteTokens,
|
||||
codeBlockRendererSync: (languageId, text) => {
|
||||
const vulns = extractVulnerabilitiesFromText(text);
|
||||
|
||||
const hideToolbar = isResponseVM(element) && element.errorDetails?.responseIsFiltered;
|
||||
const data = { languageId, text, codeBlockIndex: codeBlockIndex++, element, hideToolbar, parentContextKeyService: templateData.contextKeyService };
|
||||
const data = { languageId, text: vulns.newText, codeBlockIndex: codeBlockIndex++, element, hideToolbar, parentContextKeyService: templateData.contextKeyService, vulns: vulns.vulnerabilities };
|
||||
const ref = this.renderCodeBlock(data, disposables);
|
||||
|
||||
// Attach this after updating text/layout of the editor, so it should only be fired when the size updates later (horizontal scrollbar, wrapping)
|
||||
|
||||
@@ -8,10 +8,11 @@ import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { revive } from 'vs/base/common/marshalling';
|
||||
import { basename } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { Location } from 'vs/editor/common/languages';
|
||||
import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||
import { IChatProgressRenderableResponseContent, IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||
import { ChatRequestTextPart, IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||
import { IChatContentInlineReference } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
import { IChatAgentMarkdownContentWithVulnerability, IChatContentInlineReference } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
|
||||
const variableRefUrl = 'http://_vscodedecoration_';
|
||||
|
||||
@@ -58,10 +59,39 @@ function renderFileWidget(href: string, a: HTMLAnchorElement): void {
|
||||
a.setAttribute('data-href', location.uri.with({ fragment }).toString());
|
||||
}
|
||||
|
||||
export interface IMarkdownVulnerability {
|
||||
title: string;
|
||||
description: string;
|
||||
range: IRange;
|
||||
}
|
||||
|
||||
export function extractVulnerabilitiesFromText(text: string): { newText: string; vulnerabilities: IMarkdownVulnerability[] } {
|
||||
const vulnerabilities: IMarkdownVulnerability[] = [];
|
||||
let newText = text;
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = /<vscode_annotation title="(.*?)" description="(.*?)">(.*?)<\/vscode_annotation>/ms.exec(newText)) !== null) {
|
||||
const [full, title, description, content] = match;
|
||||
const start = match.index;
|
||||
const textBefore = newText.substring(0, start);
|
||||
const linesBefore = textBefore.split('\n').length - 1;
|
||||
const linesInside = content.split('\n').length - 1;
|
||||
|
||||
const previousNewlineIdx = textBefore.lastIndexOf('\n');
|
||||
const startColumn = start - (previousNewlineIdx + 1) + 1;
|
||||
const endPreviousNewlineIdx = (textBefore + content).lastIndexOf('\n');
|
||||
const endColumn = start + content.length - (endPreviousNewlineIdx + 1) + 1;
|
||||
|
||||
vulnerabilities.push({ title: decodeURIComponent(title), description: decodeURIComponent(description), range: { startLineNumber: linesBefore + 1, startColumn, endLineNumber: linesBefore + linesInside + 1, endColumn } });
|
||||
newText = newText.substring(0, start) + content + newText.substring(start + full.length);
|
||||
}
|
||||
|
||||
return { newText, vulnerabilities };
|
||||
}
|
||||
|
||||
const contentRefUrl = 'http://_vscodecontentref_'; // must be lowercase for URI
|
||||
|
||||
export function reduceInlineContentReferences(response: ReadonlyArray<IChatProgressResponseContent>): ReadonlyArray<Exclude<IChatProgressResponseContent, IChatContentInlineReference>> {
|
||||
const result: Exclude<IChatProgressResponseContent, IChatContentInlineReference>[] = [];
|
||||
export function annotateSpecialMarkdownContent(response: ReadonlyArray<IChatProgressResponseContent>): ReadonlyArray<IChatProgressRenderableResponseContent> {
|
||||
const result: Exclude<IChatProgressResponseContent, IChatContentInlineReference | IChatAgentMarkdownContentWithVulnerability>[] = [];
|
||||
for (const item of response) {
|
||||
const previousItem = result[result.length - 1];
|
||||
if (item.kind === 'inlineReference') {
|
||||
@@ -75,6 +105,13 @@ export function reduceInlineContentReferences(response: ReadonlyArray<IChatProgr
|
||||
}
|
||||
} else if (item.kind === 'markdownContent' && previousItem?.kind === 'markdownContent') {
|
||||
result[result.length - 1] = { content: new MarkdownString(previousItem.content.value + item.content.value, { isTrusted: previousItem.content.isTrusted }), kind: 'markdownContent' };
|
||||
} else if (item.kind === 'markdownVuln') {
|
||||
const markdownText = `<vscode_annotation title="${encodeURIComponent(item.title)}" description="${encodeURIComponent(item.description)}">${item.content.value}</vscode_annotation>`;
|
||||
if (previousItem?.kind === 'markdownContent') {
|
||||
result[result.length - 1] = { content: new MarkdownString(previousItem.content.value + markdownText, { isTrusted: previousItem.content.isTrusted }), kind: 'markdownContent' };
|
||||
} else {
|
||||
result.push({ content: new MarkdownString(markdownText), kind: 'markdownContent' });
|
||||
}
|
||||
} else {
|
||||
result.push(item);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart';
|
||||
import { ChatAccessibilityProvider, ChatListDelegate, ChatListItemRenderer, IChatListItemRendererOptions, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer';
|
||||
import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions';
|
||||
import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane';
|
||||
import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
||||
import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_SESSION, CONTEXT_RESPONSE_FILTERED } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
||||
import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService';
|
||||
import { ChatModelInitState, IChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||
import { IChatReplyFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
@@ -354,12 +354,16 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||
e.browserEvent.preventDefault();
|
||||
e.browserEvent.stopPropagation();
|
||||
|
||||
const selected = e.element;
|
||||
const scopedContextKeyService = this.contextKeyService.createOverlay([
|
||||
[CONTEXT_RESPONSE_FILTERED.key, isResponseVM(selected) && !!selected.errorDetails?.responseIsFiltered]
|
||||
]);
|
||||
this.contextMenuService.showContextMenu({
|
||||
menuId: MenuId.ChatContext,
|
||||
menuActionOptions: { shouldForwardArgs: true },
|
||||
contextKeyService: this.contextKeyService,
|
||||
contextKeyService: scopedContextKeyService,
|
||||
getAnchor: () => e.anchor,
|
||||
getActionsContext: () => e.element,
|
||||
getActionsContext: () => selected,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -44,11 +44,12 @@
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.interactive-result-code-block .interactive-result-editor .monaco-editor {
|
||||
.interactive-result-code-block {
|
||||
border: 1px solid var(--vscode-input-border, transparent);
|
||||
background-color: var(--vscode-interactive-result-editor-background-color);
|
||||
}
|
||||
|
||||
.interactive-result-code-block .interactive-result-editor .monaco-editor.focused {
|
||||
.interactive-result-code-block:has(.monaco-editor.focused) {
|
||||
border-color: var(--vscode-focusBorder, transparent);
|
||||
}
|
||||
|
||||
@@ -57,3 +58,56 @@
|
||||
.interactive-result-code-block .monaco-editor .overflow-guard {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.interactive-result-code-block .interactive-result-vulns {
|
||||
font-size: 0.9em;
|
||||
padding: 0px 8px 2px 8px;
|
||||
}
|
||||
|
||||
.interactive-result-code-block .interactive-result-vulns-header {
|
||||
display: flex;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.interactive-result-code-block .interactive-result-vulns-header,
|
||||
.interactive-result-code-block .interactive-result-vulns-list {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.interactive-result-code-block .interactive-result-vulns-list {
|
||||
margin: 0px;
|
||||
padding-bottom: 3px;
|
||||
padding-left: 16px !important; /* Override markdown styles */
|
||||
}
|
||||
|
||||
.interactive-result-code-block.chat-vulnerabilities-collapsed .interactive-result-vulns-list {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.interactive-result-code-block .interactive-result-vulns-list .chat-vuln-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.interactive-result-code-block.no-vulns .interactive-result-vulns {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.interactive-result-code-block .interactive-result-vulns-header .monaco-button {
|
||||
/* unset Button styles */
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
border: none;
|
||||
padding: 0;
|
||||
text-align: initial;
|
||||
justify-content: initial;
|
||||
color: var(--vscode-foreground) !important; /* This is inside .rendered-markdown */
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.interactive-result-code-block .interactive-result-vulns-header .monaco-text-button:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.interactive-result-code-block .interactive-result-vulns-header .monaco-text-button:focus-visible {
|
||||
outline: 1px solid var(--vscode-focusBorder);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import * as dom from 'vs/base/browser/dom';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions';
|
||||
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||
import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
@@ -31,14 +33,15 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration';
|
||||
import { IMarkdownVulnerability } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer';
|
||||
import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions';
|
||||
import { IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
|
||||
import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer';
|
||||
import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard';
|
||||
import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
|
||||
export interface ICodeBlockData {
|
||||
text: string;
|
||||
languageId: string;
|
||||
@@ -46,6 +49,7 @@ export interface ICodeBlockData {
|
||||
element: unknown;
|
||||
parentContextKeyService?: IContextKeyService;
|
||||
hideToolbar?: boolean;
|
||||
vulns?: IMarkdownVulnerability[];
|
||||
}
|
||||
|
||||
export interface ICodeBlockActionContext {
|
||||
@@ -57,7 +61,7 @@ export interface ICodeBlockActionContext {
|
||||
|
||||
|
||||
export interface ICodeBlockPart {
|
||||
readonly onDidChangeContentHeight: Event<number>;
|
||||
readonly onDidChangeContentHeight: Event<void>;
|
||||
readonly element: HTMLElement;
|
||||
readonly textModel: ITextModel;
|
||||
layout(width: number): void;
|
||||
@@ -69,16 +73,20 @@ export interface ICodeBlockPart {
|
||||
const defaultCodeblockPadding = 10;
|
||||
|
||||
export class CodeBlockPart extends Disposable implements ICodeBlockPart {
|
||||
private readonly _onDidChangeContentHeight = this._register(new Emitter<number>());
|
||||
private readonly _onDidChangeContentHeight = this._register(new Emitter<void>());
|
||||
public readonly onDidChangeContentHeight = this._onDidChangeContentHeight.event;
|
||||
|
||||
private readonly editor: CodeEditorWidget;
|
||||
private readonly toolbar: MenuWorkbenchToolBar;
|
||||
private readonly contextKeyService: IContextKeyService;
|
||||
|
||||
private readonly vulnsButton: Button;
|
||||
private readonly vulnsListElement: HTMLElement;
|
||||
|
||||
public readonly textModel: ITextModel;
|
||||
public readonly element: HTMLElement;
|
||||
|
||||
private currentCodeBlockData: ICodeBlockData | undefined;
|
||||
private currentScrollWidth = 0;
|
||||
|
||||
constructor(
|
||||
@@ -134,6 +142,32 @@ export class CodeBlockPart extends Disposable implements ICodeBlockPart {
|
||||
shouldForwardArgs: true
|
||||
}
|
||||
}));
|
||||
|
||||
const vulnsContainer = dom.append(this.element, $('.interactive-result-vulns'));
|
||||
const vulnsHeaderElement = dom.append(vulnsContainer, $('.interactive-result-vulns-header', undefined));
|
||||
this.vulnsButton = new Button(vulnsHeaderElement, {
|
||||
buttonBackground: undefined,
|
||||
buttonBorder: undefined,
|
||||
buttonForeground: undefined,
|
||||
buttonHoverBackground: undefined,
|
||||
buttonSecondaryBackground: undefined,
|
||||
buttonSecondaryForeground: undefined,
|
||||
buttonSecondaryHoverBackground: undefined,
|
||||
buttonSeparator: undefined,
|
||||
supportIcons: true
|
||||
});
|
||||
|
||||
this.vulnsListElement = dom.append(vulnsContainer, $('ul.interactive-result-vulns-list'));
|
||||
|
||||
this.vulnsButton.onDidClick(() => {
|
||||
const element = this.currentCodeBlockData!.element as IChatResponseViewModel;
|
||||
element.vulnerabilitiesListExpanded = !element.vulnerabilitiesListExpanded;
|
||||
this.vulnsButton.label = this.getVulnerabilitiesLabel();
|
||||
this.element.classList.toggle('chat-vulnerabilities-collapsed', !element.vulnerabilitiesListExpanded);
|
||||
this._onDidChangeContentHeight.fire();
|
||||
// this.updateAriaLabel(collapseButton.element, referencesLabel, element.usedReferencesExpanded);
|
||||
});
|
||||
|
||||
this._register(this.toolbar.onDidChangeDropdownVisibility(e => {
|
||||
toolbarElement.classList.toggle('force-visibility', e);
|
||||
}));
|
||||
@@ -155,7 +189,7 @@ export class CodeBlockPart extends Disposable implements ICodeBlockPart {
|
||||
}));
|
||||
this._register(this.editor.onDidContentSizeChange(e => {
|
||||
if (e.contentHeightChanged) {
|
||||
this._onDidChangeContentHeight.fire(e.contentHeight);
|
||||
this._onDidChangeContentHeight.fire();
|
||||
}
|
||||
}));
|
||||
this._register(this.editor.onDidBlurEditorWidget(() => {
|
||||
@@ -220,6 +254,7 @@ export class CodeBlockPart extends Disposable implements ICodeBlockPart {
|
||||
|
||||
|
||||
render(data: ICodeBlockData, width: number): void {
|
||||
this.currentCodeBlockData = data;
|
||||
if (data.parentContextKeyService) {
|
||||
this.contextKeyService.updateParent(data.parentContextKeyService);
|
||||
}
|
||||
@@ -250,6 +285,28 @@ export class CodeBlockPart extends Disposable implements ICodeBlockPart {
|
||||
} else {
|
||||
dom.show(this.toolbar.getElement());
|
||||
}
|
||||
|
||||
if (data.vulns?.length && isResponseVM(data.element)) {
|
||||
dom.clearNode(this.vulnsListElement);
|
||||
this.element.classList.remove('no-vulns');
|
||||
this.element.classList.toggle('chat-vulnerabilities-collapsed', !data.element.vulnerabilitiesListExpanded);
|
||||
dom.append(this.vulnsListElement, ...data.vulns.map(v => $('li', undefined, $('span.chat-vuln-title', undefined, v.title), ' ' + v.description)));
|
||||
this.vulnsButton.label = this.getVulnerabilitiesLabel();
|
||||
} else {
|
||||
this.element.classList.add('no-vulns');
|
||||
}
|
||||
}
|
||||
|
||||
private getVulnerabilitiesLabel(): string {
|
||||
if (!this.currentCodeBlockData || !this.currentCodeBlockData.vulns) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const referencesLabel = this.currentCodeBlockData.vulns.length > 1 ?
|
||||
localize('vulnerabilitiesPlural', "{0} vulnerabilities", this.currentCodeBlockData.vulns.length) :
|
||||
localize('vulnerabilitiesSingular', "{0} vulnerability", 1);
|
||||
const icon = (element: IChatResponseViewModel) => element.vulnerabilitiesListExpanded ? Codicon.chevronDown : Codicon.chevronRight;
|
||||
return `${referencesLabel} $(${icon(this.currentCodeBlockData.element as IChatResponseViewModel).id})`;
|
||||
}
|
||||
|
||||
private fixCodeText(text: string, languageId: string): string {
|
||||
|
||||
@@ -31,6 +31,10 @@
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.interactive-item-container .header.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.interactive-item-container .header .user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -270,6 +274,10 @@
|
||||
padding: 8px 20px;
|
||||
}
|
||||
|
||||
.interactive-item-container.interactive-item-compact.no-padding {
|
||||
padding: unset;
|
||||
}
|
||||
|
||||
.interactive-item-container.interactive-item-compact .header {
|
||||
height: 16px;
|
||||
}
|
||||
@@ -460,8 +468,9 @@
|
||||
}
|
||||
|
||||
.interactive-item-container.filtered-response .value > .rendered-markdown {
|
||||
-webkit-mask-image: linear-gradient(rgba(0, 0, 0, 0.85), rgba(0, 0, 0, 0.05));
|
||||
mask-image: linear-gradient(rgba(0, 0, 0, 0.85), rgba(0, 0, 0, 0.05));
|
||||
pointer-events: none;
|
||||
-webkit-mask-image: linear-gradient(rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0.05) 60%, rgba(0, 0, 0, 0.00) 80%);
|
||||
mask-image: linear-gradient(rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0.05) 60%, rgba(0, 0, 0, 0.00) 80%);
|
||||
}
|
||||
|
||||
/* #region Quick Chat */
|
||||
@@ -602,5 +611,6 @@
|
||||
}
|
||||
|
||||
.interactive-item-container .progress-steps .progress-step .codicon-check {
|
||||
font-size: 14px;
|
||||
color: var(--vscode-debugIcon-startForeground);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { OffsetRange } from 'vs/editor/common/core/offsetRange';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||
import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||
import { IChat, IChatAsyncContent, IChatContent, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatReplyFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
import { IChat, IChatAgentMarkdownContentWithVulnerability, IChatAsyncContent, IChatContent, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatReplyFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
|
||||
export interface IChatRequestModel {
|
||||
readonly id: string;
|
||||
@@ -29,10 +29,13 @@ export interface IChatRequestModel {
|
||||
|
||||
export type IChatProgressResponseContent =
|
||||
| IChatMarkdownContent
|
||||
| IChatAgentMarkdownContentWithVulnerability
|
||||
| IChatTreeData
|
||||
| IChatAsyncContent
|
||||
| IChatContentInlineReference;
|
||||
|
||||
export type IChatProgressRenderableResponseContent = Exclude<IChatProgressResponseContent, IChatContentInlineReference | IChatAgentMarkdownContentWithVulnerability>;
|
||||
|
||||
export interface IResponse {
|
||||
readonly value: ReadonlyArray<IChatProgressResponseContent>;
|
||||
asString(): string;
|
||||
@@ -100,7 +103,7 @@ export class Response implements IResponse {
|
||||
return this._responseParts;
|
||||
}
|
||||
|
||||
constructor(value: IMarkdownString | ReadonlyArray<IMarkdownString | IChatResponseProgressFileTreeData | IChatContentInlineReference>) {
|
||||
constructor(value: IMarkdownString | ReadonlyArray<IMarkdownString | IChatResponseProgressFileTreeData | IChatContentInlineReference | IChatAgentMarkdownContentWithVulnerability>) {
|
||||
this._responseParts = asArray(value).map((v) => (isMarkdownString(v) ?
|
||||
{ content: v, kind: 'markdownContent' } satisfies IChatMarkdownContent :
|
||||
'kind' in v ? v : { kind: 'treeData', treeData: v }));
|
||||
@@ -112,6 +115,11 @@ export class Response implements IResponse {
|
||||
return this._responseRepr;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this._responseParts = [];
|
||||
this._updateRepr(true);
|
||||
}
|
||||
|
||||
updateContent(progress: IChatProgressResponseContent | IChatContent, quiet?: boolean): void {
|
||||
if (progress.kind === 'content' || progress.kind === 'markdownContent') {
|
||||
const responsePartLength = this._responseParts.length - 1;
|
||||
@@ -155,7 +163,7 @@ export class Response implements IResponse {
|
||||
}
|
||||
this._updateRepr(quiet);
|
||||
});
|
||||
} else if (progress.kind === 'treeData' || progress.kind === 'inlineReference') {
|
||||
} else if (progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln') {
|
||||
this._responseParts.push(progress);
|
||||
this._updateRepr(quiet);
|
||||
}
|
||||
@@ -256,7 +264,7 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel
|
||||
}
|
||||
|
||||
constructor(
|
||||
_response: IMarkdownString | ReadonlyArray<IMarkdownString | IChatResponseProgressFileTreeData | IChatContentInlineReference>,
|
||||
_response: IMarkdownString | ReadonlyArray<IMarkdownString | IChatResponseProgressFileTreeData | IChatContentInlineReference | IChatAgentMarkdownContentWithVulnerability>,
|
||||
public readonly session: ChatModel,
|
||||
agent: IChatAgentData | undefined,
|
||||
public readonly requestId: string,
|
||||
@@ -307,7 +315,11 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
complete(): void {
|
||||
complete(errorDetails?: IChatResponseErrorDetails): void {
|
||||
if (errorDetails?.responseIsRedacted) {
|
||||
this._response.clear();
|
||||
}
|
||||
|
||||
this._isComplete = true;
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
@@ -352,7 +364,7 @@ export type ISerializableChatAgentData = UriDto<IChatAgentData>;
|
||||
|
||||
export interface ISerializableChatRequestData {
|
||||
message: string | IParsedChatRequest;
|
||||
response: ReadonlyArray<IMarkdownString | IChatResponseProgressFileTreeData | IChatContentInlineReference> | undefined;
|
||||
response: ReadonlyArray<IMarkdownString | IChatResponseProgressFileTreeData | IChatContentInlineReference | IChatAgentMarkdownContentWithVulnerability> | undefined;
|
||||
agent?: ISerializableChatAgentData;
|
||||
slashCommand?: IChatAgentCommand;
|
||||
responseErrorDetails: IChatResponseErrorDetails | undefined;
|
||||
@@ -640,7 +652,10 @@ export class ChatModel extends Disposable implements IChatModel {
|
||||
throw new Error('acceptResponseProgress: Adding progress to a completed response');
|
||||
}
|
||||
|
||||
if (progress.kind === 'content' || progress.kind === 'markdownContent' || progress.kind === 'asyncContent' || progress.kind === 'treeData' || progress.kind === 'inlineReference') {
|
||||
if (progress.kind === 'vulnerability') {
|
||||
// TODO@roblourens ChatModel should just work with strings
|
||||
request.response.updateContent({ kind: 'markdownVuln', content: { value: progress.content }, title: progress.title, description: progress.description }, quiet);
|
||||
} else if (progress.kind === 'content' || progress.kind === 'markdownContent' || progress.kind === 'asyncContent' || progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln') {
|
||||
request.response.updateContent(progress, quiet);
|
||||
} else if (progress.kind === 'usedContext' || progress.kind === 'reference' || progress.kind === 'progressMessage') {
|
||||
request.response.applyProgress(progress);
|
||||
@@ -683,12 +698,12 @@ export class ChatModel extends Disposable implements IChatModel {
|
||||
request.response.setErrorDetails(rawResponse.errorDetails);
|
||||
}
|
||||
|
||||
completeResponse(request: ChatRequestModel): void {
|
||||
completeResponse(request: ChatRequestModel, errorDetails: IChatResponseErrorDetails | undefined): void {
|
||||
if (!request.response) {
|
||||
throw new Error('Call setResponse before completeResponse');
|
||||
}
|
||||
|
||||
request.response.complete();
|
||||
request.response.complete(errorDetails);
|
||||
}
|
||||
|
||||
setFollowups(request: ChatRequestModel, followups: IChatFollowup[] | undefined): void {
|
||||
|
||||
@@ -36,6 +36,7 @@ export interface IChatResponseErrorDetails {
|
||||
message: string;
|
||||
responseIsIncomplete?: boolean;
|
||||
responseIsFiltered?: boolean;
|
||||
responseIsRedacted?: boolean;
|
||||
}
|
||||
|
||||
export interface IChatResponse {
|
||||
@@ -130,9 +131,26 @@ export interface IChatProgressMessage {
|
||||
kind: 'progressMessage';
|
||||
}
|
||||
|
||||
export interface IChatAgentContentWithVulnerability {
|
||||
content: string;
|
||||
title: string;
|
||||
description: string;
|
||||
kind: 'vulnerability';
|
||||
}
|
||||
|
||||
// TODO@roblourens Temp until I get MarkdownString out of ChatModel
|
||||
export interface IChatAgentMarkdownContentWithVulnerability {
|
||||
content: IMarkdownString;
|
||||
title: string;
|
||||
description: string;
|
||||
kind: 'markdownVuln';
|
||||
}
|
||||
|
||||
export type IChatProgress =
|
||||
| IChatContent
|
||||
| IChatMarkdownContent
|
||||
| IChatAgentContentWithVulnerability
|
||||
| IChatAgentMarkdownContentWithVulnerability
|
||||
| IChatTreeData
|
||||
| IChatAsyncContent
|
||||
| IChatUsedContext
|
||||
|
||||
@@ -558,7 +558,7 @@ export class ChatService extends Disposable implements IChatService {
|
||||
rawResponse = { session: model.session! };
|
||||
|
||||
} else {
|
||||
throw new Error(`Can't handle request`);
|
||||
throw new Error(`Cannot handle request`);
|
||||
}
|
||||
|
||||
if (token.isCancellationRequested) {
|
||||
@@ -590,10 +590,10 @@ export class ChatService extends Disposable implements IChatService {
|
||||
if (agentOrCommandFollowups) {
|
||||
agentOrCommandFollowups.then(followups => {
|
||||
model.setFollowups(request, followups);
|
||||
model.completeResponse(request);
|
||||
model.completeResponse(request, rawResponse?.errorDetails);
|
||||
});
|
||||
} else {
|
||||
model.completeResponse(request);
|
||||
model.completeResponse(request, rawResponse?.errorDetails);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@@ -656,7 +656,7 @@ export class ChatService extends Disposable implements IChatService {
|
||||
if (response.followups !== undefined) {
|
||||
model.setFollowups(request, response.followups);
|
||||
}
|
||||
model.completeResponse(request);
|
||||
model.completeResponse(request, response.errorDetails);
|
||||
}
|
||||
|
||||
cancelCurrentRequestForSession(sessionId: string): void {
|
||||
|
||||
@@ -79,6 +79,7 @@ export interface IChatLiveUpdateData {
|
||||
loadingStartTime: number;
|
||||
lastUpdateTime: number;
|
||||
impliedWordLoadRate: number;
|
||||
lastWordCount: number;
|
||||
}
|
||||
|
||||
export interface IChatResponseViewModel {
|
||||
@@ -109,6 +110,7 @@ export interface IChatResponseViewModel {
|
||||
currentRenderedHeight: number | undefined;
|
||||
setVote(vote: InteractiveSessionVoteDirection): void;
|
||||
usedReferencesExpanded?: boolean;
|
||||
vulnerabilitiesListExpanded: boolean;
|
||||
}
|
||||
|
||||
export class ChatViewModel extends Disposable implements IChatViewModel {
|
||||
@@ -335,7 +337,6 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi
|
||||
currentRenderedHeight: number | undefined;
|
||||
|
||||
private _usedReferencesExpanded: boolean | undefined;
|
||||
|
||||
get usedReferencesExpanded(): boolean | undefined {
|
||||
if (typeof this._usedReferencesExpanded === 'boolean') {
|
||||
return this._usedReferencesExpanded;
|
||||
@@ -348,6 +349,15 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi
|
||||
this._usedReferencesExpanded = v;
|
||||
}
|
||||
|
||||
private _vulnerabilitiesListExpanded: boolean = false;
|
||||
get vulnerabilitiesListExpanded(): boolean {
|
||||
return this._vulnerabilitiesListExpanded;
|
||||
}
|
||||
|
||||
set vulnerabilitiesListExpanded(v: boolean) {
|
||||
this._vulnerabilitiesListExpanded = v;
|
||||
}
|
||||
|
||||
private _contentUpdateTimings: IChatLiveUpdateData | undefined = undefined;
|
||||
get contentUpdateTimings(): IChatLiveUpdateData | undefined {
|
||||
return this._contentUpdateTimings;
|
||||
@@ -363,7 +373,8 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi
|
||||
this._contentUpdateTimings = {
|
||||
loadingStartTime: Date.now(),
|
||||
lastUpdateTime: Date.now(),
|
||||
impliedWordLoadRate: 0
|
||||
impliedWordLoadRate: 0,
|
||||
lastWordCount: 0
|
||||
};
|
||||
}
|
||||
|
||||
@@ -373,18 +384,14 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi
|
||||
const now = Date.now();
|
||||
const wordCount = countWords(_model.response.asString());
|
||||
const timeDiff = now - this._contentUpdateTimings!.loadingStartTime;
|
||||
const impliedWordLoadRate = wordCount / (timeDiff / 1000);
|
||||
const renderedWordCount = this.renderData?.renderedParts.reduce((acc, part) => acc += ('label' in part ? 0 : part.renderedWordCount), 0);
|
||||
if (!this.isComplete) {
|
||||
this.trace('onDidChange', `Update- got ${wordCount} words over ${timeDiff}ms = ${impliedWordLoadRate} words/s. ${renderedWordCount} words are rendered.`);
|
||||
this._contentUpdateTimings = {
|
||||
loadingStartTime: this._contentUpdateTimings!.loadingStartTime,
|
||||
lastUpdateTime: now,
|
||||
impliedWordLoadRate
|
||||
};
|
||||
} else {
|
||||
this.trace(`onDidChange`, `Done- got ${wordCount} words over ${timeDiff}ms = ${impliedWordLoadRate} words/s. ${renderedWordCount} words are rendered.`);
|
||||
}
|
||||
const impliedWordLoadRate = this._contentUpdateTimings.lastWordCount / (timeDiff / 1000);
|
||||
this.trace('onDidChange', `Update- got ${this._contentUpdateTimings.lastWordCount} words over ${timeDiff}ms = ${impliedWordLoadRate} words/s. ${wordCount} words are now available.`);
|
||||
this._contentUpdateTimings = {
|
||||
loadingStartTime: this._contentUpdateTimings!.loadingStartTime,
|
||||
lastUpdateTime: now,
|
||||
impliedWordLoadRate,
|
||||
lastWordCount: wordCount
|
||||
};
|
||||
} else {
|
||||
this.logService.warn('ChatResponseViewModel#onDidChange: got model update but contentUpdateTimings is not initialized');
|
||||
}
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
content: {
|
||||
value: "some code\nover\nmultiple lines <vscode_annotation title=\"title\" description=\"vuln\">content with vuln\nand\nnewlines</vscode_annotation>more code\nwith newline",
|
||||
isTrusted: false,
|
||||
supportThemeIcons: false,
|
||||
supportHtml: false
|
||||
},
|
||||
kind: "markdownContent"
|
||||
}
|
||||
]
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
newText: "some code\nover\nmultiple lines content with vuln\nand\nnewlinesmore code\nwith newline",
|
||||
vulnerabilities: [
|
||||
{
|
||||
title: "title",
|
||||
description: "vuln",
|
||||
range: {
|
||||
startLineNumber: 3,
|
||||
startColumn: 16,
|
||||
endLineNumber: 5,
|
||||
endColumn: 9
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
content: {
|
||||
value: "some code\nover\nmultiple lines <vscode_annotation title=\"title\" description=\"vuln\">content with vuln\nand\nnewlines</vscode_annotation>more code\nwith newline<vscode_annotation title=\"title\" description=\"vuln\">content with vuln\nand\nnewlines</vscode_annotation>",
|
||||
isTrusted: false,
|
||||
supportThemeIcons: false,
|
||||
supportHtml: false
|
||||
},
|
||||
kind: "markdownContent"
|
||||
}
|
||||
]
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
{
|
||||
newText: "some code\nover\nmultiple lines content with vuln\nand\nnewlinesmore code\nwith newlinecontent with vuln\nand\nnewlines",
|
||||
vulnerabilities: [
|
||||
{
|
||||
title: "title",
|
||||
description: "vuln",
|
||||
range: {
|
||||
startLineNumber: 3,
|
||||
startColumn: 16,
|
||||
endLineNumber: 5,
|
||||
endColumn: 9
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "title",
|
||||
description: "vuln",
|
||||
range: {
|
||||
startLineNumber: 6,
|
||||
startColumn: 13,
|
||||
endLineNumber: 8,
|
||||
endColumn: 9
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user