mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-26 10:16:01 +01:00
Merge branch 'main' into fix-json-schema-parse-uri
This commit is contained in:
@@ -45,11 +45,11 @@ jobs:
|
||||
path: ${{ steps.npmCacheDirPath.outputs.dir }}
|
||||
key: ${{ runner.os }}-npmCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
restore-keys: ${{ runner.os }}-npmCacheDir-
|
||||
- name: Install libkrb5-dev
|
||||
- name: Install system dependencies
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y libkrb5-dev
|
||||
sudo apt install -y libxkbfile-dev pkg-config libkrb5-dev libxss1
|
||||
- name: Execute npm
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
env:
|
||||
|
||||
Vendored
+1
-1
@@ -7,7 +7,7 @@
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "github-issues",
|
||||
"value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"November 2024\""
|
||||
"value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"January 2025\""
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
|
||||
@@ -912,13 +912,6 @@
|
||||
"category": "Git",
|
||||
"enablement": "!operationInProgress"
|
||||
},
|
||||
{
|
||||
"command": "git.viewAllChanges",
|
||||
"title": "%command.viewAllChanges%",
|
||||
"icon": "$(diff-multiple)",
|
||||
"category": "Git",
|
||||
"enablement": "!operationInProgress"
|
||||
},
|
||||
{
|
||||
"command": "git.copyCommitId",
|
||||
"title": "%command.timelineCopyCommitId%",
|
||||
@@ -1444,10 +1437,6 @@
|
||||
"command": "git.viewCommit",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "git.viewAllChanges",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "git.stageFile",
|
||||
"when": "false"
|
||||
@@ -3229,6 +3218,14 @@
|
||||
"type": "string",
|
||||
"default": "${authorName} (${authorDateAgo})",
|
||||
"markdownDescription": "%config.blameStatusBarItem.template%"
|
||||
},
|
||||
"git.commitShortHashLength": {
|
||||
"type": "number",
|
||||
"default": 7,
|
||||
"minimum": 7,
|
||||
"maximum": 40,
|
||||
"markdownDescription": "%config.commitShortHashLength%",
|
||||
"scope": "resource"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -125,7 +125,6 @@
|
||||
"command.viewChanges": "View Changes",
|
||||
"command.viewStagedChanges": "View Staged Changes",
|
||||
"command.viewUntrackedChanges": "View Untracked Changes",
|
||||
"command.viewAllChanges": "View All Changes",
|
||||
"command.viewCommit": "View Commit",
|
||||
"command.api.getRepositories": "Get Repositories",
|
||||
"command.api.getRepositoryState": "Get Repository State",
|
||||
@@ -278,9 +277,10 @@
|
||||
"config.publishBeforeContinueOn.prompt": "Prompt to publish unpublished Git state when using Continue Working On from a Git repository",
|
||||
"config.similarityThreshold": "Controls the threshold of the similarity index (the amount of additions/deletions compared to the file's size) for changes in a pair of added/deleted files to be considered a rename. **Note:** Requires Git version `2.18.0` or later.",
|
||||
"config.blameEditorDecoration.enabled": "Controls whether to show blame information in the editor using editor decorations.",
|
||||
"config.blameEditorDecoration.template": "Template for the blame information editor decoration. Supported variables:\n\n* `hash`: Commit hash\n\n* `hashShort`: First 8 characters of the commit hash\n\n* `subject`: First line of the commit message\n\n* `authorName`: Author name\n\n* `authorEmail`: Author email\n\n* `authorDate`: Author date\n\n* `authorDateAgo`: Time difference between now and the author date\n\n",
|
||||
"config.blameEditorDecoration.template": "Template for the blame information editor decoration. Supported variables:\n\n* `hash`: Commit hash\n\n* `hashShort`: First N characters of the commit hash according to `#git.commitShortHashLength#`\n\n* `subject`: First line of the commit message\n\n* `authorName`: Author name\n\n* `authorEmail`: Author email\n\n* `authorDate`: Author date\n\n* `authorDateAgo`: Time difference between now and the author date\n\n",
|
||||
"config.blameStatusBarItem.enabled": "Controls whether to show blame information in the status bar.",
|
||||
"config.blameStatusBarItem.template": "Template for the blame information status bar item. Supported variables:\n\n* `hash`: Commit hash\n\n* `hashShort`: First 8 characters of the commit hash\n\n* `subject`: First line of the commit message\n\n* `authorName`: Author name\n\n* `authorEmail`: Author email\n\n* `authorDate`: Author date\n\n* `authorDateAgo`: Time difference between now and the author date\n\n",
|
||||
"config.blameStatusBarItem.template": "Template for the blame information status bar item. Supported variables:\n\n* `hash`: Commit hash\n\n* `hashShort`: First N characters of the commit hash according to `#git.commitShortHashLength#`\n\n* `subject`: First line of the commit message\n\n* `authorName`: Author name\n\n* `authorEmail`: Author email\n\n* `authorDate`: Author date\n\n* `authorDateAgo`: Time difference between now and the author date\n\n",
|
||||
"config.commitShortHashLength": "Controls the length of the commit short hash.",
|
||||
"submenu.explorer": "Git",
|
||||
"submenu.commit": "Commit",
|
||||
"submenu.commit.amend": "Amend",
|
||||
|
||||
+19
-11
@@ -5,7 +5,7 @@
|
||||
|
||||
import { DecorationOptions, l10n, Position, Range, TextEditor, TextEditorChange, TextEditorDecorationType, TextEditorChangeKind, ThemeColor, Uri, window, workspace, EventEmitter, ConfigurationChangeEvent, StatusBarItem, StatusBarAlignment, Command, MarkdownString, languages, HoverProvider, CancellationToken, Hover, TextDocument } from 'vscode';
|
||||
import { Model } from './model';
|
||||
import { dispose, fromNow, IDisposable } from './util';
|
||||
import { dispose, fromNow, getCommitShortHash, IDisposable } from './util';
|
||||
import { Repository } from './repository';
|
||||
import { throttle } from './decorators';
|
||||
import { BlameInformation, Commit } from './git';
|
||||
@@ -186,14 +186,14 @@ export class GitBlameController {
|
||||
this._onDidChangeConfiguration();
|
||||
}
|
||||
|
||||
formatBlameInformationMessage(template: string, blameInformation: BlameInformation): string {
|
||||
formatBlameInformationMessage(documentUri: Uri, template: string, blameInformation: BlameInformation): string {
|
||||
const subject = blameInformation.subject && blameInformation.subject.length > this._subjectMaxLength
|
||||
? `${blameInformation.subject.substring(0, this._subjectMaxLength)}\u2026`
|
||||
: blameInformation.subject;
|
||||
|
||||
const templateTokens = {
|
||||
hash: blameInformation.hash,
|
||||
hashShort: blameInformation.hash.substring(0, 8),
|
||||
hashShort: getCommitShortHash(documentUri, blameInformation.hash),
|
||||
subject: emojify(subject ?? ''),
|
||||
authorName: blameInformation.authorName ?? '',
|
||||
authorEmail: blameInformation.authorEmail ?? '',
|
||||
@@ -227,7 +227,12 @@ export class GitBlameController {
|
||||
markdownString.supportThemeIcons = true;
|
||||
|
||||
if (blameInformationOrCommit.authorName) {
|
||||
markdownString.appendMarkdown(`$(account) **${blameInformationOrCommit.authorName}**`);
|
||||
if (blameInformationOrCommit.authorEmail) {
|
||||
const emailTitle = l10n.t('Email');
|
||||
markdownString.appendMarkdown(`$(account) [**${blameInformationOrCommit.authorName}**](mailto:${blameInformationOrCommit.authorEmail} "${emailTitle} ${blameInformationOrCommit.authorName}")`);
|
||||
} else {
|
||||
markdownString.appendMarkdown(`$(account) **${blameInformationOrCommit.authorName}**`);
|
||||
}
|
||||
|
||||
if (blameInformationOrCommit.authorDate) {
|
||||
const dateString = new Date(blameInformationOrCommit.authorDate).toLocaleString(undefined, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' });
|
||||
@@ -260,9 +265,9 @@ export class GitBlameController {
|
||||
markdownString.appendMarkdown(`\n\n---\n\n`);
|
||||
}
|
||||
|
||||
markdownString.appendMarkdown(`[\`$(git-commit) ${blameInformationOrCommit.hash.substring(0, 8)} \`](command:git.blameStatusBarItem.viewCommit?${encodeURIComponent(JSON.stringify([documentUri, blameInformationOrCommit.hash]))} "${l10n.t('View Commit')}")`);
|
||||
markdownString.appendMarkdown(`[\`$(git-commit) ${getCommitShortHash(documentUri, blameInformationOrCommit.hash)} \`](command:git.viewCommit?${encodeURIComponent(JSON.stringify([documentUri, blameInformationOrCommit.hash]))} "${l10n.t('View Commit')}")`);
|
||||
markdownString.appendMarkdown(' ');
|
||||
markdownString.appendMarkdown(`[$(copy)](command:git.blameStatusBarItem.copyContent?${encodeURIComponent(JSON.stringify(blameInformationOrCommit.hash))} "${l10n.t('Copy Commit Hash')}")`);
|
||||
markdownString.appendMarkdown(`[$(copy)](command:git.copyContentToClipboard?${encodeURIComponent(JSON.stringify(blameInformationOrCommit.hash))} "${l10n.t('Copy Commit Hash')}")`);
|
||||
markdownString.appendMarkdown(' | ');
|
||||
markdownString.appendMarkdown(`[$(gear)](command:workbench.action.openSettings?%5B%22git.blame%22%5D "${l10n.t('Open Settings')}")`);
|
||||
|
||||
@@ -571,7 +576,9 @@ class GitBlameEditorDecoration implements HoverProvider {
|
||||
}
|
||||
|
||||
private _onDidChangeConfiguration(e?: ConfigurationChangeEvent): void {
|
||||
if (e && !e.affectsConfiguration('git.blame.editorDecoration.template')) {
|
||||
if (e &&
|
||||
!e.affectsConfiguration('git.commitShortHashLength') &&
|
||||
!e.affectsConfiguration('git.blame.editorDecoration.template')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -610,7 +617,7 @@ class GitBlameEditorDecoration implements HoverProvider {
|
||||
|
||||
const decorations = blameInformation.map(blame => {
|
||||
const contentText = typeof blame.blameInformation !== 'string'
|
||||
? this._controller.formatBlameInformationMessage(template, blame.blameInformation)
|
||||
? this._controller.formatBlameInformationMessage(textEditor.document.uri, template, blame.blameInformation)
|
||||
: blame.blameInformation;
|
||||
|
||||
return this._createDecoration(blame.lineNumber, contentText);
|
||||
@@ -663,7 +670,8 @@ class GitBlameStatusBarItem {
|
||||
}
|
||||
|
||||
private _onDidChangeConfiguration(e: ConfigurationChangeEvent): void {
|
||||
if (!e.affectsConfiguration('git.blame.statusBarItem.template')) {
|
||||
if (!e.affectsConfiguration('git.commitShortHashLength') &&
|
||||
!e.affectsConfiguration('git.blame.statusBarItem.template')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -690,11 +698,11 @@ class GitBlameStatusBarItem {
|
||||
const config = workspace.getConfiguration('git');
|
||||
const template = config.get<string>('blame.statusBarItem.template', '${authorName} (${authorDateAgo})');
|
||||
|
||||
this._statusBarItem.text = `$(git-commit) ${this._controller.formatBlameInformationMessage(template, blameInformation[0].blameInformation)}`;
|
||||
this._statusBarItem.text = `$(git-commit) ${this._controller.formatBlameInformationMessage(window.activeTextEditor.document.uri, template, blameInformation[0].blameInformation)}`;
|
||||
this._statusBarItem.tooltip = this._controller.getBlameInformationHover(window.activeTextEditor.document.uri, blameInformation[0].blameInformation);
|
||||
this._statusBarItem.command = {
|
||||
title: l10n.t('View Commit'),
|
||||
command: 'git.blameStatusBarItem.viewCommit',
|
||||
command: 'git.viewCommit',
|
||||
arguments: [window.activeTextEditor.document.uri, blameInformation[0].blameInformation.hash]
|
||||
} satisfies Command;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Model } from './model';
|
||||
import { GitResourceGroup, Repository, Resource, ResourceGroupType } from './repository';
|
||||
import { DiffEditorSelectionHunkToolbarContext, applyLineChanges, getModifiedRange, getWorkingTreeAndIndexDiffInformation, getWorkingTreeDiffInformation, intersectDiffWithRange, invertLineChange, toLineChanges, toLineRanges } from './staging';
|
||||
import { fromGitUri, toGitUri, isGitUri, toMergeUris, toMultiFileDiffEditorUris } from './uri';
|
||||
import { dispose, grep, isDefined, isDescendant, pathEquals, relativePath, truncate } from './util';
|
||||
import { dispose, getCommitShortHash, grep, isDefined, isDescendant, pathEquals, relativePath, truncate } from './util';
|
||||
import { GitTimelineItem } from './timelineProvider';
|
||||
import { ApiRepository } from './api/api1';
|
||||
import { getRemoteSourceActions, pickRemoteSource } from './remoteSource';
|
||||
@@ -4271,61 +4271,6 @@ export class CommandCenter {
|
||||
});
|
||||
}
|
||||
|
||||
@command('git.viewCommit', { repository: true })
|
||||
async viewCommit(repository: Repository, historyItem1: SourceControlHistoryItem, historyItem2?: SourceControlHistoryItem): Promise<void> {
|
||||
if (!repository || !historyItem1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (historyItem2) {
|
||||
const mergeBase = await repository.getMergeBase(historyItem1.id, historyItem2.id);
|
||||
if (!mergeBase || (mergeBase !== historyItem1.id && mergeBase !== historyItem2.id)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let title: string | undefined;
|
||||
let historyItemParentId: string | undefined;
|
||||
|
||||
// If historyItem2 is not provided, we are viewing a single commit. If historyItem2 is
|
||||
// provided, we are viewing a range and we have to include both start and end commits.
|
||||
// TODO@lszomoru - handle the case when historyItem2 is the first commit in the repository
|
||||
if (!historyItem2) {
|
||||
const commit = await repository.getCommit(historyItem1.id);
|
||||
title = `${historyItem1.id.substring(0, 8)} - ${truncate(commit.message)}`;
|
||||
historyItemParentId = historyItem1.parentIds.length > 0 ? historyItem1.parentIds[0] : `${historyItem1.id}^`;
|
||||
} else {
|
||||
title = l10n.t('All Changes ({0} ↔ {1})', historyItem2.id.substring(0, 8), historyItem1.id.substring(0, 8));
|
||||
historyItemParentId = historyItem2.parentIds.length > 0 ? historyItem2.parentIds[0] : `${historyItem2.id}^`;
|
||||
}
|
||||
|
||||
const multiDiffSourceUri = Uri.from({ scheme: 'scm-history-item', path: `${repository.root}/${historyItemParentId}..${historyItem1.id}` });
|
||||
|
||||
await this._viewChanges(repository, historyItem1.id, historyItemParentId, multiDiffSourceUri, title);
|
||||
}
|
||||
|
||||
@command('git.viewAllChanges', { repository: true })
|
||||
async viewAllChanges(repository: Repository, historyItem: SourceControlHistoryItem): Promise<void> {
|
||||
if (!repository || !historyItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
const modifiedShortRef = historyItem.id.substring(0, 8);
|
||||
const originalShortRef = historyItem.parentIds.length > 0 ? historyItem.parentIds[0].substring(0, 8) : `${modifiedShortRef}^`;
|
||||
const title = l10n.t('All Changes ({0} ↔ {1})', originalShortRef, modifiedShortRef);
|
||||
|
||||
const multiDiffSourceUri = toGitUri(Uri.file(repository.root), historyItem.id, { scheme: 'git-changes' });
|
||||
|
||||
await this._viewChanges(repository, modifiedShortRef, originalShortRef, multiDiffSourceUri, title);
|
||||
}
|
||||
|
||||
async _viewChanges(repository: Repository, historyItemId: string, historyItemParentId: string, multiDiffSourceUri: Uri, title: string): Promise<void> {
|
||||
const changes = await repository.diffBetween(historyItemParentId, historyItemId);
|
||||
const resources = changes.map(c => toMultiFileDiffEditorUris(c, historyItemParentId, historyItemId));
|
||||
|
||||
await commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, title, resources });
|
||||
}
|
||||
|
||||
@command('git.copyCommitId', { repository: true })
|
||||
async copyCommitId(repository: Repository, historyItem: SourceControlHistoryItem): Promise<void> {
|
||||
if (!repository || !historyItem) {
|
||||
@@ -4344,14 +4289,15 @@ export class CommandCenter {
|
||||
env.clipboard.writeText(historyItem.message);
|
||||
}
|
||||
|
||||
@command('git.blameStatusBarItem.viewCommit', { repository: true })
|
||||
async viewStatusBarCommit(repository: Repository, historyItemId: string): Promise<void> {
|
||||
@command('git.viewCommit', { repository: true })
|
||||
async viewCommit(repository: Repository, historyItemId: string): Promise<void> {
|
||||
if (!repository || !historyItemId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rootUri = Uri.file(repository.root);
|
||||
const commit = await repository.getCommit(historyItemId);
|
||||
const title = `${historyItemId.substring(0, 8)} - ${truncate(commit.message)}`;
|
||||
const title = `${getCommitShortHash(rootUri, historyItemId)} - ${truncate(commit.message)}`;
|
||||
const historyItemParentId = commit.parents.length > 0 ? commit.parents[0] : `${historyItemId}^`;
|
||||
|
||||
const multiDiffSourceUri = Uri.from({ scheme: 'scm-history-item', path: `${repository.root}/${historyItemParentId}..${historyItemId}` });
|
||||
@@ -4362,8 +4308,8 @@ export class CommandCenter {
|
||||
await commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, title, resources });
|
||||
}
|
||||
|
||||
@command('git.blameStatusBarItem.copyContent')
|
||||
async blameStatusBarCopyContent(content: string): Promise<void> {
|
||||
@command('git.copyContentToClipboard')
|
||||
async copyContentToClipboard(content: string): Promise<void> {
|
||||
if (typeof content !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ export interface LogFileOptions {
|
||||
/** Optional. Specifies whether to start retrieving log entries in reverse order. */
|
||||
readonly reverse?: boolean;
|
||||
readonly sortByAuthorDate?: boolean;
|
||||
readonly shortStats?: boolean;
|
||||
}
|
||||
|
||||
function parseVersion(raw: string): string {
|
||||
@@ -1290,6 +1291,10 @@ export class Repository {
|
||||
}
|
||||
}
|
||||
|
||||
if (options?.shortStats) {
|
||||
args.push('--shortstat');
|
||||
}
|
||||
|
||||
if (options?.sortByAuthorDate) {
|
||||
args.push('--author-date-order');
|
||||
}
|
||||
|
||||
@@ -6,20 +6,22 @@
|
||||
|
||||
import { Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, LogOutputChannel, SourceControlHistoryItemRef, l10n, SourceControlHistoryItemRefsChangeEvent } from 'vscode';
|
||||
import { Repository, Resource } from './repository';
|
||||
import { IDisposable, deltaHistoryItemRefs, dispose, filterEvent } from './util';
|
||||
import { IDisposable, deltaHistoryItemRefs, dispose, filterEvent, getCommitShortHash } from './util';
|
||||
import { toGitUri } from './uri';
|
||||
import { Branch, LogOptions, Ref, RefType } from './api/git';
|
||||
import { emojify, ensureEmojis } from './emoji';
|
||||
import { Commit } from './git';
|
||||
import { OperationKind, OperationResult } from './operation';
|
||||
|
||||
function toSourceControlHistoryItemRef(ref: Ref): SourceControlHistoryItemRef {
|
||||
function toSourceControlHistoryItemRef(repository: Repository, ref: Ref): SourceControlHistoryItemRef {
|
||||
const rootUri = Uri.file(repository.root);
|
||||
|
||||
switch (ref.type) {
|
||||
case RefType.RemoteHead:
|
||||
return {
|
||||
id: `refs/remotes/${ref.name}`,
|
||||
name: ref.name ?? '',
|
||||
description: ref.commit ? l10n.t('Remote branch at {0}', ref.commit.substring(0, 8)) : undefined,
|
||||
description: ref.commit ? l10n.t('Remote branch at {0}', getCommitShortHash(rootUri, ref.commit)) : undefined,
|
||||
revision: ref.commit,
|
||||
icon: new ThemeIcon('cloud'),
|
||||
category: l10n.t('remote branches')
|
||||
@@ -28,7 +30,7 @@ function toSourceControlHistoryItemRef(ref: Ref): SourceControlHistoryItemRef {
|
||||
return {
|
||||
id: `refs/tags/${ref.name}`,
|
||||
name: ref.name ?? '',
|
||||
description: ref.commit ? l10n.t('Tag at {0}', ref.commit.substring(0, 8)) : undefined,
|
||||
description: ref.commit ? l10n.t('Tag at {0}', getCommitShortHash(rootUri, ref.commit)) : undefined,
|
||||
revision: ref.commit,
|
||||
icon: new ThemeIcon('tag'),
|
||||
category: l10n.t('tags')
|
||||
@@ -37,7 +39,7 @@ function toSourceControlHistoryItemRef(ref: Ref): SourceControlHistoryItemRef {
|
||||
return {
|
||||
id: `refs/heads/${ref.name}`,
|
||||
name: ref.name ?? '',
|
||||
description: ref.commit ? ref.commit.substring(0, 8) : undefined,
|
||||
description: ref.commit ? getCommitShortHash(rootUri, ref.commit) : undefined,
|
||||
revision: ref.commit,
|
||||
icon: new ThemeIcon('git-branch'),
|
||||
category: l10n.t('branches')
|
||||
@@ -178,7 +180,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
|
||||
|
||||
// Refs (alphabetically)
|
||||
const historyItemRefs = this.repository.refs
|
||||
.map(ref => toSourceControlHistoryItemRef(ref))
|
||||
.map(ref => toSourceControlHistoryItemRef(this.repository, ref))
|
||||
.sort((a, b) => a.id.localeCompare(b.id));
|
||||
|
||||
// Auto-fetch
|
||||
@@ -207,13 +209,13 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
|
||||
for (const ref of refs) {
|
||||
switch (ref.type) {
|
||||
case RefType.RemoteHead:
|
||||
remoteBranches.push(toSourceControlHistoryItemRef(ref));
|
||||
remoteBranches.push(toSourceControlHistoryItemRef(this.repository, ref));
|
||||
break;
|
||||
case RefType.Tag:
|
||||
tags.push(toSourceControlHistoryItemRef(ref));
|
||||
tags.push(toSourceControlHistoryItemRef(this.repository, ref));
|
||||
break;
|
||||
default:
|
||||
branches.push(toSourceControlHistoryItemRef(ref));
|
||||
branches.push(toSourceControlHistoryItemRef(this.repository, ref));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -258,8 +260,9 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
|
||||
parentIds: commit.parents,
|
||||
message: emojify(commit.message),
|
||||
author: commit.authorName,
|
||||
authorEmail: commit.authorEmail,
|
||||
icon: new ThemeIcon('git-commit'),
|
||||
displayId: commit.hash.substring(0, 8),
|
||||
displayId: getCommitShortHash(Uri.file(this.repository.root), commit.hash),
|
||||
timestamp: commit.authorDate?.getTime(),
|
||||
statistics: commit.shortStat ?? { files: 0, insertions: 0, deletions: 0 },
|
||||
references: references.length !== 0 ? references : undefined
|
||||
|
||||
@@ -23,7 +23,7 @@ import { IPushErrorHandlerRegistry } from './pushError';
|
||||
import { IRemoteSourcePublisherRegistry } from './remotePublisher';
|
||||
import { StatusBarCommands } from './statusbar';
|
||||
import { toGitUri } from './uri';
|
||||
import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, IDisposable, isDescendant, onceEvent, pathEquals, relativePath } from './util';
|
||||
import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, getCommitShortHash, IDisposable, isDescendant, onceEvent, pathEquals, relativePath } from './util';
|
||||
import { IFileWatcher, watch } from './watch';
|
||||
import { detectEncoding } from './encoding';
|
||||
|
||||
@@ -1657,7 +1657,7 @@ export class Repository implements Disposable {
|
||||
}
|
||||
|
||||
async checkout(treeish: string, opts?: { detached?: boolean; pullBeforeCheckout?: boolean }): Promise<void> {
|
||||
const refLabel = opts?.detached ? treeish.substring(0, 8) : treeish;
|
||||
const refLabel = opts?.detached ? getCommitShortHash(Uri.file(this.root), treeish) : treeish;
|
||||
|
||||
await this.run(Operation.Checkout(refLabel),
|
||||
async () => {
|
||||
@@ -1675,7 +1675,7 @@ export class Repository implements Disposable {
|
||||
}
|
||||
|
||||
async checkoutTracking(treeish: string, opts: { detached?: boolean } = {}): Promise<void> {
|
||||
const refLabel = opts.detached ? treeish.substring(0, 8) : treeish;
|
||||
const refLabel = opts.detached ? getCommitShortHash(Uri.file(this.root), treeish) : treeish;
|
||||
await this.run(Operation.CheckoutTracking(refLabel), () => this.repository.checkout(treeish, [], { ...opts, track: true }));
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import { debounce } from './decorators';
|
||||
import { emojify, ensureEmojis } from './emoji';
|
||||
import { CommandCenter } from './commands';
|
||||
import { OperationKind, OperationResult } from './operation';
|
||||
import { getCommitShortHash } from './util';
|
||||
import { CommitShortStat } from './git';
|
||||
|
||||
export class GitTimelineItem extends TimelineItem {
|
||||
static is(item: TimelineItem): item is GitTimelineItem {
|
||||
@@ -48,18 +50,46 @@ export class GitTimelineItem extends TimelineItem {
|
||||
return this.shortenRef(this.previousRef);
|
||||
}
|
||||
|
||||
setItemDetails(author: string, email: string | undefined, date: string, message: string): void {
|
||||
setItemDetails(uri: Uri, hash: string | undefined, author: string, email: string | undefined, date: string, message: string, shortStat?: CommitShortStat): void {
|
||||
this.tooltip = new MarkdownString('', true);
|
||||
this.tooltip.isTrusted = true;
|
||||
this.tooltip.supportHtml = true;
|
||||
|
||||
if (email) {
|
||||
const emailTitle = l10n.t('Email');
|
||||
this.tooltip.appendMarkdown(`$(account) [**${author}**](mailto:${email} "${emailTitle} ${author}")\n\n`);
|
||||
this.tooltip.appendMarkdown(`$(account) [**${author}**](mailto:${email} "${emailTitle} ${author}")`);
|
||||
} else {
|
||||
this.tooltip.appendMarkdown(`$(account) **${author}**\n\n`);
|
||||
this.tooltip.appendMarkdown(`$(account) **${author}**`);
|
||||
}
|
||||
|
||||
this.tooltip.appendMarkdown(`$(history) ${date}\n\n`);
|
||||
this.tooltip.appendMarkdown(message);
|
||||
this.tooltip.appendMarkdown(`, $(history) ${date}\n\n`);
|
||||
this.tooltip.appendMarkdown(`${message}\n\n`);
|
||||
|
||||
if (shortStat) {
|
||||
this.tooltip.appendMarkdown(`---\n\n`);
|
||||
|
||||
if (shortStat.insertions) {
|
||||
this.tooltip.appendMarkdown(`<span style="color:var(--vscode-scmGraph-historyItemHoverAdditionsForeground);">${shortStat.insertions === 1 ?
|
||||
l10n.t('{0} insertion{1}', shortStat.insertions, '(+)') :
|
||||
l10n.t('{0} insertions{1}', shortStat.insertions, '(+)')}</span>`);
|
||||
}
|
||||
|
||||
if (shortStat.deletions) {
|
||||
this.tooltip.appendMarkdown(`, <span style="color:var(--vscode-scmGraph-historyItemHoverDeletionsForeground);">${shortStat.deletions === 1 ?
|
||||
l10n.t('{0} deletion{1}', shortStat.deletions, '(-)') :
|
||||
l10n.t('{0} deletions{1}', shortStat.deletions, '(-)')}</span>`);
|
||||
}
|
||||
|
||||
this.tooltip.appendMarkdown(`\n\n`);
|
||||
}
|
||||
|
||||
if (hash) {
|
||||
this.tooltip.appendMarkdown(`---\n\n`);
|
||||
|
||||
this.tooltip.appendMarkdown(`[\`$(git-commit) ${getCommitShortHash(uri, hash)} \`](command:git.viewCommit?${encodeURIComponent(JSON.stringify([uri, hash]))} "${l10n.t('View Commit')}")`);
|
||||
this.tooltip.appendMarkdown(' ');
|
||||
this.tooltip.appendMarkdown(`[$(copy)](command:git.copyContentToClipboard?${encodeURIComponent(JSON.stringify(hash))} "${l10n.t('Copy Commit Hash')}")`);
|
||||
}
|
||||
}
|
||||
|
||||
private shortenRef(ref: string): string {
|
||||
@@ -153,6 +183,7 @@ export class GitTimelineProvider implements TimelineProvider {
|
||||
maxEntries: limit,
|
||||
hash: options.cursor,
|
||||
follow: true,
|
||||
shortStats: true,
|
||||
// sortByAuthorDate: true
|
||||
});
|
||||
|
||||
@@ -184,7 +215,7 @@ export class GitTimelineProvider implements TimelineProvider {
|
||||
item.description = c.authorName;
|
||||
}
|
||||
|
||||
item.setItemDetails(c.authorName!, c.authorEmail, dateFormatter.format(date), message);
|
||||
item.setItemDetails(uri, c.hash, c.authorName!, c.authorEmail, dateFormatter.format(date), message, c.shortStat);
|
||||
|
||||
const cmd = this.commands.resolveTimelineOpenDiffCommand(item, uri);
|
||||
if (cmd) {
|
||||
@@ -209,7 +240,7 @@ export class GitTimelineProvider implements TimelineProvider {
|
||||
// TODO@eamodio: Replace with a better icon -- reflecting its status maybe?
|
||||
item.iconPath = new ThemeIcon('git-commit');
|
||||
item.description = '';
|
||||
item.setItemDetails(you, undefined, dateFormatter.format(date), Resource.getStatusText(index.type));
|
||||
item.setItemDetails(uri, undefined, you, undefined, dateFormatter.format(date), Resource.getStatusText(index.type));
|
||||
|
||||
const cmd = this.commands.resolveTimelineOpenDiffCommand(item, uri);
|
||||
if (cmd) {
|
||||
@@ -231,7 +262,7 @@ export class GitTimelineProvider implements TimelineProvider {
|
||||
const item = new GitTimelineItem('', index ? '~' : 'HEAD', l10n.t('Uncommitted Changes'), date.getTime(), 'working', 'git:file:working');
|
||||
item.iconPath = new ThemeIcon('circle-outline');
|
||||
item.description = '';
|
||||
item.setItemDetails(you, undefined, dateFormatter.format(date), Resource.getStatusText(working.type));
|
||||
item.setItemDetails(uri, undefined, you, undefined, dateFormatter.format(date), Resource.getStatusText(working.type));
|
||||
|
||||
const cmd = this.commands.resolveTimelineOpenDiffCommand(item, uri);
|
||||
if (cmd) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event, Disposable, EventEmitter, SourceControlHistoryItemRef, l10n } from 'vscode';
|
||||
import { Event, Disposable, EventEmitter, SourceControlHistoryItemRef, l10n, workspace, Uri } from 'vscode';
|
||||
import { dirname, sep, relative } from 'path';
|
||||
import { Readable } from 'stream';
|
||||
import { promises as fs, createReadStream } from 'fs';
|
||||
@@ -766,3 +766,9 @@ export function fromNow(date: number | Date, appendAgoLabel?: boolean, useFullTi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getCommitShortHash(scope: Uri, hash: string): string {
|
||||
const config = workspace.getConfiguration('git', scope);
|
||||
const shortHashLength = config.get<number>('commitShortHashLength', 7);
|
||||
return hash.substring(0, shortHashLength);
|
||||
}
|
||||
|
||||
@@ -149,8 +149,12 @@ function getNotebookCellMetadata(cell: nbformat.IBaseCell): {
|
||||
// We put this only for VSC to display in diff view.
|
||||
// Else we don't use this.
|
||||
const cellMetadata: CellMetadata = {};
|
||||
if (cell.cell_type === 'code' && typeof cell['execution_count'] === 'number') {
|
||||
cellMetadata.execution_count = cell['execution_count'];
|
||||
if (cell.cell_type === 'code') {
|
||||
if (typeof cell['execution_count'] === 'number') {
|
||||
cellMetadata.execution_count = cell['execution_count'];
|
||||
} else {
|
||||
cellMetadata.execution_count = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (cell['metadata']) {
|
||||
|
||||
@@ -165,6 +165,13 @@ function onDidChangeNotebookCells(e: NotebookDocumentChangeEventEx) {
|
||||
metadata.execution_count = null;
|
||||
metadataUpdated = true;
|
||||
pendingCellUpdates.delete(e.cell);
|
||||
} else if (!e.executionSummary?.executionOrder && !e.executionSummary?.success && !e.executionSummary?.timing
|
||||
&& !e.metadata && !e.outputs && currentMetadata.execution_count && !pendingCellUpdates.has(e.cell)) {
|
||||
// This is a result of the cell without outupts but has execution count being cleared
|
||||
// Create two cells, one that produces output and one that doesn't. Run both and then clear the output or all cells.
|
||||
// This condition will be satisfied for first cell without outputs.
|
||||
metadata.execution_count = null;
|
||||
metadataUpdated = true;
|
||||
}
|
||||
|
||||
if (e.document?.languageId && e.document?.languageId !== preferredCellLanguage && e.document?.languageId !== languageIdInMetadata) {
|
||||
|
||||
@@ -0,0 +1,736 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as sinon from 'sinon';
|
||||
import type * as nbformat from '@jupyterlab/nbformat';
|
||||
import * as assert from 'assert';
|
||||
import * as vscode from 'vscode';
|
||||
import { jupyterNotebookModelToNotebookData } from '../deserializers';
|
||||
import { activate } from '../notebookModelStoreSync';
|
||||
|
||||
|
||||
suite(`ipynb Clear Outputs`, () => {
|
||||
const disposables: vscode.Disposable[] = [];
|
||||
const context = { subscriptions: disposables } as vscode.ExtensionContext;
|
||||
setup(() => {
|
||||
disposables.length = 0;
|
||||
activate(context);
|
||||
});
|
||||
teardown(async () => {
|
||||
disposables.forEach(d => d.dispose());
|
||||
disposables.length = 0;
|
||||
sinon.restore();
|
||||
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
|
||||
});
|
||||
|
||||
test('Clear outputs after opening Notebook', async () => {
|
||||
const cells: nbformat.ICell[] = [
|
||||
{
|
||||
cell_type: 'code',
|
||||
execution_count: 10,
|
||||
outputs: [{ output_type: 'stream', name: 'stdout', text: ['Hello'] }],
|
||||
source: 'print(1)',
|
||||
metadata: {}
|
||||
},
|
||||
{
|
||||
cell_type: 'code',
|
||||
outputs: [],
|
||||
source: 'print(2)',
|
||||
metadata: {}
|
||||
},
|
||||
{
|
||||
cell_type: 'markdown',
|
||||
source: '# HEAD',
|
||||
metadata: {}
|
||||
}
|
||||
];
|
||||
const notebook = jupyterNotebookModelToNotebookData({ cells }, 'python');
|
||||
|
||||
const notebookDocument = await vscode.workspace.openNotebookDocument('jupyter-notebook', notebook);
|
||||
await vscode.window.showNotebookDocument(notebookDocument);
|
||||
|
||||
assert.strictEqual(notebookDocument.cellCount, 3);
|
||||
assert.strictEqual(notebookDocument.cellAt(0).metadata.execution_count, 10);
|
||||
assert.strictEqual(notebookDocument.cellAt(1).metadata.execution_count, null);
|
||||
assert.strictEqual(notebookDocument.cellAt(2).metadata.execution_count, undefined);
|
||||
|
||||
// Clear all outputs
|
||||
await vscode.commands.executeCommand('notebook.clearAllCellsOutputs');
|
||||
|
||||
// Wait for all changes to be applied, could take a few ms.
|
||||
const verifyMetadataChanges = () => {
|
||||
assert.strictEqual(notebookDocument.cellAt(0).metadata.execution_count, null);
|
||||
assert.strictEqual(notebookDocument.cellAt(1).metadata.execution_count, null);
|
||||
assert.strictEqual(notebookDocument.cellAt(2).metadata.execution_count, undefined);
|
||||
};
|
||||
|
||||
vscode.workspace.onDidChangeNotebookDocument(() => verifyMetadataChanges(), undefined, disposables);
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const interval = setInterval(() => {
|
||||
try {
|
||||
verifyMetadataChanges();
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
}, 50);
|
||||
disposables.push({ dispose: () => clearInterval(interval) });
|
||||
const timeout = setTimeout(() => {
|
||||
try {
|
||||
verifyMetadataChanges();
|
||||
resolve();
|
||||
} catch (ex) {
|
||||
reject(ex);
|
||||
}
|
||||
}, 1000);
|
||||
disposables.push({ dispose: () => clearTimeout(timeout) });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// test('Serialize', async () => {
|
||||
// const markdownCell = new vscode.NotebookCellData(vscode.NotebookCellKind.Markup, '# header1', 'markdown');
|
||||
// markdownCell.metadata = {
|
||||
// attachments: {
|
||||
// 'image.png': {
|
||||
// 'image/png': 'abc'
|
||||
// }
|
||||
// },
|
||||
// id: '123',
|
||||
// metadata: {
|
||||
// foo: 'bar'
|
||||
// }
|
||||
// };
|
||||
|
||||
// const cellMetadata = getCellMetadata({ cell: markdownCell });
|
||||
// assert.deepStrictEqual(cellMetadata, {
|
||||
// id: '123',
|
||||
// metadata: {
|
||||
// foo: 'bar',
|
||||
// },
|
||||
// attachments: {
|
||||
// 'image.png': {
|
||||
// 'image/png': 'abc'
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// const markdownCell2 = new vscode.NotebookCellData(vscode.NotebookCellKind.Markup, '# header1', 'markdown');
|
||||
// markdownCell2.metadata = {
|
||||
// id: '123',
|
||||
// metadata: {
|
||||
// foo: 'bar'
|
||||
// },
|
||||
// attachments: {
|
||||
// 'image.png': {
|
||||
// 'image/png': 'abc'
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
|
||||
// const nbMarkdownCell = createMarkdownCellFromNotebookCell(markdownCell);
|
||||
// const nbMarkdownCell2 = createMarkdownCellFromNotebookCell(markdownCell2);
|
||||
// assert.deepStrictEqual(nbMarkdownCell, nbMarkdownCell2);
|
||||
|
||||
// assert.deepStrictEqual(nbMarkdownCell, {
|
||||
// cell_type: 'markdown',
|
||||
// source: ['# header1'],
|
||||
// metadata: {
|
||||
// foo: 'bar',
|
||||
// },
|
||||
// attachments: {
|
||||
// 'image.png': {
|
||||
// 'image/png': 'abc'
|
||||
// }
|
||||
// },
|
||||
// id: '123'
|
||||
// });
|
||||
// });
|
||||
|
||||
// suite('Outputs', () => {
|
||||
// function validateCellOutputTranslation(
|
||||
// outputs: nbformat.IOutput[],
|
||||
// expectedOutputs: vscode.NotebookCellOutput[],
|
||||
// propertiesToExcludeFromComparison: string[] = []
|
||||
// ) {
|
||||
// const cells: nbformat.ICell[] = [
|
||||
// {
|
||||
// cell_type: 'code',
|
||||
// execution_count: 10,
|
||||
// outputs,
|
||||
// source: 'print(1)',
|
||||
// metadata: {}
|
||||
// }
|
||||
// ];
|
||||
// const notebook = jupyterNotebookModelToNotebookData({ cells }, 'python');
|
||||
|
||||
// // OutputItems contain an `id` property generated by VSC.
|
||||
// // Exclude that property when comparing.
|
||||
// const propertiesToExclude = propertiesToExcludeFromComparison.concat(['id']);
|
||||
// const actualOuts = notebook.cells[0].outputs;
|
||||
// deepStripProperties(actualOuts, propertiesToExclude);
|
||||
// deepStripProperties(expectedOutputs, propertiesToExclude);
|
||||
// assert.deepStrictEqual(actualOuts, expectedOutputs);
|
||||
// }
|
||||
|
||||
// test('Empty output', () => {
|
||||
// validateCellOutputTranslation([], []);
|
||||
// });
|
||||
|
||||
// test('Stream output', () => {
|
||||
// validateCellOutputTranslation(
|
||||
// [
|
||||
// {
|
||||
// output_type: 'stream',
|
||||
// name: 'stderr',
|
||||
// text: 'Error'
|
||||
// },
|
||||
// {
|
||||
// output_type: 'stream',
|
||||
// name: 'stdout',
|
||||
// text: 'NoError'
|
||||
// }
|
||||
// ],
|
||||
// [
|
||||
// new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stderr('Error')], {
|
||||
// outputType: 'stream'
|
||||
// }),
|
||||
// new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stdout('NoError')], {
|
||||
// outputType: 'stream'
|
||||
// })
|
||||
// ]
|
||||
// );
|
||||
// });
|
||||
// test('Stream output and line endings', () => {
|
||||
// validateCellOutputTranslation(
|
||||
// [
|
||||
// {
|
||||
// output_type: 'stream',
|
||||
// name: 'stdout',
|
||||
// text: [
|
||||
// 'Line1\n',
|
||||
// '\n',
|
||||
// 'Line3\n',
|
||||
// 'Line4'
|
||||
// ]
|
||||
// }
|
||||
// ],
|
||||
// [
|
||||
// new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stdout('Line1\n\nLine3\nLine4')], {
|
||||
// outputType: 'stream'
|
||||
// })
|
||||
// ]
|
||||
// );
|
||||
// validateCellOutputTranslation(
|
||||
// [
|
||||
// {
|
||||
// output_type: 'stream',
|
||||
// name: 'stdout',
|
||||
// text: [
|
||||
// 'Hello\n',
|
||||
// 'Hello\n',
|
||||
// 'Hello\n',
|
||||
// 'Hello\n',
|
||||
// 'Hello\n',
|
||||
// 'Hello\n'
|
||||
// ]
|
||||
// }
|
||||
// ],
|
||||
// [
|
||||
// new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stdout('Hello\nHello\nHello\nHello\nHello\nHello\n')], {
|
||||
// outputType: 'stream'
|
||||
// })
|
||||
// ]
|
||||
// );
|
||||
// });
|
||||
// test('Multi-line Stream output', () => {
|
||||
// validateCellOutputTranslation(
|
||||
// [
|
||||
// {
|
||||
// name: 'stdout',
|
||||
// output_type: 'stream',
|
||||
// text: [
|
||||
// 'Epoch 1/5\n',
|
||||
// '...\n',
|
||||
// 'Epoch 2/5\n',
|
||||
// '...\n',
|
||||
// 'Epoch 3/5\n',
|
||||
// '...\n',
|
||||
// 'Epoch 4/5\n',
|
||||
// '...\n',
|
||||
// 'Epoch 5/5\n',
|
||||
// '...\n'
|
||||
// ]
|
||||
// }
|
||||
// ],
|
||||
// [
|
||||
// new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stdout(['Epoch 1/5\n',
|
||||
// '...\n',
|
||||
// 'Epoch 2/5\n',
|
||||
// '...\n',
|
||||
// 'Epoch 3/5\n',
|
||||
// '...\n',
|
||||
// 'Epoch 4/5\n',
|
||||
// '...\n',
|
||||
// 'Epoch 5/5\n',
|
||||
// '...\n'].join(''))], {
|
||||
// outputType: 'stream'
|
||||
// })
|
||||
// ]
|
||||
// );
|
||||
// });
|
||||
|
||||
// test('Multi-line Stream output (last empty line should not be saved in ipynb)', () => {
|
||||
// validateCellOutputTranslation(
|
||||
// [
|
||||
// {
|
||||
// name: 'stderr',
|
||||
// output_type: 'stream',
|
||||
// text: [
|
||||
// 'Epoch 1/5\n',
|
||||
// '...\n',
|
||||
// 'Epoch 2/5\n',
|
||||
// '...\n',
|
||||
// 'Epoch 3/5\n',
|
||||
// '...\n',
|
||||
// 'Epoch 4/5\n',
|
||||
// '...\n',
|
||||
// 'Epoch 5/5\n',
|
||||
// '...\n'
|
||||
// ]
|
||||
// }
|
||||
// ],
|
||||
// [
|
||||
// new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stderr(['Epoch 1/5\n',
|
||||
// '...\n',
|
||||
// 'Epoch 2/5\n',
|
||||
// '...\n',
|
||||
// 'Epoch 3/5\n',
|
||||
// '...\n',
|
||||
// 'Epoch 4/5\n',
|
||||
// '...\n',
|
||||
// 'Epoch 5/5\n',
|
||||
// '...\n',
|
||||
// // This last empty line should not be saved in ipynb.
|
||||
// '\n'].join(''))], {
|
||||
// outputType: 'stream'
|
||||
// })
|
||||
// ]
|
||||
// );
|
||||
// });
|
||||
|
||||
// test('Streamed text with Ansi characters', async () => {
|
||||
// validateCellOutputTranslation(
|
||||
// [
|
||||
// {
|
||||
// name: 'stderr',
|
||||
// text: '\u001b[K\u001b[33m✅ \u001b[0m Loading\n',
|
||||
// output_type: 'stream'
|
||||
// }
|
||||
// ],
|
||||
// [
|
||||
// new vscode.NotebookCellOutput(
|
||||
// [vscode.NotebookCellOutputItem.stderr('\u001b[K\u001b[33m✅ \u001b[0m Loading\n')],
|
||||
// {
|
||||
// outputType: 'stream'
|
||||
// }
|
||||
// )
|
||||
// ]
|
||||
// );
|
||||
// });
|
||||
|
||||
// test('Streamed text with angle bracket characters', async () => {
|
||||
// validateCellOutputTranslation(
|
||||
// [
|
||||
// {
|
||||
// name: 'stderr',
|
||||
// text: '1 is < 2',
|
||||
// output_type: 'stream'
|
||||
// }
|
||||
// ],
|
||||
// [
|
||||
// new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stderr('1 is < 2')], {
|
||||
// outputType: 'stream'
|
||||
// })
|
||||
// ]
|
||||
// );
|
||||
// });
|
||||
|
||||
// test('Streamed text with angle bracket characters and ansi chars', async () => {
|
||||
// validateCellOutputTranslation(
|
||||
// [
|
||||
// {
|
||||
// name: 'stderr',
|
||||
// text: '1 is < 2\u001b[K\u001b[33m✅ \u001b[0m Loading\n',
|
||||
// output_type: 'stream'
|
||||
// }
|
||||
// ],
|
||||
// [
|
||||
// new vscode.NotebookCellOutput(
|
||||
// [vscode.NotebookCellOutputItem.stderr('1 is < 2\u001b[K\u001b[33m✅ \u001b[0m Loading\n')],
|
||||
// {
|
||||
// outputType: 'stream'
|
||||
// }
|
||||
// )
|
||||
// ]
|
||||
// );
|
||||
// });
|
||||
|
||||
// test('Error', async () => {
|
||||
// validateCellOutputTranslation(
|
||||
// [
|
||||
// {
|
||||
// ename: 'Error Name',
|
||||
// evalue: 'Error Value',
|
||||
// traceback: ['stack1', 'stack2', 'stack3'],
|
||||
// output_type: 'error'
|
||||
// }
|
||||
// ],
|
||||
// [
|
||||
// new vscode.NotebookCellOutput(
|
||||
// [
|
||||
// vscode.NotebookCellOutputItem.error({
|
||||
// name: 'Error Name',
|
||||
// message: 'Error Value',
|
||||
// stack: ['stack1', 'stack2', 'stack3'].join('\n')
|
||||
// })
|
||||
// ],
|
||||
// {
|
||||
// outputType: 'error',
|
||||
// originalError: {
|
||||
// ename: 'Error Name',
|
||||
// evalue: 'Error Value',
|
||||
// traceback: ['stack1', 'stack2', 'stack3'],
|
||||
// output_type: 'error'
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
// ]
|
||||
// );
|
||||
// });
|
||||
|
||||
// ['display_data', 'execute_result'].forEach(output_type => {
|
||||
// suite(`Rich output for output_type = ${output_type}`, () => {
|
||||
// // Properties to exclude when comparing.
|
||||
// let propertiesToExcludeFromComparison: string[] = [];
|
||||
// setup(() => {
|
||||
// if (output_type === 'display_data') {
|
||||
// // With display_data the execution_count property will never exist in the output.
|
||||
// // We can ignore that (as it will never exist).
|
||||
// // But we leave it in the case of `output_type === 'execute_result'`
|
||||
// propertiesToExcludeFromComparison = ['execution_count', 'executionCount'];
|
||||
// }
|
||||
// });
|
||||
|
||||
// test('Text mimeType output', async () => {
|
||||
// validateCellOutputTranslation(
|
||||
// [
|
||||
// {
|
||||
// data: {
|
||||
// 'text/plain': 'Hello World!'
|
||||
// },
|
||||
// output_type,
|
||||
// metadata: {},
|
||||
// execution_count: 1
|
||||
// }
|
||||
// ],
|
||||
// [
|
||||
// new vscode.NotebookCellOutput(
|
||||
// [new vscode.NotebookCellOutputItem(Buffer.from('Hello World!', 'utf8'), 'text/plain')],
|
||||
// {
|
||||
// outputType: output_type,
|
||||
// metadata: {}, // display_data & execute_result always have metadata.
|
||||
// executionCount: 1
|
||||
// }
|
||||
// )
|
||||
// ],
|
||||
// propertiesToExcludeFromComparison
|
||||
// );
|
||||
// });
|
||||
|
||||
// test('png,jpeg images', async () => {
|
||||
// validateCellOutputTranslation(
|
||||
// [
|
||||
// {
|
||||
// execution_count: 1,
|
||||
// data: {
|
||||
// 'image/png': base64EncodedImage,
|
||||
// 'image/jpeg': base64EncodedImage
|
||||
// },
|
||||
// metadata: {},
|
||||
// output_type
|
||||
// }
|
||||
// ],
|
||||
// [
|
||||
// new vscode.NotebookCellOutput(
|
||||
// [
|
||||
// new vscode.NotebookCellOutputItem(Buffer.from(base64EncodedImage, 'base64'), 'image/png'),
|
||||
// new vscode.NotebookCellOutputItem(Buffer.from(base64EncodedImage, 'base64'), 'image/jpeg')
|
||||
// ],
|
||||
// {
|
||||
// executionCount: 1,
|
||||
// outputType: output_type,
|
||||
// metadata: {} // display_data & execute_result always have metadata.
|
||||
// }
|
||||
// )
|
||||
// ],
|
||||
// propertiesToExcludeFromComparison
|
||||
// );
|
||||
// });
|
||||
|
||||
// test('png image with a light background', async () => {
|
||||
// validateCellOutputTranslation(
|
||||
// [
|
||||
// {
|
||||
// execution_count: 1,
|
||||
// data: {
|
||||
// 'image/png': base64EncodedImage
|
||||
// },
|
||||
// metadata: {
|
||||
// needs_background: 'light'
|
||||
// },
|
||||
// output_type
|
||||
// }
|
||||
// ],
|
||||
// [
|
||||
// new vscode.NotebookCellOutput(
|
||||
// [new vscode.NotebookCellOutputItem(Buffer.from(base64EncodedImage, 'base64'), 'image/png')],
|
||||
// {
|
||||
// executionCount: 1,
|
||||
// metadata: {
|
||||
// needs_background: 'light'
|
||||
// },
|
||||
// outputType: output_type
|
||||
// }
|
||||
// )
|
||||
// ],
|
||||
// propertiesToExcludeFromComparison
|
||||
// );
|
||||
// });
|
||||
|
||||
// test('png image with a dark background', async () => {
|
||||
// validateCellOutputTranslation(
|
||||
// [
|
||||
// {
|
||||
// execution_count: 1,
|
||||
// data: {
|
||||
// 'image/png': base64EncodedImage
|
||||
// },
|
||||
// metadata: {
|
||||
// needs_background: 'dark'
|
||||
// },
|
||||
// output_type
|
||||
// }
|
||||
// ],
|
||||
// [
|
||||
// new vscode.NotebookCellOutput(
|
||||
// [new vscode.NotebookCellOutputItem(Buffer.from(base64EncodedImage, 'base64'), 'image/png')],
|
||||
// {
|
||||
// executionCount: 1,
|
||||
// metadata: {
|
||||
// needs_background: 'dark'
|
||||
// },
|
||||
// outputType: output_type
|
||||
// }
|
||||
// )
|
||||
// ],
|
||||
// propertiesToExcludeFromComparison
|
||||
// );
|
||||
// });
|
||||
|
||||
// test('png image with custom dimensions', async () => {
|
||||
// validateCellOutputTranslation(
|
||||
// [
|
||||
// {
|
||||
// execution_count: 1,
|
||||
// data: {
|
||||
// 'image/png': base64EncodedImage
|
||||
// },
|
||||
// metadata: {
|
||||
// 'image/png': { height: '111px', width: '999px' }
|
||||
// },
|
||||
// output_type
|
||||
// }
|
||||
// ],
|
||||
// [
|
||||
// new vscode.NotebookCellOutput(
|
||||
// [new vscode.NotebookCellOutputItem(Buffer.from(base64EncodedImage, 'base64'), 'image/png')],
|
||||
// {
|
||||
// executionCount: 1,
|
||||
// metadata: {
|
||||
// 'image/png': { height: '111px', width: '999px' }
|
||||
// },
|
||||
// outputType: output_type
|
||||
// }
|
||||
// )
|
||||
// ],
|
||||
// propertiesToExcludeFromComparison
|
||||
// );
|
||||
// });
|
||||
|
||||
// test('png allowed to scroll', async () => {
|
||||
// validateCellOutputTranslation(
|
||||
// [
|
||||
// {
|
||||
// execution_count: 1,
|
||||
// data: {
|
||||
// 'image/png': base64EncodedImage
|
||||
// },
|
||||
// metadata: {
|
||||
// unconfined: true,
|
||||
// 'image/png': { width: '999px' }
|
||||
// },
|
||||
// output_type
|
||||
// }
|
||||
// ],
|
||||
// [
|
||||
// new vscode.NotebookCellOutput(
|
||||
// [new vscode.NotebookCellOutputItem(Buffer.from(base64EncodedImage, 'base64'), 'image/png')],
|
||||
// {
|
||||
// executionCount: 1,
|
||||
// metadata: {
|
||||
// unconfined: true,
|
||||
// 'image/png': { width: '999px' }
|
||||
// },
|
||||
// outputType: output_type
|
||||
// }
|
||||
// )
|
||||
// ],
|
||||
// propertiesToExcludeFromComparison
|
||||
// );
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
// suite('Output Order', () => {
|
||||
// test('Verify order of outputs', async () => {
|
||||
// const dataAndExpectedOrder: { output: nbformat.IDisplayData; expectedMimeTypesOrder: string[] }[] = [
|
||||
// {
|
||||
// output: {
|
||||
// data: {
|
||||
// 'application/vnd.vegalite.v4+json': 'some json',
|
||||
// 'text/html': '<a>Hello</a>'
|
||||
// },
|
||||
// metadata: {},
|
||||
// output_type: 'display_data'
|
||||
// },
|
||||
// expectedMimeTypesOrder: ['application/vnd.vegalite.v4+json', 'text/html']
|
||||
// },
|
||||
// {
|
||||
// output: {
|
||||
// data: {
|
||||
// 'application/vnd.vegalite.v4+json': 'some json',
|
||||
// 'application/javascript': 'some js',
|
||||
// 'text/plain': 'some text',
|
||||
// 'text/html': '<a>Hello</a>'
|
||||
// },
|
||||
// metadata: {},
|
||||
// output_type: 'display_data'
|
||||
// },
|
||||
// expectedMimeTypesOrder: [
|
||||
// 'application/vnd.vegalite.v4+json',
|
||||
// 'text/html',
|
||||
// 'application/javascript',
|
||||
// 'text/plain'
|
||||
// ]
|
||||
// },
|
||||
// {
|
||||
// output: {
|
||||
// data: {
|
||||
// 'application/vnd.vegalite.v4+json': '', // Empty, should give preference to other mimetypes.
|
||||
// 'application/javascript': 'some js',
|
||||
// 'text/plain': 'some text',
|
||||
// 'text/html': '<a>Hello</a>'
|
||||
// },
|
||||
// metadata: {},
|
||||
// output_type: 'display_data'
|
||||
// },
|
||||
// expectedMimeTypesOrder: [
|
||||
// 'text/html',
|
||||
// 'application/javascript',
|
||||
// 'text/plain',
|
||||
// 'application/vnd.vegalite.v4+json'
|
||||
// ]
|
||||
// },
|
||||
// {
|
||||
// output: {
|
||||
// data: {
|
||||
// 'text/plain': 'some text',
|
||||
// 'text/html': '<a>Hello</a>'
|
||||
// },
|
||||
// metadata: {},
|
||||
// output_type: 'display_data'
|
||||
// },
|
||||
// expectedMimeTypesOrder: ['text/html', 'text/plain']
|
||||
// },
|
||||
// {
|
||||
// output: {
|
||||
// data: {
|
||||
// 'application/javascript': 'some js',
|
||||
// 'text/plain': 'some text'
|
||||
// },
|
||||
// metadata: {},
|
||||
// output_type: 'display_data'
|
||||
// },
|
||||
// expectedMimeTypesOrder: ['application/javascript', 'text/plain']
|
||||
// },
|
||||
// {
|
||||
// output: {
|
||||
// data: {
|
||||
// 'image/svg+xml': 'some svg',
|
||||
// 'text/plain': 'some text'
|
||||
// },
|
||||
// metadata: {},
|
||||
// output_type: 'display_data'
|
||||
// },
|
||||
// expectedMimeTypesOrder: ['image/svg+xml', 'text/plain']
|
||||
// },
|
||||
// {
|
||||
// output: {
|
||||
// data: {
|
||||
// 'text/latex': 'some latex',
|
||||
// 'text/plain': 'some text'
|
||||
// },
|
||||
// metadata: {},
|
||||
// output_type: 'display_data'
|
||||
// },
|
||||
// expectedMimeTypesOrder: ['text/latex', 'text/plain']
|
||||
// },
|
||||
// {
|
||||
// output: {
|
||||
// data: {
|
||||
// 'application/vnd.jupyter.widget-view+json': 'some widget',
|
||||
// 'text/plain': 'some text'
|
||||
// },
|
||||
// metadata: {},
|
||||
// output_type: 'display_data'
|
||||
// },
|
||||
// expectedMimeTypesOrder: ['application/vnd.jupyter.widget-view+json', 'text/plain']
|
||||
// },
|
||||
// {
|
||||
// output: {
|
||||
// data: {
|
||||
// 'text/plain': 'some text',
|
||||
// 'image/svg+xml': 'some svg',
|
||||
// 'image/png': 'some png'
|
||||
// },
|
||||
// metadata: {},
|
||||
// output_type: 'display_data'
|
||||
// },
|
||||
// expectedMimeTypesOrder: ['image/png', 'image/svg+xml', 'text/plain']
|
||||
// }
|
||||
// ];
|
||||
|
||||
// dataAndExpectedOrder.forEach(({ output, expectedMimeTypesOrder }) => {
|
||||
// const sortedOutputs = jupyterCellOutputToCellOutput(output);
|
||||
// const mimeTypes = sortedOutputs.items.map((item) => item.mime).join(',');
|
||||
// assert.equal(mimeTypes, expectedMimeTypesOrder.join(','));
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
});
|
||||
@@ -41,6 +41,12 @@ suite(`ipynb serializer`, () => {
|
||||
source: 'print(1)',
|
||||
metadata: {}
|
||||
},
|
||||
{
|
||||
cell_type: 'code',
|
||||
outputs: [],
|
||||
source: 'print(2)',
|
||||
metadata: {}
|
||||
},
|
||||
{
|
||||
cell_type: 'markdown',
|
||||
source: '# HEAD',
|
||||
@@ -55,13 +61,18 @@ suite(`ipynb serializer`, () => {
|
||||
expectedCodeCell.metadata = { execution_count: 10, metadata: {} };
|
||||
expectedCodeCell.executionSummary = { executionOrder: 10 };
|
||||
|
||||
const expectedCodeCell2 = new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'print(2)', 'python');
|
||||
expectedCodeCell2.outputs = [];
|
||||
expectedCodeCell2.metadata = { execution_count: null, metadata: {} };
|
||||
expectedCodeCell2.executionSummary = {};
|
||||
|
||||
const expectedMarkdownCell = new vscode.NotebookCellData(vscode.NotebookCellKind.Markup, '# HEAD', 'markdown');
|
||||
expectedMarkdownCell.outputs = [];
|
||||
expectedMarkdownCell.metadata = {
|
||||
metadata: {}
|
||||
};
|
||||
|
||||
assert.deepStrictEqual(notebook.cells, [expectedCodeCell, expectedMarkdownCell]);
|
||||
assert.deepStrictEqual(notebook.cells, [expectedCodeCell, expectedCodeCell2, expectedMarkdownCell]);
|
||||
});
|
||||
|
||||
|
||||
|
||||
Generated
+43
-43
@@ -27,15 +27,15 @@
|
||||
"@vscode/windows-mutex": "^0.5.0",
|
||||
"@vscode/windows-process-tree": "^0.6.0",
|
||||
"@vscode/windows-registry": "^1.1.0",
|
||||
"@xterm/addon-clipboard": "^0.2.0-beta.53",
|
||||
"@xterm/addon-image": "^0.9.0-beta.70",
|
||||
"@xterm/addon-ligatures": "^0.10.0-beta.70",
|
||||
"@xterm/addon-search": "^0.16.0-beta.70",
|
||||
"@xterm/addon-serialize": "^0.14.0-beta.70",
|
||||
"@xterm/addon-unicode11": "^0.9.0-beta.70",
|
||||
"@xterm/addon-webgl": "^0.19.0-beta.70",
|
||||
"@xterm/headless": "^5.6.0-beta.70",
|
||||
"@xterm/xterm": "^5.6.0-beta.70",
|
||||
"@xterm/addon-clipboard": "^0.2.0-beta.57",
|
||||
"@xterm/addon-image": "^0.9.0-beta.74",
|
||||
"@xterm/addon-ligatures": "^0.10.0-beta.74",
|
||||
"@xterm/addon-search": "^0.16.0-beta.74",
|
||||
"@xterm/addon-serialize": "^0.14.0-beta.74",
|
||||
"@xterm/addon-unicode11": "^0.9.0-beta.74",
|
||||
"@xterm/addon-webgl": "^0.19.0-beta.74",
|
||||
"@xterm/headless": "^5.6.0-beta.74",
|
||||
"@xterm/xterm": "^5.6.0-beta.74",
|
||||
"http-proxy-agent": "^7.0.0",
|
||||
"https-proxy-agent": "^7.0.2",
|
||||
"jschardet": "3.1.4",
|
||||
@@ -3457,30 +3457,30 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-clipboard": {
|
||||
"version": "0.2.0-beta.53",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.53.tgz",
|
||||
"integrity": "sha512-kCcBuGvF8mwzExU+Tm9eylPvp1kXTkvm+kO0V4qP7HI3ZCw5vfKmnlRn41FvNIylsK2hnmrFtxauPHEGBy/dfA==",
|
||||
"version": "0.2.0-beta.57",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.57.tgz",
|
||||
"integrity": "sha512-/GSI8Fkmb8s/V1t2EGc2U2PUfSqge6f9gAeob65EwarsfBf66cmCxMG0ZSPE8+nti1pGIsrJA8XfeEaJt4clcA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-base64": "^3.7.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.6.0-beta.70"
|
||||
"@xterm/xterm": "^5.6.0-beta.74"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-image": {
|
||||
"version": "0.9.0-beta.70",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.70.tgz",
|
||||
"integrity": "sha512-QLhy77i0sjnffkLuxj1yB/mBUJI64bbL86eMW+1g5XEsZnSevY8YwU/cEJg02PAGK0ggwQNNfiRwoO6VBCdYFg==",
|
||||
"version": "0.9.0-beta.74",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.74.tgz",
|
||||
"integrity": "sha512-mJPWNPov2mqrUkYZCs6UCn5p6DBLeN6xjpLu5mLh8cmXr544VWfqEVNAYPQ9+8uNgXdzSsKInBv9ZGtbXV0SfA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.6.0-beta.70"
|
||||
"@xterm/xterm": "^5.6.0-beta.74"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-ligatures": {
|
||||
"version": "0.10.0-beta.70",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.70.tgz",
|
||||
"integrity": "sha512-BPDHHOybUWO6mjHf/RMDBjSKDl9QdyyGTyHvmlyhuI/2sma3lu98bA4U03F8nBnvj/6otzEFARuOeoN7rkfRng==",
|
||||
"version": "0.10.0-beta.74",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.74.tgz",
|
||||
"integrity": "sha512-/X3OemVqPSgdely8OgdQb0cJqv9HqiMaBLeLe2QHfTWdXDBOLG/O4g8n/lChqR9rulEMwPCt2LqvtyIMA3iZsw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"font-finder": "^1.1.0",
|
||||
@@ -3490,55 +3490,55 @@
|
||||
"node": ">8.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.6.0-beta.70"
|
||||
"@xterm/xterm": "^5.6.0-beta.74"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-search": {
|
||||
"version": "0.16.0-beta.70",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.70.tgz",
|
||||
"integrity": "sha512-uaNBf77cr5Jikj69TDkTfe3V3wHA+4tDTFcv8DwJ/KGORPLUfBk8cn9HpbIJ+0jc1kOZr5xDrEjaYNwEVDkGeQ==",
|
||||
"version": "0.16.0-beta.74",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.74.tgz",
|
||||
"integrity": "sha512-KMOeOu3EvtDWcH/HCs6fe4KyaMMdjoUjP1C7R3AtZwJVdm5GaFxASxdol+9evfGhUUei4qt+zVsaFraNKyFvsA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.6.0-beta.70"
|
||||
"@xterm/xterm": "^5.6.0-beta.74"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-serialize": {
|
||||
"version": "0.14.0-beta.70",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.70.tgz",
|
||||
"integrity": "sha512-4ijqHU7xDRcZ4Gm6yN/tKsZzovUwzttE9/pPfhxpWIgSdm9c8i5R4rGyOAlAZCnuU3DT5vPplQO7W/0zwAmJsQ==",
|
||||
"version": "0.14.0-beta.74",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.74.tgz",
|
||||
"integrity": "sha512-zBhQAoXagBMhKFGYQ6n52sKapY61Jt3hKT8awhG/49f04JkaX1Jhc6xohD+aZs6J2ABHI7EWW/y0xTN9BDLLBw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.6.0-beta.70"
|
||||
"@xterm/xterm": "^5.6.0-beta.74"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-unicode11": {
|
||||
"version": "0.9.0-beta.70",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.70.tgz",
|
||||
"integrity": "sha512-9UE4v2SpWtqd85Hqvk8LW4QA9Phe93RMVltrNtt9jCUmkAok/QLFOEbLqoq2JUOvlmfsYfXmIl11Wg4CYIsvwA==",
|
||||
"version": "0.9.0-beta.74",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.74.tgz",
|
||||
"integrity": "sha512-1AyuDST77Xg33ed+3neNrQfsfZVwa+C16uWP9eTdJ1rO48ylqPcFTDnahCfIZGPHGjPqLl90ZKZ8tt1zWSefmA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.6.0-beta.70"
|
||||
"@xterm/xterm": "^5.6.0-beta.74"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-webgl": {
|
||||
"version": "0.19.0-beta.70",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.70.tgz",
|
||||
"integrity": "sha512-3BkmF24i66SHCfAkcHy1VC6H715qyelfOIjd6n7sTqHon56J1bUO782Q1al/MK0vSvplElBRKVdR31SDvITzpg==",
|
||||
"version": "0.19.0-beta.74",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.74.tgz",
|
||||
"integrity": "sha512-1wKiuv6WHMqOcSlLeIRU7UF8zkU4KU35rnPvLw2G45aAJSr9B3f7EVIaJ4IjXGeY/C+WCM3wWuybPN5VdB8qAQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.6.0-beta.70"
|
||||
"@xterm/xterm": "^5.6.0-beta.74"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/headless": {
|
||||
"version": "5.6.0-beta.70",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.70.tgz",
|
||||
"integrity": "sha512-npSJzmtJq8LLAzV3nwD9KkBQLNWSusi+cOBPyc3zlYYE63Vkqbtbp/iQS27zH2GxJ95rzCzDwnX6VOn0OoUPYQ==",
|
||||
"version": "5.6.0-beta.74",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.74.tgz",
|
||||
"integrity": "sha512-6PSNk1/CaLGNZLhXULswjfbf/rJrG1EomT9hR2nNbX6Osm9LVbhgIzg/mYHPu9wX5Pz7Gpd1VWp4/1NcKpN53w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@xterm/xterm": {
|
||||
"version": "5.6.0-beta.70",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.70.tgz",
|
||||
"integrity": "sha512-qviQMVWhRtgPn4z8PHNH6D/ffSKkNBHmUX1HyJxl325QM2xF8M8met83uFv7JZm7a5OQYScnLGsFAoTreSgdew==",
|
||||
"version": "5.6.0-beta.74",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.74.tgz",
|
||||
"integrity": "sha512-gVu7+4Cfd7O/6cQ/UK0sZM+TJBaI/VgQ/JFAhuAnNFj29wts2MzxSH3fIp3KUG1kqlJBWEUShCTwB8nKwtbCjQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@xtuc/ieee754": {
|
||||
|
||||
+9
-9
@@ -85,15 +85,15 @@
|
||||
"@vscode/windows-mutex": "^0.5.0",
|
||||
"@vscode/windows-process-tree": "^0.6.0",
|
||||
"@vscode/windows-registry": "^1.1.0",
|
||||
"@xterm/addon-clipboard": "^0.2.0-beta.53",
|
||||
"@xterm/addon-image": "^0.9.0-beta.70",
|
||||
"@xterm/addon-ligatures": "^0.10.0-beta.70",
|
||||
"@xterm/addon-search": "^0.16.0-beta.70",
|
||||
"@xterm/addon-serialize": "^0.14.0-beta.70",
|
||||
"@xterm/addon-unicode11": "^0.9.0-beta.70",
|
||||
"@xterm/addon-webgl": "^0.19.0-beta.70",
|
||||
"@xterm/headless": "^5.6.0-beta.70",
|
||||
"@xterm/xterm": "^5.6.0-beta.70",
|
||||
"@xterm/addon-clipboard": "^0.2.0-beta.57",
|
||||
"@xterm/addon-image": "^0.9.0-beta.74",
|
||||
"@xterm/addon-ligatures": "^0.10.0-beta.74",
|
||||
"@xterm/addon-search": "^0.16.0-beta.74",
|
||||
"@xterm/addon-serialize": "^0.14.0-beta.74",
|
||||
"@xterm/addon-unicode11": "^0.9.0-beta.74",
|
||||
"@xterm/addon-webgl": "^0.19.0-beta.74",
|
||||
"@xterm/headless": "^5.6.0-beta.74",
|
||||
"@xterm/xterm": "^5.6.0-beta.74",
|
||||
"http-proxy-agent": "^7.0.0",
|
||||
"https-proxy-agent": "^7.0.2",
|
||||
"jschardet": "3.1.4",
|
||||
|
||||
Generated
+43
-43
@@ -20,15 +20,15 @@
|
||||
"@vscode/vscode-languagedetection": "1.0.21",
|
||||
"@vscode/windows-process-tree": "^0.6.0",
|
||||
"@vscode/windows-registry": "^1.1.0",
|
||||
"@xterm/addon-clipboard": "^0.2.0-beta.53",
|
||||
"@xterm/addon-image": "^0.9.0-beta.70",
|
||||
"@xterm/addon-ligatures": "^0.10.0-beta.70",
|
||||
"@xterm/addon-search": "^0.16.0-beta.70",
|
||||
"@xterm/addon-serialize": "^0.14.0-beta.70",
|
||||
"@xterm/addon-unicode11": "^0.9.0-beta.70",
|
||||
"@xterm/addon-webgl": "^0.19.0-beta.70",
|
||||
"@xterm/headless": "^5.6.0-beta.70",
|
||||
"@xterm/xterm": "^5.6.0-beta.70",
|
||||
"@xterm/addon-clipboard": "^0.2.0-beta.57",
|
||||
"@xterm/addon-image": "^0.9.0-beta.74",
|
||||
"@xterm/addon-ligatures": "^0.10.0-beta.74",
|
||||
"@xterm/addon-search": "^0.16.0-beta.74",
|
||||
"@xterm/addon-serialize": "^0.14.0-beta.74",
|
||||
"@xterm/addon-unicode11": "^0.9.0-beta.74",
|
||||
"@xterm/addon-webgl": "^0.19.0-beta.74",
|
||||
"@xterm/headless": "^5.6.0-beta.74",
|
||||
"@xterm/xterm": "^5.6.0-beta.74",
|
||||
"cookie": "^0.7.0",
|
||||
"http-proxy-agent": "^7.0.0",
|
||||
"https-proxy-agent": "^7.0.2",
|
||||
@@ -520,30 +520,30 @@
|
||||
"hasInstallScript": true
|
||||
},
|
||||
"node_modules/@xterm/addon-clipboard": {
|
||||
"version": "0.2.0-beta.53",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.53.tgz",
|
||||
"integrity": "sha512-kCcBuGvF8mwzExU+Tm9eylPvp1kXTkvm+kO0V4qP7HI3ZCw5vfKmnlRn41FvNIylsK2hnmrFtxauPHEGBy/dfA==",
|
||||
"version": "0.2.0-beta.57",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.57.tgz",
|
||||
"integrity": "sha512-/GSI8Fkmb8s/V1t2EGc2U2PUfSqge6f9gAeob65EwarsfBf66cmCxMG0ZSPE8+nti1pGIsrJA8XfeEaJt4clcA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-base64": "^3.7.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.6.0-beta.70"
|
||||
"@xterm/xterm": "^5.6.0-beta.74"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-image": {
|
||||
"version": "0.9.0-beta.70",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.70.tgz",
|
||||
"integrity": "sha512-QLhy77i0sjnffkLuxj1yB/mBUJI64bbL86eMW+1g5XEsZnSevY8YwU/cEJg02PAGK0ggwQNNfiRwoO6VBCdYFg==",
|
||||
"version": "0.9.0-beta.74",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.74.tgz",
|
||||
"integrity": "sha512-mJPWNPov2mqrUkYZCs6UCn5p6DBLeN6xjpLu5mLh8cmXr544VWfqEVNAYPQ9+8uNgXdzSsKInBv9ZGtbXV0SfA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.6.0-beta.70"
|
||||
"@xterm/xterm": "^5.6.0-beta.74"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-ligatures": {
|
||||
"version": "0.10.0-beta.70",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.70.tgz",
|
||||
"integrity": "sha512-BPDHHOybUWO6mjHf/RMDBjSKDl9QdyyGTyHvmlyhuI/2sma3lu98bA4U03F8nBnvj/6otzEFARuOeoN7rkfRng==",
|
||||
"version": "0.10.0-beta.74",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.74.tgz",
|
||||
"integrity": "sha512-/X3OemVqPSgdely8OgdQb0cJqv9HqiMaBLeLe2QHfTWdXDBOLG/O4g8n/lChqR9rulEMwPCt2LqvtyIMA3iZsw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"font-finder": "^1.1.0",
|
||||
@@ -553,55 +553,55 @@
|
||||
"node": ">8.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.6.0-beta.70"
|
||||
"@xterm/xterm": "^5.6.0-beta.74"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-search": {
|
||||
"version": "0.16.0-beta.70",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.70.tgz",
|
||||
"integrity": "sha512-uaNBf77cr5Jikj69TDkTfe3V3wHA+4tDTFcv8DwJ/KGORPLUfBk8cn9HpbIJ+0jc1kOZr5xDrEjaYNwEVDkGeQ==",
|
||||
"version": "0.16.0-beta.74",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.74.tgz",
|
||||
"integrity": "sha512-KMOeOu3EvtDWcH/HCs6fe4KyaMMdjoUjP1C7R3AtZwJVdm5GaFxASxdol+9evfGhUUei4qt+zVsaFraNKyFvsA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.6.0-beta.70"
|
||||
"@xterm/xterm": "^5.6.0-beta.74"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-serialize": {
|
||||
"version": "0.14.0-beta.70",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.70.tgz",
|
||||
"integrity": "sha512-4ijqHU7xDRcZ4Gm6yN/tKsZzovUwzttE9/pPfhxpWIgSdm9c8i5R4rGyOAlAZCnuU3DT5vPplQO7W/0zwAmJsQ==",
|
||||
"version": "0.14.0-beta.74",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.74.tgz",
|
||||
"integrity": "sha512-zBhQAoXagBMhKFGYQ6n52sKapY61Jt3hKT8awhG/49f04JkaX1Jhc6xohD+aZs6J2ABHI7EWW/y0xTN9BDLLBw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.6.0-beta.70"
|
||||
"@xterm/xterm": "^5.6.0-beta.74"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-unicode11": {
|
||||
"version": "0.9.0-beta.70",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.70.tgz",
|
||||
"integrity": "sha512-9UE4v2SpWtqd85Hqvk8LW4QA9Phe93RMVltrNtt9jCUmkAok/QLFOEbLqoq2JUOvlmfsYfXmIl11Wg4CYIsvwA==",
|
||||
"version": "0.9.0-beta.74",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.74.tgz",
|
||||
"integrity": "sha512-1AyuDST77Xg33ed+3neNrQfsfZVwa+C16uWP9eTdJ1rO48ylqPcFTDnahCfIZGPHGjPqLl90ZKZ8tt1zWSefmA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.6.0-beta.70"
|
||||
"@xterm/xterm": "^5.6.0-beta.74"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-webgl": {
|
||||
"version": "0.19.0-beta.70",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.70.tgz",
|
||||
"integrity": "sha512-3BkmF24i66SHCfAkcHy1VC6H715qyelfOIjd6n7sTqHon56J1bUO782Q1al/MK0vSvplElBRKVdR31SDvITzpg==",
|
||||
"version": "0.19.0-beta.74",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.74.tgz",
|
||||
"integrity": "sha512-1wKiuv6WHMqOcSlLeIRU7UF8zkU4KU35rnPvLw2G45aAJSr9B3f7EVIaJ4IjXGeY/C+WCM3wWuybPN5VdB8qAQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.6.0-beta.70"
|
||||
"@xterm/xterm": "^5.6.0-beta.74"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/headless": {
|
||||
"version": "5.6.0-beta.70",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.70.tgz",
|
||||
"integrity": "sha512-npSJzmtJq8LLAzV3nwD9KkBQLNWSusi+cOBPyc3zlYYE63Vkqbtbp/iQS27zH2GxJ95rzCzDwnX6VOn0OoUPYQ==",
|
||||
"version": "5.6.0-beta.74",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.74.tgz",
|
||||
"integrity": "sha512-6PSNk1/CaLGNZLhXULswjfbf/rJrG1EomT9hR2nNbX6Osm9LVbhgIzg/mYHPu9wX5Pz7Gpd1VWp4/1NcKpN53w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@xterm/xterm": {
|
||||
"version": "5.6.0-beta.70",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.70.tgz",
|
||||
"integrity": "sha512-qviQMVWhRtgPn4z8PHNH6D/ffSKkNBHmUX1HyJxl325QM2xF8M8met83uFv7JZm7a5OQYScnLGsFAoTreSgdew==",
|
||||
"version": "5.6.0-beta.74",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.74.tgz",
|
||||
"integrity": "sha512-gVu7+4Cfd7O/6cQ/UK0sZM+TJBaI/VgQ/JFAhuAnNFj29wts2MzxSH3fIp3KUG1kqlJBWEUShCTwB8nKwtbCjQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
|
||||
+9
-9
@@ -15,15 +15,15 @@
|
||||
"@vscode/vscode-languagedetection": "1.0.21",
|
||||
"@vscode/windows-process-tree": "^0.6.0",
|
||||
"@vscode/windows-registry": "^1.1.0",
|
||||
"@xterm/addon-clipboard": "^0.2.0-beta.53",
|
||||
"@xterm/addon-image": "^0.9.0-beta.70",
|
||||
"@xterm/addon-ligatures": "^0.10.0-beta.70",
|
||||
"@xterm/addon-search": "^0.16.0-beta.70",
|
||||
"@xterm/addon-serialize": "^0.14.0-beta.70",
|
||||
"@xterm/addon-unicode11": "^0.9.0-beta.70",
|
||||
"@xterm/addon-webgl": "^0.19.0-beta.70",
|
||||
"@xterm/headless": "^5.6.0-beta.70",
|
||||
"@xterm/xterm": "^5.6.0-beta.70",
|
||||
"@xterm/addon-clipboard": "^0.2.0-beta.57",
|
||||
"@xterm/addon-image": "^0.9.0-beta.74",
|
||||
"@xterm/addon-ligatures": "^0.10.0-beta.74",
|
||||
"@xterm/addon-search": "^0.16.0-beta.74",
|
||||
"@xterm/addon-serialize": "^0.14.0-beta.74",
|
||||
"@xterm/addon-unicode11": "^0.9.0-beta.74",
|
||||
"@xterm/addon-webgl": "^0.19.0-beta.74",
|
||||
"@xterm/headless": "^5.6.0-beta.74",
|
||||
"@xterm/xterm": "^5.6.0-beta.74",
|
||||
"cookie": "^0.7.0",
|
||||
"http-proxy-agent": "^7.0.0",
|
||||
"https-proxy-agent": "^7.0.2",
|
||||
|
||||
Generated
+39
-39
@@ -13,14 +13,14 @@
|
||||
"@vscode/iconv-lite-umd": "0.7.0",
|
||||
"@vscode/tree-sitter-wasm": "^0.0.4",
|
||||
"@vscode/vscode-languagedetection": "1.0.21",
|
||||
"@xterm/addon-clipboard": "^0.2.0-beta.53",
|
||||
"@xterm/addon-image": "^0.9.0-beta.70",
|
||||
"@xterm/addon-ligatures": "^0.10.0-beta.70",
|
||||
"@xterm/addon-search": "^0.16.0-beta.70",
|
||||
"@xterm/addon-serialize": "^0.14.0-beta.70",
|
||||
"@xterm/addon-unicode11": "^0.9.0-beta.70",
|
||||
"@xterm/addon-webgl": "^0.19.0-beta.70",
|
||||
"@xterm/xterm": "^5.6.0-beta.70",
|
||||
"@xterm/addon-clipboard": "^0.2.0-beta.57",
|
||||
"@xterm/addon-image": "^0.9.0-beta.74",
|
||||
"@xterm/addon-ligatures": "^0.10.0-beta.74",
|
||||
"@xterm/addon-search": "^0.16.0-beta.74",
|
||||
"@xterm/addon-serialize": "^0.14.0-beta.74",
|
||||
"@xterm/addon-unicode11": "^0.9.0-beta.74",
|
||||
"@xterm/addon-webgl": "^0.19.0-beta.74",
|
||||
"@xterm/xterm": "^5.6.0-beta.74",
|
||||
"jschardet": "3.1.4",
|
||||
"tas-client-umd": "0.2.0",
|
||||
"vscode-oniguruma": "1.7.0",
|
||||
@@ -88,30 +88,30 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-clipboard": {
|
||||
"version": "0.2.0-beta.53",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.53.tgz",
|
||||
"integrity": "sha512-kCcBuGvF8mwzExU+Tm9eylPvp1kXTkvm+kO0V4qP7HI3ZCw5vfKmnlRn41FvNIylsK2hnmrFtxauPHEGBy/dfA==",
|
||||
"version": "0.2.0-beta.57",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.57.tgz",
|
||||
"integrity": "sha512-/GSI8Fkmb8s/V1t2EGc2U2PUfSqge6f9gAeob65EwarsfBf66cmCxMG0ZSPE8+nti1pGIsrJA8XfeEaJt4clcA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-base64": "^3.7.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.6.0-beta.70"
|
||||
"@xterm/xterm": "^5.6.0-beta.74"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-image": {
|
||||
"version": "0.9.0-beta.70",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.70.tgz",
|
||||
"integrity": "sha512-QLhy77i0sjnffkLuxj1yB/mBUJI64bbL86eMW+1g5XEsZnSevY8YwU/cEJg02PAGK0ggwQNNfiRwoO6VBCdYFg==",
|
||||
"version": "0.9.0-beta.74",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.74.tgz",
|
||||
"integrity": "sha512-mJPWNPov2mqrUkYZCs6UCn5p6DBLeN6xjpLu5mLh8cmXr544VWfqEVNAYPQ9+8uNgXdzSsKInBv9ZGtbXV0SfA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.6.0-beta.70"
|
||||
"@xterm/xterm": "^5.6.0-beta.74"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-ligatures": {
|
||||
"version": "0.10.0-beta.70",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.70.tgz",
|
||||
"integrity": "sha512-BPDHHOybUWO6mjHf/RMDBjSKDl9QdyyGTyHvmlyhuI/2sma3lu98bA4U03F8nBnvj/6otzEFARuOeoN7rkfRng==",
|
||||
"version": "0.10.0-beta.74",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.74.tgz",
|
||||
"integrity": "sha512-/X3OemVqPSgdely8OgdQb0cJqv9HqiMaBLeLe2QHfTWdXDBOLG/O4g8n/lChqR9rulEMwPCt2LqvtyIMA3iZsw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"font-finder": "^1.1.0",
|
||||
@@ -121,49 +121,49 @@
|
||||
"node": ">8.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.6.0-beta.70"
|
||||
"@xterm/xterm": "^5.6.0-beta.74"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-search": {
|
||||
"version": "0.16.0-beta.70",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.70.tgz",
|
||||
"integrity": "sha512-uaNBf77cr5Jikj69TDkTfe3V3wHA+4tDTFcv8DwJ/KGORPLUfBk8cn9HpbIJ+0jc1kOZr5xDrEjaYNwEVDkGeQ==",
|
||||
"version": "0.16.0-beta.74",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.74.tgz",
|
||||
"integrity": "sha512-KMOeOu3EvtDWcH/HCs6fe4KyaMMdjoUjP1C7R3AtZwJVdm5GaFxASxdol+9evfGhUUei4qt+zVsaFraNKyFvsA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.6.0-beta.70"
|
||||
"@xterm/xterm": "^5.6.0-beta.74"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-serialize": {
|
||||
"version": "0.14.0-beta.70",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.70.tgz",
|
||||
"integrity": "sha512-4ijqHU7xDRcZ4Gm6yN/tKsZzovUwzttE9/pPfhxpWIgSdm9c8i5R4rGyOAlAZCnuU3DT5vPplQO7W/0zwAmJsQ==",
|
||||
"version": "0.14.0-beta.74",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.74.tgz",
|
||||
"integrity": "sha512-zBhQAoXagBMhKFGYQ6n52sKapY61Jt3hKT8awhG/49f04JkaX1Jhc6xohD+aZs6J2ABHI7EWW/y0xTN9BDLLBw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.6.0-beta.70"
|
||||
"@xterm/xterm": "^5.6.0-beta.74"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-unicode11": {
|
||||
"version": "0.9.0-beta.70",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.70.tgz",
|
||||
"integrity": "sha512-9UE4v2SpWtqd85Hqvk8LW4QA9Phe93RMVltrNtt9jCUmkAok/QLFOEbLqoq2JUOvlmfsYfXmIl11Wg4CYIsvwA==",
|
||||
"version": "0.9.0-beta.74",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.74.tgz",
|
||||
"integrity": "sha512-1AyuDST77Xg33ed+3neNrQfsfZVwa+C16uWP9eTdJ1rO48ylqPcFTDnahCfIZGPHGjPqLl90ZKZ8tt1zWSefmA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.6.0-beta.70"
|
||||
"@xterm/xterm": "^5.6.0-beta.74"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-webgl": {
|
||||
"version": "0.19.0-beta.70",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.70.tgz",
|
||||
"integrity": "sha512-3BkmF24i66SHCfAkcHy1VC6H715qyelfOIjd6n7sTqHon56J1bUO782Q1al/MK0vSvplElBRKVdR31SDvITzpg==",
|
||||
"version": "0.19.0-beta.74",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.74.tgz",
|
||||
"integrity": "sha512-1wKiuv6WHMqOcSlLeIRU7UF8zkU4KU35rnPvLw2G45aAJSr9B3f7EVIaJ4IjXGeY/C+WCM3wWuybPN5VdB8qAQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.6.0-beta.70"
|
||||
"@xterm/xterm": "^5.6.0-beta.74"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/xterm": {
|
||||
"version": "5.6.0-beta.70",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.70.tgz",
|
||||
"integrity": "sha512-qviQMVWhRtgPn4z8PHNH6D/ffSKkNBHmUX1HyJxl325QM2xF8M8met83uFv7JZm7a5OQYScnLGsFAoTreSgdew==",
|
||||
"version": "5.6.0-beta.74",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.74.tgz",
|
||||
"integrity": "sha512-gVu7+4Cfd7O/6cQ/UK0sZM+TJBaI/VgQ/JFAhuAnNFj29wts2MzxSH3fIp3KUG1kqlJBWEUShCTwB8nKwtbCjQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/font-finder": {
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
"@vscode/iconv-lite-umd": "0.7.0",
|
||||
"@vscode/tree-sitter-wasm": "^0.0.4",
|
||||
"@vscode/vscode-languagedetection": "1.0.21",
|
||||
"@xterm/addon-clipboard": "^0.2.0-beta.53",
|
||||
"@xterm/addon-image": "^0.9.0-beta.70",
|
||||
"@xterm/addon-ligatures": "^0.10.0-beta.70",
|
||||
"@xterm/addon-search": "^0.16.0-beta.70",
|
||||
"@xterm/addon-serialize": "^0.14.0-beta.70",
|
||||
"@xterm/addon-unicode11": "^0.9.0-beta.70",
|
||||
"@xterm/addon-webgl": "^0.19.0-beta.70",
|
||||
"@xterm/xterm": "^5.6.0-beta.70",
|
||||
"@xterm/addon-clipboard": "^0.2.0-beta.57",
|
||||
"@xterm/addon-image": "^0.9.0-beta.74",
|
||||
"@xterm/addon-ligatures": "^0.10.0-beta.74",
|
||||
"@xterm/addon-search": "^0.16.0-beta.74",
|
||||
"@xterm/addon-serialize": "^0.14.0-beta.74",
|
||||
"@xterm/addon-unicode11": "^0.9.0-beta.74",
|
||||
"@xterm/addon-webgl": "^0.19.0-beta.74",
|
||||
"@xterm/xterm": "^5.6.0-beta.74",
|
||||
"jschardet": "3.1.4",
|
||||
"tas-client-umd": "0.2.0",
|
||||
"vscode-oniguruma": "1.7.0",
|
||||
|
||||
Vendored
+2
-2
@@ -58,8 +58,8 @@ interface EditContextEventHandlersEventMap {
|
||||
|
||||
type EventHandler<TEvent extends Event = Event> = (event: TEvent) => void;
|
||||
|
||||
interface TextUpdateEvent extends Event {
|
||||
new(type: DOMString, options?: TextUpdateEventInit): TextUpdateEvent;
|
||||
declare class TextUpdateEvent extends Event {
|
||||
constructor(type: DOMString, options?: TextUpdateEventInit);
|
||||
|
||||
readonly updateRangeStart: number;
|
||||
readonly updateRangeEnd: number;
|
||||
|
||||
@@ -83,7 +83,7 @@ import { NativeURLService } from '../../platform/url/common/urlService.js';
|
||||
import { ElectronURLListener } from '../../platform/url/electron-main/electronUrlListener.js';
|
||||
import { IWebviewManagerService } from '../../platform/webview/common/webviewManagerService.js';
|
||||
import { WebviewMainService } from '../../platform/webview/electron-main/webviewMainService.js';
|
||||
import { isFolderToOpen, isWorkspaceToOpen, IWindowOpenable } from '../../platform/window/common/window.js';
|
||||
import { isFolderToOpen, isWorkspaceToOpen, IWindowOpenable, TitlebarStyle, overrideDefaultTitlebarStyle } from '../../platform/window/common/window.js';
|
||||
import { IWindowsMainService, OpenContext } from '../../platform/windows/electron-main/windows.js';
|
||||
import { ICodeWindow } from '../../platform/window/electron-main/window.js';
|
||||
import { WindowsMainService } from '../../platform/windows/electron-main/windowsMainService.js';
|
||||
@@ -593,6 +593,14 @@ export class CodeApplication extends Disposable {
|
||||
// Services
|
||||
const appInstantiationService = await this.initServices(machineId, sqmId, devDeviceId, sharedProcessReady);
|
||||
|
||||
// Linux (stable only): custom title default style override
|
||||
if (isLinux && this.productService.quality === 'stable') {
|
||||
const titleBarDefaultStyleOverride = this.stateService.getItem('window.titleBarStyleOverride');
|
||||
if (titleBarDefaultStyleOverride === TitlebarStyle.CUSTOM || titleBarDefaultStyleOverride === TitlebarStyle.NATIVE) {
|
||||
overrideDefaultTitlebarStyle(titleBarDefaultStyleOverride);
|
||||
}
|
||||
}
|
||||
|
||||
// Auth Handler
|
||||
appInstantiationService.invokeFunction(accessor => accessor.get(IProxyAuthService));
|
||||
|
||||
@@ -605,7 +613,7 @@ export class CodeApplication extends Disposable {
|
||||
// Setup Protocol URL Handlers
|
||||
const initialProtocolUrls = await appInstantiationService.invokeFunction(accessor => this.setupProtocolUrlHandlers(accessor, mainProcessElectronServer));
|
||||
|
||||
// Setup vscode-remote-resource protocol handler.
|
||||
// Setup vscode-remote-resource protocol handler
|
||||
this.setupManagedRemoteResourceUrlHandler(mainProcessElectronServer);
|
||||
|
||||
// Signal phase: ready - before opening first window
|
||||
|
||||
@@ -150,13 +150,13 @@ export class EditorMouseEventFactory {
|
||||
}
|
||||
|
||||
public onContextMenu(target: HTMLElement, callback: (e: EditorMouseEvent) => void): IDisposable {
|
||||
return dom.addDisposableListener(target, 'contextmenu', (e: MouseEvent) => {
|
||||
return dom.addDisposableListener(target, dom.EventType.CONTEXT_MENU, (e: MouseEvent) => {
|
||||
callback(this._create(e));
|
||||
});
|
||||
}
|
||||
|
||||
public onMouseUp(target: HTMLElement, callback: (e: EditorMouseEvent) => void): IDisposable {
|
||||
return dom.addDisposableListener(target, 'mouseup', (e: MouseEvent) => {
|
||||
return dom.addDisposableListener(target, dom.EventType.MOUSE_UP, (e: MouseEvent) => {
|
||||
callback(this._create(e));
|
||||
});
|
||||
}
|
||||
@@ -180,7 +180,7 @@ export class EditorMouseEventFactory {
|
||||
}
|
||||
|
||||
public onMouseMove(target: HTMLElement, callback: (e: EditorMouseEvent) => void): IDisposable {
|
||||
return dom.addDisposableListener(target, 'mousemove', (e) => callback(this._create(e)));
|
||||
return dom.addDisposableListener(target, dom.EventType.MOUSE_MOVE, (e) => callback(this._create(e)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import { USUAL_WORD_SEPARATORS } from '../core/wordHelper.js';
|
||||
import * as nls from '../../../nls.js';
|
||||
import { AccessibilitySupport } from '../../../platform/accessibility/common/accessibility.js';
|
||||
import { IConfigurationPropertySchema } from '../../../platform/configuration/common/configurationRegistry.js';
|
||||
import product from '../../../platform/product/common/product.js';
|
||||
|
||||
//#region typed options
|
||||
|
||||
@@ -5822,7 +5823,7 @@ export const EditorOptions = {
|
||||
emptySelectionClipboard: register(new EditorEmptySelectionClipboard()),
|
||||
dropIntoEditor: register(new EditorDropIntoEditor()),
|
||||
experimentalEditContextEnabled: register(new EditorBooleanOption(
|
||||
EditorOption.experimentalEditContextEnabled, 'experimentalEditContextEnabled', false,
|
||||
EditorOption.experimentalEditContextEnabled, 'experimentalEditContextEnabled', product.quality !== 'stable',
|
||||
{
|
||||
description: nls.localize('experimentalEditContextEnabled', "Sets whether the new experimental edit context should be used instead of the text area."),
|
||||
included: platform.isChrome || platform.isEdge || platform.isNative
|
||||
|
||||
@@ -80,7 +80,7 @@ export const editorBracketHighlightingForeground4 = registerColor('editorBracket
|
||||
export const editorBracketHighlightingForeground5 = registerColor('editorBracketHighlight.foreground5', '#00000000', nls.localize('editorBracketHighlightForeground5', 'Foreground color of brackets (5). Requires enabling bracket pair colorization.'));
|
||||
export const editorBracketHighlightingForeground6 = registerColor('editorBracketHighlight.foreground6', '#00000000', nls.localize('editorBracketHighlightForeground6', 'Foreground color of brackets (6). Requires enabling bracket pair colorization.'));
|
||||
|
||||
export const editorBracketHighlightingUnexpectedBracketForeground = registerColor('editorBracketHighlight.unexpectedBracket.foreground', { dark: new Color(new RGBA(255, 18, 18, 0.8)), light: new Color(new RGBA(255, 18, 18, 0.8)), hcDark: 'new Color(new RGBA(255, 50, 50, 1))', hcLight: '#B5200D' }, nls.localize('editorBracketHighlightUnexpectedBracketForeground', 'Foreground color of unexpected brackets.'));
|
||||
export const editorBracketHighlightingUnexpectedBracketForeground = registerColor('editorBracketHighlight.unexpectedBracket.foreground', { dark: new Color(new RGBA(255, 18, 18, 0.8)), light: new Color(new RGBA(255, 18, 18, 0.8)), hcDark: new Color(new RGBA(255, 50, 50, 1)), hcLight: '#B5200D' }, nls.localize('editorBracketHighlightUnexpectedBracketForeground', 'Foreground color of unexpected brackets.'));
|
||||
|
||||
export const editorBracketPairGuideBackground1 = registerColor('editorBracketPairGuide.background1', '#00000000', nls.localize('editorBracketPairGuide.background1', 'Background color of inactive bracket pair guides (1). Requires enabling bracket pair guides.'));
|
||||
export const editorBracketPairGuideBackground2 = registerColor('editorBracketPairGuide.background2', '#00000000', nls.localize('editorBracketPairGuide.background2', 'Background color of inactive bracket pair guides (2). Requires enabling bracket pair guides.'));
|
||||
|
||||
@@ -85,7 +85,7 @@ export interface IExtensionsProfileScannerService {
|
||||
scanProfileExtensions(profileLocation: URI, options?: IProfileExtensionsScanOptions): Promise<IScannedProfileExtension[]>;
|
||||
addExtensionsToProfile(extensions: [IExtension, Metadata | undefined][], profileLocation: URI, keepExistingVersions?: boolean): Promise<IScannedProfileExtension[]>;
|
||||
updateMetadata(extensions: [IExtension, Metadata | undefined][], profileLocation: URI): Promise<IScannedProfileExtension[]>;
|
||||
removeExtensionFromProfile(extension: IExtension, profileLocation: URI): Promise<void>;
|
||||
removeExtensionsFromProfile(extensions: IExtensionIdentifier[], profileLocation: URI): Promise<void>;
|
||||
}
|
||||
|
||||
export abstract class AbstractExtensionsProfileScannerService extends Disposable implements IExtensionsProfileScannerService {
|
||||
@@ -193,13 +193,13 @@ export abstract class AbstractExtensionsProfileScannerService extends Disposable
|
||||
return updatedExtensions;
|
||||
}
|
||||
|
||||
async removeExtensionFromProfile(extension: IExtension, profileLocation: URI): Promise<void> {
|
||||
async removeExtensionsFromProfile(extensions: IExtensionIdentifier[], profileLocation: URI): Promise<void> {
|
||||
const extensionsToRemove: IScannedProfileExtension[] = [];
|
||||
try {
|
||||
await this.withProfileExtensions(profileLocation, profileExtensions => {
|
||||
const result: IScannedProfileExtension[] = [];
|
||||
for (const e of profileExtensions) {
|
||||
if (areSameExtensions(e.identifier, extension.identifier)) {
|
||||
if (extensions.some(extension => areSameExtensions(e.identifier, extension))) {
|
||||
extensionsToRemove.push(e);
|
||||
} else {
|
||||
result.push(e);
|
||||
|
||||
@@ -100,16 +100,24 @@ interface IBuiltInExtensionControl {
|
||||
[name: string]: 'marketplace' | 'disabled' | string;
|
||||
}
|
||||
|
||||
export type ScanOptions = {
|
||||
readonly profileLocation?: URI;
|
||||
readonly includeInvalid?: boolean;
|
||||
readonly includeAllVersions?: boolean;
|
||||
export type SystemExtensionsScanOptions = {
|
||||
readonly checkControlFile?: boolean;
|
||||
readonly language?: string;
|
||||
};
|
||||
|
||||
export type UserExtensionsScanOptions = {
|
||||
readonly profileLocation: URI;
|
||||
readonly includeInvalid?: boolean;
|
||||
readonly language?: string;
|
||||
readonly useCache?: boolean;
|
||||
readonly productVersion?: IProductVersion;
|
||||
};
|
||||
|
||||
export type ScanOptions = {
|
||||
readonly includeInvalid?: boolean;
|
||||
readonly language?: string;
|
||||
};
|
||||
|
||||
export const IExtensionsScannerService = createDecorator<IExtensionsScannerService>('IExtensionsScannerService');
|
||||
export interface IExtensionsScannerService {
|
||||
readonly _serviceBrand: undefined;
|
||||
@@ -118,17 +126,16 @@ export interface IExtensionsScannerService {
|
||||
readonly userExtensionsLocation: URI;
|
||||
readonly onDidChangeCache: Event<ExtensionType>;
|
||||
|
||||
getTargetPlatform(): Promise<TargetPlatform>;
|
||||
scanAllExtensions(systemScanOptions: SystemExtensionsScanOptions, userScanOptions: UserExtensionsScanOptions): Promise<IScannedExtension[]>;
|
||||
scanSystemExtensions(scanOptions: SystemExtensionsScanOptions): Promise<IScannedExtension[]>;
|
||||
scanUserExtensions(scanOptions: UserExtensionsScanOptions): Promise<IScannedExtension[]>;
|
||||
scanAllUserExtensions(): Promise<IScannedExtension[]>;
|
||||
|
||||
scanAllExtensions(systemScanOptions: ScanOptions, userScanOptions: ScanOptions, includeExtensionsUnderDev: boolean): Promise<IScannedExtension[]>;
|
||||
scanSystemExtensions(scanOptions: ScanOptions): Promise<IScannedExtension[]>;
|
||||
scanUserExtensions(scanOptions: ScanOptions): Promise<IScannedExtension[]>;
|
||||
scanExtensionsUnderDevelopment(scanOptions: ScanOptions, existingExtensions: IScannedExtension[]): Promise<IScannedExtension[]>;
|
||||
scanExtensionsUnderDevelopment(existingExtensions: IScannedExtension[], scanOptions: ScanOptions): Promise<IScannedExtension[]>;
|
||||
scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType, scanOptions: ScanOptions): Promise<IScannedExtension | null>;
|
||||
scanOneOrMultipleExtensions(extensionLocation: URI, extensionType: ExtensionType, scanOptions: ScanOptions): Promise<IScannedExtension[]>;
|
||||
scanMultipleExtensions(extensionLocations: URI[], extensionType: ExtensionType, scanOptions: ScanOptions): Promise<IScannedExtension[]>;
|
||||
scanOneOrMultipleExtensions(extensionLocation: URI, extensionType: ExtensionType, scanOptions: ScanOptions): Promise<IScannedExtension[]>;
|
||||
|
||||
scanMetadata(extensionLocation: URI): Promise<Metadata | undefined>;
|
||||
updateMetadata(extensionLocation: URI, metadata: Partial<Metadata>): Promise<void>;
|
||||
initializeDefaultProfileExtensions(): Promise<void>;
|
||||
}
|
||||
@@ -167,35 +174,33 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
|
||||
}
|
||||
|
||||
private _targetPlatformPromise: Promise<TargetPlatform> | undefined;
|
||||
getTargetPlatform(): Promise<TargetPlatform> {
|
||||
private getTargetPlatform(): Promise<TargetPlatform> {
|
||||
if (!this._targetPlatformPromise) {
|
||||
this._targetPlatformPromise = computeTargetPlatform(this.fileService, this.logService);
|
||||
}
|
||||
return this._targetPlatformPromise;
|
||||
}
|
||||
|
||||
async scanAllExtensions(systemScanOptions: ScanOptions, userScanOptions: ScanOptions, includeExtensionsUnderDev: boolean): Promise<IScannedExtension[]> {
|
||||
async scanAllExtensions(systemScanOptions: SystemExtensionsScanOptions, userScanOptions: UserExtensionsScanOptions): Promise<IScannedExtension[]> {
|
||||
const [system, user] = await Promise.all([
|
||||
this.scanSystemExtensions(systemScanOptions),
|
||||
this.scanUserExtensions(userScanOptions),
|
||||
]);
|
||||
const development = includeExtensionsUnderDev ? await this.scanExtensionsUnderDevelopment(systemScanOptions, [...system, ...user]) : [];
|
||||
return this.dedupExtensions(system, user, development, await this.getTargetPlatform(), true);
|
||||
return this.dedupExtensions(system, user, [], await this.getTargetPlatform(), true);
|
||||
}
|
||||
|
||||
async scanSystemExtensions(scanOptions: ScanOptions): Promise<IScannedExtension[]> {
|
||||
async scanSystemExtensions(scanOptions: SystemExtensionsScanOptions): Promise<IScannedExtension[]> {
|
||||
const promises: Promise<IRelaxedScannedExtension[]>[] = [];
|
||||
promises.push(this.scanDefaultSystemExtensions(!!scanOptions.useCache, scanOptions.language));
|
||||
promises.push(this.scanDefaultSystemExtensions(scanOptions.language));
|
||||
promises.push(this.scanDevSystemExtensions(scanOptions.language, !!scanOptions.checkControlFile));
|
||||
const [defaultSystemExtensions, devSystemExtensions] = await Promise.all(promises);
|
||||
return this.applyScanOptions([...defaultSystemExtensions, ...devSystemExtensions], ExtensionType.System, scanOptions, false);
|
||||
return this.applyScanOptions([...defaultSystemExtensions, ...devSystemExtensions], ExtensionType.System, { pickLatest: false });
|
||||
}
|
||||
|
||||
async scanUserExtensions(scanOptions: ScanOptions): Promise<IScannedExtension[]> {
|
||||
const location = scanOptions.profileLocation ?? this.userExtensionsLocation;
|
||||
this.logService.trace('Started scanning user extensions', location);
|
||||
async scanUserExtensions(scanOptions: UserExtensionsScanOptions): Promise<IScannedExtension[]> {
|
||||
this.logService.trace('Started scanning user extensions', scanOptions.profileLocation);
|
||||
const profileScanOptions: IProfileExtensionsScanOptions | undefined = this.uriIdentityService.extUri.isEqual(scanOptions.profileLocation, this.userDataProfilesService.defaultProfile.extensionsResource) ? { bailOutWhenFileNotFound: true } : undefined;
|
||||
const extensionsScannerInput = await this.createExtensionScannerInput(location, !!scanOptions.profileLocation, ExtensionType.User, scanOptions.language, true, profileScanOptions, scanOptions.productVersion ?? this.getProductVersion());
|
||||
const extensionsScannerInput = await this.createExtensionScannerInput(scanOptions.profileLocation, true, ExtensionType.User, scanOptions.language, true, profileScanOptions, scanOptions.productVersion ?? this.getProductVersion());
|
||||
const extensionsScanner = scanOptions.useCache && !extensionsScannerInput.devMode ? this.userExtensionsCachedScanner : this.extensionsScanner;
|
||||
let extensions: IRelaxedScannedExtension[];
|
||||
try {
|
||||
@@ -208,16 +213,22 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
extensions = await this.applyScanOptions(extensions, ExtensionType.User, scanOptions, true);
|
||||
extensions = await this.applyScanOptions(extensions, ExtensionType.User, { includeInvalid: scanOptions.includeInvalid, pickLatest: true });
|
||||
this.logService.trace('Scanned user extensions:', extensions.length);
|
||||
return extensions;
|
||||
}
|
||||
|
||||
async scanExtensionsUnderDevelopment(scanOptions: ScanOptions, existingExtensions: IScannedExtension[]): Promise<IScannedExtension[]> {
|
||||
async scanAllUserExtensions(scanOptions: { includeAllVersions?: boolean; includeInvalid: boolean } = { includeInvalid: true, includeAllVersions: true }): Promise<IScannedExtension[]> {
|
||||
const extensionsScannerInput = await this.createExtensionScannerInput(this.userExtensionsLocation, false, ExtensionType.User, undefined, true, undefined, this.getProductVersion());
|
||||
const extensions = await this.extensionsScanner.scanExtensions(extensionsScannerInput);
|
||||
return this.applyScanOptions(extensions, ExtensionType.User, { includeAllVersions: scanOptions.includeAllVersions, includeInvalid: scanOptions.includeInvalid });
|
||||
}
|
||||
|
||||
async scanExtensionsUnderDevelopment(existingExtensions: IScannedExtension[], scanOptions: ScanOptions): Promise<IScannedExtension[]> {
|
||||
if (this.environmentService.isExtensionDevelopment && this.environmentService.extensionDevelopmentLocationURI) {
|
||||
const extensions = (await Promise.all(this.environmentService.extensionDevelopmentLocationURI.filter(extLoc => extLoc.scheme === Schemas.file)
|
||||
.map(async extensionDevelopmentLocationURI => {
|
||||
const input = await this.createExtensionScannerInput(extensionDevelopmentLocationURI, false, ExtensionType.User, scanOptions.language, false /* do not validate */, undefined, scanOptions.productVersion ?? this.getProductVersion());
|
||||
const input = await this.createExtensionScannerInput(extensionDevelopmentLocationURI, false, ExtensionType.User, scanOptions.language, false /* do not validate */, undefined, this.getProductVersion());
|
||||
const extensions = await this.extensionsScanner.scanOneOrMultipleExtensions(input);
|
||||
return extensions.map(extension => {
|
||||
// Override the extension type from the existing extensions
|
||||
@@ -227,13 +238,13 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
|
||||
});
|
||||
})))
|
||||
.flat();
|
||||
return this.applyScanOptions(extensions, 'development', scanOptions, true);
|
||||
return this.applyScanOptions(extensions, 'development', { includeInvalid: scanOptions.includeInvalid, pickLatest: true });
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
async scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType, scanOptions: ScanOptions): Promise<IScannedExtension | null> {
|
||||
const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, scanOptions.language, true, undefined, scanOptions.productVersion ?? this.getProductVersion());
|
||||
const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, scanOptions.language, true, undefined, this.getProductVersion());
|
||||
const extension = await this.extensionsScanner.scanExtension(extensionsScannerInput);
|
||||
if (!extension) {
|
||||
return null;
|
||||
@@ -245,9 +256,9 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
|
||||
}
|
||||
|
||||
async scanOneOrMultipleExtensions(extensionLocation: URI, extensionType: ExtensionType, scanOptions: ScanOptions): Promise<IScannedExtension[]> {
|
||||
const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, scanOptions.language, true, undefined, scanOptions.productVersion ?? this.getProductVersion());
|
||||
const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, scanOptions.language, true, undefined, this.getProductVersion());
|
||||
const extensions = await this.extensionsScanner.scanOneOrMultipleExtensions(extensionsScannerInput);
|
||||
return this.applyScanOptions(extensions, extensionType, scanOptions, true);
|
||||
return this.applyScanOptions(extensions, extensionType, { includeInvalid: scanOptions.includeInvalid, pickLatest: true });
|
||||
}
|
||||
|
||||
async scanMultipleExtensions(extensionLocations: URI[], extensionType: ExtensionType, scanOptions: ScanOptions): Promise<IScannedExtension[]> {
|
||||
@@ -256,14 +267,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
|
||||
const scannedExtensions = await this.scanOneOrMultipleExtensions(extensionLocation, extensionType, scanOptions);
|
||||
extensions.push(...scannedExtensions);
|
||||
}));
|
||||
return this.applyScanOptions(extensions, extensionType, scanOptions, true);
|
||||
}
|
||||
|
||||
async scanMetadata(extensionLocation: URI): Promise<Metadata | undefined> {
|
||||
const manifestLocation = joinPath(extensionLocation, 'package.json');
|
||||
const content = (await this.fileService.readFile(manifestLocation)).value.toString();
|
||||
const manifest: IScannedExtensionManifest = JSON.parse(content);
|
||||
return manifest.__metadata;
|
||||
return this.applyScanOptions(extensions, extensionType, { includeInvalid: scanOptions.includeInvalid, pickLatest: true });
|
||||
}
|
||||
|
||||
async updateMetadata(extensionLocation: URI, metaData: Partial<Metadata>): Promise<void> {
|
||||
@@ -301,7 +305,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
|
||||
this.initializeDefaultProfileExtensionsPromise = (async () => {
|
||||
try {
|
||||
this.logService.info('Started initializing default profile extensions in extensions installation folder.', this.userExtensionsLocation.toString());
|
||||
const userExtensions = await this.scanUserExtensions({ includeInvalid: true });
|
||||
const userExtensions = await this.scanAllUserExtensions({ includeInvalid: true });
|
||||
if (userExtensions.length) {
|
||||
await this.extensionsProfileScannerService.addExtensionsToProfile(userExtensions.map(e => [e, e.metadata]), this.userDataProfilesService.defaultProfile.extensionsResource);
|
||||
} else {
|
||||
@@ -324,9 +328,9 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
|
||||
return this.initializeDefaultProfileExtensionsPromise;
|
||||
}
|
||||
|
||||
private async applyScanOptions(extensions: IRelaxedScannedExtension[], type: ExtensionType | 'development', scanOptions: ScanOptions, pickLatest: boolean): Promise<IRelaxedScannedExtension[]> {
|
||||
private async applyScanOptions(extensions: IRelaxedScannedExtension[], type: ExtensionType | 'development', scanOptions: { includeAllVersions?: boolean; includeInvalid?: boolean; pickLatest?: boolean } = {}): Promise<IRelaxedScannedExtension[]> {
|
||||
if (!scanOptions.includeAllVersions) {
|
||||
extensions = this.dedupExtensions(type === ExtensionType.System ? extensions : undefined, type === ExtensionType.User ? extensions : undefined, type === 'development' ? extensions : undefined, await this.getTargetPlatform(), pickLatest);
|
||||
extensions = this.dedupExtensions(type === ExtensionType.System ? extensions : undefined, type === ExtensionType.User ? extensions : undefined, type === 'development' ? extensions : undefined, await this.getTargetPlatform(), !!scanOptions.pickLatest);
|
||||
}
|
||||
if (!scanOptions.includeInvalid) {
|
||||
extensions = extensions.filter(extension => extension.isValid);
|
||||
@@ -399,10 +403,10 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
|
||||
return [...result.values()];
|
||||
}
|
||||
|
||||
private async scanDefaultSystemExtensions(useCache: boolean, language: string | undefined): Promise<IRelaxedScannedExtension[]> {
|
||||
private async scanDefaultSystemExtensions(language: string | undefined): Promise<IRelaxedScannedExtension[]> {
|
||||
this.logService.trace('Started scanning system extensions');
|
||||
const extensionsScannerInput = await this.createExtensionScannerInput(this.systemExtensionsLocation, false, ExtensionType.System, language, true, undefined, this.getProductVersion());
|
||||
const extensionsScanner = useCache && !extensionsScannerInput.devMode ? this.systemExtensionsCachedScanner : this.extensionsScanner;
|
||||
const extensionsScanner = !extensionsScannerInput.devMode ? this.systemExtensionsCachedScanner : this.extensionsScanner;
|
||||
const result = await extensionsScanner.scanExtensions(extensionsScannerInput);
|
||||
this.logService.trace('Scanned system extensions:', result.length);
|
||||
return result;
|
||||
@@ -605,15 +609,16 @@ class ExtensionsScanner extends Disposable {
|
||||
if (!scannedProfileExtensions.length) {
|
||||
return [];
|
||||
}
|
||||
const extensions = await Promise.all<IRelaxedScannedExtension | null>(
|
||||
scannedProfileExtensions.map(async extensionInfo => {
|
||||
if (filter(extensionInfo)) {
|
||||
const extensionScannerInput = new ExtensionScannerInput(extensionInfo.location, input.mtime, input.applicationExtensionslocation, input.applicationExtensionslocationMtime, input.profile, input.profileScanOptions, input.type, input.validate, input.productVersion, input.productDate, input.productCommit, input.devMode, input.language, input.translations);
|
||||
return this.scanExtension(extensionScannerInput, extensionInfo.metadata);
|
||||
}
|
||||
return null;
|
||||
}));
|
||||
return coalesce(extensions);
|
||||
const extensions: IRelaxedScannedExtension[] = [];
|
||||
await Promise.all(scannedProfileExtensions.map(async extensionInfo => {
|
||||
if (!filter(extensionInfo)) {
|
||||
return;
|
||||
}
|
||||
const extensionScannerInput = new ExtensionScannerInput(extensionInfo.location, input.mtime, input.applicationExtensionslocation, input.applicationExtensionslocationMtime, input.profile, input.profileScanOptions, input.type, input.validate, input.productVersion, input.productDate, input.productCommit, input.devMode, input.language, input.translations);
|
||||
const extension = await this.scanExtension(extensionScannerInput, extensionInfo);
|
||||
extensions.push(extension);
|
||||
}));
|
||||
return extensions;
|
||||
}
|
||||
|
||||
async scanOneOrMultipleExtensions(input: ExtensionScannerInput): Promise<IRelaxedScannedExtension[]> {
|
||||
@@ -630,56 +635,76 @@ class ExtensionsScanner extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
async scanExtension(input: ExtensionScannerInput, metadata?: Metadata): Promise<IRelaxedScannedExtension | null> {
|
||||
async scanExtension(input: ExtensionScannerInput): Promise<IRelaxedScannedExtension | null>;
|
||||
async scanExtension(input: ExtensionScannerInput, scannedProfileExtension: IScannedProfileExtension): Promise<IRelaxedScannedExtension>;
|
||||
async scanExtension(input: ExtensionScannerInput, scannedProfileExtension?: IScannedProfileExtension): Promise<IRelaxedScannedExtension | null> {
|
||||
const validations: [Severity, string][] = [];
|
||||
let isValid = true;
|
||||
let manifest: IScannedExtensionManifest;
|
||||
try {
|
||||
let manifest = await this.scanExtensionManifest(input.location);
|
||||
if (manifest) {
|
||||
// allow publisher to be undefined to make the initial extension authoring experience smoother
|
||||
if (!manifest.publisher) {
|
||||
manifest.publisher = UNDEFINED_PUBLISHER;
|
||||
}
|
||||
metadata = metadata ?? manifest.__metadata;
|
||||
if (metadata && !metadata?.size && manifest.__metadata?.size) {
|
||||
metadata.size = manifest.__metadata?.size;
|
||||
}
|
||||
delete manifest.__metadata;
|
||||
const id = getGalleryExtensionId(manifest.publisher, manifest.name);
|
||||
const identifier = metadata?.id ? { id, uuid: metadata.id } : { id };
|
||||
const type = metadata?.isSystem ? ExtensionType.System : input.type;
|
||||
const isBuiltin = type === ExtensionType.System || !!metadata?.isBuiltin;
|
||||
manifest = await this.translateManifest(input.location, manifest, ExtensionScannerInput.createNlsConfiguration(input));
|
||||
let extension: IRelaxedScannedExtension = {
|
||||
type,
|
||||
identifier,
|
||||
manifest,
|
||||
location: input.location,
|
||||
isBuiltin,
|
||||
targetPlatform: metadata?.targetPlatform ?? TargetPlatform.UNDEFINED,
|
||||
publisherDisplayName: metadata?.publisherDisplayName,
|
||||
metadata,
|
||||
isValid: true,
|
||||
validations: [],
|
||||
preRelease: !!metadata?.preRelease,
|
||||
};
|
||||
if (input.validate) {
|
||||
extension = this.validate(extension, input);
|
||||
}
|
||||
if (manifest.enabledApiProposals && (!this.environmentService.isBuilt || this.extensionsEnabledWithApiProposalVersion.includes(id.toLowerCase()))) {
|
||||
manifest.originalEnabledApiProposals = manifest.enabledApiProposals;
|
||||
manifest.enabledApiProposals = parseEnabledApiProposalNames([...manifest.enabledApiProposals]);
|
||||
}
|
||||
return extension;
|
||||
}
|
||||
manifest = await this.scanExtensionManifest(input.location);
|
||||
} catch (e) {
|
||||
if (input.type !== ExtensionType.System) {
|
||||
this.logService.error(e);
|
||||
if (scannedProfileExtension) {
|
||||
validations.push([Severity.Error, getErrorMessage(e)]);
|
||||
isValid = false;
|
||||
const [publisher, name] = scannedProfileExtension.identifier.id.split('.');
|
||||
manifest = {
|
||||
name,
|
||||
publisher,
|
||||
version: scannedProfileExtension.version,
|
||||
engines: { vscode: '' }
|
||||
};
|
||||
} else {
|
||||
if (input.type !== ExtensionType.System) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
// allow publisher to be undefined to make the initial extension authoring experience smoother
|
||||
if (!manifest.publisher) {
|
||||
manifest.publisher = UNDEFINED_PUBLISHER;
|
||||
}
|
||||
const metadata = scannedProfileExtension?.metadata ?? manifest.__metadata;
|
||||
if (metadata && !metadata?.size && manifest.__metadata?.size) {
|
||||
metadata.size = manifest.__metadata?.size;
|
||||
}
|
||||
delete manifest.__metadata;
|
||||
const id = getGalleryExtensionId(manifest.publisher, manifest.name);
|
||||
const identifier = metadata?.id ? { id, uuid: metadata.id } : { id };
|
||||
const type = metadata?.isSystem ? ExtensionType.System : input.type;
|
||||
const isBuiltin = type === ExtensionType.System || !!metadata?.isBuiltin;
|
||||
try {
|
||||
manifest = await this.translateManifest(input.location, manifest, ExtensionScannerInput.createNlsConfiguration(input));
|
||||
} catch (error) {
|
||||
this.logService.warn('Failed to translate manifest', getErrorMessage(error));
|
||||
}
|
||||
let extension: IRelaxedScannedExtension = {
|
||||
type,
|
||||
identifier,
|
||||
manifest,
|
||||
location: input.location,
|
||||
isBuiltin,
|
||||
targetPlatform: metadata?.targetPlatform ?? TargetPlatform.UNDEFINED,
|
||||
publisherDisplayName: metadata?.publisherDisplayName,
|
||||
metadata,
|
||||
isValid,
|
||||
validations,
|
||||
preRelease: !!metadata?.preRelease,
|
||||
};
|
||||
if (input.validate) {
|
||||
extension = this.validate(extension, input);
|
||||
}
|
||||
if (manifest.enabledApiProposals && (!this.environmentService.isBuilt || this.extensionsEnabledWithApiProposalVersion.includes(id.toLowerCase()))) {
|
||||
manifest.originalEnabledApiProposals = manifest.enabledApiProposals;
|
||||
manifest.enabledApiProposals = parseEnabledApiProposalNames([...manifest.enabledApiProposals]);
|
||||
}
|
||||
return extension;
|
||||
}
|
||||
|
||||
validate(extension: IRelaxedScannedExtension, input: ExtensionScannerInput): IRelaxedScannedExtension {
|
||||
let isValid = true;
|
||||
let isValid = extension.isValid;
|
||||
const validateApiVersion = this.environmentService.isBuilt && this.extensionsEnabledWithApiProposalVersion.includes(extension.identifier.id.toLowerCase());
|
||||
const validations = validateExtensionManifest(input.productVersion, input.productDate, input.location, extension.manifest, extension.isBuiltin, validateApiVersion);
|
||||
for (const [severity, message] of validations) {
|
||||
@@ -689,11 +714,11 @@ class ExtensionsScanner extends Disposable {
|
||||
}
|
||||
}
|
||||
extension.isValid = isValid;
|
||||
extension.validations = validations;
|
||||
extension.validations = [...extension.validations, ...validations];
|
||||
return extension;
|
||||
}
|
||||
|
||||
private async scanExtensionManifest(extensionLocation: URI): Promise<IScannedExtensionManifest | null> {
|
||||
private async scanExtensionManifest(extensionLocation: URI): Promise<IScannedExtensionManifest> {
|
||||
const manifestLocation = joinPath(extensionLocation, 'package.json');
|
||||
let content;
|
||||
try {
|
||||
@@ -702,7 +727,7 @@ class ExtensionsScanner extends Disposable {
|
||||
if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) {
|
||||
this.logService.error(this.formatMessage(extensionLocation, localize('fileReadFail', "Cannot read file {0}: {1}.", manifestLocation.path, error.message)));
|
||||
}
|
||||
return null;
|
||||
throw error;
|
||||
}
|
||||
let manifest: IScannedExtensionManifest;
|
||||
try {
|
||||
@@ -714,11 +739,12 @@ class ExtensionsScanner extends Disposable {
|
||||
for (const e of errors) {
|
||||
this.logService.error(this.formatMessage(extensionLocation, localize('jsonParseFail', "Failed to parse {0}: [{1}, {2}] {3}.", manifestLocation.path, e.offset, e.length, getParseErrorMessage(e.error))));
|
||||
}
|
||||
return null;
|
||||
throw err;
|
||||
}
|
||||
if (getNodeType(manifest) !== 'object') {
|
||||
this.logService.error(this.formatMessage(extensionLocation, localize('jsonParseInvalidType', "Invalid manifest file {0}: Not a JSON object.", manifestLocation.path)));
|
||||
return null;
|
||||
const errorMessage = this.formatMessage(extensionLocation, localize('jsonParseInvalidType', "Invalid manifest file {0}: Not a JSON object.", manifestLocation.path));
|
||||
this.logService.error(errorMessage);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
return manifest;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { Schemas } from '../../../base/common/network.js';
|
||||
import * as path from '../../../base/common/path.js';
|
||||
import { joinPath } from '../../../base/common/resources.js';
|
||||
import * as semver from '../../../base/common/semver/semver.js';
|
||||
import { isBoolean } from '../../../base/common/types.js';
|
||||
import { isBoolean, isDefined, isUndefined } from '../../../base/common/types.js';
|
||||
import { URI } from '../../../base/common/uri.js';
|
||||
import { generateUuid } from '../../../base/common/uuid.js';
|
||||
import * as pfs from '../../../base/node/pfs.js';
|
||||
@@ -37,7 +37,7 @@ import {
|
||||
} from '../common/extensionManagement.js';
|
||||
import { areSameExtensions, computeTargetPlatform, ExtensionKey, getGalleryExtensionId, groupByExtension } from '../common/extensionManagementUtil.js';
|
||||
import { IExtensionsProfileScannerService, IScannedProfileExtension } from '../common/extensionsProfileScannerService.js';
|
||||
import { IExtensionsScannerService, IScannedExtension, ScanOptions } from '../common/extensionsScannerService.js';
|
||||
import { IExtensionsScannerService, IScannedExtension, UserExtensionsScanOptions } from '../common/extensionsScannerService.js';
|
||||
import { ExtensionsDownloader } from './extensionDownloader.js';
|
||||
import { ExtensionsLifecycle } from './extensionLifecycle.js';
|
||||
import { fromExtractError, getManifest } from './extensionManagementUtil.js';
|
||||
@@ -45,7 +45,7 @@ import { ExtensionsManifestCache } from './extensionsManifestCache.js';
|
||||
import { DidChangeProfileExtensionsEvent, ExtensionsWatcher } from './extensionsWatcher.js';
|
||||
import { ExtensionType, IExtension, IExtensionManifest, TargetPlatform } from '../../extensions/common/extensions.js';
|
||||
import { isEngineValid } from '../../extensions/common/extensionValidator.js';
|
||||
import { FileChangesEvent, FileChangeType, FileOperationResult, IFileService, toFileOperationResult } from '../../files/common/files.js';
|
||||
import { FileChangesEvent, FileChangeType, FileOperationResult, IFileService, IFileStat, toFileOperationResult } from '../../files/common/files.js';
|
||||
import { IInstantiationService, refineServiceDecorator } from '../../instantiation/common/instantiation.js';
|
||||
import { ILogService } from '../../log/common/log.js';
|
||||
import { IProductService } from '../../product/common/productService.js';
|
||||
@@ -132,7 +132,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
|
||||
}
|
||||
|
||||
scanAllUserInstalledExtensions(): Promise<ILocalExtension[]> {
|
||||
return this.extensionsScanner.scanAllUserExtensions(false);
|
||||
return this.extensionsScanner.scanAllUserExtensions();
|
||||
}
|
||||
|
||||
scanInstalledExtensionAtLocation(location: URI): Promise<ILocalExtension | null> {
|
||||
@@ -298,23 +298,6 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
|
||||
const local = await this.extensionsScanner.extractUserExtension(
|
||||
extensionKey,
|
||||
location.fsPath,
|
||||
{
|
||||
id: gallery.identifier.uuid,
|
||||
publisherId: gallery.publisherId,
|
||||
publisherDisplayName: gallery.publisherDisplayName,
|
||||
targetPlatform: gallery.properties.targetPlatform,
|
||||
isApplicationScoped: options.isApplicationScoped,
|
||||
isMachineScoped: options.isMachineScoped,
|
||||
isBuiltin: options.isBuiltin,
|
||||
isPreReleaseVersion: gallery.properties.isPreReleaseVersion,
|
||||
hasPreReleaseVersion: gallery.properties.isPreReleaseVersion,
|
||||
installedTimestamp: Date.now(),
|
||||
pinned: options.installGivenVersion ? true : !!options.pinned,
|
||||
preRelease: isBoolean(options.preRelease)
|
||||
? options.preRelease
|
||||
: options.installPreReleaseVersion || gallery.properties.isPreReleaseVersion,
|
||||
source: 'gallery',
|
||||
},
|
||||
false,
|
||||
token);
|
||||
|
||||
@@ -382,14 +365,6 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
|
||||
const local = await this.extensionsScanner.extractUserExtension(
|
||||
extensionKey,
|
||||
path.resolve(location.fsPath),
|
||||
{
|
||||
isApplicationScoped: options.isApplicationScoped,
|
||||
isMachineScoped: options.isMachineScoped,
|
||||
isBuiltin: options.isBuiltin,
|
||||
installedTimestamp: Date.now(),
|
||||
pinned: options.installGivenVersion ? true : !!options.pinned,
|
||||
source: 'vsix',
|
||||
},
|
||||
isBoolean(options.keepExisting) ? !options.keepExisting : true,
|
||||
token);
|
||||
return { local };
|
||||
@@ -561,17 +536,18 @@ export class ExtensionsScanner extends Disposable {
|
||||
async cleanUp(): Promise<void> {
|
||||
await this.removeTemporarilyDeletedFolders();
|
||||
await this.deleteExtensionsMarkedForRemoval();
|
||||
await this.initializeMetadata();
|
||||
//TODO: Remove this initiialization after coupe of releases
|
||||
await this.initializeExtensionSize();
|
||||
}
|
||||
|
||||
async scanExtensions(type: ExtensionType | null, profileLocation: URI, productVersion: IProductVersion): Promise<ILocalExtension[]> {
|
||||
try {
|
||||
const userScanOptions: ScanOptions = { includeInvalid: true, profileLocation, productVersion };
|
||||
const userScanOptions: UserExtensionsScanOptions = { includeInvalid: true, profileLocation, productVersion };
|
||||
let scannedExtensions: IScannedExtension[] = [];
|
||||
if (type === null || type === ExtensionType.System) {
|
||||
let scanAllExtensionsPromise = this.scanAllExtensionPromise.get(profileLocation);
|
||||
if (!scanAllExtensionsPromise) {
|
||||
scanAllExtensionsPromise = this.extensionsScannerService.scanAllExtensions({ includeInvalid: true, useCache: true }, userScanOptions, false)
|
||||
scanAllExtensionsPromise = this.extensionsScannerService.scanAllExtensions({}, userScanOptions)
|
||||
.finally(() => this.scanAllExtensionPromise.delete(profileLocation));
|
||||
this.scanAllExtensionPromise.set(profileLocation, scanAllExtensionsPromise);
|
||||
}
|
||||
@@ -592,9 +568,9 @@ export class ExtensionsScanner extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
async scanAllUserExtensions(excludeOutdated: boolean): Promise<ILocalExtension[]> {
|
||||
async scanAllUserExtensions(): Promise<ILocalExtension[]> {
|
||||
try {
|
||||
const scannedExtensions = await this.extensionsScannerService.scanUserExtensions({ includeAllVersions: !excludeOutdated, includeInvalid: true });
|
||||
const scannedExtensions = await this.extensionsScannerService.scanAllUserExtensions();
|
||||
return await Promise.all(scannedExtensions.map(extension => this.toLocalExtension(extension)));
|
||||
} catch (error) {
|
||||
throw toExtensionManagementError(error, ExtensionManagementErrorCode.Scanning);
|
||||
@@ -613,7 +589,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
return null;
|
||||
}
|
||||
|
||||
async extractUserExtension(extensionKey: ExtensionKey, zipPath: string, metadata: Metadata, removeIfExists: boolean, token: CancellationToken): Promise<ILocalExtension> {
|
||||
async extractUserExtension(extensionKey: ExtensionKey, zipPath: string, removeIfExists: boolean, token: CancellationToken): Promise<ILocalExtension> {
|
||||
const folderName = extensionKey.toString();
|
||||
const tempLocation = URI.file(path.join(this.extensionsScannerService.userExtensionsLocation.fsPath, `.${generateUuid()}`));
|
||||
const extensionLocation = URI.file(path.join(this.extensionsScannerService.userExtensionsLocation.fsPath, folderName));
|
||||
@@ -648,6 +624,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
throw fromExtractError(e);
|
||||
}
|
||||
|
||||
const metadata: Metadata = { installedTimestamp: Date.now() };
|
||||
try {
|
||||
metadata.size = await computeSize(tempLocation, this.fileService);
|
||||
} catch (error) {
|
||||
@@ -691,13 +668,9 @@ export class ExtensionsScanner extends Disposable {
|
||||
return this.scanLocalExtension(extensionLocation, ExtensionType.User);
|
||||
}
|
||||
|
||||
async scanMetadata(local: ILocalExtension, profileLocation?: URI): Promise<Metadata | undefined> {
|
||||
if (profileLocation) {
|
||||
const extension = await this.getScannedExtension(local, profileLocation);
|
||||
return extension?.metadata;
|
||||
} else {
|
||||
return this.extensionsScannerService.scanMetadata(local.location);
|
||||
}
|
||||
async scanMetadata(local: ILocalExtension, profileLocation: URI): Promise<Metadata | undefined> {
|
||||
const extension = await this.getScannedExtension(local, profileLocation);
|
||||
return extension?.metadata;
|
||||
}
|
||||
|
||||
private async getScannedExtension(local: ILocalExtension, profileLocation: URI): Promise<IScannedProfileExtension | undefined> {
|
||||
@@ -763,7 +736,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
await this.extensionsProfileScannerService.updateMetadata([[extension, { ...target.metadata, ...metadata }]], toProfileLocation);
|
||||
} else {
|
||||
const targetExtension = await this.scanLocalExtension(target.location, extension.type, toProfileLocation);
|
||||
await this.extensionsProfileScannerService.removeExtensionFromProfile(targetExtension, toProfileLocation);
|
||||
await this.extensionsProfileScannerService.removeExtensionsFromProfile([targetExtension.identifier], toProfileLocation);
|
||||
await this.extensionsProfileScannerService.addExtensionsToProfile([[extension, { ...target.metadata, ...metadata }]], toProfileLocation);
|
||||
}
|
||||
} else {
|
||||
@@ -856,10 +829,14 @@ export class ExtensionsScanner extends Disposable {
|
||||
}
|
||||
|
||||
private async toLocalExtension(extension: IScannedExtension): Promise<ILocalExtension> {
|
||||
const stat = await this.fileService.resolve(extension.location);
|
||||
let stat: IFileStat | undefined;
|
||||
try {
|
||||
stat = await this.fileService.resolve(extension.location);
|
||||
} catch (error) {/* ignore */ }
|
||||
|
||||
let readmeUrl: URI | undefined;
|
||||
let changelogUrl: URI | undefined;
|
||||
if (stat.children) {
|
||||
if (stat?.children) {
|
||||
readmeUrl = stat.children.find(({ name }) => /^readme(\.txt|\.md|)$/i.test(name))?.resource;
|
||||
changelogUrl = stat.children.find(({ name }) => /^changelog(\.txt|\.md|)$/i.test(name))?.resource;
|
||||
}
|
||||
@@ -890,11 +867,11 @@ export class ExtensionsScanner extends Disposable {
|
||||
};
|
||||
}
|
||||
|
||||
private async initializeMetadata(): Promise<void> {
|
||||
const extensions = await this.extensionsScannerService.scanUserExtensions({ includeInvalid: true });
|
||||
private async initializeExtensionSize(): Promise<void> {
|
||||
const extensions = await this.extensionsScannerService.scanAllUserExtensions();
|
||||
await Promise.all(extensions.map(async extension => {
|
||||
// set size if not set before
|
||||
if (!extension.metadata?.size && extension.metadata?.source !== 'resource') {
|
||||
if (isDefined(extension.metadata?.installedTimestamp) && isUndefined(extension.metadata?.size)) {
|
||||
const size = await computeSize(extension.location, this.fileService);
|
||||
await this.extensionsScannerService.updateMetadata(extension.location, { size });
|
||||
}
|
||||
@@ -916,7 +893,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
|
||||
this.logService.debug(`Deleting extensions marked as removed:`, Object.keys(removed));
|
||||
|
||||
const extensions = await this.extensionsScannerService.scanUserExtensions({ includeAllVersions: true, includeInvalid: true }); // All user extensions
|
||||
const extensions = await this.scanAllUserExtensions();
|
||||
const installed: Set<string> = new Set<string>();
|
||||
for (const e of extensions) {
|
||||
if (!removed[ExtensionKey.create(e).toString()]) {
|
||||
@@ -930,14 +907,14 @@ export class ExtensionsScanner extends Disposable {
|
||||
await Promises.settled(byExtension.map(async e => {
|
||||
const latest = e.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version))[0];
|
||||
if (!installed.has(latest.identifier.id.toLowerCase())) {
|
||||
await this.beforeRemovingExtension(await this.toLocalExtension(latest));
|
||||
await this.beforeRemovingExtension(latest);
|
||||
}
|
||||
}));
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
|
||||
const toRemove = extensions.filter(e => e.metadata /* Installed by System */ && removed[ExtensionKey.create(e).toString()]);
|
||||
const toRemove = extensions.filter(e => e.installedTimestamp /* Installed by System */ && removed[ExtensionKey.create(e).toString()]);
|
||||
await Promise.allSettled(toRemove.map(e => this.deleteExtension(e, 'marked for removal')));
|
||||
}
|
||||
|
||||
@@ -1115,7 +1092,7 @@ class InstallExtensionInProfileTask extends AbstractExtensionTask<ILocalExtensio
|
||||
const [removed] = await this.extensionsScanner.unsetExtensionsForRemoval(extensionKey);
|
||||
if (removed) {
|
||||
this.logService.info('Removed the extension from removed list:', extensionKey.id);
|
||||
const userExtensions = await this.extensionsScanner.scanAllUserExtensions(true);
|
||||
const userExtensions = await this.extensionsScanner.scanAllUserExtensions();
|
||||
return userExtensions.find(i => ExtensionKey.create(i).equals(extensionKey));
|
||||
}
|
||||
return undefined;
|
||||
@@ -1155,7 +1132,7 @@ class UninstallExtensionInProfileTask extends AbstractExtensionTask<void> implem
|
||||
}
|
||||
|
||||
protected doRun(token: CancellationToken): Promise<void> {
|
||||
return this.extensionsProfileScannerService.removeExtensionFromProfile(this.extension, this.options.profileLocation);
|
||||
return this.extensionsProfileScannerService.removeExtensionsFromProfile([this.extension.identifier], this.options.profileLocation);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+19
-12
@@ -356,7 +356,7 @@ suite('ExtensionsProfileScannerService', () => {
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target2.args[0][0])).extensions[0].location.toString(), extension.location.toString());
|
||||
});
|
||||
|
||||
test('remove extension trigger events', async () => {
|
||||
test('remove extensions trigger events', async () => {
|
||||
const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation));
|
||||
const target1 = sinon.stub();
|
||||
const target2 = sinon.stub();
|
||||
@@ -364,26 +364,33 @@ suite('ExtensionsProfileScannerService', () => {
|
||||
disposables.add(testObject.onDidRemoveExtensions(target2));
|
||||
|
||||
const extensionsManifest = joinPath(extensionsLocation, 'extensions.json');
|
||||
const extension = aExtension('pub.a', joinPath(ROOT, 'foo', 'pub.a-1.0.0'));
|
||||
await testObject.addExtensionsToProfile([[extension, undefined]], extensionsManifest);
|
||||
await testObject.removeExtensionFromProfile(extension, extensionsManifest);
|
||||
const extension1 = aExtension('pub.a', joinPath(ROOT, 'foo', 'pub.a-1.0.0'));
|
||||
const extension2 = aExtension('pub.b', joinPath(ROOT, 'foo', 'pub.b-1.0.0'));
|
||||
await testObject.addExtensionsToProfile([[extension1, undefined], [extension2, undefined]], extensionsManifest);
|
||||
await testObject.removeExtensionsFromProfile([extension1.identifier, extension2.identifier], extensionsManifest);
|
||||
|
||||
const actual = await testObject.scanProfileExtensions(extensionsManifest);
|
||||
assert.deepStrictEqual(actual.length, 0);
|
||||
|
||||
assert.ok(target1.calledOnce);
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target1.args[0][0])).profileLocation.toString(), extensionsManifest.toString());
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target1.args[0][0])).extensions.length, 1);
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target1.args[0][0])).extensions[0].identifier, extension.identifier);
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target1.args[0][0])).extensions[0].version, extension.manifest.version);
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target1.args[0][0])).extensions[0].location.toString(), extension.location.toString());
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target1.args[0][0])).extensions.length, 2);
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target1.args[0][0])).extensions[0].identifier, extension1.identifier);
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target1.args[0][0])).extensions[0].version, extension1.manifest.version);
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target1.args[0][0])).extensions[0].location.toString(), extension1.location.toString());
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target1.args[0][0])).extensions[1].identifier, extension2.identifier);
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target1.args[0][0])).extensions[1].version, extension2.manifest.version);
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target1.args[0][0])).extensions[1].location.toString(), extension2.location.toString());
|
||||
|
||||
assert.ok(target2.calledOnce);
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target2.args[0][0])).profileLocation.toString(), extensionsManifest.toString());
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target2.args[0][0])).extensions.length, 1);
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target2.args[0][0])).extensions[0].identifier, extension.identifier);
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target2.args[0][0])).extensions[0].version, extension.manifest.version);
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target2.args[0][0])).extensions[0].location.toString(), extension.location.toString());
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target2.args[0][0])).extensions.length, 2);
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target2.args[0][0])).extensions[0].identifier, extension1.identifier);
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target2.args[0][0])).extensions[0].version, extension1.manifest.version);
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target2.args[0][0])).extensions[0].location.toString(), extension1.location.toString());
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target2.args[0][0])).extensions[1].identifier, extension2.identifier);
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target2.args[0][0])).extensions[1].version, extension2.manifest.version);
|
||||
assert.deepStrictEqual((<ProfileExtensionsEvent>(target2.args[0][0])).extensions[1].location.toString(), extension2.location.toString());
|
||||
});
|
||||
|
||||
test('add extension with same id but different version', async () => {
|
||||
|
||||
@@ -105,12 +105,12 @@ suite('NativeExtensionsScanerService Test', () => {
|
||||
assert.deepStrictEqual(actual[0].manifest, manifest);
|
||||
});
|
||||
|
||||
test('scan user extension', async () => {
|
||||
test('scan user extensions', async () => {
|
||||
const manifest: Partial<IScannedExtensionManifest> = anExtensionManifest({ 'name': 'name', 'publisher': 'pub', __metadata: { id: 'uuid' } });
|
||||
const extensionLocation = await aUserExtension(manifest);
|
||||
const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
|
||||
const actual = await testObject.scanUserExtensions({});
|
||||
const actual = await testObject.scanAllUserExtensions();
|
||||
|
||||
assert.deepStrictEqual(actual.length, 1);
|
||||
assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name', uuid: 'uuid' });
|
||||
@@ -175,24 +175,24 @@ suite('NativeExtensionsScanerService Test', () => {
|
||||
assert.deepStrictEqual(actual[1].identifier, { id: 'pub.name2' });
|
||||
});
|
||||
|
||||
test('scan user extension with different versions', async () => {
|
||||
test('scan all user extensions with different versions', async () => {
|
||||
await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.0.1' }));
|
||||
await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.0.2' }));
|
||||
const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
const testObject = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
|
||||
const actual = await testObject.scanUserExtensions({});
|
||||
const actual = await testObject.scanAllUserExtensions({ includeAllVersions: false, includeInvalid: false });
|
||||
|
||||
assert.deepStrictEqual(actual.length, 1);
|
||||
assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' });
|
||||
assert.deepStrictEqual(actual[0].manifest.version, '1.0.2');
|
||||
});
|
||||
|
||||
test('scan user extension include all versions', async () => {
|
||||
test('scan all user extensions include all versions', async () => {
|
||||
await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.0.1' }));
|
||||
await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.0.2' }));
|
||||
const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
const testObject = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
|
||||
const actual = await testObject.scanUserExtensions({ includeAllVersions: true });
|
||||
const actual = await testObject.scanAllUserExtensions();
|
||||
|
||||
assert.deepStrictEqual(actual.length, 2);
|
||||
assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' });
|
||||
@@ -201,35 +201,35 @@ suite('NativeExtensionsScanerService Test', () => {
|
||||
assert.deepStrictEqual(actual[1].manifest.version, '1.0.2');
|
||||
});
|
||||
|
||||
test('scan user extension with different versions and higher version is not compatible', async () => {
|
||||
test('scan all user extensions with different versions and higher version is not compatible', async () => {
|
||||
await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.0.1' }));
|
||||
await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.0.2', engines: { vscode: '^1.67.0' } }));
|
||||
const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
const testObject = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
|
||||
const actual = await testObject.scanUserExtensions({});
|
||||
const actual = await testObject.scanAllUserExtensions({ includeAllVersions: false, includeInvalid: false });
|
||||
|
||||
assert.deepStrictEqual(actual.length, 1);
|
||||
assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' });
|
||||
assert.deepStrictEqual(actual[0].manifest.version, '1.0.1');
|
||||
});
|
||||
|
||||
test('scan exclude invalid extensions', async () => {
|
||||
test('scan all user extensions exclude invalid extensions', async () => {
|
||||
await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' }));
|
||||
await aUserExtension(anExtensionManifest({ 'name': 'name2', 'publisher': 'pub', engines: { vscode: '^1.67.0' } }));
|
||||
const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
const testObject = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
|
||||
const actual = await testObject.scanUserExtensions({});
|
||||
const actual = await testObject.scanAllUserExtensions({ includeAllVersions: false, includeInvalid: false });
|
||||
|
||||
assert.deepStrictEqual(actual.length, 1);
|
||||
assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' });
|
||||
});
|
||||
|
||||
test('scan include invalid extensions', async () => {
|
||||
test('scan all user extensions include invalid extensions', async () => {
|
||||
await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' }));
|
||||
await aUserExtension(anExtensionManifest({ 'name': 'name2', 'publisher': 'pub', engines: { vscode: '^1.67.0' } }));
|
||||
const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
const testObject = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
|
||||
const actual = await testObject.scanUserExtensions({ includeInvalid: true });
|
||||
const actual = await testObject.scanAllUserExtensions({ includeAllVersions: false, includeInvalid: true });
|
||||
|
||||
assert.deepStrictEqual(actual.length, 2);
|
||||
assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' });
|
||||
@@ -257,12 +257,12 @@ suite('NativeExtensionsScanerService Test', () => {
|
||||
assert.deepStrictEqual(actual[0].manifest.version, '1.0.0');
|
||||
});
|
||||
|
||||
test('scan extension with default nls replacements', async () => {
|
||||
test('scan all user extensions with default nls replacements', async () => {
|
||||
const extensionLocation = await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', displayName: '%displayName%' }));
|
||||
await instantiationService.get(IFileService).writeFile(joinPath(extensionLocation, 'package.nls.json'), VSBuffer.fromString(JSON.stringify({ displayName: 'Hello World' })));
|
||||
const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
const testObject = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
|
||||
const actual = await testObject.scanUserExtensions({});
|
||||
const actual = await testObject.scanAllUserExtensions();
|
||||
|
||||
assert.deepStrictEqual(actual.length, 1);
|
||||
assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' });
|
||||
@@ -277,11 +277,11 @@ suite('NativeExtensionsScanerService Test', () => {
|
||||
const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
|
||||
translations = { 'pub.name': nlsLocation.fsPath };
|
||||
const actual = await testObject.scanUserExtensions({ language: 'en' });
|
||||
const actual = await testObject.scanExistingExtension(extensionLocation, ExtensionType.User, { language: 'en' });
|
||||
|
||||
assert.deepStrictEqual(actual.length, 1);
|
||||
assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' });
|
||||
assert.deepStrictEqual(actual[0].manifest.displayName, 'Hello World EN');
|
||||
assert.ok(actual !== null);
|
||||
assert.deepStrictEqual(actual!.identifier, { id: 'pub.name' });
|
||||
assert.deepStrictEqual(actual!.manifest.displayName, 'Hello World EN');
|
||||
});
|
||||
|
||||
test('scan extension falls back to default nls replacements', async () => {
|
||||
@@ -292,11 +292,11 @@ suite('NativeExtensionsScanerService Test', () => {
|
||||
const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
|
||||
translations = { 'pub.name2': nlsLocation.fsPath };
|
||||
const actual = await testObject.scanUserExtensions({ language: 'en' });
|
||||
const actual = await testObject.scanExistingExtension(extensionLocation, ExtensionType.User, { language: 'en' });
|
||||
|
||||
assert.deepStrictEqual(actual.length, 1);
|
||||
assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' });
|
||||
assert.deepStrictEqual(actual[0].manifest.displayName, 'Hello World');
|
||||
assert.ok(actual !== null);
|
||||
assert.deepStrictEqual(actual!.identifier, { id: 'pub.name' });
|
||||
assert.deepStrictEqual(actual!.manifest.displayName, 'Hello World');
|
||||
});
|
||||
|
||||
async function aUserExtension(manifest: Partial<IScannedExtensionManifest>): Promise<URI> {
|
||||
|
||||
@@ -113,6 +113,9 @@ export interface ICommonNativeHostService {
|
||||
*/
|
||||
focusWindow(options?: INativeHostOptions & { force?: boolean }): Promise<void>;
|
||||
|
||||
// Titlebar default style override
|
||||
overrideDefaultTitlebarStyle(style: 'native' | 'custom' | undefined): Promise<void>;
|
||||
|
||||
// Dialogs
|
||||
showMessageBox(options: MessageBoxOptions & INativeHostOptions): Promise<MessageBoxReturnValue>;
|
||||
showSaveDialog(options: SaveDialogOptions & INativeHostOptions): Promise<SaveDialogReturnValue>;
|
||||
@@ -143,10 +146,6 @@ export interface ICommonNativeHostService {
|
||||
hasWSLFeatureInstalled(): Promise<boolean>;
|
||||
|
||||
// Screenshots
|
||||
|
||||
/**
|
||||
* Gets a screenshot of the currently active Electron window.
|
||||
*/
|
||||
getScreenshot(): Promise<ArrayBufferLike | undefined>;
|
||||
|
||||
// Process
|
||||
@@ -199,7 +198,7 @@ export interface ICommonNativeHostService {
|
||||
loadCertificates(): Promise<string[]>;
|
||||
findFreePort(startPort: number, giveUpAfter: number, timeout: number, stride?: number): Promise<number>;
|
||||
|
||||
// Registry (windows only)
|
||||
// Registry (Windows only)
|
||||
windowsGetStringRegKey(hive: 'HKEY_CURRENT_USER' | 'HKEY_LOCAL_MACHINE' | 'HKEY_CLASSES_ROOT' | 'HKEY_USERS' | 'HKEY_CURRENT_CONFIG', path: string, name: string): Promise<string | undefined>;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ import { IProductService } from '../../product/common/productService.js';
|
||||
import { IPartsSplash } from '../../theme/common/themeService.js';
|
||||
import { IThemeMainService } from '../../theme/electron-main/themeMainService.js';
|
||||
import { defaultWindowState, ICodeWindow } from '../../window/electron-main/window.js';
|
||||
import { IColorScheme, IOpenedAuxiliaryWindow, IOpenedMainWindow, IOpenEmptyWindowOptions, IOpenWindowOptions, IPoint, IRectangle, IWindowOpenable } from '../../window/common/window.js';
|
||||
import { IColorScheme, IOpenedAuxiliaryWindow, IOpenedMainWindow, IOpenEmptyWindowOptions, IOpenWindowOptions, IPoint, IRectangle, IWindowOpenable, overrideDefaultTitlebarStyle } from '../../window/common/window.js';
|
||||
import { defaultBrowserWindowOptions, IWindowsMainService, OpenContext } from '../../windows/electron-main/windows.js';
|
||||
import { isWorkspaceIdentifier, toWorkspaceIdentifier } from '../../workspace/common/workspace.js';
|
||||
import { IWorkspacesManagementMainService } from '../../workspaces/electron-main/workspacesManagementMainService.js';
|
||||
@@ -48,6 +48,7 @@ import { IConfigurationService } from '../../configuration/common/configuration.
|
||||
import { IProxyAuthService } from './auth.js';
|
||||
import { AuthInfo, Credentials, IRequestService } from '../../request/common/request.js';
|
||||
import { randomPath } from '../../../base/common/extpath.js';
|
||||
import { IStateService } from '../../state/node/state.js';
|
||||
|
||||
export interface INativeHostMainService extends AddFirstParameterToFunctions<ICommonNativeHostService, Promise<unknown> /* only methods, not events */, number | undefined /* window ID */> { }
|
||||
|
||||
@@ -70,7 +71,8 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IRequestService private readonly requestService: IRequestService,
|
||||
@IProxyAuthService private readonly proxyAuthService: IProxyAuthService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IStateService private readonly stateService: IStateService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -324,6 +326,15 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
|
||||
this.themeMainService.saveWindowSplash(windowId, splash);
|
||||
}
|
||||
|
||||
async overrideDefaultTitlebarStyle(windowId: number | undefined, style: 'native' | 'custom' | undefined): Promise<void> {
|
||||
if (typeof style === 'string') {
|
||||
this.stateService.setItem('window.titleBarStyleOverride', style);
|
||||
} else {
|
||||
this.stateService.removeItem('window.titleBarStyleOverride');
|
||||
}
|
||||
overrideDefaultTitlebarStyle(style);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
@@ -697,6 +708,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
|
||||
async getScreenshot(windowId: number | undefined, options?: INativeHostOptions): Promise<ArrayBufferLike | undefined> {
|
||||
const window = this.windowById(options?.targetWindowId, windowId);
|
||||
const captured = await window?.win?.webContents.capturePage();
|
||||
|
||||
return captured?.toJPEG(95);
|
||||
}
|
||||
|
||||
|
||||
@@ -187,10 +187,23 @@ export const enum CustomTitleBarVisibility {
|
||||
NEVER = 'never',
|
||||
}
|
||||
|
||||
export let titlebarStyleDefaultOverride: TitlebarStyle | undefined = undefined;
|
||||
export function overrideDefaultTitlebarStyle(style: 'native' | 'custom' | undefined): void {
|
||||
switch (style) {
|
||||
case 'native':
|
||||
titlebarStyleDefaultOverride = TitlebarStyle.NATIVE;
|
||||
break;
|
||||
case 'custom':
|
||||
titlebarStyleDefaultOverride = TitlebarStyle.CUSTOM;
|
||||
break;
|
||||
default:
|
||||
titlebarStyleDefaultOverride = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function hasCustomTitlebar(configurationService: IConfigurationService, titleBarStyle?: TitlebarStyle): boolean {
|
||||
// Returns if it possible to have a custom title bar in the curren session
|
||||
// Does not imply that the title bar is visible
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -198,6 +211,7 @@ export function hasNativeTitlebar(configurationService: IConfigurationService, t
|
||||
if (!titleBarStyle) {
|
||||
titleBarStyle = getTitleBarStyle(configurationService);
|
||||
}
|
||||
|
||||
return titleBarStyle === TitlebarStyle.NATIVE;
|
||||
}
|
||||
|
||||
@@ -224,6 +238,10 @@ export function getTitleBarStyle(configurationService: IConfigurationService): T
|
||||
}
|
||||
}
|
||||
|
||||
if (titlebarStyleDefaultOverride) {
|
||||
return titlebarStyleDefaultOverride;
|
||||
}
|
||||
|
||||
return isLinux && product.quality === 'stable' ? TitlebarStyle.NATIVE : TitlebarStyle.CUSTOM; // default to custom on all OS except Linux stable (for now)
|
||||
}
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ export class RemoteExtensionsScannerService implements IRemoteExtensionsScannerS
|
||||
}
|
||||
|
||||
private async _scanBuiltinExtensions(language: string): Promise<IExtensionDescription[]> {
|
||||
const scannedExtensions = await this._extensionsScannerService.scanSystemExtensions({ language, useCache: true });
|
||||
const scannedExtensions = await this._extensionsScannerService.scanSystemExtensions({ language });
|
||||
return scannedExtensions.map(e => toExtensionDescription(e, false));
|
||||
}
|
||||
|
||||
|
||||
@@ -1500,10 +1500,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
return extHostLanguageModelTools.registerTool(extension, name, tool);
|
||||
},
|
||||
invokeTool<T>(name: string, parameters: vscode.LanguageModelToolInvocationOptions<T>, token?: vscode.CancellationToken) {
|
||||
return extHostLanguageModelTools.invokeTool(name, parameters, token);
|
||||
return extHostLanguageModelTools.invokeTool(extension, name, parameters, token);
|
||||
},
|
||||
get tools() {
|
||||
return extHostLanguageModelTools.tools;
|
||||
return extHostLanguageModelTools.getTools(extension);
|
||||
},
|
||||
fileIsIgnored(uri: vscode.Uri, token: vscode.CancellationToken) {
|
||||
return extHostLanguageModels.fileIsIgnored(extension, uri, token);
|
||||
|
||||
@@ -1606,6 +1606,7 @@ export interface SCMHistoryItemDto {
|
||||
readonly message: string;
|
||||
readonly displayId?: string;
|
||||
readonly author?: string;
|
||||
readonly authorEmail?: string;
|
||||
readonly timestamp?: number;
|
||||
readonly statistics?: {
|
||||
readonly files: number;
|
||||
|
||||
@@ -14,6 +14,7 @@ import { IExtensionDescription } from '../../../platform/extensions/common/exten
|
||||
import { IPreparedToolInvocation, isToolInvocationContext, IToolInvocation, IToolInvocationContext, IToolResult } from '../../contrib/chat/common/languageModelToolsService.js';
|
||||
import { ExtHostLanguageModelToolsShape, IMainContext, IToolDataDto, MainContext, MainThreadLanguageModelToolsShape } from './extHost.protocol.js';
|
||||
import * as typeConvert from './extHostTypeConverters.js';
|
||||
import { isProposedApiEnabled } from '../../services/extensions/common/extensions.js';
|
||||
|
||||
|
||||
|
||||
@@ -45,17 +46,22 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape
|
||||
return await fn(input, token);
|
||||
}
|
||||
|
||||
async invokeTool(toolId: string, options: vscode.LanguageModelToolInvocationOptions<any>, token?: CancellationToken): Promise<vscode.LanguageModelToolResult> {
|
||||
async invokeTool(extension: IExtensionDescription, toolId: string, options: vscode.LanguageModelToolInvocationOptions<any>, token?: CancellationToken): Promise<vscode.LanguageModelToolResult> {
|
||||
const callId = generateUuid();
|
||||
if (options.tokenizationOptions) {
|
||||
this._tokenCountFuncs.set(callId, options.tokenizationOptions.countTokens);
|
||||
}
|
||||
|
||||
if (options.toolInvocationToken && !isToolInvocationContext(options.toolInvocationToken)) {
|
||||
throw new Error(`Invalid tool invocation token`);
|
||||
}
|
||||
|
||||
try {
|
||||
if (options.toolInvocationToken && !isToolInvocationContext(options.toolInvocationToken)) {
|
||||
throw new Error(`Invalid tool invocation token`);
|
||||
}
|
||||
|
||||
const tool = this._allTools.get(toolId);
|
||||
if (tool?.tags?.includes('vscode_editing') && !isProposedApiEnabled(extension, 'chatParticipantPrivate')) {
|
||||
throw new Error(`Invalid tool: ${toolId}`);
|
||||
}
|
||||
|
||||
// Making the round trip here because not all tools were necessarily registered in this EH
|
||||
const result = await this._proxy.$invokeTool({
|
||||
toolId,
|
||||
@@ -77,9 +83,16 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape
|
||||
}
|
||||
}
|
||||
|
||||
get tools(): vscode.LanguageModelToolInformation[] {
|
||||
getTools(extension: IExtensionDescription): vscode.LanguageModelToolInformation[] {
|
||||
return Array.from(this._allTools.values())
|
||||
.map(tool => typeConvert.LanguageModelToolDescription.to(tool));
|
||||
.map(tool => typeConvert.LanguageModelToolDescription.to(tool))
|
||||
.filter(tool => {
|
||||
if (tool.tags.includes('vscode_editing')) {
|
||||
return isProposedApiEnabled(extension, 'chatParticipantPrivate');
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
async $invokeTool(dto: IToolInvocation, token: CancellationToken): Promise<IToolResult> {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Disposable, MutableDisposable } from '../../../../base/common/lifecycle
|
||||
import { SimpleIconLabel } from '../../../../base/browser/ui/iconLabel/simpleIconLabel.js';
|
||||
import { ICommandService } from '../../../../platform/commands/common/commands.js';
|
||||
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
|
||||
import { IStatusbarEntry, ShowTooltipCommand, StatusbarEntryKinds } from '../../../services/statusbar/browser/statusbar.js';
|
||||
import { IStatusbarEntry, isTooltipWithCommands, ShowTooltipCommand, StatusbarEntryKinds, TooltipContent } from '../../../services/statusbar/browser/statusbar.js';
|
||||
import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from '../../../../base/common/actions.js';
|
||||
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
|
||||
import { ThemeColor } from '../../../../base/common/themables.js';
|
||||
@@ -24,7 +24,7 @@ import { spinningLoading, syncing } from '../../../../platform/theme/common/icon
|
||||
import { isMarkdownString, markdownStringEqual } from '../../../../base/common/htmlContent.js';
|
||||
import { IHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegate.js';
|
||||
import { Gesture, EventType as TouchEventType } from '../../../../base/browser/touch.js';
|
||||
import type { IManagedHover } from '../../../../base/browser/ui/hover/hover.js';
|
||||
import { IManagedHover, IManagedHoverOptions } from '../../../../base/browser/ui/hover/hover.js';
|
||||
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
|
||||
|
||||
export class StatusbarEntryItem extends Disposable {
|
||||
@@ -116,11 +116,26 @@ export class StatusbarEntryItem extends Disposable {
|
||||
|
||||
// Update: Hover
|
||||
if (!this.entry || !this.isEqualTooltip(this.entry, entry)) {
|
||||
const hoverContents = isMarkdownString(entry.tooltip) ? { markdown: entry.tooltip, markdownNotSupportedFallback: undefined } : entry.tooltip;
|
||||
if (this.hover) {
|
||||
this.hover.update(hoverContents);
|
||||
let hoverOptions: IManagedHoverOptions | undefined;
|
||||
let hoverTooltip: TooltipContent | undefined;
|
||||
if (isTooltipWithCommands(entry.tooltip)) {
|
||||
hoverTooltip = entry.tooltip.content;
|
||||
hoverOptions = {
|
||||
actions: entry.tooltip.commands.map(command => ({
|
||||
commandId: command.id,
|
||||
label: command.title,
|
||||
run: () => this.executeCommand(command)
|
||||
}))
|
||||
};
|
||||
} else {
|
||||
this.hover = this._register(this.hoverService.setupManagedHover(this.hoverDelegate, this.container, hoverContents));
|
||||
hoverTooltip = entry.tooltip;
|
||||
}
|
||||
|
||||
const hoverContents = isMarkdownString(hoverTooltip) ? { markdown: hoverTooltip, markdownNotSupportedFallback: undefined } : hoverTooltip;
|
||||
if (this.hover) {
|
||||
this.hover.update(hoverContents, hoverOptions);
|
||||
} else {
|
||||
this.hover = this._register(this.hoverService.setupManagedHover(this.hoverDelegate, this.container, hoverContents, hoverOptions));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,13 +75,22 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel
|
||||
return true;
|
||||
}
|
||||
|
||||
private _hasLanguageSetExplicitly: boolean = false;
|
||||
get hasLanguageSetExplicitly(): boolean { return this._hasLanguageSetExplicitly; }
|
||||
private _blockLanguageChangeListener = false;
|
||||
private _languageChangeSource: 'user' | 'api' | undefined = undefined;
|
||||
get languageChangeSource() { return this._languageChangeSource; }
|
||||
get hasLanguageSetExplicitly() {
|
||||
// This is technically not 100% correct, because 'api' can also be
|
||||
// set as source if a model is resolved as text first and then
|
||||
// transitions into the resolved language. But to preserve the current
|
||||
// behaviour, we do not change this property. Rather, `languageChangeSource`
|
||||
// can be used to get more fine grained information.
|
||||
return typeof this._languageChangeSource === 'string';
|
||||
}
|
||||
|
||||
setLanguageId(languageId: string, source?: string): void {
|
||||
|
||||
// Remember that an explicit language was set
|
||||
this._hasLanguageSetExplicitly = true;
|
||||
this._languageChangeSource = 'user';
|
||||
|
||||
this.setLanguageIdInternal(languageId, source);
|
||||
}
|
||||
@@ -95,18 +104,26 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel
|
||||
return;
|
||||
}
|
||||
|
||||
this.textEditorModel.setLanguage(this.languageService.createById(languageId), source);
|
||||
this._blockLanguageChangeListener = true;
|
||||
try {
|
||||
this.textEditorModel.setLanguage(this.languageService.createById(languageId), source);
|
||||
} finally {
|
||||
this._blockLanguageChangeListener = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected installModelListeners(model: ITextModel): void {
|
||||
|
||||
// Setup listener for lower level language changes
|
||||
const disposable = this._register(model.onDidChangeLanguage((e) => {
|
||||
if (e.source === LanguageDetectionLanguageEventSource) {
|
||||
const disposable = this._register(model.onDidChangeLanguage(e => {
|
||||
if (
|
||||
e.source === LanguageDetectionLanguageEventSource ||
|
||||
this._blockLanguageChangeListener
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._hasLanguageSetExplicitly = true;
|
||||
this._languageChangeSource = 'api';
|
||||
disposable.dispose();
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
import { Codicon } from '../../../../../base/common/codicons.js';
|
||||
import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js';
|
||||
import { URI } from '../../../../../base/common/uri.js';
|
||||
import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';
|
||||
import { localize, localize2 } from '../../../../../nls.js';
|
||||
import { Action2, MenuId, MenuRegistry, registerAction2 } from '../../../../../platform/actions/common/actions.js';
|
||||
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
|
||||
import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js';
|
||||
import { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js';
|
||||
import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';
|
||||
import { IViewsService } from '../../../../services/views/common/viewsService.js';
|
||||
import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js';
|
||||
@@ -75,6 +75,52 @@ export class ChatSubmitAction extends SubmitAction {
|
||||
}
|
||||
}
|
||||
|
||||
export const ToggleAgentModeActionId = 'workbench.action.chat.toggleAgentMode';
|
||||
export class ToggleAgentModeAction extends Action2 {
|
||||
static readonly ID = ToggleAgentModeActionId;
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: ToggleAgentModeAction.ID,
|
||||
title: localize2('interactive.toggleAgent.label', "Toggle Agent Mode"),
|
||||
f1: true,
|
||||
category: CHAT_CATEGORY,
|
||||
precondition: ContextKeyExpr.and(
|
||||
ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession),
|
||||
ChatContextKeys.Editing.hasToolsAgent),
|
||||
icon: Codicon.edit,
|
||||
toggled: {
|
||||
condition: ChatContextKeys.Editing.agentMode,
|
||||
icon: Codicon.tools,
|
||||
tooltip: localize('agentEnabled', "Agent Mode Enabled"),
|
||||
},
|
||||
tooltip: localize('agentDisabled', "Agent Mode Disabled"),
|
||||
keybinding: {
|
||||
when: ContextKeyExpr.and(
|
||||
ChatContextKeys.inChatInput,
|
||||
ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession)),
|
||||
primary: KeyMod.CtrlCmd | KeyCode.Period,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menu: [
|
||||
{
|
||||
id: MenuId.ChatExecute,
|
||||
order: 1,
|
||||
when: ContextKeyExpr.and(
|
||||
ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession),
|
||||
ChatContextKeys.Editing.hasToolsAgent),
|
||||
group: 'navigation',
|
||||
},
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
override run(accessor: ServicesAccessor, ...args: any[]): void {
|
||||
const agentService = accessor.get(IChatAgentService);
|
||||
agentService.toggleToolsAgentMode();
|
||||
}
|
||||
}
|
||||
|
||||
export class ChatEditingSessionSubmitAction extends SubmitAction {
|
||||
static readonly ID = 'workbench.action.edits.submit';
|
||||
|
||||
@@ -388,4 +434,5 @@ export function registerChatExecuteActions() {
|
||||
registerAction2(SendToNewChatAction);
|
||||
registerAction2(ChatSubmitSecondaryAgentAction);
|
||||
registerAction2(SendToChatEditingAction);
|
||||
registerAction2(ToggleAgentModeAction);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { IDefaultChatAgent } from '../../../../../base/common/product.js';
|
||||
import { IViewDescriptorService } from '../../../../common/views.js';
|
||||
import { IWorkbenchLayoutService } from '../../../../services/layout/browser/layoutService.js';
|
||||
import { ensureSideBarChatViewSize } from '../chat.js';
|
||||
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
|
||||
|
||||
|
||||
export class ChatGettingStartedContribution extends Disposable implements IWorkbenchContribution {
|
||||
@@ -32,6 +33,7 @@ export class ChatGettingStartedContribution extends Disposable implements IWorkb
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,
|
||||
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -60,14 +62,25 @@ export class ChatGettingStartedContribution extends Disposable implements IWorkb
|
||||
if (ExtensionIdentifier.equals(defaultChatAgent.extensionId, ext.value)) {
|
||||
const extensionStatus = this.extensionService.getExtensionsStatus();
|
||||
if (extensionStatus[ext.value].activationTimes && this.recentlyInstalled) {
|
||||
await this.commandService.executeCommand(CHAT_OPEN_ACTION_ID);
|
||||
ensureSideBarChatViewSize(400, this.viewDescriptorService, this.layoutService);
|
||||
this.storageService.store(ChatGettingStartedContribution.hideWelcomeView, true, StorageScope.APPLICATION, StorageTarget.MACHINE);
|
||||
this.recentlyInstalled = false;
|
||||
this.onDidInstallChat();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private async onDidInstallChat() {
|
||||
|
||||
// Enable chat command center if previously disabled
|
||||
this.configurationService.updateValue('chat.commandCenter.enabled', true);
|
||||
|
||||
// Open and configure chat view
|
||||
await this.commandService.executeCommand(CHAT_OPEN_ACTION_ID);
|
||||
ensureSideBarChatViewSize(400, this.viewDescriptorService, this.layoutService);
|
||||
|
||||
// Only do this once
|
||||
this.storageService.store(ChatGettingStartedContribution.hideWelcomeView, true, StorageScope.APPLICATION, StorageTarget.MACHINE);
|
||||
this.recentlyInstalled = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import { IEditorGroupsService } from '../../../../services/editor/common/editorG
|
||||
import { ACTIVE_GROUP, AUX_WINDOW_GROUP, IEditorService } from '../../../../services/editor/common/editorService.js';
|
||||
import { IViewsService } from '../../../../services/views/common/viewsService.js';
|
||||
import { isChatViewTitleActionContext } from '../../common/chatActions.js';
|
||||
import { ChatAgentLocation } from '../../common/chatAgents.js';
|
||||
|
||||
enum MoveToNewLocation {
|
||||
Editor = 'Editor',
|
||||
@@ -99,7 +100,7 @@ async function executeMoveToAction(accessor: ServicesAccessor, moveTo: MoveToNew
|
||||
|
||||
const widget = (_sessionId ? widgetService.getWidgetBySessionId(_sessionId) : undefined)
|
||||
?? widgetService.lastFocusedWidget;
|
||||
if (!widget || !('viewId' in widget.viewContext)) {
|
||||
if (!widget || widget.location !== ChatAgentLocation.Panel) {
|
||||
await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { pinned: true } }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ import { Extensions, IConfigurationMigrationRegistry } from '../../../common/con
|
||||
import { ChatEditorOverlayController } from './chatEditorOverlay.js';
|
||||
import { ChatRelatedFilesContribution } from './contrib/chatInputRelatedFilesContrib.js';
|
||||
import { ChatQuotasService, ChatQuotasStatusBarEntry, IChatQuotasService } from './chatQuotasService.js';
|
||||
import { BuiltinToolsContribution } from './tools/tools.js';
|
||||
import { ChatSetupContribution } from './chatSetup.js';
|
||||
|
||||
// Register configuration
|
||||
@@ -319,6 +320,7 @@ registerWorkbenchContribution2(ChatViewsWelcomeHandler.ID, ChatViewsWelcomeHandl
|
||||
registerWorkbenchContribution2(ChatGettingStartedContribution.ID, ChatGettingStartedContribution, WorkbenchPhase.Eventually);
|
||||
registerWorkbenchContribution2(ChatSetupContribution.ID, ChatSetupContribution, WorkbenchPhase.BlockRestore);
|
||||
registerWorkbenchContribution2(ChatQuotasStatusBarEntry.ID, ChatQuotasStatusBarEntry, WorkbenchPhase.Eventually);
|
||||
registerWorkbenchContribution2(BuiltinToolsContribution.ID, BuiltinToolsContribution, WorkbenchPhase.Eventually);
|
||||
|
||||
registerChatActions();
|
||||
registerChatCopyActions();
|
||||
|
||||
@@ -76,17 +76,22 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP
|
||||
|
||||
// We release editors in order so that it's more likely that the same editor will be assigned if this element is re-rendered right away, like it often is during progressive rendering
|
||||
const orderedDisposablesList: IDisposable[] = [];
|
||||
let codeBlockIndex = codeBlockStartIndex;
|
||||
|
||||
// Need to track the index of the codeblock within the response so it can have a unique ID,
|
||||
// and within this part to find it within the codeblocks array
|
||||
let globalCodeBlockIndexStart = codeBlockStartIndex;
|
||||
let thisPartCodeBlockIndexStart = 0;
|
||||
const result = this._register(renderer.render(markdown.content, {
|
||||
fillInIncompleteTokens,
|
||||
codeBlockRendererSync: (languageId, text, raw) => {
|
||||
const isCodeBlockComplete = !isResponseVM(context.element) || context.element.isComplete || !raw || raw?.endsWith('```');
|
||||
const isCodeBlockComplete = !isResponseVM(context.element) || context.element.isComplete || !raw || raw?.trim().endsWith('```');
|
||||
if ((!text || (text.startsWith('<vscode_codeblock_uri>') && !text.includes('\n'))) && !isCodeBlockComplete && rendererOptions.renderCodeBlockPills) {
|
||||
const hideEmptyCodeblock = $('div');
|
||||
hideEmptyCodeblock.style.display = 'none';
|
||||
return hideEmptyCodeblock;
|
||||
}
|
||||
const index = codeBlockIndex++;
|
||||
const globalIndex = globalCodeBlockIndexStart++;
|
||||
const thisPartIndex = thisPartCodeBlockIndexStart++;
|
||||
let textModel: Promise<ITextModel>;
|
||||
let range: Range | undefined;
|
||||
let vulns: readonly IMarkdownVulnerability[] | undefined;
|
||||
@@ -101,15 +106,15 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP
|
||||
}
|
||||
} else {
|
||||
const sessionId = isResponseVM(element) || isRequestVM(element) ? element.sessionId : '';
|
||||
const modelEntry = this.codeBlockModelCollection.getOrCreate(sessionId, element, index);
|
||||
const fastUpdateModelEntry = this.codeBlockModelCollection.updateSync(sessionId, element, index, { text, languageId, isComplete: isCodeBlockComplete });
|
||||
const modelEntry = this.codeBlockModelCollection.getOrCreate(sessionId, element, globalIndex);
|
||||
const fastUpdateModelEntry = this.codeBlockModelCollection.updateSync(sessionId, element, globalIndex, { text, languageId, isComplete: isCodeBlockComplete });
|
||||
vulns = modelEntry.vulns;
|
||||
codemapperUri = fastUpdateModelEntry.codemapperUri;
|
||||
textModel = modelEntry.model;
|
||||
}
|
||||
|
||||
const hideToolbar = isResponseVM(element) && element.errorDetails?.responseIsFiltered;
|
||||
const codeBlockInfo: ICodeBlockData = { languageId, textModel, codeBlockIndex: index, element, range, hideToolbar, parentContextKeyService: contextKeyService, vulns, codemapperUri };
|
||||
const codeBlockInfo: ICodeBlockData = { languageId, textModel, codeBlockIndex: globalIndex, codeBlockPartIndex: thisPartIndex, element, range, hideToolbar, parentContextKeyService: contextKeyService, vulns, codemapperUri };
|
||||
|
||||
if (!rendererOptions.renderCodeBlockPills || element.isCompleteAddedRequest || !codemapperUri) {
|
||||
const ref = this.renderCodeBlock(codeBlockInfo, text, isCodeBlockComplete, currentWidth);
|
||||
@@ -122,7 +127,7 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP
|
||||
const ownerMarkdownPartId = this.id;
|
||||
const info: IChatCodeBlockInfo = new class {
|
||||
readonly ownerMarkdownPartId = ownerMarkdownPartId;
|
||||
readonly codeBlockIndex = index;
|
||||
readonly codeBlockIndex = globalIndex;
|
||||
readonly element = element;
|
||||
readonly isStreaming = !rendererOptions.renderCodeBlockPills;
|
||||
codemapperUri = undefined; // will be set async
|
||||
@@ -149,7 +154,7 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP
|
||||
// TODO@joyceerhl: remove this code when we change the codeblockUri API to make the URI available synchronously
|
||||
this.codeBlockModelCollection.update(codeBlockInfo.element.sessionId, codeBlockInfo.element, codeBlockInfo.codeBlockIndex, { text, languageId: codeBlockInfo.languageId, isComplete: isCodeBlockComplete }).then((e) => {
|
||||
// Update the existing object's codemapperUri
|
||||
this.codeblocks[codeBlockInfo.codeBlockIndex].codemapperUri = e.codemapperUri;
|
||||
this.codeblocks[codeBlockInfo.codeBlockPartIndex].codemapperUri = e.codemapperUri;
|
||||
this._onDidChangeHeight.fire();
|
||||
});
|
||||
}
|
||||
@@ -157,7 +162,7 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP
|
||||
const ownerMarkdownPartId = this.id;
|
||||
const info: IChatCodeBlockInfo = new class {
|
||||
readonly ownerMarkdownPartId = ownerMarkdownPartId;
|
||||
readonly codeBlockIndex = index;
|
||||
readonly codeBlockIndex = globalIndex;
|
||||
readonly element = element;
|
||||
readonly isStreaming = !isCodeBlockComplete;
|
||||
readonly codemapperUri = codemapperUri;
|
||||
@@ -205,7 +210,7 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP
|
||||
if (isResponseVM(data.element)) {
|
||||
this.codeBlockModelCollection.update(data.element.sessionId, data.element, data.codeBlockIndex, { text, languageId: data.languageId, isComplete }).then((e) => {
|
||||
// Update the existing object's codemapperUri
|
||||
this.codeblocks[data.codeBlockIndex].codemapperUri = e.codemapperUri;
|
||||
this.codeblocks[data.codeBlockPartIndex].codemapperUri = e.codemapperUri;
|
||||
this._onDidChangeHeight.fire();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { EditOperation, ISingleEditOperation } from '../../../../../editor/commo
|
||||
import { OffsetEdit } from '../../../../../editor/common/core/offsetEdit.js';
|
||||
import { Range } from '../../../../../editor/common/core/range.js';
|
||||
import { IDocumentDiff, nullDocumentDiff } from '../../../../../editor/common/diff/documentDiffProvider.js';
|
||||
import { DetailedLineRangeMapping } from '../../../../../editor/common/diff/rangeMapping.js';
|
||||
import { TextEdit } from '../../../../../editor/common/languages.js';
|
||||
import { ILanguageService } from '../../../../../editor/common/languages/language.js';
|
||||
import { IModelDeltaDecoration, ITextModel, OverviewRulerLane } from '../../../../../editor/common/model.js';
|
||||
@@ -343,6 +344,41 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie
|
||||
});
|
||||
}
|
||||
|
||||
async acceptHunk(change: DetailedLineRangeMapping): Promise<boolean> {
|
||||
if (!this._diffInfo.get().changes.includes(change)) {
|
||||
// diffInfo should have model version ids and check them (instead of the caller doing that)
|
||||
return false;
|
||||
}
|
||||
const edits: ISingleEditOperation[] = [];
|
||||
for (const edit of change.innerChanges ?? []) {
|
||||
const newText = this.modifiedModel.getValueInRange(edit.modifiedRange);
|
||||
edits.push(EditOperation.replace(edit.originalRange, newText));
|
||||
}
|
||||
this.docSnapshot.pushEditOperations(null, edits, _ => null);
|
||||
await this._updateDiffInfoSeq();
|
||||
if (this.diffInfo.get().identical) {
|
||||
this._stateObs.set(WorkingSetEntryState.Accepted, undefined);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async rejectHunk(change: DetailedLineRangeMapping): Promise<boolean> {
|
||||
if (!this._diffInfo.get().changes.includes(change)) {
|
||||
return false;
|
||||
}
|
||||
const edits: ISingleEditOperation[] = [];
|
||||
for (const edit of change.innerChanges ?? []) {
|
||||
const newText = this.docSnapshot.getValueInRange(edit.originalRange);
|
||||
edits.push(EditOperation.replace(edit.modifiedRange, newText));
|
||||
}
|
||||
this.doc.pushEditOperations(null, edits, _ => null);
|
||||
await this._updateDiffInfoSeq();
|
||||
if (this.diffInfo.get().identical) {
|
||||
this._stateObs.set(WorkingSetEntryState.Rejected, undefined);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private _applyEdits(edits: ISingleEditOperation[]) {
|
||||
// make the actual edit
|
||||
this._isEditFromUs = true;
|
||||
@@ -358,13 +394,14 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie
|
||||
}
|
||||
}
|
||||
|
||||
private _updateDiffInfoSeq() {
|
||||
private async _updateDiffInfoSeq() {
|
||||
const myDiffOperationId = ++this._diffOperationIds;
|
||||
Promise.resolve(this._diffOperation).then(() => {
|
||||
if (this._diffOperationIds === myDiffOperationId) {
|
||||
this._diffOperation = this._updateDiffInfo();
|
||||
}
|
||||
});
|
||||
await Promise.resolve(this._diffOperation);
|
||||
if (this._diffOperationIds === myDiffOperationId) {
|
||||
const thisDiffOperation = this._updateDiffInfo();
|
||||
this._diffOperation = thisDiffOperation;
|
||||
await thisDiffOperation;
|
||||
}
|
||||
}
|
||||
|
||||
private async _updateDiffInfo(): Promise<void> {
|
||||
|
||||
@@ -217,6 +217,33 @@ class UndoHunkAction extends EditorAction2 {
|
||||
}
|
||||
}
|
||||
|
||||
class AcceptHunkAction extends EditorAction2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'chatEditor.action.acceptHunk',
|
||||
title: localize2('acceptHunk', 'Accept this Change'),
|
||||
shortTitle: localize2('acceptHunk2', 'Accept'),
|
||||
category: CHAT_CATEGORY,
|
||||
precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey),
|
||||
icon: Codicon.check,
|
||||
f1: true,
|
||||
keybinding: {
|
||||
when: EditorContextKeys.focus,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter
|
||||
},
|
||||
menu: {
|
||||
id: MenuId.ChatEditingEditorHunk,
|
||||
order: 0
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
override runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) {
|
||||
ChatEditorController.get(editor)?.acceptNearestChange(args[0]);
|
||||
}
|
||||
}
|
||||
|
||||
class OpenDiffFromHunkAction extends EditorAction2 {
|
||||
constructor() {
|
||||
super({
|
||||
@@ -243,5 +270,6 @@ export function registerChatEditorActions() {
|
||||
registerAction2(AcceptAction);
|
||||
registerAction2(RejectAction);
|
||||
registerAction2(UndoHunkAction);
|
||||
registerAction2(AcceptHunkAction);
|
||||
registerAction2(OpenDiffFromHunkAction);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, IOverlayWidgetPosi
|
||||
import { LineSource, renderLines, RenderOptions } from '../../../../editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js';
|
||||
import { diffAddDecoration, diffDeleteDecoration, diffWholeLineAddDecoration } from '../../../../editor/browser/widget/diffEditor/registrations.contribution.js';
|
||||
import { EditorOption, IEditorStickyScrollOptions } from '../../../../editor/common/config/editorOptions.js';
|
||||
import { EditOperation, ISingleEditOperation } from '../../../../editor/common/core/editOperation.js';
|
||||
import { Range } from '../../../../editor/common/core/range.js';
|
||||
import { IDocumentDiff } from '../../../../editor/common/diff/documentDiffProvider.js';
|
||||
import { IEditorContribution, ScrollType } from '../../../../editor/common/editorCommon.js';
|
||||
@@ -31,6 +30,7 @@ import { Selection } from '../../../../editor/common/core/selection.js';
|
||||
import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js';
|
||||
import { observableCodeEditor } from '../../../../editor/browser/observableCodeEditor.js';
|
||||
import { minimapGutterAddedBackground, minimapGutterDeletedBackground, minimapGutterModifiedBackground, overviewRulerAddedForeground, overviewRulerDeletedForeground, overviewRulerModifiedForeground } from '../../scm/common/quickDiff.js';
|
||||
import { DetailedLineRangeMapping } from '../../../../editor/common/diff/rangeMapping.js';
|
||||
|
||||
export const ctxHasEditorModification = new RawContextKey<boolean>('chat.hasEditorModifications', undefined, localize('chat.hasEditorModifications', "The current editor contains chat modifications"));
|
||||
export const ctxHasRequestInProgress = new RawContextKey<boolean>('chat.ctxHasRequestInProgress', false, localize('chat.ctxHasRequestInProgress', "The current editor shows a file from an edit session which is still in progress"));
|
||||
@@ -328,13 +328,7 @@ export class ChatEditorController extends Disposable implements IEditorContribut
|
||||
}
|
||||
|
||||
// Add content widget for each diff change
|
||||
const undoEdits: ISingleEditOperation[] = [];
|
||||
for (const c of diffEntry.innerChanges ?? []) {
|
||||
const oldText = originalModel.getValueInRange(c.originalRange);
|
||||
undoEdits.push(EditOperation.replace(c.modifiedRange, oldText));
|
||||
}
|
||||
|
||||
const widget = this._instantiationService.createInstance(DiffHunkWidget, entry, undoEdits, this._editor.getModel()!.getVersionId(), this._editor, isCreatedContent ? 0 : result.heightInLines);
|
||||
const widget = this._instantiationService.createInstance(DiffHunkWidget, entry, diffEntry, this._editor.getModel()!.getVersionId(), this._editor, isCreatedContent ? 0 : result.heightInLines);
|
||||
widget.layout(diffEntry.modified.startLineNumber);
|
||||
|
||||
this._diffHunkWidgets.push(widget);
|
||||
@@ -492,28 +486,41 @@ export class ChatEditorController extends Disposable implements IEditorContribut
|
||||
return true;
|
||||
}
|
||||
|
||||
undoNearestChange(closestWidget: DiffHunkWidget | undefined): void {
|
||||
private _findClosestWidget(): DiffHunkWidget | undefined {
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
const lineRelativeTop = this._editor.getTopForLineNumber(this._editor.getPosition().lineNumber) - this._editor.getScrollTop();
|
||||
let closestWidget: DiffHunkWidget | undefined;
|
||||
let closestDistance = Number.MAX_VALUE;
|
||||
|
||||
if (!(closestWidget instanceof DiffHunkWidget)) {
|
||||
for (const widget of this._diffHunkWidgets) {
|
||||
const widgetTop = (<IOverlayWidgetPositionCoordinates | undefined>widget.getPosition()?.preference)?.top;
|
||||
if (widgetTop !== undefined) {
|
||||
const distance = Math.abs(widgetTop - lineRelativeTop);
|
||||
if (distance < closestDistance) {
|
||||
closestDistance = distance;
|
||||
closestWidget = widget;
|
||||
}
|
||||
for (const widget of this._diffHunkWidgets) {
|
||||
const widgetTop = (<IOverlayWidgetPositionCoordinates | undefined>widget.getPosition()?.preference)?.top;
|
||||
if (widgetTop !== undefined) {
|
||||
const distance = Math.abs(widgetTop - lineRelativeTop);
|
||||
if (distance < closestDistance) {
|
||||
closestDistance = distance;
|
||||
closestWidget = widget;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return closestWidget;
|
||||
}
|
||||
|
||||
undoNearestChange(closestWidget: DiffHunkWidget | undefined): void {
|
||||
closestWidget = closestWidget ?? this._findClosestWidget();
|
||||
if (closestWidget instanceof DiffHunkWidget) {
|
||||
closestWidget.undo();
|
||||
closestWidget.reject();
|
||||
this.revealNext();
|
||||
}
|
||||
}
|
||||
|
||||
acceptNearestChange(closestWidget: DiffHunkWidget | undefined): void {
|
||||
closestWidget = closestWidget ?? this._findClosestWidget();
|
||||
if (closestWidget instanceof DiffHunkWidget) {
|
||||
closestWidget.accept();
|
||||
this.revealNext();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -570,7 +577,7 @@ class DiffHunkWidget implements IOverlayWidget {
|
||||
|
||||
constructor(
|
||||
readonly entry: IModifiedFileEntry,
|
||||
private readonly _undoEdits: ISingleEditOperation[],
|
||||
private readonly _change: DetailedLineRangeMapping,
|
||||
private readonly _versionId: number,
|
||||
private readonly _editor: ICodeEditor,
|
||||
private readonly _lineDelta: number,
|
||||
@@ -641,9 +648,15 @@ class DiffHunkWidget implements IOverlayWidget {
|
||||
|
||||
// ---
|
||||
|
||||
undo() {
|
||||
reject(): void {
|
||||
if (this._versionId === this._editor.getModel()?.getVersionId()) {
|
||||
this._editor.executeEdits('chatEdits.undo', this._undoEdits);
|
||||
this.entry.rejectHunk(this._change);
|
||||
}
|
||||
}
|
||||
|
||||
accept(): void {
|
||||
if (this._versionId === this._editor.getModel()?.getVersionId()) {
|
||||
this.entry.acceptHunk(this._change);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,6 +310,10 @@ export class ChatEditorOverlayController implements IEditorContribution {
|
||||
}
|
||||
|
||||
const entry = entries[idx];
|
||||
if (entry.state.read(r) === WorkingSetEntryState.Accepted || entry.state.read(r) === WorkingSetEntryState.Rejected) {
|
||||
widget.hide();
|
||||
return;
|
||||
}
|
||||
widget.show(session, entry, entries[(idx + 1) % entries.length]);
|
||||
|
||||
}));
|
||||
|
||||
@@ -199,7 +199,7 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (providerDescriptor.isDefault && !isProposedApiEnabled(extension.description, 'defaultChatParticipant')) {
|
||||
if ((providerDescriptor.isDefault || providerDescriptor.isAgent) && !isProposedApiEnabled(extension.description, 'defaultChatParticipant')) {
|
||||
this.logService.error(`Extension '${extension.description.identifier.value}' CANNOT use API proposal: defaultChatParticipant.`);
|
||||
continue;
|
||||
}
|
||||
@@ -245,6 +245,7 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
|
||||
name: providerDescriptor.name,
|
||||
fullName: providerDescriptor.fullName,
|
||||
isDefault: providerDescriptor.isDefault,
|
||||
isToolsAgent: providerDescriptor.isAgent,
|
||||
locations: isNonEmptyArray(providerDescriptor.locations) ?
|
||||
providerDescriptor.locations.map(ChatAgentLocation.fromRaw) :
|
||||
[ChatAgentLocation.Panel],
|
||||
|
||||
@@ -123,7 +123,7 @@ export class ChatQuotasService extends Disposable implements IChatQuotasService
|
||||
message = localize('chatAndCompletionsQuotaExceeded', "You've reached the limit of the Copilot Free plan. These limits will reset on {0}.", dateFormatter.format(that.quotas.quotaResetDate));
|
||||
}
|
||||
|
||||
const upgradeToPro = localize('upgradeToPro', "Here's what you can expect when upgrading to Copilot Pro:\n- Unlimited code completions\n- Unlimited chat messages\n- 30-day free trial");
|
||||
const upgradeToPro = localize('upgradeToPro', "Upgrade to Copilot Pro (your first 30 days are free) for:\n- Unlimited code completions\n- Unlimited chat messages\n- Access to additional models");
|
||||
|
||||
await dialogService.prompt({
|
||||
type: 'none',
|
||||
|
||||
@@ -60,6 +60,7 @@ import { IHostService } from '../../../services/host/browser/host.js';
|
||||
import Severity from '../../../../base/common/severity.js';
|
||||
import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js';
|
||||
import { isWeb } from '../../../../base/common/platform.js';
|
||||
import { ExtensionUrlHandlerOverrideRegistry } from '../../../services/extensions/browser/extensionUrlHandler.js';
|
||||
|
||||
const defaultChat = {
|
||||
extensionId: product.defaultChatAgent?.extensionId ?? '',
|
||||
@@ -109,7 +110,9 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr
|
||||
constructor(
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
|
||||
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -122,6 +125,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr
|
||||
|
||||
this.registerChatWelcome();
|
||||
this.registerActions();
|
||||
this.registerUrlLinkHandler();
|
||||
}
|
||||
|
||||
private registerChatWelcome(): void {
|
||||
@@ -292,6 +296,18 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr
|
||||
registerAction2(ChatSetupHideAction);
|
||||
registerAction2(UpgradePlanAction);
|
||||
}
|
||||
|
||||
private registerUrlLinkHandler(): void {
|
||||
this._register(ExtensionUrlHandlerOverrideRegistry.registerHandler(URI.parse(`${this.productService.urlProtocol}://${defaultChat.chatExtensionId}`), {
|
||||
handleURL: async () => {
|
||||
this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: TRIGGER_SETUP_COMMAND_ID, from: 'url' });
|
||||
|
||||
await this.commandService.executeCommand(TRIGGER_SETUP_COMMAND_ID);
|
||||
|
||||
return true;
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@@ -302,6 +318,7 @@ type EntitlementClassification = {
|
||||
entitlement: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Flag indicating the chat entitlement state' };
|
||||
quotaChat: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of chat completions available to the user' };
|
||||
quotaCompletions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of chat completions available to the user' };
|
||||
quotaResetDate: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The date the quota will reset' };
|
||||
owner: 'bpasero';
|
||||
comment: 'Reporting chat setup entitlements';
|
||||
};
|
||||
@@ -310,6 +327,7 @@ type EntitlementEvent = {
|
||||
entitlement: ChatEntitlement;
|
||||
quotaChat: number | undefined;
|
||||
quotaCompletions: number | undefined;
|
||||
quotaResetDate: string | undefined;
|
||||
};
|
||||
|
||||
interface IEntitlementsResponse {
|
||||
@@ -520,7 +538,8 @@ class ChatSetupRequests extends Disposable {
|
||||
this.telemetryService.publicLog2<EntitlementEvent, EntitlementClassification>('chatInstallEntitlement', {
|
||||
entitlement: entitlements.entitlement,
|
||||
quotaChat: entitlementsResponse.limited_user_quotas?.chat,
|
||||
quotaCompletions: entitlementsResponse.limited_user_quotas?.completions
|
||||
quotaCompletions: entitlementsResponse.limited_user_quotas?.completions,
|
||||
quotaResetDate: entitlementsResponse.limited_user_reset_date
|
||||
});
|
||||
|
||||
return entitlements;
|
||||
|
||||
@@ -74,6 +74,7 @@ const $ = dom.$;
|
||||
|
||||
export interface ICodeBlockData {
|
||||
readonly codeBlockIndex: number;
|
||||
readonly codeBlockPartIndex: number;
|
||||
readonly element: unknown;
|
||||
|
||||
readonly textModel: Promise<ITextModel>;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { coalesce } from '../../../../../base/common/arrays.js';
|
||||
import { raceTimeout } from '../../../../../base/common/async.js';
|
||||
import { CancellationToken } from '../../../../../base/common/cancellation.js';
|
||||
import { isPatternInWord } from '../../../../../base/common/filters.js';
|
||||
@@ -258,7 +259,11 @@ class AgentCompletions extends Disposable {
|
||||
|
||||
return {
|
||||
suggestions: justAgents.concat(
|
||||
agents.flatMap(agent => agent.slashCommands.map((c, i) => {
|
||||
coalesce(agents.flatMap(agent => agent.slashCommands.map((c, i) => {
|
||||
if (agent.isDefault && this.chatAgentService.getDefaultAgent(widget.location)?.id !== agent.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { label: agentLabel, isDupe } = this.getAgentCompletionDetails(agent);
|
||||
const label = `${agentLabel} ${chatSubcommandLeader}${c.name}`;
|
||||
const item: CompletionItem = {
|
||||
@@ -284,7 +289,7 @@ class AgentCompletions extends Disposable {
|
||||
}
|
||||
|
||||
return item;
|
||||
})))
|
||||
}))))
|
||||
};
|
||||
}
|
||||
}));
|
||||
@@ -313,7 +318,11 @@ class AgentCompletions extends Disposable {
|
||||
.filter(a => a.locations.includes(widget.location));
|
||||
|
||||
return {
|
||||
suggestions: agents.flatMap(agent => agent.slashCommands.map((c, i) => {
|
||||
suggestions: coalesce(agents.flatMap(agent => agent.slashCommands.map((c, i) => {
|
||||
if (agent.isDefault && this.chatAgentService.getDefaultAgent(widget.location)?.id !== agent.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { label: agentLabel, isDupe } = this.getAgentCompletionDetails(agent);
|
||||
const withSlash = `${chatSubcommandLeader}${c.name}`;
|
||||
const extraSortText = agent.id === 'github.copilot.terminalPanel' ? `z` : ``;
|
||||
@@ -338,7 +347,7 @@ class AgentCompletions extends Disposable {
|
||||
}
|
||||
|
||||
return item;
|
||||
}))
|
||||
})))
|
||||
};
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from
|
||||
import { localize } from '../../../../nls.js';
|
||||
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
|
||||
import { ILogService } from '../../../../platform/log/common/log.js';
|
||||
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
|
||||
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
|
||||
import { ChatModel } from '../common/chatModel.js';
|
||||
@@ -46,6 +47,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo
|
||||
@IChatService private readonly _chatService: IChatService,
|
||||
@IDialogService private readonly _dialogService: IDialogService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -125,6 +127,8 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo
|
||||
}
|
||||
|
||||
async invokeTool(dto: IToolInvocation, countTokens: CountTokensCallback, token: CancellationToken): Promise<IToolResult> {
|
||||
this._logService.trace(`[LanguageModelToolsService#invokeTool] Invoking tool ${dto.toolId} with parameters ${JSON.stringify(dto.parameters)}`);
|
||||
|
||||
// When invoking a tool, don't validate the "when" clause. An extension may have invoked a tool just as it was becoming disabled, and just let it go through rather than throw and break the chat.
|
||||
let tool = this._tools.get(dto.toolId);
|
||||
if (!tool) {
|
||||
@@ -165,12 +169,15 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo
|
||||
|
||||
const source = new CancellationTokenSource();
|
||||
store.add(toDisposable(() => {
|
||||
toolInvocation!.confirmed.complete(false);
|
||||
source.dispose(true);
|
||||
}));
|
||||
store.add(token.onCancellationRequested(() => {
|
||||
toolInvocation?.confirmed.complete(false);
|
||||
source.cancel();
|
||||
}));
|
||||
store.add(source.token.onCancellationRequested(() => {
|
||||
toolInvocation?.confirmed.complete(false);
|
||||
}));
|
||||
token = source.token;
|
||||
|
||||
const prepared = tool.impl.prepareToolInvocation ?
|
||||
@@ -179,13 +186,14 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo
|
||||
|
||||
const defaultMessage = localize('toolInvocationMessage', "Using {0}", `"${tool.data.displayName}"`);
|
||||
const invocationMessage = prepared?.invocationMessage ?? defaultMessage;
|
||||
toolInvocation = new ChatToolInvocation(invocationMessage, prepared?.confirmationMessages);
|
||||
|
||||
model.acceptResponseProgress(request, toolInvocation);
|
||||
if (prepared?.confirmationMessages) {
|
||||
const userConfirmed = await toolInvocation.confirmed.p;
|
||||
if (!userConfirmed) {
|
||||
throw new CancellationError();
|
||||
if (tool.data.id !== 'vscode_editFile') {
|
||||
toolInvocation = new ChatToolInvocation(invocationMessage, prepared?.confirmationMessages);
|
||||
model.acceptResponseProgress(request, toolInvocation);
|
||||
if (prepared?.confirmationMessages) {
|
||||
const userConfirmed = await toolInvocation.confirmed.p;
|
||||
if (!userConfirmed) {
|
||||
throw new CancellationError();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken } from '../../../../../base/common/cancellation.js';
|
||||
import { MarkdownString } from '../../../../../base/common/htmlContent.js';
|
||||
import { IJSONSchema } from '../../../../../base/common/jsonSchema.js';
|
||||
import { Disposable } from '../../../../../base/common/lifecycle.js';
|
||||
import { autorun } from '../../../../../base/common/observable.js';
|
||||
import { URI } from '../../../../../base/common/uri.js';
|
||||
import { localize } from '../../../../../nls.js';
|
||||
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
|
||||
import { IWorkbenchContribution } from '../../../../common/contributions.js';
|
||||
import { ICodeMapperService } from '../../common/chatCodeMapperService.js';
|
||||
import { IChatEditingService } from '../../common/chatEditingService.js';
|
||||
import { ChatModel } from '../../common/chatModel.js';
|
||||
import { IChatService } from '../../common/chatService.js';
|
||||
import { CountTokensCallback, ILanguageModelToolsService, IToolData, IToolImpl, IToolInvocation, IToolResult } from '../../common/languageModelToolsService.js';
|
||||
|
||||
export class BuiltinToolsContribution extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
static readonly ID = 'chat.builtinTools';
|
||||
|
||||
constructor(
|
||||
@ILanguageModelToolsService toolsService: ILanguageModelToolsService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
|
||||
const editTool = instantiationService.createInstance(EditTool);
|
||||
this._register(toolsService.registerToolData(editTool));
|
||||
this._register(toolsService.registerToolImplementation(editTool.id, editTool));
|
||||
}
|
||||
}
|
||||
|
||||
interface EditToolParams {
|
||||
filePath: string;
|
||||
explanation: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
const codeInstructions = `
|
||||
The user is very smart and can understand how to apply your edits to their files, you just need to provide minimal hints.
|
||||
Avoid repeating existing code, instead use comments to represent regions of unchanged code. The user prefers that you are as concise as possible. For example:
|
||||
// ...existing code...
|
||||
{ changed code }
|
||||
// ...existing code...
|
||||
{ changed code }
|
||||
// ...existing code...
|
||||
|
||||
Here is an example of how you should format an edit to an existing Person class:
|
||||
class Person {
|
||||
// ...existing code...
|
||||
age: number;
|
||||
// ...existing code...
|
||||
getAge() {
|
||||
return this.age;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
class EditTool implements IToolData, IToolImpl {
|
||||
readonly id = 'vscode_editFile';
|
||||
readonly tags = ['vscode_editing'];
|
||||
readonly displayName = localize('chat.tools.editFile', "Edit File");
|
||||
readonly modelDescription = `Edit a file in the workspace. Use this tool once per file that needs to be modified, even if there are multiple changes for a file. ${codeInstructions}`;
|
||||
readonly inputSchema: IJSONSchema;
|
||||
|
||||
constructor(
|
||||
@IChatService private readonly chatService: IChatService,
|
||||
@IChatEditingService private readonly chatEditingService: IChatEditingService,
|
||||
@ICodeMapperService private readonly codeMapperService: ICodeMapperService
|
||||
) {
|
||||
this.inputSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
filePath: {
|
||||
type: 'string',
|
||||
description: 'An absolute path to the file to edit',
|
||||
},
|
||||
explanation: {
|
||||
type: 'string',
|
||||
description: 'A short explanation of the edit being made. Can be the same as the explanation you showed to the user.',
|
||||
},
|
||||
code: {
|
||||
type: 'string',
|
||||
description: 'The code change to apply to the file. ' + codeInstructions
|
||||
}
|
||||
},
|
||||
required: ['filePath', 'explanation', 'code']
|
||||
};
|
||||
}
|
||||
|
||||
async invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, token: CancellationToken): Promise<IToolResult> {
|
||||
if (!invocation.context) {
|
||||
throw new Error('toolInvocationToken is required for this tool');
|
||||
}
|
||||
|
||||
|
||||
const parameters = invocation.parameters as EditToolParams;
|
||||
if (!parameters.filePath || !parameters.explanation || !parameters.code) {
|
||||
throw new Error(`Invalid tool input: ${JSON.stringify(parameters)}`);
|
||||
}
|
||||
|
||||
const model = this.chatService.getSession(invocation.context?.sessionId) as ChatModel;
|
||||
const request = model.getRequests().at(-1)!;
|
||||
|
||||
const uri = URI.file(parameters.filePath);
|
||||
model.acceptResponseProgress(request, {
|
||||
kind: 'markdownContent',
|
||||
content: new MarkdownString('\n````\n')
|
||||
});
|
||||
model.acceptResponseProgress(request, {
|
||||
kind: 'codeblockUri',
|
||||
uri
|
||||
});
|
||||
model.acceptResponseProgress(request, {
|
||||
kind: 'markdownContent',
|
||||
content: new MarkdownString(parameters.code + '\n````\n')
|
||||
});
|
||||
|
||||
if (this.chatEditingService.currentEditingSession?.chatSessionId !== model.sessionId) {
|
||||
throw new Error('This tool must be called from within an editing session');
|
||||
}
|
||||
|
||||
const result = await this.codeMapperService.mapCode({
|
||||
codeBlocks: [{ code: parameters.code, resource: uri, markdownBeforeBlock: parameters.explanation }],
|
||||
conversation: []
|
||||
}, {
|
||||
textEdit: (target, edits) => {
|
||||
model.acceptResponseProgress(request, { kind: 'textEdit', uri: target, edits });
|
||||
}
|
||||
}, token);
|
||||
|
||||
model.acceptResponseProgress(request, { kind: 'textEdit', uri, edits: [], done: true });
|
||||
|
||||
if (result?.errorMessage) {
|
||||
throw new Error(result.errorMessage);
|
||||
}
|
||||
|
||||
await new Promise((resolve) => {
|
||||
autorun((r) => {
|
||||
const currentEditingSession = this.chatEditingService.currentEditingSessionObs.read(r);
|
||||
const entries = currentEditingSession?.entries.read(r);
|
||||
const currentFile = entries?.find((e) => e.modifiedURI.toString() === uri.toString());
|
||||
if (currentFile && !currentFile.isCurrentlyBeingModified.read(r)) {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{ kind: 'text', value: 'Success' }]
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,8 @@ export interface IChatAgentData {
|
||||
extensionDisplayName: string;
|
||||
/** The agent invoked when no agent is specified */
|
||||
isDefault?: boolean;
|
||||
/** The default agent when "agent-mode" is enabled */
|
||||
isToolsAgent?: boolean;
|
||||
/** This agent is not contributed in package.json, but is registered dynamically */
|
||||
isDynamic?: boolean;
|
||||
metadata: IChatAgentMetadata;
|
||||
@@ -201,9 +203,11 @@ export interface IChatAgentCompletionItem {
|
||||
export interface IChatAgentService {
|
||||
_serviceBrand: undefined;
|
||||
/**
|
||||
* undefined when an agent was removed IChatAgent
|
||||
* undefined when an agent was removed
|
||||
*/
|
||||
readonly onDidChangeAgents: Event<IChatAgent | undefined>;
|
||||
readonly toolsAgentModeEnabled: boolean;
|
||||
toggleToolsAgentMode(): void;
|
||||
registerAgent(id: string, data: IChatAgentData): IDisposable;
|
||||
registerAgentImplementation(id: string, agent: IChatAgentImplementation): IDisposable;
|
||||
registerDynamicAgent(data: IChatAgentData, agentImpl: IChatAgentImplementation): IDisposable;
|
||||
@@ -235,6 +239,8 @@ export interface IChatAgentService {
|
||||
updateAgent(id: string, updateMetadata: IChatAgentMetadata): void;
|
||||
}
|
||||
|
||||
const ChatToolsAgentModeStorageKey = 'chat.toolsAgentMode';
|
||||
|
||||
export class ChatAgentService extends Disposable implements IChatAgentService {
|
||||
|
||||
public static readonly AGENT_LEADER = '@';
|
||||
@@ -250,11 +256,14 @@ export class ChatAgentService extends Disposable implements IChatAgentService {
|
||||
private readonly _hasDefaultAgent: IContextKey<boolean>;
|
||||
private readonly _defaultAgentRegistered: IContextKey<boolean>;
|
||||
private readonly _editingAgentRegistered: IContextKey<boolean>;
|
||||
private readonly _agentModeContextKey: IContextKey<boolean>;
|
||||
private readonly _hasToolsAgentContextKey: IContextKey<boolean>;
|
||||
|
||||
private _chatParticipantDetectionProviders = new Map<number, IChatParticipantDetectionProvider>();
|
||||
|
||||
constructor(
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
) {
|
||||
super();
|
||||
this._hasDefaultAgent = ChatContextKeys.enabled.bindTo(this.contextKeyService);
|
||||
@@ -265,6 +274,13 @@ export class ChatAgentService extends Disposable implements IChatAgentService {
|
||||
this._updateContextKeys();
|
||||
}
|
||||
}));
|
||||
|
||||
this._agentModeContextKey = ChatContextKeys.Editing.agentMode.bindTo(contextKeyService);
|
||||
this._hasToolsAgentContextKey = ChatContextKeys.Editing.hasToolsAgent.bindTo(contextKeyService);
|
||||
this._agentModeContextKey.set(
|
||||
this.storageService.getBoolean(ChatToolsAgentModeStorageKey, StorageScope.WORKSPACE, false));
|
||||
this._register(
|
||||
this.storageService.onWillSaveState(() => this.storageService.store(ChatToolsAgentModeStorageKey, this._agentModeContextKey.get(), StorageScope.WORKSPACE, StorageTarget.USER)));
|
||||
}
|
||||
|
||||
registerAgent(id: string, data: IChatAgentData): IDisposable {
|
||||
@@ -311,15 +327,23 @@ export class ChatAgentService extends Disposable implements IChatAgentService {
|
||||
private _updateContextKeys(): void {
|
||||
let editingAgentRegistered = false;
|
||||
let defaultAgentRegistered = false;
|
||||
let toolsAgentRegistered = false;
|
||||
for (const agent of this.getAgents()) {
|
||||
if (agent.isDefault && agent.locations.includes(ChatAgentLocation.EditingSession)) {
|
||||
editingAgentRegistered = true;
|
||||
if (agent.isToolsAgent) {
|
||||
toolsAgentRegistered = true;
|
||||
}
|
||||
} else if (agent.isDefault) {
|
||||
defaultAgentRegistered = true;
|
||||
}
|
||||
}
|
||||
this._editingAgentRegistered.set(editingAgentRegistered);
|
||||
this._defaultAgentRegistered.set(defaultAgentRegistered);
|
||||
if (toolsAgentRegistered !== this._hasToolsAgentContextKey.get()) {
|
||||
this._hasToolsAgentContextKey.set(toolsAgentRegistered);
|
||||
this._onDidChangeAgents.fire(this.getDefaultAgent(ChatAgentLocation.EditingSession));
|
||||
}
|
||||
}
|
||||
|
||||
registerAgentImplementation(id: string, agentImpl: IChatAgentImplementation): IDisposable {
|
||||
@@ -384,7 +408,22 @@ export class ChatAgentService extends Disposable implements IChatAgentService {
|
||||
}
|
||||
|
||||
getDefaultAgent(location: ChatAgentLocation): IChatAgent | undefined {
|
||||
return findLast(this.getActivatedAgents(), a => !!a.isDefault && a.locations.includes(location));
|
||||
return findLast(this.getActivatedAgents(), a => {
|
||||
if (location === ChatAgentLocation.EditingSession && this.toolsAgentModeEnabled !== !!a.isToolsAgent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !!a.isDefault && a.locations.includes(location);
|
||||
});
|
||||
}
|
||||
|
||||
public get toolsAgentModeEnabled(): boolean {
|
||||
return !!this._hasToolsAgentContextKey.get() && !!this._agentModeContextKey.get();
|
||||
}
|
||||
|
||||
toggleToolsAgentMode(): void {
|
||||
this._agentModeContextKey.set(!this._agentModeContextKey.get());
|
||||
this._onDidChangeAgents.fire(this.getDefaultAgent(ChatAgentLocation.EditingSession));
|
||||
}
|
||||
|
||||
getContributedDefaultAgent(location: ChatAgentLocation): IChatAgentData | undefined {
|
||||
@@ -404,8 +443,8 @@ export class ChatAgentService extends Disposable implements IChatAgentService {
|
||||
return this._agents.get(id)?.data;
|
||||
}
|
||||
|
||||
private _agentIsEnabled(id: string): boolean {
|
||||
const entry = this._agents.get(id);
|
||||
private _agentIsEnabled(idOrAgent: string | IChatAgentEntry): boolean {
|
||||
const entry = typeof idOrAgent === 'string' ? this._agents.get(idOrAgent) : idOrAgent;
|
||||
return !entry?.data.when || this.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(entry.data.when));
|
||||
}
|
||||
|
||||
@@ -554,6 +593,7 @@ export class MergedChatAgent implements IChatAgent {
|
||||
get extensionPublisherDisplayName() { return this.data.publisherDisplayName; }
|
||||
get extensionDisplayName(): string { return this.data.extensionDisplayName; }
|
||||
get isDefault(): boolean | undefined { return this.data.isDefault; }
|
||||
get isToolsAgent(): boolean | undefined { return this.data.isToolsAgent; }
|
||||
get metadata(): IChatAgentMetadata { return this.data.metadata; }
|
||||
get slashCommands(): IChatAgentCommand[] { return this.data.slashCommands; }
|
||||
get locations(): ChatAgentLocation[] { return this.data.locations; }
|
||||
|
||||
@@ -76,4 +76,9 @@ export namespace ChatContextKeys {
|
||||
|
||||
export const chatQuotaExceeded = new RawContextKey<boolean>('chatQuotaExceeded', false, true);
|
||||
export const completionsQuotaExceeded = new RawContextKey<boolean>('completionsQuotaExceeded', false, true);
|
||||
|
||||
export const Editing = {
|
||||
hasToolsAgent: new RawContextKey<boolean>('chatHasToolsAgent', false, { type: 'boolean', description: localize('chatEditingHasToolsAgent', "True when a tools agent is registered.") }),
|
||||
agentMode: new RawContextKey<boolean>('chatAgentMode', false, { type: 'boolean', description: localize('chatEditingAgentMode', "True when edits is in agent mode.") }),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ResourceMap } from '../../../../base/common/map.js';
|
||||
import { IObservable, IReader, ITransaction } from '../../../../base/common/observable.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { IDocumentDiff } from '../../../../editor/common/diff/documentDiffProvider.js';
|
||||
import { DetailedLineRangeMapping } from '../../../../editor/common/diff/rangeMapping.js';
|
||||
import { TextEdit } from '../../../../editor/common/languages.js';
|
||||
import { ITextModel } from '../../../../editor/common/model.js';
|
||||
import { localize } from '../../../../nls.js';
|
||||
@@ -120,6 +121,8 @@ export interface IModifiedFileEntry {
|
||||
readonly rewriteRatio: IObservable<number>;
|
||||
readonly maxLineNumber: IObservable<number>;
|
||||
readonly diffInfo: IObservable<IDocumentDiff>;
|
||||
acceptHunk(change: DetailedLineRangeMapping): Promise<boolean>;
|
||||
rejectHunk(change: DetailedLineRangeMapping): Promise<boolean>;
|
||||
readonly lastModifyingRequestId: string;
|
||||
accept(transaction: ITransaction | undefined): Promise<void>;
|
||||
reject(transaction: ITransaction | undefined): Promise<void>;
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface IRawChatParticipantContribution {
|
||||
when?: string;
|
||||
description?: string;
|
||||
isDefault?: boolean;
|
||||
isAgent?: boolean;
|
||||
isSticky?: boolean;
|
||||
sampleRequest?: string;
|
||||
commands?: IRawChatCommandContribution[];
|
||||
|
||||
@@ -48,10 +48,6 @@ export class ChatToolInvocation implements IChatToolInvocation {
|
||||
this._confirmDeferred.p.then(confirmed => {
|
||||
this._isConfirmed = confirmed;
|
||||
this._confirmationMessages = undefined;
|
||||
if (!confirmed) {
|
||||
// Spinner -> check
|
||||
this._isCompleteDeferred.complete();
|
||||
}
|
||||
});
|
||||
|
||||
this._isCompleteDeferred.p.then(() => {
|
||||
|
||||
@@ -147,7 +147,7 @@ export interface IChatCodeCitations {
|
||||
export type IChatRendererContent = IChatProgressRenderableResponseContent | IChatReferences | IChatCodeCitations;
|
||||
|
||||
export interface IChatLiveUpdateData {
|
||||
firstWordTime: number;
|
||||
totalTime: number;
|
||||
lastUpdateTime: number;
|
||||
impliedWordLoadRate: number;
|
||||
lastWordCount: number;
|
||||
@@ -566,10 +566,10 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi
|
||||
|
||||
if (!_model.isComplete) {
|
||||
this._contentUpdateTimings = {
|
||||
firstWordTime: 0,
|
||||
totalTime: 0,
|
||||
lastUpdateTime: Date.now(),
|
||||
impliedWordLoadRate: 0,
|
||||
lastWordCount: 0
|
||||
lastWordCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -579,12 +579,14 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi
|
||||
const now = Date.now();
|
||||
const wordCount = countWords(_model.response.getMarkdown());
|
||||
|
||||
// Apply a min time difference, or the rate is typically too high for first few words
|
||||
const timeDiff = Math.max(now - this._contentUpdateTimings.firstWordTime, 250);
|
||||
const impliedWordLoadRate = this._contentUpdateTimings.lastWordCount / (timeDiff / 1000);
|
||||
this.trace('onDidChange', `Update- got ${this._contentUpdateTimings.lastWordCount} words over last ${timeDiff}ms = ${impliedWordLoadRate} words/s. ${wordCount} words are now available.`);
|
||||
const timeDiff = Math.min(now - this._contentUpdateTimings.lastUpdateTime, 1000);
|
||||
const newTotalTime = Math.max(this._contentUpdateTimings.totalTime + timeDiff, 250);
|
||||
const impliedWordLoadRate = this._contentUpdateTimings.lastWordCount / (newTotalTime / 1000);
|
||||
this.trace('onDidChange', `Update- got ${this._contentUpdateTimings.lastWordCount} words over last ${newTotalTime}ms = ${impliedWordLoadRate} words/s. ${wordCount} words are now available.`);
|
||||
this._contentUpdateTimings = {
|
||||
firstWordTime: this._contentUpdateTimings.firstWordTime === 0 && this.response.value.some(v => v.kind === 'markdownContent') ? now : this._contentUpdateTimings.firstWordTime,
|
||||
totalTime: this._contentUpdateTimings.totalTime !== 0 || this.response.value.some(v => v.kind === 'markdownContent') ?
|
||||
newTotalTime :
|
||||
this._contentUpdateTimings.totalTime,
|
||||
lastUpdateTime: now,
|
||||
impliedWordLoadRate,
|
||||
lastWordCount: wordCount
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ContextKeyExpression } from '../../../../../platform/contextkey/common/
|
||||
import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js';
|
||||
import { MockContextKeyService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js';
|
||||
import { ChatAgentService, IChatAgentData, IChatAgentImplementation } from '../../common/chatAgents.js';
|
||||
import { TestStorageService } from '../../../../test/common/workbenchTestServices.js';
|
||||
|
||||
const testAgentId = 'testAgent';
|
||||
const testAgentData: IChatAgentData = {
|
||||
@@ -41,7 +42,7 @@ suite('ChatAgents', function () {
|
||||
let contextKeyService: TestingContextKeyService;
|
||||
setup(() => {
|
||||
contextKeyService = new TestingContextKeyService();
|
||||
chatAgentService = store.add(new ChatAgentService(contextKeyService));
|
||||
chatAgentService = store.add(new ChatAgentService(contextKeyService, store.add(new TestStorageService())));
|
||||
});
|
||||
|
||||
test('registerAgent', async () => {
|
||||
|
||||
@@ -64,15 +64,6 @@ suite('VoiceChat', () => {
|
||||
];
|
||||
|
||||
class TestChatAgentService implements IChatAgentService {
|
||||
hasChatParticipantDetectionProviders(): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
registerChatParticipantDetectionProvider(handle: number, provider: IChatParticipantDetectionProvider): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
detectAgentOrCommand(request: IChatAgentRequest, history: IChatAgentHistoryEntry[], options: { location: ChatAgentLocation }, token: CancellationToken): Promise<{ agent: IChatAgentData; command?: IChatAgentCommand } | undefined> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
_serviceBrand: undefined;
|
||||
readonly onDidChangeAgents = Event.None;
|
||||
registerAgentImplementation(id: string, agent: IChatAgentImplementation): IDisposable { throw new Error(); }
|
||||
@@ -93,6 +84,19 @@ suite('VoiceChat', () => {
|
||||
getAgentCompletionItems(id: string, query: string, token: CancellationToken): Promise<IChatAgentCompletionItem[]> { throw new Error('Method not implemented.'); }
|
||||
agentHasDupeName(id: string): boolean { throw new Error('Method not implemented.'); }
|
||||
getChatTitle(id: string, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<string | undefined> { throw new Error('Method not implemented.'); }
|
||||
readonly toolsAgentModeEnabled: boolean = false;
|
||||
toggleToolsAgentMode(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
hasChatParticipantDetectionProviders(): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
registerChatParticipantDetectionProvider(handle: number, provider: IChatParticipantDetectionProvider): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
detectAgentOrCommand(request: IChatAgentRequest, history: IChatAgentHistoryEntry[], options: { location: ChatAgentLocation }, token: CancellationToken): Promise<{ agent: IChatAgentData; command?: IChatAgentCommand } | undefined> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
class TestSpeechService implements ISpeechService {
|
||||
|
||||
@@ -41,7 +41,6 @@ export class CommentThreadBody<T extends IRange | ICellRange = IRange> extends D
|
||||
return this._commentElements.filter(node => node.isEditing)[0];
|
||||
}
|
||||
|
||||
|
||||
constructor(
|
||||
private readonly _parentEditor: LayoutableEditor,
|
||||
readonly owner: string,
|
||||
@@ -77,6 +76,10 @@ export class CommentThreadBody<T extends IRange | ICellRange = IRange> extends D
|
||||
this._commentsElement.focus();
|
||||
}
|
||||
|
||||
hasCommentsInEditMode() {
|
||||
return this._commentElements.some(commentNode => commentNode.isEditing);
|
||||
}
|
||||
|
||||
ensureFocusIntoNewEditingComment() {
|
||||
if (this._commentElements.length === 1 && this._commentElements[0].isEditing) {
|
||||
this._commentElements[0].setFocus(true);
|
||||
|
||||
@@ -44,9 +44,9 @@ export class CommentThreadHeader<T = IRange> extends Disposable {
|
||||
private _delegate: { collapse: () => void },
|
||||
private _commentMenus: CommentMenus,
|
||||
private _commentThread: languages.CommentThread<T>,
|
||||
private _contextKeyService: IContextKeyService,
|
||||
private instantiationService: IInstantiationService,
|
||||
private _contextMenuService: IContextMenuService
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IContextMenuService private readonly _contextMenuService: IContextMenuService
|
||||
) {
|
||||
super();
|
||||
this._headElement = <HTMLDivElement>dom.$('.head');
|
||||
@@ -63,7 +63,7 @@ export class CommentThreadHeader<T = IRange> extends Disposable {
|
||||
|
||||
const actionsContainer = dom.append(this._headElement, dom.$('.review-actions'));
|
||||
this._actionbarWidget = new ActionBar(actionsContainer, {
|
||||
actionViewItemProvider: createActionViewItem.bind(undefined, this.instantiationService)
|
||||
actionViewItemProvider: createActionViewItem.bind(undefined, this._instantiationService)
|
||||
});
|
||||
|
||||
this._register(this._actionbarWidget);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import './media/review.css';
|
||||
import * as dom from '../../../../base/browser/dom.js';
|
||||
import * as nls from '../../../../nls.js';
|
||||
import * as domStylesheets from '../../../../base/browser/domStylesheets.js';
|
||||
import { Emitter } from '../../../../base/common/event.js';
|
||||
import { Disposable, dispose, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
|
||||
@@ -28,7 +29,6 @@ import { IRange, Range } from '../../../../editor/common/core/range.js';
|
||||
import { commentThreadStateBackgroundColorVar, commentThreadStateColorVar } from './commentColors.js';
|
||||
import { ICellRange } from '../../notebook/common/notebookRange.js';
|
||||
import { FontInfo } from '../../../../editor/common/config/fontInfo.js';
|
||||
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
|
||||
import { registerNavigableContainer } from '../../../browser/actions/widgetNavigationCommands.js';
|
||||
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
|
||||
import { COMMENTS_SECTION, ICommentsConfiguration } from '../common/commentsConfiguration.js';
|
||||
@@ -38,6 +38,8 @@ import { IKeybindingService } from '../../../../platform/keybinding/common/keybi
|
||||
import { AccessibilityCommandId } from '../../accessibility/common/accessibilityCommands.js';
|
||||
import { LayoutableEditor } from './simpleCommentEditor.js';
|
||||
import { isCodeEditor } from '../../../../editor/browser/editorBrowser.js';
|
||||
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
|
||||
import Severity from '../../../../base/common/severity.js';
|
||||
|
||||
export const COMMENTEDITOR_DECORATION_KEY = 'commenteditordecoration';
|
||||
|
||||
@@ -76,10 +78,11 @@ export class CommentThreadWidget<T extends IRange | ICellRange = IRange> extends
|
||||
actionRunner: (() => void) | null;
|
||||
collapse: () => void;
|
||||
},
|
||||
@ICommentService private commentService: ICommentService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IKeybindingService private _keybindingService: IKeybindingService
|
||||
@ICommentService private readonly commentService: ICommentService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IKeybindingService private readonly _keybindingService: IKeybindingService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@IDialogService private readonly _dialogService: IDialogService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -89,16 +92,14 @@ export class CommentThreadWidget<T extends IRange | ICellRange = IRange> extends
|
||||
|
||||
this._commentMenus = this.commentService.getCommentMenus(this._owner);
|
||||
|
||||
this._register(this._header = new CommentThreadHeader<T>(
|
||||
this._register(this._header = this._scopedInstantiationService.createInstance(
|
||||
CommentThreadHeader,
|
||||
container,
|
||||
{
|
||||
collapse: this.collapse.bind(this)
|
||||
collapse: this.collapseAction.bind(this)
|
||||
},
|
||||
this._commentMenus,
|
||||
this._commentThread,
|
||||
this._contextKeyService,
|
||||
this._scopedInstantiationService,
|
||||
contextMenuService
|
||||
this._commentThread
|
||||
));
|
||||
|
||||
this._header.updateCommentThread(this._commentThread);
|
||||
@@ -159,6 +160,21 @@ export class CommentThreadWidget<T extends IRange | ICellRange = IRange> extends
|
||||
this.currentThreadListeners();
|
||||
}
|
||||
|
||||
private async confirmCollapse(): Promise<boolean> {
|
||||
const confirmSetting = this._configurationService.getValue<'whenHasUnsubmittedComments' | 'never'>('comments.thread.confirmOnCollapse');
|
||||
|
||||
const hasUnsubmitted = !!this._commentReply?.commentEditor.getValue() || this._body.hasCommentsInEditMode();
|
||||
if (confirmSetting === 'whenHasUnsubmittedComments' && hasUnsubmitted) {
|
||||
const result = await this._dialogService.confirm({
|
||||
message: nls.localize('confirmCollapse', "This comment thread has unsubmitted comments. Do you want to collapse it?"),
|
||||
primaryButton: nls.localize('collapse', "Collapse"),
|
||||
type: Severity.Warning
|
||||
});
|
||||
return result.confirmed;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private _setAriaLabel(): void {
|
||||
let ariaLabel = localize('commentLabel', "Comment");
|
||||
let keybinding: string | undefined;
|
||||
@@ -375,6 +391,12 @@ export class CommentThreadWidget<T extends IRange | ICellRange = IRange> extends
|
||||
}
|
||||
}
|
||||
|
||||
private async collapseAction() {
|
||||
if (await this.confirmCollapse()) {
|
||||
this.collapse();
|
||||
}
|
||||
}
|
||||
|
||||
collapse() {
|
||||
if (Range.isIRange(this.commentThread.range) && isCodeEditor(this._parentEditor)) {
|
||||
this._parentEditor.setSelection(this.commentThread.range);
|
||||
|
||||
@@ -138,6 +138,12 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: nls.localize('collapseOnResolve', "Controls whether the comment thread should collapse when the thread is resolved.")
|
||||
},
|
||||
'comments.thread.confirmOnCollapse': {
|
||||
type: 'string',
|
||||
enum: ['whenHasUnsubmittedComments', 'never'],
|
||||
default: 'never',
|
||||
description: nls.localize('confirmOnCollapse', "Controls whether a confirmation dialog is shown when collapsing a comment thread with unsubmitted comments.")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ import { COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMA
|
||||
import { CommandsRegistry, ICommandHandler } from '../../../../platform/commands/common/commands.js';
|
||||
import { ContextKeyExpr, ContextKeyExpression } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
|
||||
import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext, ExplorerResourceNotReadonlyContext, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerResourceAvailableEditorIdsContext, FoldersViewVisibleContext } from '../common/files.js';
|
||||
import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext, ExplorerResourceWritableContext, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerResourceAvailableEditorIdsContext, FoldersViewVisibleContext } from '../common/files.js';
|
||||
import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL } from '../../../browser/actions/workspaceCommands.js';
|
||||
import { CLOSE_SAVED_EDITORS_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, REOPEN_WITH_COMMAND_ID } from '../../../browser/parts/editor/editorCommands.js';
|
||||
import { AutoSaveAfterShortDelayContext } from '../../../services/filesConfiguration/common/filesConfigurationService.js';
|
||||
@@ -52,7 +52,7 @@ const RENAME_ID = 'renameFile';
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: RENAME_ID,
|
||||
weight: KeybindingWeight.WorkbenchContrib + explorerCommandsWeightBonus,
|
||||
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerRootContext.toNegated(), ExplorerResourceNotReadonlyContext),
|
||||
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerRootContext.toNegated(), ExplorerResourceWritableContext),
|
||||
primary: KeyCode.F2,
|
||||
mac: {
|
||||
primary: KeyCode.Enter
|
||||
@@ -64,7 +64,7 @@ const MOVE_FILE_TO_TRASH_ID = 'moveFileToTrash';
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: MOVE_FILE_TO_TRASH_ID,
|
||||
weight: KeybindingWeight.WorkbenchContrib + explorerCommandsWeightBonus,
|
||||
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerResourceNotReadonlyContext, ExplorerResourceMoveableToTrash),
|
||||
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerResourceMoveableToTrash),
|
||||
primary: KeyCode.Delete,
|
||||
mac: {
|
||||
primary: KeyMod.CtrlCmd | KeyCode.Backspace,
|
||||
@@ -77,7 +77,7 @@ const DELETE_FILE_ID = 'deleteFile';
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: DELETE_FILE_ID,
|
||||
weight: KeybindingWeight.WorkbenchContrib + explorerCommandsWeightBonus,
|
||||
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerResourceNotReadonlyContext),
|
||||
when: FilesExplorerFocusCondition,
|
||||
primary: KeyMod.Shift | KeyCode.Delete,
|
||||
mac: {
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Backspace
|
||||
@@ -88,7 +88,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: DELETE_FILE_ID,
|
||||
weight: KeybindingWeight.WorkbenchContrib + explorerCommandsWeightBonus,
|
||||
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerResourceNotReadonlyContext, ExplorerResourceMoveableToTrash.toNegated()),
|
||||
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerResourceMoveableToTrash.toNegated()),
|
||||
primary: KeyCode.Delete,
|
||||
mac: {
|
||||
primary: KeyMod.CtrlCmd | KeyCode.Backspace
|
||||
@@ -100,7 +100,7 @@ const CUT_FILE_ID = 'filesExplorer.cut';
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: CUT_FILE_ID,
|
||||
weight: KeybindingWeight.WorkbenchContrib + explorerCommandsWeightBonus,
|
||||
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerRootContext.toNegated(), ExplorerResourceNotReadonlyContext),
|
||||
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerRootContext.toNegated(), ExplorerResourceWritableContext),
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KeyX,
|
||||
handler: cutFileHandler,
|
||||
});
|
||||
@@ -121,7 +121,7 @@ CommandsRegistry.registerCommand(PASTE_FILE_ID, pasteFileHandler);
|
||||
KeybindingsRegistry.registerKeybindingRule({
|
||||
id: `^${PASTE_FILE_ID}`, // the `^` enables pasting files into the explorer by preventing default bubble up
|
||||
weight: KeybindingWeight.WorkbenchContrib + explorerCommandsWeightBonus,
|
||||
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerResourceNotReadonlyContext),
|
||||
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerResourceWritableContext),
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KeyV,
|
||||
});
|
||||
|
||||
@@ -479,7 +479,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
command: {
|
||||
id: NEW_FILE_COMMAND_ID,
|
||||
title: NEW_FILE_LABEL,
|
||||
precondition: ExplorerResourceNotReadonlyContext
|
||||
precondition: ExplorerResourceWritableContext
|
||||
},
|
||||
when: ExplorerFolderContext
|
||||
});
|
||||
@@ -490,7 +490,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
command: {
|
||||
id: NEW_FOLDER_COMMAND_ID,
|
||||
title: NEW_FOLDER_LABEL,
|
||||
precondition: ExplorerResourceNotReadonlyContext
|
||||
precondition: ExplorerResourceWritableContext
|
||||
},
|
||||
when: ExplorerFolderContext
|
||||
});
|
||||
@@ -540,7 +540,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
id: CUT_FILE_ID,
|
||||
title: nls.localize('cut', "Cut"),
|
||||
},
|
||||
when: ContextKeyExpr.and(ExplorerRootContext.toNegated(), ExplorerResourceNotReadonlyContext)
|
||||
when: ContextKeyExpr.and(ExplorerRootContext.toNegated(), ExplorerResourceWritableContext)
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
@@ -559,7 +559,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
command: {
|
||||
id: PASTE_FILE_ID,
|
||||
title: PASTE_FILE_LABEL,
|
||||
precondition: ContextKeyExpr.and(ExplorerResourceNotReadonlyContext, FileCopiedContext)
|
||||
precondition: ContextKeyExpr.and(ExplorerResourceWritableContext, FileCopiedContext)
|
||||
},
|
||||
when: ExplorerFolderContext
|
||||
});
|
||||
@@ -593,8 +593,8 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, ({
|
||||
IsWebContext,
|
||||
// only on folders
|
||||
ExplorerFolderContext,
|
||||
// only on editable folders
|
||||
ExplorerResourceNotReadonlyContext
|
||||
// only on writable folders
|
||||
ExplorerResourceWritableContext
|
||||
)
|
||||
}));
|
||||
|
||||
@@ -638,7 +638,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
command: {
|
||||
id: RENAME_ID,
|
||||
title: TRIGGER_RENAME_LABEL,
|
||||
precondition: ExplorerResourceNotReadonlyContext,
|
||||
precondition: ExplorerResourceWritableContext,
|
||||
},
|
||||
when: ExplorerRootContext.toNegated()
|
||||
});
|
||||
@@ -648,13 +648,11 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
order: 20,
|
||||
command: {
|
||||
id: MOVE_FILE_TO_TRASH_ID,
|
||||
title: MOVE_FILE_TO_TRASH_LABEL,
|
||||
precondition: ContextKeyExpr.and(ExplorerResourceNotReadonlyContext),
|
||||
title: MOVE_FILE_TO_TRASH_LABEL
|
||||
},
|
||||
alt: {
|
||||
id: DELETE_FILE_ID,
|
||||
title: nls.localize('deleteFile', "Delete Permanently"),
|
||||
precondition: ContextKeyExpr.and(ExplorerResourceNotReadonlyContext),
|
||||
title: nls.localize('deleteFile', "Delete Permanently")
|
||||
},
|
||||
when: ContextKeyExpr.and(ExplorerRootContext.toNegated(), ExplorerResourceMoveableToTrash)
|
||||
});
|
||||
@@ -664,8 +662,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
order: 20,
|
||||
command: {
|
||||
id: DELETE_FILE_ID,
|
||||
title: nls.localize('deleteFile', "Delete Permanently"),
|
||||
precondition: ExplorerResourceNotReadonlyContext,
|
||||
title: nls.localize('deleteFile', "Delete Permanently")
|
||||
},
|
||||
when: ContextKeyExpr.and(ExplorerRootContext.toNegated(), ExplorerResourceMoveableToTrash.toNegated())
|
||||
});
|
||||
|
||||
@@ -93,7 +93,7 @@ async function refreshIfSeparator(value: string, explorerService: IExplorerServi
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteFiles(explorerService: IExplorerService, workingCopyFileService: IWorkingCopyFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false, ignoreIfNotExists = false): Promise<void> {
|
||||
async function deleteFiles(explorerService: IExplorerService, workingCopyFileService: IWorkingCopyFileService, dialogService: IDialogService, configurationService: IConfigurationService, filesConfigurationService: IFilesConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false, ignoreIfNotExists = false): Promise<void> {
|
||||
let primaryButton: string;
|
||||
if (useTrash) {
|
||||
primaryButton = isWindows ? nls.localize('deleteButtonLabelRecycleBin', "&&Move to Recycle Bin") : nls.localize({ key: 'deleteButtonLabelTrash', comment: ['&& denotes a mnemonic'] }, "&&Move to Trash");
|
||||
@@ -109,7 +109,7 @@ async function deleteFiles(explorerService: IExplorerService, workingCopyFileSer
|
||||
dirtyWorkingCopies.add(dirtyWorkingCopy);
|
||||
}
|
||||
}
|
||||
let confirmed = true;
|
||||
|
||||
if (dirtyWorkingCopies.size) {
|
||||
let message: string;
|
||||
if (distinctElements.length > 1) {
|
||||
@@ -132,18 +132,40 @@ async function deleteFiles(explorerService: IExplorerService, workingCopyFileSer
|
||||
});
|
||||
|
||||
if (!response.confirmed) {
|
||||
confirmed = false;
|
||||
return;
|
||||
} else {
|
||||
skipConfirm = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if file is dirty in editor and save it to avoid data loss
|
||||
if (!confirmed) {
|
||||
return;
|
||||
// Handle readonly
|
||||
if (!skipConfirm) {
|
||||
const readonlyResources = distinctElements.filter(e => filesConfigurationService.isReadonly(e.resource));
|
||||
if (readonlyResources.length) {
|
||||
let message: string;
|
||||
if (readonlyResources.length > 1) {
|
||||
message = nls.localize('readonlyMessageFilesDelete', "You are deleting files that are configured to be read-only. Do you want to continue?");
|
||||
} else if (readonlyResources[0].isDirectory) {
|
||||
message = nls.localize('readonlyMessageFolderOneDelete', "You are deleting a folder {0} that is configured to be read-only. Do you want to continue?", distinctElements[0].name);
|
||||
} else {
|
||||
message = nls.localize('readonlyMessageFolderDelete', "You are deleting a file {0} that is configured to be read-only. Do you want to continue?", distinctElements[0].name);
|
||||
}
|
||||
|
||||
const response = await dialogService.confirm({
|
||||
type: 'warning',
|
||||
message,
|
||||
detail: nls.localize('continueDetail', "The read-only protection will be overridden if you continue."),
|
||||
primaryButton: nls.localize('continueButtonLabel', "Continue")
|
||||
});
|
||||
|
||||
if (!response.confirmed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let confirmation: IConfirmationResult;
|
||||
|
||||
// We do not support undo of folders, so in that case the delete action is irreversible
|
||||
const deleteDetail = distinctElements.some(e => e.isDirectory) ? nls.localize('irreversible', "This action is irreversible!") :
|
||||
distinctElements.length > 1 ? nls.localize('restorePlural', "You can restore these files using the Undo command.") : nls.localize('restore', "You can restore this file using the Undo command.");
|
||||
@@ -234,7 +256,7 @@ async function deleteFiles(explorerService: IExplorerService, workingCopyFileSer
|
||||
skipConfirm = true;
|
||||
ignoreIfNotExists = true;
|
||||
|
||||
return deleteFiles(explorerService, workingCopyFileService, dialogService, configurationService, elements, useTrash, skipConfirm, ignoreIfNotExists);
|
||||
return deleteFiles(explorerService, workingCopyFileService, dialogService, configurationService, filesConfigurationService, elements, useTrash, skipConfirm, ignoreIfNotExists);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1020,7 +1042,7 @@ export const moveFileToTrashHandler = async (accessor: ServicesAccessor) => {
|
||||
const explorerService = accessor.get(IExplorerService);
|
||||
const stats = explorerService.getContext(true).filter(s => !s.isRoot);
|
||||
if (stats.length) {
|
||||
await deleteFiles(accessor.get(IExplorerService), accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true);
|
||||
await deleteFiles(accessor.get(IExplorerService), accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), accessor.get(IFilesConfigurationService), stats, true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1029,7 +1051,7 @@ export const deleteFileHandler = async (accessor: ServicesAccessor) => {
|
||||
const stats = explorerService.getContext(true).filter(s => !s.isRoot);
|
||||
|
||||
if (stats.length) {
|
||||
await deleteFiles(accessor.get(IExplorerService), accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false);
|
||||
await deleteFiles(accessor.get(IExplorerService), accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), accessor.get(IFilesConfigurationService), stats, false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { URI } from '../../../../../base/common/uri.js';
|
||||
import * as perf from '../../../../../base/common/performance.js';
|
||||
import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from '../../../../../base/common/actions.js';
|
||||
import { memoize } from '../../../../../base/common/decorators.js';
|
||||
import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, ExplorerResourceAvailableEditorIdsContext, VIEW_ID, ExplorerResourceNotReadonlyContext, ViewHasSomeCollapsibleRootItemContext, FoldersViewVisibleContext, ExplorerResourceParentReadOnlyContext, ExplorerFindProviderActive } from '../../common/files.js';
|
||||
import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, ExplorerResourceAvailableEditorIdsContext, VIEW_ID, ExplorerResourceWritableContext, ViewHasSomeCollapsibleRootItemContext, FoldersViewVisibleContext, ExplorerResourceParentReadOnlyContext, ExplorerFindProviderActive } from '../../common/files.js';
|
||||
import { FileCopiedContext, NEW_FILE_COMMAND_ID, NEW_FOLDER_COMMAND_ID } from '../fileActions.js';
|
||||
import * as DOM from '../../../../../base/browser/dom.js';
|
||||
import { IWorkbenchLayoutService } from '../../../../services/layout/browser/layoutService.js';
|
||||
@@ -988,7 +988,7 @@ export function createFileIconThemableTreeContainerScope(container: HTMLElement,
|
||||
|
||||
const CanCreateContext = ContextKeyExpr.or(
|
||||
// Folder: can create unless readonly
|
||||
ContextKeyExpr.and(ExplorerFolderContext, ExplorerResourceNotReadonlyContext),
|
||||
ContextKeyExpr.and(ExplorerFolderContext, ExplorerResourceWritableContext),
|
||||
// File: can create unless parent is readonly
|
||||
ContextKeyExpr.and(ExplorerFolderContext.toNegated(), ExplorerResourceParentReadOnlyContext.toNegated())
|
||||
);
|
||||
|
||||
@@ -40,7 +40,7 @@ export const ExplorerViewletVisibleContext = new RawContextKey<boolean>('explore
|
||||
export const FoldersViewVisibleContext = new RawContextKey<boolean>('foldersViewVisible', true, { type: 'boolean', description: localize('foldersViewVisible', "True when the FOLDERS view (the file tree within the explorer view container) is visible.") });
|
||||
export const ExplorerFolderContext = new RawContextKey<boolean>('explorerResourceIsFolder', false, { type: 'boolean', description: localize('explorerResourceIsFolder', "True when the focused item in the EXPLORER is a folder.") });
|
||||
export const ExplorerResourceReadonlyContext = new RawContextKey<boolean>('explorerResourceReadonly', false, { type: 'boolean', description: localize('explorerResourceReadonly', "True when the focused item in the EXPLORER is read-only.") });
|
||||
export const ExplorerResourceNotReadonlyContext = ExplorerResourceReadonlyContext.toNegated();
|
||||
export const ExplorerResourceWritableContext = ExplorerResourceReadonlyContext.toNegated();
|
||||
export const ExplorerResourceParentReadOnlyContext = new RawContextKey<boolean>('explorerResourceParentReadonly', false, { type: 'boolean', description: localize('explorerResourceParentReadonly', "True when the focused item in the EXPLORER's parent is read-only.") });
|
||||
|
||||
/**
|
||||
|
||||
@@ -847,7 +847,7 @@ abstract class AbstractElementRenderer extends Disposable {
|
||||
return;
|
||||
}
|
||||
|
||||
const modifiedMetadataSource = getFormattedMetadataJSON(this.notebookEditor.textModel?.transientOptions.transientCellMetadata, this.cell.modified?.metadata || {}, this.cell.modified?.language);
|
||||
const modifiedMetadataSource = getFormattedMetadataJSON(this.notebookEditor.textModel?.transientOptions.transientCellMetadata, this.cell.modified?.metadata || {}, this.cell.modified?.language, true);
|
||||
modifiedMetadataModel.object.textEditorModel.setValue(modifiedMetadataSource);
|
||||
}));
|
||||
|
||||
@@ -869,7 +869,7 @@ abstract class AbstractElementRenderer extends Disposable {
|
||||
const originalMetadataSource = getFormattedMetadataJSON(this.notebookEditor.textModel?.transientOptions.transientCellMetadata,
|
||||
this.cell.type === 'insert'
|
||||
? this.cell.modified!.metadata || {}
|
||||
: this.cell.original!.metadata || {});
|
||||
: this.cell.original!.metadata || {}, undefined, true);
|
||||
const uri = this.cell.type === 'insert'
|
||||
? this.cell.modified!.uri
|
||||
: this.cell.original!.uri;
|
||||
|
||||
@@ -452,7 +452,7 @@ class CellInfoContentProvider {
|
||||
for (const cell of ref.object.notebook.cells) {
|
||||
if (cell.handle === data.handle) {
|
||||
const cellIndex = ref.object.notebook.cells.indexOf(cell);
|
||||
const metadataSource = getFormattedMetadataJSON(ref.object.notebook.transientOptions.transientCellMetadata, cell.metadata, cell.language);
|
||||
const metadataSource = getFormattedMetadataJSON(ref.object.notebook.transientOptions.transientCellMetadata, cell.metadata, cell.language, true);
|
||||
result = this._modelService.createModel(
|
||||
metadataSource,
|
||||
mode,
|
||||
@@ -460,9 +460,9 @@ class CellInfoContentProvider {
|
||||
);
|
||||
this._disposables.push(disposables.add(ref.object.notebook.onDidChangeContent(e => {
|
||||
if (result && e.rawEvents.some(event => (event.kind === NotebookCellsChangeType.ChangeCellMetadata || event.kind === NotebookCellsChangeType.ChangeCellLanguage) && event.index === cellIndex)) {
|
||||
const value = getFormattedMetadataJSON(ref.object.notebook.transientOptions.transientCellMetadata, cell.metadata, cell.language);
|
||||
const value = getFormattedMetadataJSON(ref.object.notebook.transientOptions.transientCellMetadata, cell.metadata, cell.language, true);
|
||||
if (result.getValue() !== value) {
|
||||
result.setValue(getFormattedMetadataJSON(ref.object.notebook.transientOptions.transientCellMetadata, cell.metadata, cell.language));
|
||||
result.setValue(value);
|
||||
}
|
||||
}
|
||||
})));
|
||||
|
||||
@@ -282,7 +282,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
|
||||
}
|
||||
|
||||
get visibleRanges() {
|
||||
return this._list.visibleRanges || [];
|
||||
return this._list ? (this._list.visibleRanges || []) : [];
|
||||
}
|
||||
|
||||
private _baseCellEditorOptions = new Map<string, IBaseCellEditorOptions>();
|
||||
|
||||
@@ -516,7 +516,7 @@ function computeRunStartTimeAdjustment(oldMetadata: NotebookCellInternalMetadata
|
||||
}
|
||||
|
||||
|
||||
export function getFormattedMetadataJSON(transientCellMetadata: TransientCellMetadata | undefined, metadata: NotebookCellMetadata, language?: string) {
|
||||
export function getFormattedMetadataJSON(transientCellMetadata: TransientCellMetadata | undefined, metadata: NotebookCellMetadata, language?: string, sortKeys?: boolean): string {
|
||||
let filteredMetadata: { [key: string]: any } = {};
|
||||
|
||||
if (transientCellMetadata) {
|
||||
@@ -541,7 +541,28 @@ export function getFormattedMetadataJSON(transientCellMetadata: TransientCellMet
|
||||
if (language) {
|
||||
obj.language = language;
|
||||
}
|
||||
const metadataSource = toFormattedString(obj, {});
|
||||
const metadataSource = toFormattedString(sortKeys ? sortObjectPropertiesRecursively(obj) : obj, {});
|
||||
|
||||
return metadataSource;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sort the JSON to ensure when diffing, the JSON keys are sorted & matched correctly in diff view.
|
||||
*/
|
||||
export function sortObjectPropertiesRecursively(obj: any): any {
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(sortObjectPropertiesRecursively);
|
||||
}
|
||||
if (obj !== undefined && obj !== null && typeof obj === 'object' && Object.keys(obj).length > 0) {
|
||||
return (
|
||||
Object.keys(obj)
|
||||
.sort()
|
||||
.reduce<Record<string, any>>((sortedObj, prop) => {
|
||||
sortedObj[prop] = sortObjectPropertiesRecursively(obj[prop]);
|
||||
return sortedObj;
|
||||
}, {}) as any
|
||||
);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -1606,7 +1606,7 @@ export class SettingsEditor2 extends EditorPane {
|
||||
}
|
||||
|
||||
private async triggerSearch(query: string): Promise<void> {
|
||||
const progressRunner = this.editorProgressService.show(true);
|
||||
const progressRunner = this.editorProgressService.show(true, 800);
|
||||
this.viewState.tagFilters = new Set<string>();
|
||||
this.viewState.extensionFilters = new Set<string>();
|
||||
this.viewState.featureFilters = new Set<string>();
|
||||
|
||||
@@ -459,10 +459,14 @@ class HistoryItemRenderer implements ITreeRenderer<SCMHistoryItemViewModelTreeEl
|
||||
const historyItem = element.historyItemViewModel.historyItem;
|
||||
|
||||
const markdown = new MarkdownString('', { isTrusted: true, supportThemeIcons: true });
|
||||
// markdown.appendMarkdown(`$(git-commit) \`${historyItem.displayId ?? historyItem.id}\`\n\n`);
|
||||
|
||||
if (historyItem.author) {
|
||||
markdown.appendMarkdown(`$(account) **${historyItem.author}**`);
|
||||
if (historyItem.authorEmail) {
|
||||
const emailTitle = localize('emailLinkTitle', "Email");
|
||||
markdown.appendMarkdown(`$(account) [**${historyItem.author}**](mailto:${historyItem.authorEmail} "${emailTitle} ${historyItem.author}")`);
|
||||
} else {
|
||||
markdown.appendMarkdown(`$(account) **${historyItem.author}**`);
|
||||
}
|
||||
|
||||
if (historyItem.timestamp) {
|
||||
const dateFormatter = safeIntl.DateTimeFormat(platform.language, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' });
|
||||
|
||||
@@ -63,6 +63,7 @@ export interface ISCMHistoryItem {
|
||||
readonly message: string;
|
||||
readonly displayId?: string;
|
||||
readonly author?: string;
|
||||
readonly authorEmail?: string;
|
||||
readonly timestamp?: number;
|
||||
readonly statistics?: ISCMHistoryItemStatistics;
|
||||
readonly references?: ISCMHistoryItemRef[];
|
||||
|
||||
@@ -1162,7 +1162,7 @@ class TimelineTreeRenderer implements ITreeRenderer<TreeElement, FuzzyScore, Tim
|
||||
@IThemeService private themeService: IThemeService,
|
||||
) {
|
||||
this.actionViewItemProvider = createActionViewItem.bind(undefined, this.instantiationService);
|
||||
this._hoverDelegate = this.instantiationService.createInstance(WorkbenchHoverDelegate, 'element', false, {
|
||||
this._hoverDelegate = this.instantiationService.createInstance(WorkbenchHoverDelegate, 'element', true, {
|
||||
position: {
|
||||
hoverPosition: HoverPosition.RIGHT // Will flip when there's no space
|
||||
}
|
||||
|
||||
@@ -234,6 +234,7 @@ import product from '../../platform/product/common/product.js';
|
||||
'type': 'string',
|
||||
'enum': ['native', 'custom'],
|
||||
'default': isLinux && product.quality === 'stable' ? 'native' : 'custom',
|
||||
'tags': isLinux && product.quality === 'stable' ? ['onExP'] : undefined,
|
||||
'scope': ConfigurationScope.APPLICATION,
|
||||
'description': localize('titleBarStyle', "Adjust the appearance of the window title bar to be native by the OS or custom. On Linux and Windows, this setting also affects the application and context menu appearances. Changes require a full restart to apply."),
|
||||
},
|
||||
|
||||
@@ -27,6 +27,7 @@ import { IEditorGroupsContainer, IEditorGroupsService } from '../../../services/
|
||||
import { IEditorService } from '../../../services/editor/common/editorService.js';
|
||||
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
|
||||
import { CodeWindow, mainWindow } from '../../../../base/browser/window.js';
|
||||
import { IProductService } from '../../../../platform/product/common/productService.js';
|
||||
|
||||
export class NativeTitlebarPart extends BrowserTitlebarPart {
|
||||
|
||||
@@ -70,7 +71,7 @@ export class NativeTitlebarPart extends BrowserTitlebarPart {
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IHostService hostService: IHostService,
|
||||
@INativeHostService private readonly nativeHostService: INativeHostService,
|
||||
@INativeHostService protected readonly nativeHostService: INativeHostService,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService,
|
||||
@IEditorService editorService: IEditorService,
|
||||
@IMenuService menuService: IMenuService,
|
||||
@@ -200,7 +201,7 @@ export class NativeTitlebarPart extends BrowserTitlebarPart {
|
||||
}
|
||||
|
||||
const zoomFactor = getZoomFactor(getWindow(this.element));
|
||||
this.onContextMenu(new MouseEvent('mouseup', { clientX: x / zoomFactor, clientY: y / zoomFactor }), MenuId.TitleBarContext);
|
||||
this.onContextMenu(new MouseEvent(EventType.MOUSE_UP, { clientX: x / zoomFactor, clientY: y / zoomFactor }), MenuId.TitleBarContext);
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -287,9 +288,38 @@ export class MainNativeTitlebarPart extends NativeTitlebarPart {
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService,
|
||||
@IEditorService editorService: IEditorService,
|
||||
@IMenuService menuService: IMenuService,
|
||||
@IKeybindingService keybindingService: IKeybindingService
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IProductService productService: IProductService
|
||||
) {
|
||||
super(Parts.TITLEBAR_PART, mainWindow, 'main', contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, nativeHostService, editorGroupService, editorService, menuService, keybindingService);
|
||||
|
||||
if (isLinux && productService.quality === 'stable') {
|
||||
this.handleDefaultTitlebarStyle(); // TODO@bpasero remove me eventually once settled
|
||||
}
|
||||
}
|
||||
|
||||
private handleDefaultTitlebarStyle(): void {
|
||||
this.updateDefaultTitlebarStyle();
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('window.titleBarStyle')) {
|
||||
this.updateDefaultTitlebarStyle();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private updateDefaultTitlebarStyle(): void {
|
||||
const titleBarStyle = this.configurationService.inspect('window.titleBarStyle');
|
||||
|
||||
let titleBarStyleOverride: 'custom' | undefined;
|
||||
if (titleBarStyle.applicationValue || titleBarStyle.userValue || titleBarStyle.userLocalValue) {
|
||||
// configured by user or application: clear override
|
||||
titleBarStyleOverride = undefined;
|
||||
} else {
|
||||
// not configured: set override if experiment is active
|
||||
titleBarStyleOverride = titleBarStyle.defaultValue === 'native' ? undefined : 'custom';
|
||||
}
|
||||
|
||||
this.nativeHostService.overrideDefaultTitlebarStyle(titleBarStyleOverride);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { getClientArea, getTopLeftOffset } from '../../../../base/browser/dom.js';
|
||||
import { getClientArea, getTopLeftOffset, isHTMLDivElement, isHTMLTextAreaElement } from '../../../../base/browser/dom.js';
|
||||
import { mainWindow } from '../../../../base/browser/window.js';
|
||||
import { coalesce } from '../../../../base/common/arrays.js';
|
||||
import { language, locale } from '../../../../base/common/platform.js';
|
||||
@@ -133,18 +133,36 @@ export class BrowserWindowDriver implements IWindowDriver {
|
||||
if (!element) {
|
||||
throw new Error(`Editor not found: ${selector}`);
|
||||
}
|
||||
if (isHTMLDivElement(element)) {
|
||||
// Edit context is enabled
|
||||
const editContext = element.editContext;
|
||||
if (!editContext) {
|
||||
throw new Error(`Edit context not found: ${selector}`);
|
||||
}
|
||||
const selectionStart = editContext.selectionStart;
|
||||
const selectionEnd = editContext.selectionEnd;
|
||||
const event = new TextUpdateEvent('textupdate', {
|
||||
updateRangeStart: selectionStart,
|
||||
updateRangeEnd: selectionEnd,
|
||||
text,
|
||||
selectionStart: selectionStart + text.length,
|
||||
selectionEnd: selectionStart + text.length,
|
||||
compositionStart: 0,
|
||||
compositionEnd: 0
|
||||
});
|
||||
editContext.dispatchEvent(event);
|
||||
} else if (isHTMLTextAreaElement(element)) {
|
||||
const start = element.selectionStart;
|
||||
const newStart = start + text.length;
|
||||
const value = element.value;
|
||||
const newValue = value.substr(0, start) + text + value.substr(start);
|
||||
|
||||
const textarea = element as HTMLTextAreaElement;
|
||||
const start = textarea.selectionStart;
|
||||
const newStart = start + text.length;
|
||||
const value = textarea.value;
|
||||
const newValue = value.substr(0, start) + text + value.substr(start);
|
||||
element.value = newValue;
|
||||
element.setSelectionRange(newStart, newStart);
|
||||
|
||||
textarea.value = newValue;
|
||||
textarea.setSelectionRange(newStart, newStart);
|
||||
|
||||
const event = new Event('input', { 'bubbles': true, 'cancelable': true });
|
||||
textarea.dispatchEvent(event);
|
||||
const event = new Event('input', { 'bubbles': true, 'cancelable': true });
|
||||
element.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
async getTerminalBuffer(selector: string): Promise<string[]> {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize, localize2 } from '../../../../nls.js';
|
||||
import { IDisposable, combinedDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { IDisposable, combinedDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
|
||||
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
|
||||
@@ -27,6 +27,7 @@ import { ICommandService } from '../../../../platform/commands/common/commands.j
|
||||
import { isCancellationError } from '../../../../base/common/errors.js';
|
||||
import { INotificationService } from '../../../../platform/notification/common/notification.js';
|
||||
import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js';
|
||||
import { ResourceMap } from '../../../../base/common/map.js';
|
||||
|
||||
const FIVE_MINUTES = 5 * 60 * 1000;
|
||||
const THIRTY_SECONDS = 30 * 1000;
|
||||
@@ -99,6 +100,25 @@ type ExtensionUrlReloadHandlerClassification = {
|
||||
comment: 'This is used to understand the drop funnel of extension URI handling by the OS & VS Code.';
|
||||
};
|
||||
|
||||
export interface IExtensionUrlHandlerOverride {
|
||||
handleURL(uri: URI): Promise<boolean>;
|
||||
}
|
||||
|
||||
export class ExtensionUrlHandlerOverrideRegistry {
|
||||
|
||||
private static readonly handlers = new ResourceMap<IExtensionUrlHandlerOverride>();
|
||||
|
||||
static registerHandler(uri: URI, handler: IExtensionUrlHandlerOverride): IDisposable {
|
||||
this.handlers.set(uri, handler);
|
||||
|
||||
return toDisposable(() => this.handlers.delete(uri));
|
||||
}
|
||||
|
||||
static getHandler(uri: URI): IExtensionUrlHandlerOverride | undefined {
|
||||
return this.handlers.get(uri);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class handles URLs which are directed towards extensions.
|
||||
* If a URL is directed towards an inactive extension, it buffers it,
|
||||
@@ -153,6 +173,14 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
const overrideHandler = ExtensionUrlHandlerOverrideRegistry.getHandler(uri);
|
||||
if (overrideHandler) {
|
||||
const handled = await overrideHandler.handleURL(uri);
|
||||
if (handled) {
|
||||
return handled;
|
||||
}
|
||||
}
|
||||
|
||||
const extensionId = uri.authority;
|
||||
this.telemetryService.publicLog2<ExtensionUrlHandlerEvent, ExtensionUrlHandlerClassification>('uri_invoked/start', { extensionId });
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ export class CachedExtensionScanner {
|
||||
try {
|
||||
const language = platform.language;
|
||||
const result = await Promise.allSettled([
|
||||
this._extensionsScannerService.scanSystemExtensions({ language, useCache: true, checkControlFile: true }),
|
||||
this._extensionsScannerService.scanSystemExtensions({ language, checkControlFile: true }),
|
||||
this._extensionsScannerService.scanUserExtensions({ language, profileLocation: this._userDataProfileService.currentProfile.extensionsResource, useCache: true }),
|
||||
this._environmentService.remoteAuthority ? [] : this._extensionManagementService.getInstalledWorkspaceExtensions(false)
|
||||
]);
|
||||
@@ -86,7 +86,7 @@ export class CachedExtensionScanner {
|
||||
}
|
||||
|
||||
try {
|
||||
scannedDevelopedExtensions = await this._extensionsScannerService.scanExtensionsUnderDevelopment({ language }, [...scannedSystemExtensions, ...scannedUserExtensions]);
|
||||
scannedDevelopedExtensions = await this._extensionsScannerService.scanExtensionsUnderDevelopment([...scannedSystemExtensions, ...scannedUserExtensions], { language });
|
||||
} catch (error) {
|
||||
this._logService.error(error);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { DisposableStore, IDisposable } from '../../../../base/common/lifecycle.
|
||||
import { ThemeColor } from '../../../../base/common/themables.js';
|
||||
import { Command } from '../../../../editor/common/languages.js';
|
||||
import { IMarkdownString } from '../../../../base/common/htmlContent.js';
|
||||
import { IManagedHoverTooltipMarkdownString } from '../../../../base/browser/ui/hover/hover.js';
|
||||
import { ColorIdentifier } from '../../../../platform/theme/common/colorRegistry.js';
|
||||
import { IAuxiliaryStatusbarPart, IStatusbarEntryContainer } from '../../../browser/parts/statusbar/statusbarPart.js';
|
||||
|
||||
@@ -111,6 +112,19 @@ export interface IStatusbarStyleOverride {
|
||||
export type StatusbarEntryKind = 'standard' | 'warning' | 'error' | 'prominent' | 'remote' | 'offline';
|
||||
export const StatusbarEntryKinds: StatusbarEntryKind[] = ['standard', 'warning', 'error', 'prominent', 'remote', 'offline'];
|
||||
|
||||
export type TooltipContent = string | IMarkdownString | IManagedHoverTooltipMarkdownString | HTMLElement;
|
||||
|
||||
export interface ITooltipWithCommands {
|
||||
readonly content: TooltipContent;
|
||||
readonly commands: Command[];
|
||||
}
|
||||
|
||||
export function isTooltipWithCommands(thing: unknown): thing is ITooltipWithCommands {
|
||||
const candidate = thing as ITooltipWithCommands | undefined;
|
||||
|
||||
return !!candidate?.content && Array.isArray(candidate?.commands);
|
||||
}
|
||||
|
||||
/**
|
||||
* A declarative way of describing a status bar entry
|
||||
*/
|
||||
@@ -141,9 +155,11 @@ export interface IStatusbarEntry {
|
||||
readonly role?: string;
|
||||
|
||||
/**
|
||||
* An optional tooltip text to show when you hover over the entry
|
||||
* An optional tooltip text to show when you hover over the entry.
|
||||
*
|
||||
* Use `ITooltipWithCommands` to show a tooltip with commands in hover footer area.
|
||||
*/
|
||||
readonly tooltip?: string | IMarkdownString | HTMLElement;
|
||||
readonly tooltip?: TooltipContent | ITooltipWithCommands;
|
||||
|
||||
/**
|
||||
* An optional color to use for the entry.
|
||||
|
||||
@@ -26,6 +26,17 @@ import { PLAINTEXT_EXTENSION, PLAINTEXT_LANGUAGE_ID } from '../../../../editor/c
|
||||
import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';
|
||||
import { IProgress, IProgressStep } from '../../../../platform/progress/common/progress.js';
|
||||
|
||||
interface ITextFileEditorModelToRestore {
|
||||
readonly source: URI;
|
||||
readonly target: URI;
|
||||
readonly snapshot?: ITextSnapshot;
|
||||
readonly language?: {
|
||||
readonly id: string;
|
||||
readonly explicit: boolean;
|
||||
};
|
||||
readonly encoding?: string;
|
||||
}
|
||||
|
||||
export class TextFileEditorModelManager extends Disposable implements ITextFileEditorModelManager {
|
||||
|
||||
private readonly _onDidCreate = this._register(new Emitter<TextFileEditorModel>({ leakWarningThreshold: 500 /* increased for users with hundreds of inputs opened */ }));
|
||||
@@ -171,13 +182,13 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
}
|
||||
}
|
||||
|
||||
private readonly mapCorrelationIdToModelsToRestore = new Map<number, { source: URI; target: URI; snapshot?: ITextSnapshot; languageId?: string; encoding?: string }[]>();
|
||||
private readonly mapCorrelationIdToModelsToRestore = new Map<number, ITextFileEditorModelToRestore[]>();
|
||||
|
||||
private onWillRunWorkingCopyFileOperation(e: WorkingCopyFileEvent): void {
|
||||
|
||||
// Move / Copy: remember models to restore after the operation
|
||||
if (e.operation === FileOperation.MOVE || e.operation === FileOperation.COPY) {
|
||||
const modelsToRestore: { source: URI; target: URI; snapshot?: ITextSnapshot; languageId?: string; encoding?: string }[] = [];
|
||||
const modelsToRestore: ITextFileEditorModelToRestore[] = [];
|
||||
|
||||
for (const { source, target } of e.files) {
|
||||
if (source) {
|
||||
@@ -210,10 +221,14 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
targetModelResource = joinPath(target, sourceModelResource.path.substr(source.path.length + 1));
|
||||
}
|
||||
|
||||
const languageId = sourceModel.getLanguageId();
|
||||
modelsToRestore.push({
|
||||
source: sourceModelResource,
|
||||
target: targetModelResource,
|
||||
languageId: sourceModel.getLanguageId(),
|
||||
language: languageId ? {
|
||||
id: languageId,
|
||||
explicit: sourceModel.languageChangeSource === 'user'
|
||||
} : undefined,
|
||||
encoding: sourceModel.getEncoding(),
|
||||
snapshot: sourceModel.isDirty() ? sourceModel.createSnapshot() : undefined
|
||||
});
|
||||
@@ -286,16 +301,22 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
encoding: modelToRestore.encoding
|
||||
});
|
||||
|
||||
// restore previous language only if the language is now unspecified and it was specified
|
||||
// but not when the file was explicitly stored with the plain text extension
|
||||
// (https://github.com/microsoft/vscode/issues/125795)
|
||||
if (
|
||||
modelToRestore.languageId &&
|
||||
modelToRestore.languageId !== PLAINTEXT_LANGUAGE_ID &&
|
||||
restoredModel.getLanguageId() === PLAINTEXT_LANGUAGE_ID &&
|
||||
extname(target) !== PLAINTEXT_EXTENSION
|
||||
) {
|
||||
restoredModel.updateTextEditorModel(undefined, modelToRestore.languageId);
|
||||
// restore model language only if it is specific
|
||||
if (modelToRestore.language?.id && modelToRestore.language.id !== PLAINTEXT_LANGUAGE_ID) {
|
||||
|
||||
// an explicitly set language is restored via `setLanguageId`
|
||||
// to preserve it as explicitly set by the user.
|
||||
// (https://github.com/microsoft/vscode/issues/203648)
|
||||
if (modelToRestore.language.explicit) {
|
||||
restoredModel.setLanguageId(modelToRestore.language.id);
|
||||
}
|
||||
|
||||
// otherwise, a model language is applied via lower level
|
||||
// APIs to not confuse it with an explicitly set language.
|
||||
// (https://github.com/microsoft/vscode/issues/125795)
|
||||
else if (restoredModel.getLanguageId() === PLAINTEXT_LANGUAGE_ID && extname(target) !== PLAINTEXT_EXTENSION) {
|
||||
restoredModel.updateTextEditorModel(undefined, modelToRestore.language.id);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -162,6 +162,7 @@ export class TestNativeHostService implements INativeHostService {
|
||||
async windowsGetStringRegKey(hive: 'HKEY_CURRENT_USER' | 'HKEY_LOCAL_MACHINE' | 'HKEY_CLASSES_ROOT' | 'HKEY_USERS' | 'HKEY_CURRENT_CONFIG', path: string, name: string): Promise<string | undefined> { return undefined; }
|
||||
async profileRenderer(): Promise<any> { throw new Error(); }
|
||||
async getScreenshot(): Promise<ArrayBufferLike | undefined> { return undefined; }
|
||||
async overrideDefaultTitlebarStyle(style: 'native' | 'custom' | undefined): Promise<void> { }
|
||||
}
|
||||
|
||||
export class TestExtensionTipsService extends AbstractNativeExtensionTipsService {
|
||||
|
||||
@@ -51,6 +51,7 @@ declare module 'vscode' {
|
||||
readonly message: string;
|
||||
readonly displayId?: string;
|
||||
readonly author?: string;
|
||||
readonly authorEmail?: string;
|
||||
readonly timestamp?: number;
|
||||
readonly statistics?: SourceControlHistoryItemStatistics;
|
||||
readonly references?: SourceControlHistoryItemRef[];
|
||||
|
||||
@@ -12,6 +12,7 @@ import { launch as launchPlaywrightBrowser } from './playwrightBrowser';
|
||||
import { PlaywrightDriver } from './playwrightDriver';
|
||||
import { launch as launchPlaywrightElectron } from './playwrightElectron';
|
||||
import { teardown } from './processes';
|
||||
import { Quality } from './application';
|
||||
|
||||
export interface LaunchOptions {
|
||||
codePath?: string;
|
||||
@@ -28,6 +29,7 @@ export interface LaunchOptions {
|
||||
readonly tracing?: boolean;
|
||||
readonly headless?: boolean;
|
||||
readonly browser?: 'chromium' | 'webkit' | 'firefox';
|
||||
readonly quality: Quality;
|
||||
}
|
||||
|
||||
interface ICodeInstance {
|
||||
@@ -77,7 +79,7 @@ export async function launch(options: LaunchOptions): Promise<Code> {
|
||||
const { serverProcess, driver } = await measureAndLog(() => launchPlaywrightBrowser(options), 'launch playwright (browser)', options.logger);
|
||||
registerInstance(serverProcess, options.logger, 'server');
|
||||
|
||||
return new Code(driver, options.logger, serverProcess);
|
||||
return new Code(driver, options.logger, serverProcess, options.quality);
|
||||
}
|
||||
|
||||
// Electron smoke tests (playwright)
|
||||
@@ -85,7 +87,7 @@ export async function launch(options: LaunchOptions): Promise<Code> {
|
||||
const { electronProcess, driver } = await measureAndLog(() => launchPlaywrightElectron(options), 'launch playwright (electron)', options.logger);
|
||||
registerInstance(electronProcess, options.logger, 'electron');
|
||||
|
||||
return new Code(driver, options.logger, electronProcess);
|
||||
return new Code(driver, options.logger, electronProcess, options.quality);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +98,8 @@ export class Code {
|
||||
constructor(
|
||||
driver: PlaywrightDriver,
|
||||
readonly logger: Logger,
|
||||
private readonly mainProcess: cp.ChildProcess
|
||||
private readonly mainProcess: cp.ChildProcess,
|
||||
readonly quality: Quality
|
||||
) {
|
||||
this.driver = new Proxy(driver, {
|
||||
get(target, prop) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Code, findElement } from './code';
|
||||
import { Editors } from './editors';
|
||||
import { Editor } from './editor';
|
||||
import { IElement } from './driver';
|
||||
import { Quality } from './application';
|
||||
|
||||
const VIEWLET = 'div[id="workbench.view.debug"]';
|
||||
const DEBUG_VIEW = `${VIEWLET}`;
|
||||
@@ -31,7 +32,8 @@ const CONSOLE_OUTPUT = `.repl .output.expression .value`;
|
||||
const CONSOLE_EVALUATION_RESULT = `.repl .evaluation-result.expression .value`;
|
||||
const CONSOLE_LINK = `.repl .value a.link`;
|
||||
|
||||
const REPL_FOCUSED = '.repl-input-wrapper .monaco-editor textarea';
|
||||
const REPL_FOCUSED_NATIVE_EDIT_CONTEXT = '.repl-input-wrapper .monaco-editor .native-edit-context';
|
||||
const REPL_FOCUSED_TEXTAREA = '.repl-input-wrapper .monaco-editor textarea';
|
||||
|
||||
export interface IStackFrame {
|
||||
name: string;
|
||||
@@ -127,8 +129,9 @@ export class Debug extends Viewlet {
|
||||
|
||||
async waitForReplCommand(text: string, accept: (result: string) => boolean): Promise<void> {
|
||||
await this.commands.runCommand('Debug: Focus on Debug Console View');
|
||||
await this.code.waitForActiveElement(REPL_FOCUSED);
|
||||
await this.code.waitForSetValue(REPL_FOCUSED, text);
|
||||
const selector = this.code.quality === Quality.Stable ? REPL_FOCUSED_TEXTAREA : REPL_FOCUSED_NATIVE_EDIT_CONTEXT;
|
||||
await this.code.waitForActiveElement(selector);
|
||||
await this.code.waitForSetValue(selector, text);
|
||||
|
||||
// Wait for the keys to be picked up by the editor model such that repl evaluates what just got typed
|
||||
await this.editor.waitForEditorContents('debug:replinput', s => s.indexOf(text) >= 0);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { References } from './peek';
|
||||
import { Commands } from './workbench';
|
||||
import { Code } from './code';
|
||||
import { Quality } from './application';
|
||||
|
||||
const RENAME_BOX = '.monaco-editor .monaco-editor.rename-box';
|
||||
const RENAME_INPUT = `${RENAME_BOX} .rename-input`;
|
||||
@@ -78,10 +79,10 @@ export class Editor {
|
||||
async waitForEditorFocus(filename: string, lineNumber: number, selectorPrefix = ''): Promise<void> {
|
||||
const editor = [selectorPrefix || '', EDITOR(filename)].join(' ');
|
||||
const line = `${editor} .view-lines > .view-line:nth-child(${lineNumber})`;
|
||||
const textarea = `${editor} textarea`;
|
||||
const editContext = `${editor} ${this._editContextSelector()}`;
|
||||
|
||||
await this.code.waitAndClick(line, 1, 1);
|
||||
await this.code.waitForActiveElement(textarea);
|
||||
await this.code.waitForActiveElement(editContext);
|
||||
}
|
||||
|
||||
async waitForTypeInEditor(filename: string, text: string, selectorPrefix = ''): Promise<any> {
|
||||
@@ -92,14 +93,18 @@ export class Editor {
|
||||
|
||||
await this.code.waitForElement(editor);
|
||||
|
||||
const textarea = `${editor} textarea`;
|
||||
await this.code.waitForActiveElement(textarea);
|
||||
const editContext = `${editor} ${this._editContextSelector()}`;
|
||||
await this.code.waitForActiveElement(editContext);
|
||||
|
||||
await this.code.waitForTypeInEditor(textarea, text);
|
||||
await this.code.waitForTypeInEditor(editContext, text);
|
||||
|
||||
await this.waitForEditorContents(filename, c => c.indexOf(text) > -1, selectorPrefix);
|
||||
}
|
||||
|
||||
private _editContextSelector() {
|
||||
return this.code.quality === Quality.Stable ? 'textarea' : '.native-edit-context';
|
||||
}
|
||||
|
||||
async waitForEditorContents(filename: string, accept: (contents: string) => boolean, selectorPrefix = ''): Promise<any> {
|
||||
const selector = [selectorPrefix || '', `${EDITOR(filename)} .view-lines`].join(' ');
|
||||
return this.code.waitForTextContent(selector, undefined, c => accept(c.replace(/\u00a0/g, ' ')));
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Quality } from './application';
|
||||
import { Code } from './code';
|
||||
|
||||
export class Editors {
|
||||
@@ -53,7 +54,7 @@ export class Editors {
|
||||
}
|
||||
|
||||
async waitForActiveEditor(fileName: string, retryCount?: number): Promise<any> {
|
||||
const selector = `.editor-instance .monaco-editor[data-uri$="${fileName}"] textarea`;
|
||||
const selector = `.editor-instance .monaco-editor[data-uri$="${fileName}"] ${this.code.quality === Quality.Stable ? 'textarea' : '.native-edit-context'}`;
|
||||
return this.code.waitForActiveElement(selector, retryCount);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Code } from './code';
|
||||
import { ncp } from 'ncp';
|
||||
import { promisify } from 'util';
|
||||
import { Commands } from './workbench';
|
||||
import { Quality } from './application';
|
||||
import path = require('path');
|
||||
import fs = require('fs');
|
||||
|
||||
@@ -20,7 +21,7 @@ export class Extensions extends Viewlet {
|
||||
|
||||
async searchForExtension(id: string): Promise<any> {
|
||||
await this.commands.runCommand('Extensions: Focus on Extensions View', { exactLabelMatch: true });
|
||||
await this.code.waitForTypeInEditor('div.extensions-viewlet[id="workbench.view.extensions"] .monaco-editor textarea', `@id:${id}`);
|
||||
await this.code.waitForTypeInEditor(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-editor ${this.code.quality === Quality.Stable ? 'textarea' : '.native-edit-context'}`, `@id:${id}`);
|
||||
await this.code.waitForTextContent(`div.part.sidebar div.composite.title h2`, 'Extensions: Marketplace');
|
||||
|
||||
let retrials = 1;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Quality } from './application';
|
||||
import { Code } from './code';
|
||||
import { QuickAccess } from './quickaccess';
|
||||
import { QuickInput } from './quickinput';
|
||||
@@ -46,10 +47,10 @@ export class Notebook {
|
||||
|
||||
await this.code.waitForElement(editor);
|
||||
|
||||
const textarea = `${editor} textarea`;
|
||||
await this.code.waitForActiveElement(textarea);
|
||||
const editContext = `${editor} ${this.code.quality === Quality.Stable ? 'textarea' : '.native-edit-context'}`;
|
||||
await this.code.waitForActiveElement(editContext);
|
||||
|
||||
await this.code.waitForTypeInEditor(textarea, text);
|
||||
await this.code.waitForTypeInEditor(editContext, text);
|
||||
|
||||
await this._waitForActiveCellEditorContents(c => c.indexOf(text) > -1);
|
||||
}
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
import { Viewlet } from './viewlet';
|
||||
import { IElement } from './driver';
|
||||
import { findElement, findElements, Code } from './code';
|
||||
import { Quality } from './application';
|
||||
|
||||
const VIEWLET = 'div[id="workbench.view.scm"]';
|
||||
const SCM_INPUT = `${VIEWLET} .scm-editor textarea`;
|
||||
const SCM_INPUT_NATIVE_EDIT_CONTEXT = `${VIEWLET} .scm-editor .native-edit-context`;
|
||||
const SCM_INPUT_TEXTAREA = `${VIEWLET} .scm-editor textarea`;
|
||||
const SCM_RESOURCE = `${VIEWLET} .monaco-list-row .resource`;
|
||||
const REFRESH_COMMAND = `div[id="workbench.parts.sidebar"] .actions-container a.action-label[aria-label="Refresh"]`;
|
||||
const COMMIT_COMMAND = `div[id="workbench.parts.sidebar"] .actions-container a.action-label[aria-label="Commit"]`;
|
||||
@@ -44,7 +46,7 @@ export class SCM extends Viewlet {
|
||||
|
||||
async openSCMViewlet(): Promise<any> {
|
||||
await this.code.dispatchKeybinding('ctrl+shift+g');
|
||||
await this.code.waitForElement(SCM_INPUT);
|
||||
await this.code.waitForElement(this._editContextSelector());
|
||||
}
|
||||
|
||||
async waitForChange(name: string, type?: string): Promise<void> {
|
||||
@@ -71,9 +73,13 @@ export class SCM extends Viewlet {
|
||||
}
|
||||
|
||||
async commit(message: string): Promise<void> {
|
||||
await this.code.waitAndClick(SCM_INPUT);
|
||||
await this.code.waitForActiveElement(SCM_INPUT);
|
||||
await this.code.waitForSetValue(SCM_INPUT, message);
|
||||
await this.code.waitAndClick(this._editContextSelector());
|
||||
await this.code.waitForActiveElement(this._editContextSelector());
|
||||
await this.code.waitForSetValue(this._editContextSelector(), message);
|
||||
await this.code.waitAndClick(COMMIT_COMMAND);
|
||||
}
|
||||
|
||||
private _editContextSelector(): string {
|
||||
return this.code.quality === Quality.Stable ? SCM_INPUT_TEXTAREA : SCM_INPUT_NATIVE_EDIT_CONTEXT;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,10 @@ import { Editor } from './editor';
|
||||
import { Editors } from './editors';
|
||||
import { Code } from './code';
|
||||
import { QuickAccess } from './quickaccess';
|
||||
import { Quality } from './application';
|
||||
|
||||
const SEARCH_BOX = '.settings-editor .suggest-input-container .monaco-editor textarea';
|
||||
const SEARCH_BOX_NATIVE_EDIT_CONTEXT = '.settings-editor .suggest-input-container .monaco-editor .native-edit-context';
|
||||
const SEARCH_BOX_TEXTAREA = '.settings-editor .suggest-input-container .monaco-editor textarea';
|
||||
|
||||
export class SettingsEditor {
|
||||
constructor(private code: Code, private editors: Editors, private editor: Editor, private quickaccess: QuickAccess) { }
|
||||
@@ -57,13 +59,13 @@ export class SettingsEditor {
|
||||
|
||||
async openUserSettingsUI(): Promise<void> {
|
||||
await this.quickaccess.runCommand('workbench.action.openSettings2');
|
||||
await this.code.waitForActiveElement(SEARCH_BOX);
|
||||
await this.code.waitForActiveElement(this._editContextSelector());
|
||||
}
|
||||
|
||||
async searchSettingsUI(query: string): Promise<void> {
|
||||
await this.openUserSettingsUI();
|
||||
|
||||
await this.code.waitAndClick(SEARCH_BOX);
|
||||
await this.code.waitAndClick(this._editContextSelector());
|
||||
if (process.platform === 'darwin') {
|
||||
await this.code.dispatchKeybinding('cmd+a');
|
||||
} else {
|
||||
@@ -71,7 +73,11 @@ export class SettingsEditor {
|
||||
}
|
||||
await this.code.dispatchKeybinding('Delete');
|
||||
await this.code.waitForElements('.settings-editor .settings-count-widget', false, results => !results || (results?.length === 1 && !results[0].textContent));
|
||||
await this.code.waitForTypeInEditor('.settings-editor .suggest-input-container .monaco-editor textarea', query);
|
||||
await this.code.waitForTypeInEditor(this._editContextSelector(), query);
|
||||
await this.code.waitForElements('.settings-editor .settings-count-widget', false, results => results?.length === 1 && results[0].textContent.includes('Found'));
|
||||
}
|
||||
|
||||
private _editContextSelector() {
|
||||
return this.code.quality === Quality.Stable ? SEARCH_BOX_TEXTAREA : SEARCH_BOX_NATIVE_EDIT_CONTEXT;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user