mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-13 15:35:20 +01:00
Merge branch 'main' into tyriar/183236
This commit is contained in:
@@ -5,9 +5,9 @@
|
||||
|
||||
import { Disposable, commands } from 'vscode';
|
||||
import { Model } from '../model';
|
||||
import { pickRemoteSource } from '../remoteSource';
|
||||
import { getRemoteSourceActions, pickRemoteSource } from '../remoteSource';
|
||||
import { GitBaseExtensionImpl } from './extension';
|
||||
import { API, PickRemoteSourceOptions, PickRemoteSourceResult, RemoteSourceProvider } from './git-base';
|
||||
import { API, PickRemoteSourceOptions, PickRemoteSourceResult, RemoteSourceAction, RemoteSourceProvider } from './git-base';
|
||||
|
||||
export class ApiImpl implements API {
|
||||
|
||||
@@ -17,6 +17,10 @@ export class ApiImpl implements API {
|
||||
return pickRemoteSource(this._model, options as any);
|
||||
}
|
||||
|
||||
getRemoteSourceActions(url: string): Promise<RemoteSourceAction[]> {
|
||||
return getRemoteSourceActions(this._model, url);
|
||||
}
|
||||
|
||||
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable {
|
||||
return this._model.registerRemoteSourceProvider(provider);
|
||||
}
|
||||
|
||||
+10
@@ -44,6 +44,15 @@ export interface PickRemoteSourceResult {
|
||||
readonly branch?: string;
|
||||
}
|
||||
|
||||
export interface RemoteSourceAction {
|
||||
readonly label: string;
|
||||
/**
|
||||
* Codicon name
|
||||
*/
|
||||
readonly icon: string;
|
||||
run(branch: string): void;
|
||||
}
|
||||
|
||||
export interface RemoteSource {
|
||||
readonly name: string;
|
||||
readonly description?: string;
|
||||
@@ -70,6 +79,7 @@ export interface RemoteSourceProvider {
|
||||
readonly supportsQuery?: boolean;
|
||||
|
||||
getBranches?(url: string): ProviderResult<string[]>;
|
||||
getRemoteSourceActions?(url: string): ProviderResult<RemoteSourceAction[]>;
|
||||
getRecentRemoteSources?(query?: string): ProviderResult<RecentRemoteSource[]>;
|
||||
getRemoteSources(query?: string): ProviderResult<RemoteSource[]>;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { QuickPickItem, window, QuickPick, QuickPickItemKind, l10n } from 'vscode';
|
||||
import { RemoteSourceProvider, RemoteSource, PickRemoteSourceOptions, PickRemoteSourceResult } from './api/git-base';
|
||||
import { RemoteSourceProvider, RemoteSource, PickRemoteSourceOptions, PickRemoteSourceResult, RemoteSourceAction } from './api/git-base';
|
||||
import { Model } from './model';
|
||||
import { throttle, debounce } from './decorators';
|
||||
|
||||
@@ -81,6 +81,20 @@ class RemoteSourceProviderQuickPick {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getRemoteSourceActions(model: Model, url: string): Promise<RemoteSourceAction[]> {
|
||||
const providers = model.getRemoteProviders();
|
||||
|
||||
const remoteSourceActions = [];
|
||||
for (const provider of providers) {
|
||||
const providerActions = await provider.getRemoteSourceActions?.(url);
|
||||
if (providerActions?.length) {
|
||||
remoteSourceActions.push(...providerActions);
|
||||
}
|
||||
}
|
||||
|
||||
return remoteSourceActions;
|
||||
}
|
||||
|
||||
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch?: false | undefined }): Promise<string | undefined>;
|
||||
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch: true }): Promise<PickRemoteSourceResult | undefined>;
|
||||
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions = {}): Promise<string | PickRemoteSourceResult | undefined> {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Model } from '../model';
|
||||
import { Repository as BaseRepository, Resource } from '../repository';
|
||||
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery, BranchProtectionProvider } from './git';
|
||||
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery, BranchProtectionProvider, InitOptions } from './git';
|
||||
import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands, CancellationToken } from 'vscode';
|
||||
import { combinedDisposable, mapEvent } from '../util';
|
||||
import { toGitUri } from '../uri';
|
||||
@@ -294,9 +294,9 @@ export class ApiImpl implements API {
|
||||
return result ? new ApiRepository(result) : null;
|
||||
}
|
||||
|
||||
async init(root: Uri): Promise<Repository | null> {
|
||||
async init(root: Uri, options?: InitOptions): Promise<Repository | null> {
|
||||
const path = root.fsPath;
|
||||
await this._model.git.init(path);
|
||||
await this._model.git.init(path, options);
|
||||
await this._model.openRepository(path);
|
||||
return this.getRepository(root) || null;
|
||||
}
|
||||
@@ -362,6 +362,7 @@ function getStatus(status: Status): string {
|
||||
case Status.UNTRACKED: return 'UNTRACKED';
|
||||
case Status.IGNORED: return 'IGNORED';
|
||||
case Status.INTENT_TO_ADD: return 'INTENT_TO_ADD';
|
||||
case Status.INTENT_TO_RENAME: return 'INTENT_TO_RENAME';
|
||||
case Status.ADDED_BY_US: return 'ADDED_BY_US';
|
||||
case Status.ADDED_BY_THEM: return 'ADDED_BY_THEM';
|
||||
case Status.DELETED_BY_US: return 'DELETED_BY_US';
|
||||
|
||||
Vendored
+27
-1
@@ -8,6 +8,7 @@ export { ProviderResult } from 'vscode';
|
||||
|
||||
export interface API {
|
||||
pickRemoteSource(options: PickRemoteSourceOptions): Promise<string | PickRemoteSourceResult | undefined>;
|
||||
getRemoteSourceActions(url: string): Promise<RemoteSourceAction[]>;
|
||||
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable;
|
||||
}
|
||||
|
||||
@@ -31,9 +32,12 @@ export interface GitBaseExtension {
|
||||
|
||||
export interface PickRemoteSourceOptions {
|
||||
readonly providerLabel?: (provider: RemoteSourceProvider) => string;
|
||||
readonly urlLabel?: string;
|
||||
readonly urlLabel?: string | ((url: string) => string);
|
||||
readonly providerName?: string;
|
||||
readonly title?: string;
|
||||
readonly placeholder?: string;
|
||||
readonly branch?: boolean; // then result is PickRemoteSourceResult
|
||||
readonly showRecentSources?: boolean;
|
||||
}
|
||||
|
||||
export interface PickRemoteSourceResult {
|
||||
@@ -41,20 +45,42 @@ export interface PickRemoteSourceResult {
|
||||
readonly branch?: string;
|
||||
}
|
||||
|
||||
export interface RemoteSourceAction {
|
||||
readonly label: string;
|
||||
/**
|
||||
* Codicon name
|
||||
*/
|
||||
readonly icon: string;
|
||||
run(branch: string): void;
|
||||
}
|
||||
|
||||
export interface RemoteSource {
|
||||
readonly name: string;
|
||||
readonly description?: string;
|
||||
readonly detail?: string;
|
||||
/**
|
||||
* Codicon name
|
||||
*/
|
||||
readonly icon?: string;
|
||||
readonly url: string | string[];
|
||||
}
|
||||
|
||||
export interface RecentRemoteSource extends RemoteSource {
|
||||
readonly timestamp: number;
|
||||
}
|
||||
|
||||
export interface RemoteSourceProvider {
|
||||
readonly name: string;
|
||||
/**
|
||||
* Codicon name
|
||||
*/
|
||||
readonly icon?: string;
|
||||
readonly label?: string;
|
||||
readonly placeholder?: string;
|
||||
readonly supportsQuery?: boolean;
|
||||
|
||||
getBranches?(url: string): ProviderResult<string[]>;
|
||||
getRemoteSourceActions?(url: string): ProviderResult<RemoteSourceAction[]>;
|
||||
getRecentRemoteSources?(query?: string): ProviderResult<RecentRemoteSource[]>;
|
||||
getRemoteSources(query?: string): ProviderResult<RemoteSource[]>;
|
||||
}
|
||||
|
||||
Vendored
+6
-1
@@ -78,6 +78,7 @@ export const enum Status {
|
||||
UNTRACKED,
|
||||
IGNORED,
|
||||
INTENT_TO_ADD,
|
||||
INTENT_TO_RENAME,
|
||||
|
||||
ADDED_BY_US,
|
||||
ADDED_BY_THEM,
|
||||
@@ -156,6 +157,10 @@ export interface FetchOptions {
|
||||
depth?: number;
|
||||
}
|
||||
|
||||
export interface InitOptions {
|
||||
defaultBranch?: string;
|
||||
}
|
||||
|
||||
export interface RefQuery {
|
||||
readonly contains?: string;
|
||||
readonly count?: number;
|
||||
@@ -307,7 +312,7 @@ export interface API {
|
||||
|
||||
toGitUri(uri: Uri, ref: string): Uri;
|
||||
getRepository(uri: Uri): Repository | null;
|
||||
init(root: Uri): Promise<Repository | null>;
|
||||
init(root: Uri, options?: InitOptions): Promise<Repository | null>;
|
||||
openRepository(root: Uri): Promise<Repository | null>
|
||||
|
||||
registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind } from 'vscode';
|
||||
import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon } from 'vscode';
|
||||
import TelemetryReporter from '@vscode/extension-telemetry';
|
||||
import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator';
|
||||
import { Branch, ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote } from './api/git';
|
||||
@@ -17,7 +17,8 @@ import { fromGitUri, toGitUri, isGitUri, toMergeUris } from './uri';
|
||||
import { grep, isDescendant, pathEquals, relativePath } from './util';
|
||||
import { GitTimelineItem } from './timelineProvider';
|
||||
import { ApiRepository } from './api/api1';
|
||||
import { pickRemoteSource } from './remoteSource';
|
||||
import { getRemoteSourceActions, pickRemoteSource } from './remoteSource';
|
||||
import { RemoteSourceAction } from './api/git-base';
|
||||
|
||||
class CheckoutItem implements QuickPickItem {
|
||||
|
||||
@@ -25,8 +26,11 @@ class CheckoutItem implements QuickPickItem {
|
||||
get label(): string { return `${this.repository.isBranchProtected(this.ref) ? '$(lock)' : '$(git-branch)'} ${this.ref.name || this.shortCommit}`; }
|
||||
get description(): string { return this.shortCommit; }
|
||||
get refName(): string | undefined { return this.ref.name; }
|
||||
get refRemote(): string | undefined { return this.ref.remote; }
|
||||
get buttons(): QuickInputButton[] | undefined { return this._buttons; }
|
||||
set buttons(newButtons: QuickInputButton[] | undefined) { this._buttons = newButtons; }
|
||||
|
||||
constructor(protected repository: Repository, protected ref: Ref) { }
|
||||
constructor(protected repository: Repository, protected ref: Ref, protected _buttons?: QuickInputButton[]) { }
|
||||
|
||||
async run(opts?: { detached?: boolean }): Promise<void> {
|
||||
if (!this.ref.name) {
|
||||
@@ -278,7 +282,54 @@ async function createCheckoutItems(repository: Repository, detached = false): Pr
|
||||
}
|
||||
}
|
||||
|
||||
return processors.reduce<CheckoutItem[]>((r, p) => r.concat(...p.items), []);
|
||||
const buttons = await getRemoteRefItemButtons(repository);
|
||||
let fallbackRemoteButtons: RemoteSourceActionButton[] | undefined = [];
|
||||
const remote = repository.remotes.find(r => r.pushUrl === repository.HEAD?.remote || r.fetchUrl === repository.HEAD?.remote) ?? repository.remotes[0];
|
||||
const remoteUrl = remote.pushUrl ?? remote.fetchUrl;
|
||||
if (remoteUrl) {
|
||||
fallbackRemoteButtons = buttons.get(remoteUrl);
|
||||
}
|
||||
|
||||
return processors.reduce<CheckoutItem[]>((r, p) => r.concat(...p.items.map((item) => {
|
||||
if (item.refRemote) {
|
||||
const matchingRemote = repository.remotes.find((remote) => remote.name === item.refRemote);
|
||||
const remoteUrl = matchingRemote?.pushUrl ?? matchingRemote?.fetchUrl;
|
||||
if (remoteUrl) {
|
||||
item.buttons = buttons.get(item.refRemote);
|
||||
}
|
||||
}
|
||||
|
||||
item.buttons = fallbackRemoteButtons;
|
||||
return item;
|
||||
})), []);
|
||||
}
|
||||
|
||||
type RemoteSourceActionButton = {
|
||||
iconPath: ThemeIcon;
|
||||
tooltip: string;
|
||||
actual: RemoteSourceAction;
|
||||
};
|
||||
|
||||
async function getRemoteRefItemButtons(repository: Repository) {
|
||||
// Compute actions for all known remotes
|
||||
const remoteUrlsToActions = new Map<string, RemoteSourceActionButton[]>();
|
||||
|
||||
const getButtons = async (remoteUrl: string) => (await getRemoteSourceActions(remoteUrl)).map((action) => ({ iconPath: new ThemeIcon(action.icon), tooltip: action.label, actual: action }));
|
||||
|
||||
for (const remote of repository.remotes) {
|
||||
if (remote.fetchUrl) {
|
||||
const actions = remoteUrlsToActions.get(remote.fetchUrl) ?? [];
|
||||
actions.push(...await getButtons(remote.fetchUrl));
|
||||
remoteUrlsToActions.set(remote.fetchUrl, actions);
|
||||
}
|
||||
if (remote.pushUrl && remote.pushUrl !== remote.fetchUrl) {
|
||||
const actions = remoteUrlsToActions.get(remote.pushUrl) ?? [];
|
||||
actions.push(...await getButtons(remote.pushUrl));
|
||||
remoteUrlsToActions.set(remote.pushUrl, actions);
|
||||
}
|
||||
}
|
||||
|
||||
return remoteUrlsToActions;
|
||||
}
|
||||
|
||||
class CheckoutProcessor {
|
||||
@@ -2084,7 +2135,17 @@ export class CommandCenter {
|
||||
quickpick.items = picks;
|
||||
quickpick.busy = false;
|
||||
|
||||
const choice = await new Promise<QuickPickItem | undefined>(c => quickpick.onDidAccept(() => c(quickpick.activeItems[0])));
|
||||
const choice = await new Promise<QuickPickItem | undefined>(c => {
|
||||
quickpick.onDidAccept(() => c(quickpick.activeItems[0]));
|
||||
quickpick.onDidTriggerItemButton((e) => {
|
||||
quickpick.hide();
|
||||
const button = e.button as QuickInputButton & { actual: RemoteSourceAction };
|
||||
const item = e.item as CheckoutItem;
|
||||
if (button.actual && item.refName) {
|
||||
button.actual.run(item.refRemote ? item.refName.substring(item.refRemote.length + 1) : item.refName);
|
||||
}
|
||||
});
|
||||
});
|
||||
quickpick.hide();
|
||||
|
||||
if (!choice) {
|
||||
|
||||
@@ -131,7 +131,7 @@ class GitDecorationProvider implements FileDecorationProvider {
|
||||
bucket.set(r.rightUri.toString(), decoration);
|
||||
}
|
||||
|
||||
if (r.type === Status.INDEX_RENAMED) {
|
||||
if (r.type === Status.INDEX_RENAMED || r.type === Status.INTENT_TO_RENAME) {
|
||||
bucket.set(r.resourceUri.toString(), decoration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import * as filetype from 'file-type';
|
||||
import { assign, groupBy, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter, Versions, isWindows, pathEquals } from './util';
|
||||
import { CancellationError, CancellationToken, ConfigurationChangeEvent, LogOutputChannel, Progress, Uri, workspace } from 'vscode';
|
||||
import { detectEncoding } from './encoding';
|
||||
import { Ref, RefType, Branch, Remote, ForcePushMode, GitErrorCodes, LogOptions, Change, Status, CommitOptions, RefQuery } from './api/git';
|
||||
import { Ref, RefType, Branch, Remote, ForcePushMode, GitErrorCodes, LogOptions, Change, Status, CommitOptions, RefQuery, InitOptions } from './api/git';
|
||||
import * as byline from 'byline';
|
||||
import { StringDecoder } from 'string_decoder';
|
||||
|
||||
@@ -401,7 +401,7 @@ export class Git {
|
||||
return new Repository(this, repository, dotGit, logger);
|
||||
}
|
||||
|
||||
async init(repository: string, options: { defaultBranch?: string } = {}): Promise<void> {
|
||||
async init(repository: string, options: InitOptions = {}): Promise<void> {
|
||||
const args = ['init'];
|
||||
|
||||
if (options.defaultBranch && options.defaultBranch !== '') {
|
||||
@@ -793,7 +793,7 @@ export class GitStatusParser {
|
||||
// space
|
||||
i++;
|
||||
|
||||
if (entry.x === 'R' || entry.x === 'C') {
|
||||
if (entry.x === 'R' || entry.y === 'R' || entry.x === 'C') {
|
||||
lastIndex = raw.indexOf('\0', i);
|
||||
|
||||
if (lastIndex === -1) {
|
||||
|
||||
@@ -11,3 +11,7 @@ export async function pickRemoteSource(options: PickRemoteSourceOptions & { bran
|
||||
export async function pickRemoteSource(options: PickRemoteSourceOptions = {}): Promise<string | PickRemoteSourceResult | undefined> {
|
||||
return GitBaseApi.getAPI().pickRemoteSource(options);
|
||||
}
|
||||
|
||||
export async function getRemoteSourceActions(url: string) {
|
||||
return GitBaseApi.getAPI().getRemoteSourceActions(url);
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ export class Resource implements SourceControlResourceState {
|
||||
case Status.UNTRACKED: return l10n.t('Untracked');
|
||||
case Status.IGNORED: return l10n.t('Ignored');
|
||||
case Status.INTENT_TO_ADD: return l10n.t('Intent to Add');
|
||||
case Status.INTENT_TO_RENAME: return l10n.t('Intent to Rename');
|
||||
case Status.BOTH_DELETED: return l10n.t('Conflict: Both Deleted');
|
||||
case Status.ADDED_BY_US: return l10n.t('Conflict: Added By Us');
|
||||
case Status.DELETED_BY_THEM: return l10n.t('Conflict: Deleted By Them');
|
||||
@@ -71,7 +72,7 @@ export class Resource implements SourceControlResourceState {
|
||||
|
||||
@memoize
|
||||
get resourceUri(): Uri {
|
||||
if (this.renameResourceUri && (this._type === Status.MODIFIED || this._type === Status.DELETED || this._type === Status.INDEX_RENAMED || this._type === Status.INDEX_COPIED)) {
|
||||
if (this.renameResourceUri && (this._type === Status.MODIFIED || this._type === Status.DELETED || this._type === Status.INDEX_RENAMED || this._type === Status.INDEX_COPIED || this._type === Status.INTENT_TO_RENAME)) {
|
||||
return this.renameResourceUri;
|
||||
}
|
||||
|
||||
@@ -136,6 +137,7 @@ export class Resource implements SourceControlResourceState {
|
||||
case Status.UNTRACKED: return Resource.Icons[theme].Untracked;
|
||||
case Status.IGNORED: return Resource.Icons[theme].Ignored;
|
||||
case Status.INTENT_TO_ADD: return Resource.Icons[theme].Added;
|
||||
case Status.INTENT_TO_RENAME: return Resource.Icons[theme].Renamed;
|
||||
case Status.BOTH_DELETED: return Resource.Icons[theme].Conflict;
|
||||
case Status.ADDED_BY_US: return Resource.Icons[theme].Conflict;
|
||||
case Status.DELETED_BY_THEM: return Resource.Icons[theme].Conflict;
|
||||
@@ -193,6 +195,7 @@ export class Resource implements SourceControlResourceState {
|
||||
case Status.DELETED:
|
||||
return 'D';
|
||||
case Status.INDEX_RENAMED:
|
||||
case Status.INTENT_TO_RENAME:
|
||||
return 'R';
|
||||
case Status.UNTRACKED:
|
||||
return 'U';
|
||||
@@ -230,6 +233,7 @@ export class Resource implements SourceControlResourceState {
|
||||
return new ThemeColor('gitDecoration.addedResourceForeground');
|
||||
case Status.INDEX_COPIED:
|
||||
case Status.INDEX_RENAMED:
|
||||
case Status.INTENT_TO_RENAME:
|
||||
return new ThemeColor('gitDecoration.renamedResourceForeground');
|
||||
case Status.UNTRACKED:
|
||||
return new ThemeColor('gitDecoration.untrackedResourceForeground');
|
||||
@@ -520,6 +524,7 @@ class ResourceCommandResolver {
|
||||
case Status.INDEX_MODIFIED:
|
||||
case Status.INDEX_RENAMED:
|
||||
case Status.INDEX_ADDED:
|
||||
case Status.INTENT_TO_RENAME:
|
||||
return toGitUri(resource.original, 'HEAD');
|
||||
|
||||
case Status.MODIFIED:
|
||||
@@ -554,7 +559,8 @@ class ResourceCommandResolver {
|
||||
case Status.MODIFIED:
|
||||
case Status.UNTRACKED:
|
||||
case Status.IGNORED:
|
||||
case Status.INTENT_TO_ADD: {
|
||||
case Status.INTENT_TO_ADD:
|
||||
case Status.INTENT_TO_RENAME: {
|
||||
const uriString = resource.resourceUri.toString();
|
||||
const [indexStatus] = this.repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString);
|
||||
|
||||
@@ -599,6 +605,10 @@ class ResourceCommandResolver {
|
||||
case Status.UNTRACKED:
|
||||
return l10n.t('{0} (Untracked)', basename);
|
||||
|
||||
case Status.INTENT_TO_ADD:
|
||||
case Status.INTENT_TO_RENAME:
|
||||
return l10n.t('{0} (Intent to add)', basename);
|
||||
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
@@ -2177,6 +2187,7 @@ export class Repository implements Disposable {
|
||||
case 'M': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.MODIFIED, useIcons, renameUri)); break;
|
||||
case 'D': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.DELETED, useIcons, renameUri)); break;
|
||||
case 'A': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.INTENT_TO_ADD, useIcons, renameUri)); break;
|
||||
case 'R': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.INTENT_TO_RENAME, useIcons, renameUri)); break;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
||||
@@ -7,11 +7,7 @@ import * as vscode from 'vscode';
|
||||
import { API as GitAPI } from './typings/git';
|
||||
import { publishRepository } from './publish';
|
||||
import { DisposableStore } from './util';
|
||||
import { LinkContext, getLink } from './links';
|
||||
|
||||
function getVscodeDevHost(): string {
|
||||
return `https://${vscode.env.appName.toLowerCase().includes('insiders') ? 'insiders.' : ''}vscode.dev/github`;
|
||||
}
|
||||
import { LinkContext, getLink, getVscodeDevHost } from './links';
|
||||
|
||||
async function copyVscodeDevLink(gitAPI: GitAPI, useSelection: boolean, context: LinkContext, includeRange = true) {
|
||||
try {
|
||||
|
||||
@@ -168,3 +168,17 @@ export function getLink(gitAPI: GitAPI, useSelection: boolean, hostPrefix?: stri
|
||||
return `${hostPrefix}/${repo.owner}/${repo.repo}${blobSegment
|
||||
}${fileSegments}`;
|
||||
}
|
||||
|
||||
export function getBranchLink(url: string, branch: string, hostPrefix: string = 'https://github.com') {
|
||||
const repo = getRepositoryFromUrl(url);
|
||||
if (!repo) {
|
||||
throw new Error('Invalid repository URL provided');
|
||||
}
|
||||
|
||||
branch = encodeURIComponentExceptSlashes(branch);
|
||||
return `${hostPrefix}/${repo.owner}/${repo.repo}/tree/${branch}`;
|
||||
}
|
||||
|
||||
export function getVscodeDevHost(): string {
|
||||
return `https://${vscode.env.appName.toLowerCase().includes('insiders') ? 'insiders.' : ''}vscode.dev/github`;
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ export async function publishRepository(gitAPI: GitAPI, repository?: Repository)
|
||||
progress.report({ message: vscode.l10n.t('Creating first commit'), increment: 25 });
|
||||
|
||||
if (!repository) {
|
||||
repository = await gitAPI.init(folder) || undefined;
|
||||
repository = await gitAPI.init(folder, { defaultBranch: createdGithubRepository.default_branch }) || undefined;
|
||||
|
||||
if (!repository) {
|
||||
return;
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { workspace } from 'vscode';
|
||||
import { RemoteSourceProvider, RemoteSource } from './typings/git-base';
|
||||
import { Uri, env, l10n, workspace } from 'vscode';
|
||||
import { RemoteSourceProvider, RemoteSource, RemoteSourceAction } from './typings/git-base';
|
||||
import { getOctokit } from './auth';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { getRepositoryFromQuery, getRepositoryFromUrl } from './util';
|
||||
import { getBranchLink, getVscodeDevHost } from './links';
|
||||
|
||||
function asRemoteSource(raw: any): RemoteSource {
|
||||
const protocol = workspace.getConfiguration('github').get<'https' | 'ssh'>('gitProtocol');
|
||||
@@ -112,4 +113,27 @@ export class GithubRemoteSourceProvider implements RemoteSourceProvider {
|
||||
|
||||
return branches.sort((a, b) => a === defaultBranch ? -1 : b === defaultBranch ? 1 : 0);
|
||||
}
|
||||
|
||||
async getRemoteSourceActions(url: string): Promise<RemoteSourceAction[]> {
|
||||
const repository = getRepositoryFromUrl(url);
|
||||
if (!repository) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [{
|
||||
label: l10n.t('Open on GitHub'),
|
||||
icon: 'github',
|
||||
run(branch: string) {
|
||||
const link = getBranchLink(url, branch);
|
||||
env.openExternal(Uri.parse(link));
|
||||
}
|
||||
}, {
|
||||
label: l10n.t('Checkout on vscode.dev'),
|
||||
icon: 'globe',
|
||||
run(branch: string) {
|
||||
const link = getBranchLink(url, branch, getVscodeDevHost());
|
||||
env.openExternal(Uri.parse(link));
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
+10
@@ -44,6 +44,15 @@ export interface PickRemoteSourceResult {
|
||||
readonly branch?: string;
|
||||
}
|
||||
|
||||
export interface RemoteSourceAction {
|
||||
readonly label: string;
|
||||
/**
|
||||
* Codicon name
|
||||
*/
|
||||
readonly icon: string;
|
||||
run(branch: string): void;
|
||||
}
|
||||
|
||||
export interface RemoteSource {
|
||||
readonly name: string;
|
||||
readonly description?: string;
|
||||
@@ -70,6 +79,7 @@ export interface RemoteSourceProvider {
|
||||
readonly supportsQuery?: boolean;
|
||||
|
||||
getBranches?(url: string): ProviderResult<string[]>;
|
||||
getRemoteSourceActions?(url: string): ProviderResult<RemoteSourceAction[]>;
|
||||
getRecentRemoteSources?(query?: string): ProviderResult<RecentRemoteSource[]>;
|
||||
getRemoteSources(query?: string): ProviderResult<RemoteSource[]>;
|
||||
}
|
||||
|
||||
+6
-1
@@ -78,6 +78,7 @@ export const enum Status {
|
||||
UNTRACKED,
|
||||
IGNORED,
|
||||
INTENT_TO_ADD,
|
||||
INTENT_TO_RENAME,
|
||||
|
||||
ADDED_BY_US,
|
||||
ADDED_BY_THEM,
|
||||
@@ -156,6 +157,10 @@ export interface FetchOptions {
|
||||
depth?: number;
|
||||
}
|
||||
|
||||
export interface InitOptions {
|
||||
defaultBranch?: string;
|
||||
}
|
||||
|
||||
export interface BranchQuery {
|
||||
readonly remote?: boolean;
|
||||
readonly pattern?: string;
|
||||
@@ -301,7 +306,7 @@ export interface API {
|
||||
|
||||
toGitUri(uri: Uri, ref: string): Uri;
|
||||
getRepository(uri: Uri): Repository | null;
|
||||
init(root: Uri): Promise<Repository | null>;
|
||||
init(root: Uri, options?: InitOptions): Promise<Repository | null>;
|
||||
openRepository(root: Uri): Promise<Repository | null>
|
||||
|
||||
registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable;
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
var updateGrammar = require('vscode-grammar-updater');
|
||||
|
||||
function adaptJSON(grammar, replacementScope) {
|
||||
grammar.name = 'JSON with comments';
|
||||
function adaptJSON(grammar, name, replacementScope) {
|
||||
grammar.name = name;
|
||||
grammar.scopeName = `source${replacementScope}`;
|
||||
|
||||
var fixScopeNames = function (rule) {
|
||||
@@ -33,9 +33,5 @@ function adaptJSON(grammar, replacementScope) {
|
||||
|
||||
var tsGrammarRepo = 'microsoft/vscode-JSON.tmLanguage';
|
||||
updateGrammar.update(tsGrammarRepo, 'JSON.tmLanguage', './syntaxes/JSON.tmLanguage.json');
|
||||
updateGrammar.update(tsGrammarRepo, 'JSON.tmLanguage', './syntaxes/JSONC.tmLanguage.json', grammar => adaptJSON(grammar, '.json.comments'));
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
updateGrammar.update(tsGrammarRepo, 'JSON.tmLanguage', './syntaxes/JSONC.tmLanguage.json', grammar => adaptJSON(grammar, 'JSON with Comments', '.json.comments'));
|
||||
updateGrammar.update(tsGrammarRepo, 'JSON.tmLanguage', './syntaxes/JSONL.tmLanguage.json', grammar => adaptJSON(grammar, 'JSON Lines', '.json.lines'));
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
".jslintrc",
|
||||
".jsonld",
|
||||
".geojson",
|
||||
".ipynb"
|
||||
".ipynb"
|
||||
],
|
||||
"filenames": [
|
||||
"composer.lock",
|
||||
@@ -65,6 +65,17 @@
|
||||
"typedoc.json"
|
||||
],
|
||||
"configuration": "./language-configuration.json"
|
||||
},
|
||||
{
|
||||
"id": "jsonl",
|
||||
"aliases": [
|
||||
"JSON Lines"
|
||||
],
|
||||
"extensions": [
|
||||
".jsonl"
|
||||
],
|
||||
"filenames": [],
|
||||
"configuration": "./language-configuration.json"
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
@@ -77,6 +88,11 @@
|
||||
"language": "jsonc",
|
||||
"scopeName": "source.json.comments",
|
||||
"path": "./syntaxes/JSONC.tmLanguage.json"
|
||||
},
|
||||
{
|
||||
"language": "jsonl",
|
||||
"scopeName": "source.json.lines",
|
||||
"path": "./syntaxes/JSONL.tmLanguage.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"Once accepted there, we are happy to receive an update request."
|
||||
],
|
||||
"version": "https://github.com/microsoft/vscode-JSON.tmLanguage/commit/9bd83f1c252b375e957203f21793316203f61f70",
|
||||
"name": "JSON with comments",
|
||||
"name": "JSON with Comments",
|
||||
"scopeName": "source.json.comments",
|
||||
"patterns": [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
{
|
||||
"information_for_contributors": [
|
||||
"This file has been converted from https://github.com/microsoft/vscode-JSON.tmLanguage/blob/master/JSON.tmLanguage",
|
||||
"If you want to provide a fix or improvement, please create a pull request against the original repository.",
|
||||
"Once accepted there, we are happy to receive an update request."
|
||||
],
|
||||
"version": "https://github.com/microsoft/vscode-JSON.tmLanguage/commit/9bd83f1c252b375e957203f21793316203f61f70",
|
||||
"name": "JSON Lines",
|
||||
"scopeName": "source.json.lines",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#value"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"array": {
|
||||
"begin": "\\[",
|
||||
"beginCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.array.begin.json.lines"
|
||||
}
|
||||
},
|
||||
"end": "\\]",
|
||||
"endCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.array.end.json.lines"
|
||||
}
|
||||
},
|
||||
"name": "meta.structure.array.json.lines",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#value"
|
||||
},
|
||||
{
|
||||
"match": ",",
|
||||
"name": "punctuation.separator.array.json.lines"
|
||||
},
|
||||
{
|
||||
"match": "[^\\s\\]]",
|
||||
"name": "invalid.illegal.expected-array-separator.json.lines"
|
||||
}
|
||||
]
|
||||
},
|
||||
"comments": {
|
||||
"patterns": [
|
||||
{
|
||||
"begin": "/\\*\\*(?!/)",
|
||||
"captures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.comment.json.lines"
|
||||
}
|
||||
},
|
||||
"end": "\\*/",
|
||||
"name": "comment.block.documentation.json.lines"
|
||||
},
|
||||
{
|
||||
"begin": "/\\*",
|
||||
"captures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.comment.json.lines"
|
||||
}
|
||||
},
|
||||
"end": "\\*/",
|
||||
"name": "comment.block.json.lines"
|
||||
},
|
||||
{
|
||||
"captures": {
|
||||
"1": {
|
||||
"name": "punctuation.definition.comment.json.lines"
|
||||
}
|
||||
},
|
||||
"match": "(//).*$\\n?",
|
||||
"name": "comment.line.double-slash.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"constant": {
|
||||
"match": "\\b(?:true|false|null)\\b",
|
||||
"name": "constant.language.json.lines"
|
||||
},
|
||||
"number": {
|
||||
"match": "(?x) # turn on extended mode\n -? # an optional minus\n (?:\n 0 # a zero\n | # ...or...\n [1-9] # a 1-9 character\n \\d* # followed by zero or more digits\n )\n (?:\n (?:\n \\. # a period\n \\d+ # followed by one or more digits\n )?\n (?:\n [eE] # an e character\n [+-]? # followed by an option +/-\n \\d+ # followed by one or more digits\n )? # make exponent optional\n )? # make decimal portion optional",
|
||||
"name": "constant.numeric.json.lines"
|
||||
},
|
||||
"object": {
|
||||
"begin": "\\{",
|
||||
"beginCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.dictionary.begin.json.lines"
|
||||
}
|
||||
},
|
||||
"end": "\\}",
|
||||
"endCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.dictionary.end.json.lines"
|
||||
}
|
||||
},
|
||||
"name": "meta.structure.dictionary.json.lines",
|
||||
"patterns": [
|
||||
{
|
||||
"comment": "the JSON object key",
|
||||
"include": "#objectkey"
|
||||
},
|
||||
{
|
||||
"include": "#comments"
|
||||
},
|
||||
{
|
||||
"begin": ":",
|
||||
"beginCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.separator.dictionary.key-value.json.lines"
|
||||
}
|
||||
},
|
||||
"end": "(,)|(?=\\})",
|
||||
"endCaptures": {
|
||||
"1": {
|
||||
"name": "punctuation.separator.dictionary.pair.json.lines"
|
||||
}
|
||||
},
|
||||
"name": "meta.structure.dictionary.value.json.lines",
|
||||
"patterns": [
|
||||
{
|
||||
"comment": "the JSON object value",
|
||||
"include": "#value"
|
||||
},
|
||||
{
|
||||
"match": "[^\\s,]",
|
||||
"name": "invalid.illegal.expected-dictionary-separator.json.lines"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": "[^\\s\\}]",
|
||||
"name": "invalid.illegal.expected-dictionary-separator.json.lines"
|
||||
}
|
||||
]
|
||||
},
|
||||
"string": {
|
||||
"begin": "\"",
|
||||
"beginCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.string.begin.json.lines"
|
||||
}
|
||||
},
|
||||
"end": "\"",
|
||||
"endCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.string.end.json.lines"
|
||||
}
|
||||
},
|
||||
"name": "string.quoted.double.json.lines",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#stringcontent"
|
||||
}
|
||||
]
|
||||
},
|
||||
"objectkey": {
|
||||
"begin": "\"",
|
||||
"beginCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.support.type.property-name.begin.json.lines"
|
||||
}
|
||||
},
|
||||
"end": "\"",
|
||||
"endCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.support.type.property-name.end.json.lines"
|
||||
}
|
||||
},
|
||||
"name": "string.json.lines support.type.property-name.json.lines",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#stringcontent"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stringcontent": {
|
||||
"patterns": [
|
||||
{
|
||||
"match": "(?x) # turn on extended mode\n \\\\ # a literal backslash\n (?: # ...followed by...\n [\"\\\\/bfnrt] # one of these characters\n | # ...or...\n u # a u\n [0-9a-fA-F]{4}) # and four hex digits",
|
||||
"name": "constant.character.escape.json.lines"
|
||||
},
|
||||
{
|
||||
"match": "\\\\.",
|
||||
"name": "invalid.illegal.unrecognized-string-escape.json.lines"
|
||||
}
|
||||
]
|
||||
},
|
||||
"value": {
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#constant"
|
||||
},
|
||||
{
|
||||
"include": "#number"
|
||||
},
|
||||
{
|
||||
"include": "#string"
|
||||
},
|
||||
{
|
||||
"include": "#array"
|
||||
},
|
||||
{
|
||||
"include": "#object"
|
||||
},
|
||||
{
|
||||
"include": "#comments"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "vscode-markdown-languageserver",
|
||||
"description": "Markdown language server",
|
||||
"version": "0.4.0-alpha.1",
|
||||
"version": "0.4.0-alpha.2",
|
||||
"author": "Microsoft Corporation",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -18,7 +18,7 @@
|
||||
"vscode-languageserver": "^8.1.0",
|
||||
"vscode-languageserver-textdocument": "^1.0.8",
|
||||
"vscode-languageserver-types": "^3.17.3",
|
||||
"vscode-markdown-languageservice": "^0.4.0-alpha.1",
|
||||
"vscode-markdown-languageservice": "^0.4.0-alpha.2",
|
||||
"vscode-uri": "^3.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -128,10 +128,10 @@ vscode-languageserver@^8.1.0:
|
||||
dependencies:
|
||||
vscode-languageserver-protocol "3.17.3"
|
||||
|
||||
vscode-markdown-languageservice@^0.4.0-alpha.1:
|
||||
version "0.4.0-alpha.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.4.0-alpha.1.tgz#367582d711a95001adb1cfae2eb8c9852cc51319"
|
||||
integrity sha512-MqNHwKaO5UliiJ2lNSt8FRj/68max/7/N4JwD0mLkx/NAWLXYLluBbE1CQh/Fgycpcueldk0cckKYj/qNWDtVA==
|
||||
vscode-markdown-languageservice@^0.4.0-alpha.2:
|
||||
version "0.4.0-alpha.2"
|
||||
resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.4.0-alpha.2.tgz#2edbd157ada35922ec762a7d6550d87a0b78959a"
|
||||
integrity sha512-m2x+3dezndpDqfviCzsfUgAySVhoN8266OnasPpPlIZIho3a/JcUmFo6GZDlWBtOQXd9FT+TSAC2BPDCtWlhPQ==
|
||||
dependencies:
|
||||
"@vscode/l10n" "^0.0.10"
|
||||
node-html-parser "^6.1.5"
|
||||
|
||||
@@ -14,6 +14,7 @@ import { getDocumentDir } from '../../util/document';
|
||||
enum MediaKind {
|
||||
Image,
|
||||
Video,
|
||||
Audio,
|
||||
}
|
||||
|
||||
export const mediaFileExtensions = new Map<string, MediaKind>([
|
||||
@@ -35,6 +36,11 @@ export const mediaFileExtensions = new Map<string, MediaKind>([
|
||||
// Videos
|
||||
['ogg', MediaKind.Video],
|
||||
['mp4', MediaKind.Video],
|
||||
|
||||
// Audio Files
|
||||
['mp3', MediaKind.Audio],
|
||||
['aac', MediaKind.Audio],
|
||||
['wav', MediaKind.Audio],
|
||||
]);
|
||||
|
||||
export const mediaMimes = new Set([
|
||||
@@ -45,6 +51,9 @@ export const mediaMimes = new Set([
|
||||
'image/webp',
|
||||
'video/mp4',
|
||||
'video/ogg',
|
||||
'audio/mpeg',
|
||||
'audio/aac',
|
||||
'audio/x-wav',
|
||||
]);
|
||||
|
||||
|
||||
@@ -97,6 +106,7 @@ export function createUriListSnippet(
|
||||
|
||||
let insertedLinkCount = 0;
|
||||
let insertedImageCount = 0;
|
||||
let insertedAudioVideoCount = 0;
|
||||
|
||||
uris.forEach((uri, i) => {
|
||||
const mdPath = getMdPath(dir, uri);
|
||||
@@ -104,12 +114,18 @@ export function createUriListSnippet(
|
||||
const ext = URI.Utils.extname(uri).toLowerCase().replace('.', '');
|
||||
const insertAsMedia = typeof options?.insertAsMedia === 'undefined' ? mediaFileExtensions.has(ext) : !!options.insertAsMedia;
|
||||
const insertAsVideo = mediaFileExtensions.get(ext) === MediaKind.Video;
|
||||
const insertAsAudio = mediaFileExtensions.get(ext) === MediaKind.Audio;
|
||||
|
||||
if (insertAsVideo) {
|
||||
insertedImageCount++;
|
||||
insertedAudioVideoCount++;
|
||||
snippet.appendText(`<video src="${mdPath}" controls title="`);
|
||||
snippet.appendPlaceholder('Title');
|
||||
snippet.appendText('"></video>');
|
||||
} else if (insertAsAudio) {
|
||||
insertedAudioVideoCount++;
|
||||
snippet.appendText(`<audio src="${mdPath}" controls title="`);
|
||||
snippet.appendPlaceholder('Title');
|
||||
snippet.appendText('"></audio>');
|
||||
} else {
|
||||
if (insertAsMedia) {
|
||||
insertedImageCount++;
|
||||
@@ -132,7 +148,13 @@ export function createUriListSnippet(
|
||||
});
|
||||
|
||||
let label: string;
|
||||
if (insertedImageCount > 0 && insertedLinkCount > 0) {
|
||||
if (insertedAudioVideoCount > 0) {
|
||||
if (insertedLinkCount > 0) {
|
||||
label = vscode.l10n.t('Insert Markdown Media and Links');
|
||||
} else {
|
||||
label = vscode.l10n.t('Insert Markdown Media');
|
||||
}
|
||||
} else if (insertedImageCount > 0 && insertedLinkCount > 0) {
|
||||
label = vscode.l10n.t('Insert Markdown Images and Links');
|
||||
} else if (insertedImageCount > 0) {
|
||||
label = insertedImageCount > 1
|
||||
|
||||
@@ -265,6 +265,11 @@ function scrollingEnabled(output: OutputItem, options: RenderOptions) {
|
||||
metadata.scrollable : options.outputScrolling;
|
||||
}
|
||||
|
||||
// div.cell_container
|
||||
// div.output_container
|
||||
// div.output.output-stream <-- outputElement parameter
|
||||
// div.scrollable? tabindex="0" <-- contentParent
|
||||
// div output-item-id="{guid}" <-- content from outputItem parameter
|
||||
function renderStream(outputInfo: OutputItem, outputElement: HTMLElement, error: boolean, ctx: IRichRenderContext): IDisposable {
|
||||
const disposableStore = createDisposableStore();
|
||||
const outputScrolling = scrollingEnabled(outputInfo, ctx.settings);
|
||||
@@ -272,39 +277,50 @@ function renderStream(outputInfo: OutputItem, outputElement: HTMLElement, error:
|
||||
outputElement.classList.add('output-stream');
|
||||
|
||||
const text = outputInfo.text();
|
||||
const content = createOutputContent(outputInfo.id, [text], ctx.settings.lineLimit, outputScrolling, false);
|
||||
content.setAttribute('output-item-id', outputInfo.id);
|
||||
const newContent = createOutputContent(outputInfo.id, [text], ctx.settings.lineLimit, outputScrolling, false);
|
||||
newContent.setAttribute('output-item-id', outputInfo.id);
|
||||
if (error) {
|
||||
content.classList.add('error');
|
||||
newContent.classList.add('error');
|
||||
}
|
||||
|
||||
const scrollTop = outputScrolling ? findScrolledHeight(outputElement) : undefined;
|
||||
|
||||
const existingContent = outputElement.querySelector(`[output-item-id="${outputInfo.id}"]`) as HTMLElement | null;
|
||||
const previousOutputParent = getPreviousMatchingContentGroup(outputElement);
|
||||
|
||||
// If the previous output item for the same cell was also a stream, append this output to the previous
|
||||
const existingContentParent = getPreviousMatchingContentGroup(outputElement);
|
||||
if (existingContentParent) {
|
||||
const existing = existingContentParent.querySelector(`[output-item-id="${outputInfo.id}"]`) as HTMLElement | null;
|
||||
if (existing) {
|
||||
existing.replaceWith(content);
|
||||
if (previousOutputParent) {
|
||||
if (existingContent) {
|
||||
existingContent.replaceWith(newContent);
|
||||
|
||||
} else {
|
||||
existingContentParent.appendChild(content);
|
||||
previousOutputParent.appendChild(newContent);
|
||||
}
|
||||
existingContentParent.classList.toggle('scrollbar-visible', existingContentParent.scrollHeight > existingContentParent.clientHeight);
|
||||
existingContentParent.scrollTop = scrollTop !== undefined ? scrollTop : existingContentParent.scrollHeight;
|
||||
previousOutputParent.classList.toggle('scrollbar-visible', previousOutputParent.scrollHeight > previousOutputParent.clientHeight);
|
||||
previousOutputParent.scrollTop = scrollTop !== undefined ? scrollTop : previousOutputParent.scrollHeight;
|
||||
} else {
|
||||
const contentParent = document.createElement('div');
|
||||
contentParent.appendChild(content);
|
||||
let contentParent = existingContent?.parentElement;
|
||||
if (existingContent && contentParent) {
|
||||
existingContent.replaceWith(newContent);
|
||||
while (newContent.nextSibling) {
|
||||
// clear out any stale content if we had previously combined streaming outputs into this one
|
||||
newContent.nextSibling.remove();
|
||||
}
|
||||
} else {
|
||||
contentParent = document.createElement('div');
|
||||
contentParent.appendChild(newContent);
|
||||
while (outputElement.firstChild) {
|
||||
outputElement.removeChild(outputElement.firstChild);
|
||||
}
|
||||
outputElement.appendChild(contentParent);
|
||||
}
|
||||
|
||||
contentParent.classList.toggle('scrollable', outputScrolling);
|
||||
contentParent.classList.toggle('word-wrap', ctx.settings.outputWordWrap);
|
||||
disposableStore.push(ctx.onDidChangeSettings(e => {
|
||||
contentParent.classList.toggle('word-wrap', e.outputWordWrap);
|
||||
contentParent!.classList.toggle('word-wrap', e.outputWordWrap);
|
||||
}));
|
||||
|
||||
|
||||
while (outputElement.firstChild) {
|
||||
outputElement.removeChild(outputElement.firstChild);
|
||||
}
|
||||
outputElement.appendChild(contentParent);
|
||||
initializeScroll(contentParent, disposableStore, scrollTop);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import * as assert from 'assert';
|
||||
import { activate } from '..';
|
||||
import { OutputItem, RendererApi } from 'vscode-notebook-renderer';
|
||||
import { IRichRenderContext, RenderOptions } from '../rendererTypes';
|
||||
import { IDisposable, IRichRenderContext, RenderOptions } from '../rendererTypes';
|
||||
import { JSDOM } from "jsdom";
|
||||
|
||||
const dom = new JSDOM();
|
||||
@@ -37,7 +37,15 @@ suite('Notebook builtin output renderer', () => {
|
||||
|
||||
type optionalRenderOptions = { [k in keyof RenderOptions]?: RenderOptions[k] };
|
||||
|
||||
type handler = (e: RenderOptions) => any;
|
||||
|
||||
const settingsChangedHandlers: handler[] = [];
|
||||
function fireSettingsChange(options: optionalRenderOptions) {
|
||||
settingsChangedHandlers.forEach((handler) => handler(options as RenderOptions));
|
||||
}
|
||||
|
||||
function createContext(settings?: optionalRenderOptions): IRichRenderContext {
|
||||
settingsChangedHandlers.length = 0;
|
||||
return {
|
||||
setState(_value: void) { },
|
||||
getState() { return undefined; },
|
||||
@@ -48,9 +56,16 @@ suite('Notebook builtin output renderer', () => {
|
||||
lineLimit: 30,
|
||||
...settings
|
||||
} as RenderOptions,
|
||||
onDidChangeSettings<T>(_listener: (e: T) => any, _thisArgs?: any, _disposables?: any) {
|
||||
onDidChangeSettings(listener: handler, _thisArgs?: any, disposables?: IDisposable[]) {
|
||||
settingsChangedHandlers.push(listener);
|
||||
|
||||
const dispose = () => {
|
||||
settingsChangedHandlers.splice(settingsChangedHandlers.indexOf(listener), 1);
|
||||
};
|
||||
|
||||
disposables?.push({ dispose });
|
||||
return {
|
||||
dispose(): void { }
|
||||
dispose
|
||||
};
|
||||
},
|
||||
workspace: {
|
||||
@@ -146,7 +161,7 @@ suite('Notebook builtin output renderer', () => {
|
||||
|
||||
const inserted = outputElement.firstChild as HTMLElement;
|
||||
assert.ok(inserted, `nothing appended to output element: ${outputElement.innerHTML}`);
|
||||
assert.ok(!outputElement.classList.contains('remove-padding'), `Padding should not be removed for non-scrollable outputs: ${outputElement.classList}`);
|
||||
assert.ok(outputElement.classList.contains('remove-padding'), `Padding should be removed for non-scrollable outputs: ${outputElement.classList}`);
|
||||
assert.ok(!inserted.classList.contains('word-wrap') && !inserted.classList.contains('scrollable'),
|
||||
`output content classList should not contain word-wrap and scrollable ${inserted.classList}`);
|
||||
assert.ok(inserted.innerHTML.indexOf('>content</') > -1, `Content was not added to output element: ${outputElement.innerHTML}`);
|
||||
@@ -225,5 +240,58 @@ suite('Notebook builtin output renderer', () => {
|
||||
assert.ok(inserted.innerHTML.indexOf('>second stream content</') > -1, `Content was not added to output element: ${outputElement.innerHTML}`);
|
||||
assert.ok(inserted.innerHTML.indexOf('>third stream content</') > -1, `Content was not added to output element: ${outputElement.innerHTML}`);
|
||||
});
|
||||
|
||||
test(`Multiple adjacent streaming outputs, rerendering the first should erase the rest`, async () => {
|
||||
const context = createContext();
|
||||
const renderer = await activate(context);
|
||||
assert.ok(renderer, 'Renderer not created');
|
||||
|
||||
const outputHtml = new OutputHtml();
|
||||
const outputElement = outputHtml.getFirstOuputElement();
|
||||
const outputItem1 = createOutputItem('first stream content', stdoutMimeType, '1');
|
||||
const outputItem2 = createOutputItem('second stream content', stdoutMimeType, '2');
|
||||
const outputItem3 = createOutputItem('third stream content', stderrMimeType, '3');
|
||||
await renderer!.renderOutputItem(outputItem1, outputElement);
|
||||
await renderer!.renderOutputItem(outputItem2, outputHtml.appendOutputElement());
|
||||
await renderer!.renderOutputItem(outputItem3, outputHtml.appendOutputElement());
|
||||
const newOutputItem1 = createOutputItem('replaced content', stderrMimeType, '1');
|
||||
await renderer!.renderOutputItem(newOutputItem1, outputElement);
|
||||
|
||||
|
||||
const inserted = outputElement.firstChild as HTMLElement;
|
||||
assert.ok(inserted, `nothing appended to output element: ${outputElement.innerHTML}`);
|
||||
assert.ok(inserted.innerHTML.indexOf('>replaced content</') > -1, `Content was not added to output element: ${outputElement.innerHTML}`);
|
||||
assert.ok(inserted.innerHTML.indexOf('>first stream content</') === -1, `Content was not cleared: ${outputElement.innerHTML}`);
|
||||
assert.ok(inserted.innerHTML.indexOf('>second stream content</') === -1, `Content was not cleared: ${outputElement.innerHTML}`);
|
||||
assert.ok(inserted.innerHTML.indexOf('>third stream content</') === -1, `Content was not cleared: ${outputElement.innerHTML}`);
|
||||
});
|
||||
|
||||
test(`Rendered output will wrap on settings change event`, async () => {
|
||||
const context = createContext({ outputWordWrap: false, outputScrolling: true });
|
||||
const renderer = await activate(context);
|
||||
assert.ok(renderer, 'Renderer not created');
|
||||
|
||||
const outputElement = new OutputHtml().getFirstOuputElement();
|
||||
const outputItem = createOutputItem('content', stdoutMimeType);
|
||||
await renderer!.renderOutputItem(outputItem, outputElement);
|
||||
fireSettingsChange({ outputWordWrap: true, outputScrolling: true });
|
||||
|
||||
const inserted = outputElement.firstChild as HTMLElement;
|
||||
assert.ok(inserted.classList.contains('word-wrap') && inserted.classList.contains('scrollable'),
|
||||
`output content classList should contain word-wrap and scrollable ${inserted.classList}`);
|
||||
});
|
||||
|
||||
test(`Settings event change listeners should not grow if output is re-rendered`, async () => {
|
||||
const context = createContext({ outputWordWrap: false });
|
||||
const renderer = await activate(context);
|
||||
assert.ok(renderer, 'Renderer not created');
|
||||
|
||||
const outputElement = new OutputHtml().getFirstOuputElement();
|
||||
await renderer!.renderOutputItem(createOutputItem('content', stdoutMimeType), outputElement);
|
||||
const handlerCount = settingsChangedHandlers.length;
|
||||
await renderer!.renderOutputItem(createOutputItem('content', stdoutMimeType), outputElement);
|
||||
|
||||
assert.equal(settingsChangedHandlers.length, handlerCount);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ const nonBuiltInLanguages = { // { fileNames, extensions }
|
||||
// list of languagesId that inherit the icon from another language
|
||||
const inheritIconFromLanguage = {
|
||||
"jsonc": 'json',
|
||||
"jsonl": 'json',
|
||||
"postcss": 'css',
|
||||
"django-html": 'html',
|
||||
"blade": 'php'
|
||||
|
||||
@@ -1943,6 +1943,7 @@
|
||||
"todo": "_todo",
|
||||
"vala": "_vala",
|
||||
"vue": "_vue",
|
||||
"jsonl": "_json",
|
||||
"postcss": "_css",
|
||||
"django-html": "_html_3",
|
||||
"blade": "_php"
|
||||
@@ -2257,6 +2258,7 @@
|
||||
"terraform": "_terraform_light",
|
||||
"vala": "_vala_light",
|
||||
"vue": "_vue_light",
|
||||
"jsonl": "_json_light",
|
||||
"postcss": "_css_light",
|
||||
"django-html": "_html_3_light",
|
||||
"blade": "_php_light"
|
||||
|
||||
@@ -143,6 +143,12 @@
|
||||
"title": "%configuration.typescript%",
|
||||
"order": 20,
|
||||
"properties": {
|
||||
"typescript.experimental.aiQuickFix": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "%typescript.experimental.aiQuickFix%",
|
||||
"scope": "resource"
|
||||
},
|
||||
"typescript.tsdk": {
|
||||
"type": "string",
|
||||
"markdownDescription": "%typescript.tsdk.desc%",
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"configuration.suggest.completeFunctionCalls": "Complete functions with their parameter signature.",
|
||||
"configuration.suggest.includeAutomaticOptionalChainCompletions": "Enable/disable showing completions on potentially undefined values that insert an optional chain call. Requires strict null checks to be enabled.",
|
||||
"configuration.suggest.includeCompletionsForImportStatements": "Enable/disable auto-import-style completions on partially-typed import statements.",
|
||||
"typescript.experimental.aiQuickFix": "Enable/disable AI-assisted quick fixes.",
|
||||
"typescript.tsdk.desc": "Specifies the folder path to the tsserver and `lib*.d.ts` files under a TypeScript install to use for IntelliSense, for example: `./node_modules/typescript/lib`.\n\n- When specified as a user setting, the TypeScript version from `typescript.tsdk` automatically replaces the built-in TypeScript version.\n- When specified as a workspace setting, `typescript.tsdk` allows you to switch to use that workspace version of TypeScript for IntelliSense with the `TypeScript: Select TypeScript version` command.\n\nSee the [TypeScript documentation](https://code.visualstudio.com/docs/typescript/typescript-compiling#_using-newer-typescript-versions) for more detail about managing TypeScript versions.",
|
||||
"typescript.disableAutomaticTypeAcquisition": "Disables [automatic type acquisition](https://code.visualstudio.com/docs/nodejs/working-with-javascript#_typings-and-automatic-type-acquisition). Automatic type acquisition fetches `@types` packages from npm to improve IntelliSense for external libraries.",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": "Enables prompting of users to use the TypeScript version configured in the workspace for Intellisense.",
|
||||
|
||||
@@ -93,7 +93,6 @@ class MyCompletionItem extends vscode.CompletionItem {
|
||||
this.range = this.getRangeFromReplacementSpan(tsEntry, completionContext);
|
||||
this.commitCharacters = MyCompletionItem.getCommitCharacters(completionContext, tsEntry);
|
||||
this.insertText = isSnippet && tsEntry.insertText ? new vscode.SnippetString(tsEntry.insertText) : tsEntry.insertText;
|
||||
// @ts-expect-error until 5.2
|
||||
this.filterText = tsEntry.filterText || this.getFilterText(completionContext.line, tsEntry.insertText);
|
||||
|
||||
if (completionContext.isMemberCompletion && completionContext.dotAccessorContext && !(this.insertText instanceof vscode.SnippetString)) {
|
||||
|
||||
@@ -20,11 +20,49 @@ import { applyCodeActionCommands, getEditForCodeAction } from './util/codeAction
|
||||
import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration';
|
||||
|
||||
type ApplyCodeActionCommand_args = {
|
||||
readonly resource: vscode.Uri;
|
||||
readonly document: vscode.TextDocument;
|
||||
readonly diagnostic: vscode.Diagnostic;
|
||||
readonly action: Proto.CodeFixAction;
|
||||
readonly followupAction?: Command;
|
||||
};
|
||||
|
||||
class EditorChatFollowUp implements Command {
|
||||
|
||||
id: string = 'needsBetterName.editorChateFollowUp';
|
||||
|
||||
constructor(private readonly prompt: string, private readonly document: vscode.TextDocument, private readonly range: vscode.Range, private readonly client: ITypeScriptServiceClient) {
|
||||
|
||||
}
|
||||
|
||||
async execute() {
|
||||
const findScopeEndLineFromNavTree = (startLine: number, navigationTree: Proto.NavigationTree[]): vscode.Range | undefined => {
|
||||
for (const node of navigationTree) {
|
||||
const range = typeConverters.Range.fromTextSpan(node.spans[0]);
|
||||
if (startLine === range.start.line) {
|
||||
return range;
|
||||
} else if (startLine > range.start.line && startLine <= range.end.line && node.childItems) {
|
||||
return findScopeEndLineFromNavTree(startLine, node.childItems);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
const filepath = this.client.toOpenTsFilePath(this.document);
|
||||
if (!filepath) {
|
||||
return;
|
||||
}
|
||||
const response = await this.client.execute('navtree', { file: filepath }, (new vscode.CancellationTokenSource()).token);
|
||||
if (response.type !== 'response' || !response.body?.childItems) {
|
||||
return;
|
||||
}
|
||||
const startLine = this.range.start.line;
|
||||
const enclosingRange = findScopeEndLineFromNavTree(startLine, response.body.childItems);
|
||||
if (!enclosingRange) {
|
||||
return;
|
||||
}
|
||||
await vscode.commands.executeCommand('vscode.editorChat.start', { initialRange: enclosingRange, message: this.prompt, autoSend: true });
|
||||
}
|
||||
}
|
||||
|
||||
class ApplyCodeActionCommand implements Command {
|
||||
public static readonly ID = '_typescript.applyCodeActionCommand';
|
||||
public readonly id = ApplyCodeActionCommand.ID;
|
||||
@@ -35,7 +73,7 @@ class ApplyCodeActionCommand implements Command {
|
||||
private readonly telemetryReporter: TelemetryReporter,
|
||||
) { }
|
||||
|
||||
public async execute({ resource, action, diagnostic }: ApplyCodeActionCommand_args): Promise<boolean> {
|
||||
public async execute({ document, action, diagnostic, followupAction }: ApplyCodeActionCommand_args): Promise<boolean> {
|
||||
/* __GDPR__
|
||||
"quickFix.execute" : {
|
||||
"owner": "mjbvz",
|
||||
@@ -49,8 +87,10 @@ class ApplyCodeActionCommand implements Command {
|
||||
fixName: action.fixName
|
||||
});
|
||||
|
||||
this.diagnosticManager.deleteDiagnostic(resource, diagnostic);
|
||||
return applyCodeActionCommands(this.client, action.commands, nulToken);
|
||||
this.diagnosticManager.deleteDiagnostic(document.uri, diagnostic);
|
||||
const codeActionResult = await applyCodeActionCommands(this.client, action.commands, nulToken);
|
||||
await followupAction?.execute();
|
||||
return codeActionResult;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,22 +353,27 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider<VsCodeCode
|
||||
diagnostic: vscode.Diagnostic,
|
||||
tsAction: Proto.CodeFixAction
|
||||
): CodeActionSet {
|
||||
results.addAction(this.getSingleFixForTsCodeAction(document.uri, diagnostic, tsAction));
|
||||
results.addAction(this.getSingleFixForTsCodeAction(document, diagnostic, tsAction));
|
||||
this.addFixAllForTsCodeAction(results, document.uri, file, diagnostic, tsAction as Proto.CodeFixAction);
|
||||
return results;
|
||||
}
|
||||
|
||||
private getSingleFixForTsCodeAction(
|
||||
resource: vscode.Uri,
|
||||
document: vscode.TextDocument,
|
||||
diagnostic: vscode.Diagnostic,
|
||||
tsAction: Proto.CodeFixAction
|
||||
): VsCodeCodeAction {
|
||||
const aiQuickFixEnabled = vscode.workspace.getConfiguration('typescript').get('experimental.aiQuickFix');
|
||||
let followupAction: Command | undefined;
|
||||
if (aiQuickFixEnabled && tsAction.fixName === fixNames.classIncorrectlyImplementsInterface) {
|
||||
followupAction = new EditorChatFollowUp('Implement the class using the interface', document, diagnostic.range, this.client);
|
||||
}
|
||||
const codeAction = new VsCodeCodeAction(tsAction, tsAction.description, vscode.CodeActionKind.QuickFix);
|
||||
codeAction.edit = getEditForCodeAction(this.client, tsAction);
|
||||
codeAction.diagnostics = [diagnostic];
|
||||
codeAction.command = {
|
||||
command: ApplyCodeActionCommand.ID,
|
||||
arguments: [<ApplyCodeActionCommand_args>{ action: tsAction, diagnostic, resource }],
|
||||
arguments: [<ApplyCodeActionCommand_args>{ action: tsAction, diagnostic, document, followupAction }],
|
||||
title: ''
|
||||
};
|
||||
return codeAction;
|
||||
|
||||
@@ -43,10 +43,13 @@
|
||||
"tokenInformation",
|
||||
"treeItemCheckbox",
|
||||
"treeViewReveal",
|
||||
"testInvalidateResults",
|
||||
"workspaceTrust",
|
||||
"telemetry",
|
||||
"windowActivity",
|
||||
"interactiveUserActions"
|
||||
"interactiveUserActions",
|
||||
"envCollectionWorkspace",
|
||||
"envCollectionOptions"
|
||||
],
|
||||
"private": true,
|
||||
"activationEvents": [],
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "code-oss-dev",
|
||||
"version": "1.79.0",
|
||||
"distro": "ba0897486f9570f3f031bd9512490baf0d646762",
|
||||
"distro": "7272f69cc607298dc986708a73bd978aa7c3af3d",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
},
|
||||
@@ -204,7 +204,7 @@
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsec": "0.1.4",
|
||||
"typescript": "^5.2.0-dev.20230516",
|
||||
"typescript": "^5.2.0-dev.20230524",
|
||||
"typescript-formatter": "7.1.0",
|
||||
"underscore": "^1.12.1",
|
||||
"util": "^0.12.4",
|
||||
|
||||
Vendored
+1
@@ -119,6 +119,7 @@
|
||||
'xterm-addon-canvas': `${baseNodeModulesPath}/xterm-addon-canvas/lib/xterm-addon-canvas.js`,
|
||||
'xterm-addon-image': `${baseNodeModulesPath}/xterm-addon-image/lib/xterm-addon-image.js`,
|
||||
'xterm-addon-search': `${baseNodeModulesPath}/xterm-addon-search/lib/xterm-addon-search.js`,
|
||||
'xterm-addon-serialize': `${baseNodeModulesPath}/xterm-addon-serialize/lib/xterm-addon-serialize.js`,
|
||||
'xterm-addon-unicode11': `${baseNodeModulesPath}/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`,
|
||||
'xterm-addon-webgl': `${baseNodeModulesPath}/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
|
||||
'@vscode/iconv-lite-umd': `${baseNodeModulesPath}/@vscode/iconv-lite-umd/lib/iconv-lite-umd.js`,
|
||||
|
||||
@@ -18,6 +18,8 @@ suite('dom', () => {
|
||||
assert(!element.classList.contains('bar'));
|
||||
assert(!element.classList.contains('foo'));
|
||||
assert(!element.classList.contains(''));
|
||||
|
||||
|
||||
});
|
||||
|
||||
test('removeClass', () => {
|
||||
|
||||
@@ -15,7 +15,7 @@ import { NativeHostService } from 'vs/platform/native/electron-sandbox/nativeHos
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { IMainProcessService } from 'vs/platform/ipc/common/mainProcessService';
|
||||
import { IssueReporter } from './IssueReporterService';
|
||||
import { IssueReporter } from './issueReporterService';
|
||||
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
|
||||
import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions';
|
||||
import { registerMainProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services';
|
||||
|
||||
@@ -183,7 +183,7 @@ export class MenuId {
|
||||
static readonly InlineSuggestionToolbar = new MenuId('InlineSuggestionToolbar');
|
||||
static readonly ChatContext = new MenuId('ChatContext');
|
||||
static readonly ChatCodeBlock = new MenuId('ChatCodeblock');
|
||||
static readonly ChatTitle = new MenuId('ChatTitle');
|
||||
static readonly ChatMessageTitle = new MenuId('ChatMessageTitle');
|
||||
static readonly ChatExecute = new MenuId('ChatExecute');
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,10 +8,10 @@ import { Disposable, DisposableMap } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { ExtHostContext, ExtHostChatShape, IChatRequestDto, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostChatShape, ExtHostContext, IChatRequestDto, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
|
||||
import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService';
|
||||
import { IChatProgress, IChatRequest, IChatResponse, IChat, IChatDynamicRequest, IChatService } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
import { IChat, IChatDynamicRequest, IChatProgress, IChatRequest, IChatResponse, IChatService } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadChat)
|
||||
@@ -134,6 +134,9 @@ export class MainThreadChat extends Disposable implements MainThreadChatShape {
|
||||
},
|
||||
provideFollowups: (session, token) => {
|
||||
return this._proxy.$provideFollowups(handle, session.id, token);
|
||||
},
|
||||
removeRequest: (session, requestId) => {
|
||||
return this._proxy.$removeRequest(handle, session.id, requestId);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -24,11 +24,12 @@ export class MainThreadShare implements MainThreadShareShape {
|
||||
this.proxy = extHostContext.getProxy(ExtHostContext.ExtHostShare);
|
||||
}
|
||||
|
||||
$registerShareProvider(handle: number, selector: IDocumentFilterDto[], id: string, label: string): void {
|
||||
$registerShareProvider(handle: number, selector: IDocumentFilterDto[], id: string, label: string, priority: number): void {
|
||||
const provider: IShareProvider = {
|
||||
id,
|
||||
label,
|
||||
selector,
|
||||
priority,
|
||||
provideShare: async (item: IShareableItem) => {
|
||||
return URI.revive(await this.proxy.$provideShare(handle, item, new CancellationTokenSource().token));
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Command } from 'vs/editor/common/languages';
|
||||
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { IExtensionStatusBarItemService } from 'vs/workbench/api/browser/statusBarExtensionPoint';
|
||||
import { StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar';
|
||||
import { IStatusbarEntry, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadStatusBar)
|
||||
export class MainThreadStatusBar implements MainThreadStatusBarShape {
|
||||
@@ -27,17 +27,27 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape {
|
||||
// once, at startup read existing items and send them over
|
||||
const entries: StatusBarItemDto[] = [];
|
||||
for (const [entryId, item] of statusbarService.getEntries()) {
|
||||
entries.push({
|
||||
entries.push(asDto(entryId, item));
|
||||
}
|
||||
|
||||
proxy.$acceptStaticEntries(entries);
|
||||
|
||||
statusbarService.onDidChange(e => {
|
||||
if (e.added) {
|
||||
proxy.$acceptStaticEntries([asDto(e.added[0], e.added[1])]);
|
||||
}
|
||||
});
|
||||
|
||||
function asDto(entryId: string, item: { entry: IStatusbarEntry; alignment: StatusbarAlignment; priority: number }): StatusBarItemDto {
|
||||
return {
|
||||
entryId,
|
||||
name: item.entry.name,
|
||||
text: item.entry.text,
|
||||
command: typeof item.entry.command === 'string' ? item.entry.command : undefined,
|
||||
priority: item.priority,
|
||||
alignLeft: item.alignment === StatusbarAlignment.LEFT
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
proxy.$acceptStaticEntries(entries);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
|
||||
@@ -52,6 +52,18 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
$markTestRetired(testId: string): void {
|
||||
for (const result of this.resultService.results) {
|
||||
// all non-live results are already entirely outdated
|
||||
if (result instanceof LiveTestResult) {
|
||||
result.markRetired(testId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { MainThreadTunnelServiceShape, MainContext, ExtHostContext, ExtHostTunnelServiceShape, CandidatePortSource, PortAttributesProviderSelector, TunnelDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { MainThreadTunnelServiceShape, MainContext, ExtHostContext, ExtHostTunnelServiceShape, CandidatePortSource, PortAttributesSelector, TunnelDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { TunnelDtoConverter } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||
import { CandidatePort, IRemoteExplorerService, makeAddress, PORT_AUTO_FORWARD_SETTING, PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_OUTPUT, TunnelCloseReason, TunnelSource } from 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||
@@ -23,7 +23,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'v
|
||||
export class MainThreadTunnelService extends Disposable implements MainThreadTunnelServiceShape, PortAttributesProvider {
|
||||
private readonly _proxy: ExtHostTunnelServiceShape;
|
||||
private elevateionRetry: boolean = false;
|
||||
private portsAttributesProviders: Map<number, PortAttributesProviderSelector> = new Map();
|
||||
private portsAttributesProviders: Map<number, PortAttributesSelector> = new Map();
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@@ -63,7 +63,7 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
|
||||
}
|
||||
|
||||
private _alreadyRegistered: boolean = false;
|
||||
async $registerPortsAttributesProvider(selector: PortAttributesProviderSelector, providerHandle: number): Promise<void> {
|
||||
async $registerPortsAttributesProvider(selector: PortAttributesSelector, providerHandle: number): Promise<void> {
|
||||
this.portsAttributesProviders.set(providerHandle, selector);
|
||||
if (!this._alreadyRegistered) {
|
||||
this.remoteExplorerService.tunnelModel.addAttributesProvider(this);
|
||||
@@ -85,9 +85,8 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
|
||||
const selector = entry[1];
|
||||
const portRange = selector.portRange;
|
||||
const portInRange = portRange ? ports.some(port => portRange[0] <= port && port < portRange[1]) : true;
|
||||
const pidMatches = !selector.pid || (selector.pid === pid);
|
||||
const commandMatches = !selector.commandPattern || (commandLine && (commandLine.match(selector.commandPattern)));
|
||||
return portInRange && pidMatches && commandMatches;
|
||||
return portInRange && commandMatches;
|
||||
}).map(entry => entry[0]);
|
||||
|
||||
if (appropriateHandles.length === 0) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import { IAccessibilityInformation } from 'vs/platform/accessibility/common/acce
|
||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { getCodiconAriaLabel } from 'vs/base/common/iconLabels';
|
||||
import { hash } from 'vs/base/common/hash';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
@@ -27,16 +28,24 @@ import { asStatusBarItemIdentifier } from 'vs/workbench/api/common/extHostTypes'
|
||||
export const IExtensionStatusBarItemService = createDecorator<IExtensionStatusBarItemService>('IExtensionStatusBarItemService');
|
||||
|
||||
export interface IExtensionStatusBarItemChangeEvent {
|
||||
readonly added?: Readonly<{ entryId: string } & IStatusbarEntry>;
|
||||
readonly added?: ExtensionStatusBarEntry;
|
||||
readonly removed?: string;
|
||||
}
|
||||
|
||||
export type ExtensionStatusBarEntry = [string, {
|
||||
entry: IStatusbarEntry;
|
||||
alignment: MainThreadStatusBarAlignment;
|
||||
priority: number;
|
||||
}];
|
||||
|
||||
export interface IExtensionStatusBarItemService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
onDidChange: Event<IExtensionStatusBarItemChangeEvent>;
|
||||
|
||||
setOrUpdateEntry(id: string, statusId: string, extensionId: string | undefined, name: string, text: string, tooltip: IMarkdownString | string | undefined, command: Command | undefined, color: string | ThemeColor | undefined, backgroundColor: string | ThemeColor | undefined, alignLeft: boolean, priority: number | undefined, accessibilityInformation: IAccessibilityInformation | undefined): IDisposable;
|
||||
|
||||
getEntries(): Iterable<[string, { entry: IStatusbarEntry; alignment: MainThreadStatusBarAlignment; priority: number }]>;
|
||||
getEntries(): Iterable<ExtensionStatusBarEntry>;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,8 +55,17 @@ class ExtensionStatusBarItemService implements IExtensionStatusBarItemService {
|
||||
|
||||
private readonly _entries: Map<string, { accessor: IStatusbarEntryAccessor; entry: IStatusbarEntry; alignment: MainThreadStatusBarAlignment; priority: number }> = new Map();
|
||||
|
||||
private readonly _onDidChange = new Emitter<IExtensionStatusBarItemChangeEvent>();
|
||||
readonly onDidChange: Event<IExtensionStatusBarItemChangeEvent> = this._onDidChange.event;
|
||||
|
||||
constructor(@IStatusbarService private readonly _statusbarService: IStatusbarService) { }
|
||||
|
||||
dispose(): void {
|
||||
this._entries.forEach(entry => entry.accessor.dispose());
|
||||
this._entries.clear();
|
||||
this._onDidChange.dispose();
|
||||
}
|
||||
|
||||
setOrUpdateEntry(entryId: string, id: string, extensionId: string | undefined, name: string, text: string, tooltip: IMarkdownString | string | undefined, command: Command | undefined, color: string | ThemeColor | undefined, backgroundColor: string | ThemeColor | undefined, alignLeft: boolean, priority: number | undefined, accessibilityInformation: IAccessibilityInformation | undefined): IDisposable {
|
||||
// if there are icons in the text use the tooltip for the aria label
|
||||
let ariaLabel: string;
|
||||
@@ -98,6 +116,8 @@ class ExtensionStatusBarItemService implements IExtensionStatusBarItemService {
|
||||
priority
|
||||
});
|
||||
|
||||
this._onDidChange.fire({ added: [entryId, { entry, alignment, priority }] });
|
||||
|
||||
} else {
|
||||
// Otherwise update
|
||||
existingEntry.accessor.update(entry);
|
||||
@@ -109,6 +129,7 @@ class ExtensionStatusBarItemService implements IExtensionStatusBarItemService {
|
||||
if (entry) {
|
||||
entry.accessor.dispose();
|
||||
this._entries.delete(entryId);
|
||||
this._onDidChange.fire({ removed: entryId });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
const extHostUriOpeners = rpcProtocol.set(ExtHostContext.ExtHostUriOpeners, new ExtHostUriOpeners(rpcProtocol));
|
||||
const extHostProfileContentHandlers = rpcProtocol.set(ExtHostContext.ExtHostProfileContentHandlers, new ExtHostProfileContentHandlers(rpcProtocol));
|
||||
rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands, extHostLogService));
|
||||
const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInteractiveEditor, new ExtHostInteractiveEditor(rpcProtocol, extHostDocuments, extHostLogService));
|
||||
const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInteractiveEditor, new ExtHostInteractiveEditor(rpcProtocol, extHostCommands, extHostDocuments, extHostLogService));
|
||||
const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol, extHostLogService));
|
||||
const extHostSemanticSimilarity = rpcProtocol.set(ExtHostContext.ExtHostSemanticSimilarity, new ExtHostSemanticSimilarity(rpcProtocol));
|
||||
const extHostIssueReporter = rpcProtocol.set(ExtHostContext.ExtHostIssueReporter, new ExtHostIssueReporter(rpcProtocol));
|
||||
@@ -1077,7 +1077,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
checkProposedApiEnabled(extension, 'tunnels');
|
||||
return extHostTunnelService.onDidChangeTunnels(listener, thisArg, disposables);
|
||||
},
|
||||
registerPortAttributesProvider: (portSelector: { pid?: number; portRange?: [number, number]; commandPattern?: RegExp }, provider: vscode.PortAttributesProvider) => {
|
||||
registerPortAttributesProvider: (portSelector: vscode.PortAttributesSelector, provider: vscode.PortAttributesProvider) => {
|
||||
checkProposedApiEnabled(extension, 'portsAttributes');
|
||||
return extHostTunnelService.registerPortsAttributesProvider(portSelector, provider);
|
||||
},
|
||||
|
||||
@@ -1174,6 +1174,7 @@ export interface ExtHostChatShape {
|
||||
$provideWelcomeMessage(handle: number, token: CancellationToken): Promise<(string | IChatReplyFollowup[])[] | undefined>;
|
||||
$provideFollowups(handle: number, sessionId: number, token: CancellationToken): Promise<IChatFollowup[] | undefined>;
|
||||
$provideReply(handle: number, sessionId: number, request: IChatRequestDto, token: CancellationToken): Promise<IChatResponseDto | undefined>;
|
||||
$removeRequest(handle: number, sessionId: number, requestId: string): void;
|
||||
$provideSlashCommands(handle: number, sessionId: number, token: CancellationToken): Promise<ISlashCommand[] | undefined>;
|
||||
$releaseSession(sessionId: number): void;
|
||||
$onDidPerformUserAction(event: IChatUserActionEvent): Promise<void>;
|
||||
@@ -1265,7 +1266,7 @@ export interface MainThreadSearchShape extends IDisposable {
|
||||
}
|
||||
|
||||
export interface MainThreadShareShape extends IDisposable {
|
||||
$registerShareProvider(handle: number, selector: IDocumentFilterDto[], id: string, label: string): void;
|
||||
$registerShareProvider(handle: number, selector: IDocumentFilterDto[], id: string, label: string, priority: number): void;
|
||||
$unregisterShareProvider(handle: number): void;
|
||||
}
|
||||
|
||||
@@ -1420,8 +1421,7 @@ export enum CandidatePortSource {
|
||||
Output = 2
|
||||
}
|
||||
|
||||
export interface PortAttributesProviderSelector {
|
||||
pid?: number;
|
||||
export interface PortAttributesSelector {
|
||||
portRange?: [number, number];
|
||||
commandPattern?: RegExp;
|
||||
}
|
||||
@@ -1435,7 +1435,7 @@ export interface MainThreadTunnelServiceShape extends IDisposable {
|
||||
$setCandidateFilter(): Promise<void>;
|
||||
$onFoundNewCandidates(candidates: CandidatePort[]): Promise<void>;
|
||||
$setCandidatePortSource(source: CandidatePortSource): Promise<void>;
|
||||
$registerPortsAttributesProvider(selector: PortAttributesProviderSelector, providerHandle: number): Promise<void>;
|
||||
$registerPortsAttributesProvider(selector: PortAttributesSelector, providerHandle: number): Promise<void>;
|
||||
$unregisterPortsAttributesProvider(providerHandle: number): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -2501,6 +2501,8 @@ export interface MainThreadTestingShape {
|
||||
$startedExtensionTestRun(req: ExtensionRunTestsRequest): void;
|
||||
/** Signals that an extension-provided test run finished. */
|
||||
$finishedExtensionTestRun(runId: string): void;
|
||||
/** Marks a test (or controller) as retired in all results. */
|
||||
$markTestRetired(testId: string): void;
|
||||
}
|
||||
|
||||
// --- proxy identifiers
|
||||
|
||||
@@ -13,7 +13,7 @@ import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/exte
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ExtHostChatShape, IChatRequestDto, IChatResponseDto, IChatDto, IMainContext, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { IChatFollowup, IChatReplyFollowup, IChatUserActionEvent, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
import { IChatFollowup, IChatProgress, IChatReplyFollowup, IChatUserActionEvent, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
import type * as vscode from 'vscode';
|
||||
|
||||
class ChatProviderWrapper<T> {
|
||||
@@ -158,6 +158,24 @@ export class ExtHostChat implements ExtHostChatShape {
|
||||
return rawFollowups?.map(f => typeConvert.ChatFollowup.from(f));
|
||||
}
|
||||
|
||||
$removeRequest(handle: number, sessionId: number, requestId: string): void {
|
||||
const entry = this._chatProvider.get(handle);
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
const realSession = this._chatSessions.get(sessionId);
|
||||
if (!realSession) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entry.provider.removeRequest) {
|
||||
return;
|
||||
}
|
||||
|
||||
entry.provider.removeRequest(realSession, requestId);
|
||||
}
|
||||
|
||||
async $provideReply(handle: number, sessionId: number, request: IChatRequestDto, token: CancellationToken): Promise<IChatResponseDto | undefined> {
|
||||
const entry = this._chatProvider.get(handle);
|
||||
if (!entry) {
|
||||
@@ -186,7 +204,8 @@ export class ExtHostChat implements ExtHostChatShape {
|
||||
firstProgress = stopWatch.elapsed();
|
||||
}
|
||||
|
||||
this._proxy.$acceptResponseProgress(handle, sessionId, progress);
|
||||
const vscodeProgress: IChatProgress = 'responseId' in progress ? { requestId: progress.responseId } : progress;
|
||||
this._proxy.$acceptResponseProgress(handle, sessionId, vscodeProgress);
|
||||
}
|
||||
};
|
||||
let result: vscode.InteractiveResponseForProgress | undefined | null;
|
||||
|
||||
@@ -15,6 +15,8 @@ import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
|
||||
import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
|
||||
import type * as vscode from 'vscode';
|
||||
import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
|
||||
class ProviderWrapper {
|
||||
|
||||
@@ -47,10 +49,40 @@ export class ExtHostInteractiveEditor implements ExtHostInteractiveEditorShape {
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
extHostCommands: ExtHostCommands,
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _logService: ILogService,
|
||||
) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadInteractiveEditor);
|
||||
|
||||
type EditorChatApiArg = {
|
||||
initialRange?: vscode.Range;
|
||||
message?: string;
|
||||
autoSend?: boolean;
|
||||
};
|
||||
|
||||
type InteractiveEditorRunOptions = {
|
||||
initialRange?: IRange;
|
||||
message?: string;
|
||||
autoSend?: boolean;
|
||||
};
|
||||
|
||||
extHostCommands.registerApiCommand(new ApiCommand(
|
||||
'vscode.editorChat.start', 'interactiveEditor.start', 'Invoke a new editor chat session',
|
||||
[new ApiCommandArgument<EditorChatApiArg | undefined, InteractiveEditorRunOptions | undefined>('Run arguments', '', _v => true, v => {
|
||||
|
||||
if (!v) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
initialRange: v.initialRange ? typeConvert.Range.from(v.initialRange) : undefined,
|
||||
message: v.message,
|
||||
autoSend: v.autoSend
|
||||
};
|
||||
})],
|
||||
ApiCommandResult.Void
|
||||
));
|
||||
}
|
||||
|
||||
registerProvider(extension: Readonly<IRelaxedExtensionDescription>, provider: vscode.InteractiveEditorSessionProvider): vscode.Disposable {
|
||||
|
||||
@@ -32,7 +32,7 @@ export class ExtHostShare implements ExtHostShareShape {
|
||||
registerShareProvider(selector: vscode.DocumentSelector, provider: vscode.ShareProvider): vscode.Disposable {
|
||||
const handle = ExtHostShare.handlePool++;
|
||||
this.providers.set(handle, provider);
|
||||
this.proxy.$registerShareProvider(handle, DocumentSelector.from(selector, this.uriTransformer), provider.id, provider.label);
|
||||
this.proxy.$registerShareProvider(handle, DocumentSelector.from(selector, this.uriTransformer), provider.id, provider.label, provider.priority);
|
||||
return {
|
||||
dispose: () => {
|
||||
this.proxy.$unregisterShareProvider(handle);
|
||||
|
||||
@@ -25,7 +25,7 @@ import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { Promises } from 'vs/base/common/async';
|
||||
import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn';
|
||||
import { ViewColumn } from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
|
||||
|
||||
export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, IDisposable {
|
||||
|
||||
@@ -898,6 +898,10 @@ class UnifiedEnvironmentVariableCollection {
|
||||
}
|
||||
|
||||
getScopedEnvironmentVariableCollection(scope: vscode.EnvironmentVariableScope | undefined): IEnvironmentVariableCollection {
|
||||
if (this._extension && scope) {
|
||||
// TODO: This should be removed when the env var extension API(s) are stabilized
|
||||
checkProposedApiEnabled(this._extension, 'envCollectionWorkspace');
|
||||
}
|
||||
const scopedCollectionKey = this.getScopeKey(scope);
|
||||
let scopedCollection = this.scopedCollections.get(scopedCollectionKey);
|
||||
if (!scopedCollection) {
|
||||
@@ -910,21 +914,21 @@ class UnifiedEnvironmentVariableCollection {
|
||||
|
||||
replace(variable: string, value: string, options: vscode.EnvironmentVariableMutatorOptions | undefined, scope: vscode.EnvironmentVariableScope | undefined): void {
|
||||
if (this._extension && options) {
|
||||
isProposedApiEnabled(this._extension, 'envCollectionOptions');
|
||||
checkProposedApiEnabled(this._extension, 'envCollectionOptions');
|
||||
}
|
||||
this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Replace, options: options ?? {}, scope });
|
||||
}
|
||||
|
||||
append(variable: string, value: string, options: vscode.EnvironmentVariableMutatorOptions | undefined, scope: vscode.EnvironmentVariableScope | undefined): void {
|
||||
if (this._extension && options) {
|
||||
isProposedApiEnabled(this._extension, 'envCollectionOptions');
|
||||
checkProposedApiEnabled(this._extension, 'envCollectionOptions');
|
||||
}
|
||||
this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Append, options: options ?? {}, scope });
|
||||
}
|
||||
|
||||
prepend(variable: string, value: string, options: vscode.EnvironmentVariableMutatorOptions | undefined, scope: vscode.EnvironmentVariableScope | undefined): void {
|
||||
if (this._extension && options) {
|
||||
isProposedApiEnabled(this._extension, 'envCollectionOptions');
|
||||
checkProposedApiEnabled(this._extension, 'envCollectionOptions');
|
||||
}
|
||||
this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Prepend, options: options ?? {}, scope });
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import { TestCommandId } from 'vs/workbench/contrib/testing/common/constants';
|
||||
import { TestId, TestIdPathParts, TestPosition } from 'vs/workbench/contrib/testing/common/testId';
|
||||
import { InvalidTestItemError } from 'vs/workbench/contrib/testing/common/testItemCollection';
|
||||
import { AbstractIncrementalTestCollection, CoverageDetails, ICallProfileRunHandler, IFileCoverage, ISerializedTestResults, IStartControllerTests, IStartControllerTestsResult, ITestItem, ITestItemContext, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, TestResultState, TestRunProfileBitset, TestsDiff, TestsDiffOp, isStartControllerTests } from 'vs/workbench/contrib/testing/common/testTypes';
|
||||
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import type * as vscode from 'vscode';
|
||||
|
||||
interface ControllerInfo {
|
||||
@@ -137,6 +138,11 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
createTestRun: (request, name, persist = true) => {
|
||||
return this.runTracker.createTestRun(controllerId, collection, request, name, persist);
|
||||
},
|
||||
invalidateTestResults: item => {
|
||||
checkProposedApiEnabled(extension, 'testInvalidateResults');
|
||||
const id = item ? TestId.fromExtHostTestItem(item, controllerId).toString() : controllerId;
|
||||
return this.proxy.$markTestRetired(id);
|
||||
},
|
||||
set resolveHandler(fn) {
|
||||
collection.resolveHandler = fn;
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ExtHostTunnelServiceShape, TunnelDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostTunnelServiceShape, PortAttributesSelector, TunnelDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import * as vscode from 'vscode';
|
||||
import { ProvidedPortAttributes, RemoteTunnel, TunnelCreationOptions, TunnelOptions, TunnelPrivacyId } from 'vs/platform/tunnel/common/tunnel';
|
||||
@@ -48,7 +48,7 @@ export interface IExtHostTunnelService extends ExtHostTunnelServiceShape {
|
||||
getTunnels(): Promise<vscode.TunnelDescription[]>;
|
||||
onDidChangeTunnels: vscode.Event<void>;
|
||||
setTunnelFactory(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable>;
|
||||
registerPortsAttributesProvider(portSelector: { pid?: number; portRange?: [number, number]; commandPattern?: RegExp }, provider: vscode.PortAttributesProvider): IDisposable;
|
||||
registerPortsAttributesProvider(portSelector: PortAttributesSelector, provider: vscode.PortAttributesProvider): IDisposable;
|
||||
}
|
||||
|
||||
export const IExtHostTunnelService = createDecorator<IExtHostTunnelService>('IExtHostTunnelService');
|
||||
@@ -74,7 +74,7 @@ export class ExtHostTunnelService implements IExtHostTunnelService {
|
||||
async setTunnelFactory(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> {
|
||||
return { dispose: () => { } };
|
||||
}
|
||||
registerPortsAttributesProvider(portSelector: { pid?: number; portRange?: [number, number] }, provider: vscode.PortAttributesProvider) {
|
||||
registerPortsAttributesProvider(portSelector: PortAttributesSelector, provider: vscode.PortAttributesProvider) {
|
||||
return { dispose: () => { } };
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MainThreadTunnelServiceShape, MainContext, PortAttributesProviderSelector, TunnelDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { MainThreadTunnelServiceShape, MainContext, PortAttributesSelector, TunnelDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import type * as vscode from 'vscode';
|
||||
import * as nls from 'vs/nls';
|
||||
@@ -186,7 +186,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
private _initialCandidates: CandidatePort[] | undefined = undefined;
|
||||
|
||||
private _providerHandleCounter: number = 0;
|
||||
private _portAttributesProviders: Map<number, { provider: vscode.PortAttributesProvider; selector: PortAttributesProviderSelector }> = new Map();
|
||||
private _portAttributesProviders: Map<number, { provider: vscode.PortAttributesProvider; selector: PortAttributesSelector }> = new Map();
|
||||
|
||||
constructor(
|
||||
@IExtHostRpcService extHostRpc: IExtHostRpcService,
|
||||
@@ -232,7 +232,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
return this._providerHandleCounter++;
|
||||
}
|
||||
|
||||
registerPortsAttributesProvider(portSelector: PortAttributesProviderSelector, provider: vscode.PortAttributesProvider): vscode.Disposable {
|
||||
registerPortsAttributesProvider(portSelector: PortAttributesSelector, provider: vscode.PortAttributesProvider): vscode.Disposable {
|
||||
const providerHandle = this.nextPortAttributesProviderHandle();
|
||||
this._portAttributesProviders.set(providerHandle, { selector: portSelector, provider });
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ export class BrowserDialogHandler extends AbstractDialogHandler {
|
||||
|
||||
const buttons = this.getInputButtons(input);
|
||||
|
||||
const { button, checkboxChecked, values } = await this.doShow(input.type ?? 'question', input.message, buttons, input.detail, buttons.length - 1, input?.checkbox, input.inputs);
|
||||
const { button, checkboxChecked, values } = await this.doShow(input.type ?? 'question', input.message, buttons, input.detail, buttons.length - 1, input?.checkbox, input.inputs, typeof input.custom === 'object' ? input.custom : undefined);
|
||||
|
||||
return { confirmed: button === 0, checkboxChecked, values };
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ import type { IProgress, IProgressCompositeOptions, IProgressDialogOptions, IPro
|
||||
import type { ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import type { EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import type { IEmbedderTerminalOptions } from 'vs/workbench/services/terminal/common/embedderTerminalService';
|
||||
import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
|
||||
import { ITranslations } from 'vs/platform/extensionManagement/common/extensionNls';
|
||||
|
||||
/**
|
||||
* The `IWorkbench` interface is the API facade for web embedders
|
||||
@@ -220,7 +222,7 @@ export interface IWorkbenchConstructionOptions {
|
||||
* - an extension in the Marketplace
|
||||
* - location of the extension where it is hosted.
|
||||
*/
|
||||
readonly additionalBuiltinExtensions?: readonly (MarketplaceExtension | UriComponents)[];
|
||||
readonly additionalBuiltinExtensions?: readonly (MarketplaceExtension | UriComponents | HostedExtension)[];
|
||||
|
||||
/**
|
||||
* List of extensions to be enabled if they are installed.
|
||||
@@ -373,6 +375,15 @@ export interface IResourceUriProvider {
|
||||
export type ExtensionId = string;
|
||||
|
||||
export type MarketplaceExtension = ExtensionId | { readonly id: ExtensionId; preRelease?: boolean; migrateStorageFrom?: ExtensionId };
|
||||
export interface HostedExtension {
|
||||
readonly location: UriComponents;
|
||||
readonly preRelease?: boolean;
|
||||
readonly packageJSON?: IExtensionManifest;
|
||||
readonly defaultPackageTranslations?: ITranslations | null;
|
||||
readonly packageNLSUris?: Map<string, UriComponents>;
|
||||
readonly readmeUri?: UriComponents;
|
||||
readonly changelogUri?: UriComponents;
|
||||
}
|
||||
|
||||
export interface ICommonTelemetryPropertiesResolver {
|
||||
(): { [key: string]: any };
|
||||
|
||||
@@ -14,9 +14,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { ActiveEditorContext } from 'vs/workbench/common/contextkeys';
|
||||
import { runAccessibilityHelpAction } from 'vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp';
|
||||
import { clearChatEditor, clearChatSession } from 'vs/workbench/contrib/chat/browser/actions/chatClear';
|
||||
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
|
||||
import { IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor';
|
||||
import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput';
|
||||
@@ -53,36 +51,6 @@ export function registerChatActions() {
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class ClearEditorAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.action.chatEditor.clear',
|
||||
title: {
|
||||
value: localize('interactiveSession.clear.label', "Clear"),
|
||||
original: 'Clear'
|
||||
},
|
||||
icon: Codicon.clearAll,
|
||||
f1: false,
|
||||
menu: [{
|
||||
id: MenuId.EditorTitle,
|
||||
group: 'navigation',
|
||||
order: 0,
|
||||
when: ActiveEditorContext.isEqualTo(ChatEditorInput.EditorID),
|
||||
}]
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor, ...args: any[]) {
|
||||
const widgetService = accessor.get(IChatWidgetService);
|
||||
|
||||
const widget = widgetService.lastFocusedWidget;
|
||||
if (!widget) {
|
||||
return;
|
||||
}
|
||||
|
||||
await clearChatEditor(accessor, widget);
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class ClearChatHistoryAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
@@ -165,38 +133,6 @@ export function registerChatActions() {
|
||||
widgetService.lastFocusedWidget?.focusInput();
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class GlobalClearChatAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.action.chat.clear`,
|
||||
title: {
|
||||
value: localize('interactiveSession.clear.label', "Clear"),
|
||||
original: 'Clear'
|
||||
},
|
||||
category: CHAT_CATEGORY,
|
||||
icon: Codicon.clearAll,
|
||||
precondition: CONTEXT_PROVIDER_EXISTS,
|
||||
f1: true,
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.WinCtrl | KeyCode.KeyL,
|
||||
when: CONTEXT_IN_CHAT_SESSION
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async run(accessor: ServicesAccessor, ...args: any[]) {
|
||||
const widgetService = accessor.get(IChatWidgetService);
|
||||
|
||||
const widget = widgetService.lastFocusedWidget;
|
||||
if (!widget) {
|
||||
return;
|
||||
}
|
||||
|
||||
await clearChatSession(accessor, widget);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function getOpenChatEditorAction(id: string, label: string, when?: string) {
|
||||
@@ -218,36 +154,6 @@ export function getOpenChatEditorAction(id: string, label: string, when?: string
|
||||
};
|
||||
}
|
||||
|
||||
const getClearChatActionDescriptorForViewTitle = (viewId: string, providerId: string): Readonly<IAction2Options> & { viewId: string } => ({
|
||||
viewId,
|
||||
id: `workbench.action.chat.${providerId}.clear`,
|
||||
title: {
|
||||
value: localize('interactiveSession.clear.label', "Clear"),
|
||||
original: 'Clear'
|
||||
},
|
||||
menu: {
|
||||
id: MenuId.ViewTitle,
|
||||
when: ContextKeyExpr.equals('view', viewId),
|
||||
group: 'navigation',
|
||||
order: 0
|
||||
},
|
||||
category: CHAT_CATEGORY,
|
||||
icon: Codicon.clearAll,
|
||||
f1: false
|
||||
});
|
||||
|
||||
export function getClearAction(viewId: string, providerId: string) {
|
||||
return class ClearAction extends ViewAction<ChatViewPane> {
|
||||
constructor() {
|
||||
super(getClearChatActionDescriptorForViewTitle(viewId, providerId));
|
||||
}
|
||||
|
||||
async runInView(accessor: ServicesAccessor, view: ChatViewPane) {
|
||||
await view.clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const getHistoryChatActionDescriptorForViewTitle = (viewId: string, providerId: string): Readonly<IAction2Options> & { viewId: string } => ({
|
||||
viewId,
|
||||
id: `workbench.action.chat.${providerId}.history`,
|
||||
|
||||
@@ -5,16 +5,15 @@
|
||||
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IViewsService } from 'vs/workbench/common/views';
|
||||
import { IChatWidgetService, type IChatViewPane, type IChatWidget } from 'vs/workbench/contrib/chat/browser/chat';
|
||||
import { type IChatViewPane, type IChatWidget } from 'vs/workbench/contrib/chat/browser/chat';
|
||||
import { IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor';
|
||||
import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
export async function clearChatSession(accessor: ServicesAccessor, widget: IChatWidget): Promise<void> {
|
||||
const viewsService = accessor.get(IViewsService);
|
||||
|
||||
if ('viewId' in widget.viewContext) {
|
||||
const viewsService = accessor.get(IViewsService);
|
||||
// This cast is to break a circular dependency- ideally this would not be called directly for `/clear`
|
||||
// from the widget class, but from some contribution.
|
||||
const view = viewsService.getViewWithId(widget.viewContext.viewId);
|
||||
@@ -24,17 +23,19 @@ export async function clearChatSession(accessor: ServicesAccessor, widget: IChat
|
||||
|
||||
(view as any as IChatViewPane).clear();
|
||||
} else {
|
||||
await clearChatEditor(accessor, widget);
|
||||
await clearChatEditor(accessor);
|
||||
}
|
||||
}
|
||||
|
||||
export async function clearChatEditor(accessor: ServicesAccessor, widget: IChatWidget): Promise<void> {
|
||||
export async function clearChatEditor(accessor: ServicesAccessor): Promise<void> {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const editorGroupsService = accessor.get(IEditorGroupsService);
|
||||
const widgetService = accessor.get(IChatWidgetService);
|
||||
|
||||
await editorService.replaceEditors([{
|
||||
editor: editorService.activeEditor!,
|
||||
replacement: { resource: ChatEditorInput.getNewEditorUri(), options: <IChatEditorOptions>{ target: { providerId: widgetService.lastFocusedWidget!.providerId, pinned: true } } }
|
||||
}], editorGroupsService.activeGroup);
|
||||
const chatEditorInput = editorService.activeEditor;
|
||||
if (chatEditorInput instanceof ChatEditorInput && chatEditorInput.providerId) {
|
||||
await editorService.replaceEditors([{
|
||||
editor: chatEditorInput,
|
||||
replacement: { resource: ChatEditorInput.getNewEditorUri(), options: <IChatEditorOptions>{ target: { providerId: chatEditorInput.providerId, pinned: true } } }
|
||||
}], editorGroupsService.activeGroup);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Action2, IAction2Options, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { ActiveEditorContext } from 'vs/workbench/common/contextkeys';
|
||||
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
|
||||
import { clearChatEditor, clearChatSession } from 'vs/workbench/contrib/chat/browser/actions/chatClear';
|
||||
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
|
||||
import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput';
|
||||
import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane';
|
||||
import { CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
||||
|
||||
export function registerClearActions() {
|
||||
|
||||
registerAction2(class ClearEditorAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.action.chatEditor.clear',
|
||||
title: {
|
||||
value: localize('interactiveSession.clear.label', "Clear"),
|
||||
original: 'Clear'
|
||||
},
|
||||
icon: Codicon.clearAll,
|
||||
f1: false,
|
||||
menu: [{
|
||||
id: MenuId.EditorTitle,
|
||||
group: 'navigation',
|
||||
order: 0,
|
||||
when: ActiveEditorContext.isEqualTo(ChatEditorInput.EditorID),
|
||||
}]
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor, ...args: any[]) {
|
||||
await clearChatEditor(accessor);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
registerAction2(class GlobalClearChatAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.action.chat.clear`,
|
||||
title: {
|
||||
value: localize('interactiveSession.clear.label', "Clear"),
|
||||
original: 'Clear'
|
||||
},
|
||||
category: CHAT_CATEGORY,
|
||||
icon: Codicon.clearAll,
|
||||
precondition: CONTEXT_PROVIDER_EXISTS,
|
||||
f1: true,
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.WinCtrl | KeyCode.KeyL,
|
||||
when: CONTEXT_IN_CHAT_SESSION
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async run(accessor: ServicesAccessor, ...args: any[]) {
|
||||
const widgetService = accessor.get(IChatWidgetService);
|
||||
|
||||
const widget = widgetService.lastFocusedWidget;
|
||||
if (!widget) {
|
||||
return;
|
||||
}
|
||||
|
||||
await clearChatSession(accessor, widget);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const getClearChatActionDescriptorForViewTitle = (viewId: string, providerId: string): Readonly<IAction2Options> & { viewId: string } => ({
|
||||
viewId,
|
||||
id: `workbench.action.chat.${providerId}.clear`,
|
||||
title: {
|
||||
value: localize('interactiveSession.clear.label', "Clear"),
|
||||
original: 'Clear'
|
||||
},
|
||||
menu: {
|
||||
id: MenuId.ViewTitle,
|
||||
when: ContextKeyExpr.equals('view', viewId),
|
||||
group: 'navigation',
|
||||
order: 0
|
||||
},
|
||||
category: CHAT_CATEGORY,
|
||||
icon: Codicon.clearAll,
|
||||
f1: false
|
||||
});
|
||||
|
||||
export function getClearAction(viewId: string, providerId: string) {
|
||||
return class ClearAction extends ViewAction<ChatViewPane> {
|
||||
constructor() {
|
||||
super(getClearChatActionDescriptorForViewTitle(viewId, providerId));
|
||||
}
|
||||
|
||||
async runInView(accessor: ServicesAccessor, view: ChatViewPane) {
|
||||
await view.clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Action2, IAction2Options, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { ActiveEditorContext } from 'vs/workbench/common/contextkeys';
|
||||
import { IViewsService } from 'vs/workbench/common/views';
|
||||
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
|
||||
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
|
||||
import { IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor';
|
||||
import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput';
|
||||
import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane';
|
||||
import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
||||
import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
const getMoveToEditorChatActionDescriptorForViewTitle = (viewId: string, providerId: string): Readonly<IAction2Options> & { viewId: string } => ({
|
||||
id: `workbench.action.chat.${providerId}.openInEditor`,
|
||||
title: {
|
||||
value: localize('chat.openInEditor.label', "Open In Editor"),
|
||||
original: 'Open In Editor'
|
||||
},
|
||||
category: CHAT_CATEGORY,
|
||||
icon: Codicon.arrowLeft,
|
||||
precondition: CONTEXT_PROVIDER_EXISTS,
|
||||
f1: false,
|
||||
viewId,
|
||||
menu: {
|
||||
id: MenuId.ViewTitle,
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.equals('view', viewId), ContextKeyExpr.deserialize('config.chat.experimiental.moveIcons')),
|
||||
group: 'navigation',
|
||||
order: 0
|
||||
},
|
||||
});
|
||||
|
||||
export function getMoveToEditorAction(viewId: string, providerId: string) {
|
||||
return class MoveToEditorAction extends ViewAction<ChatViewPane> {
|
||||
constructor() {
|
||||
super(getMoveToEditorChatActionDescriptorForViewTitle(viewId, providerId));
|
||||
}
|
||||
|
||||
async runInView(accessor: ServicesAccessor, view: ChatViewPane) {
|
||||
const viewModel = view.widget.viewModel;
|
||||
if (!viewModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
const editorService = accessor.get(IEditorService);
|
||||
view.clear();
|
||||
await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: <IChatEditorOptions>{ target: { sessionId: viewModel.sessionId }, pinned: true } });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const getMoveToSidebarChatActionDescriptorForViewTitle = (viewId: string, providerId: string): Readonly<IAction2Options> & { viewId: string } => ({
|
||||
id: `workbench.action.chat.${providerId}.openInSidebar`,
|
||||
title: {
|
||||
value: localize('chat.openInSidebar.label', "Open In Sidebar"),
|
||||
original: 'Open In Sidebar'
|
||||
},
|
||||
category: CHAT_CATEGORY,
|
||||
icon: Codicon.arrowRight,
|
||||
precondition: CONTEXT_PROVIDER_EXISTS,
|
||||
f1: false, // TODO
|
||||
viewId,
|
||||
menu: [{
|
||||
id: MenuId.EditorTitle,
|
||||
group: 'navigation',
|
||||
order: 0,
|
||||
when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(ChatEditorInput.EditorID), ContextKeyExpr.deserialize('config.chat.experimental.moveIcons')),
|
||||
}]
|
||||
});
|
||||
|
||||
export function getMoveToSidebarAction(viewId: string, providerId: string) {
|
||||
return class MoveToSidebarAction extends Action2 {
|
||||
constructor() {
|
||||
super(getMoveToSidebarChatActionDescriptorForViewTitle(viewId, providerId));
|
||||
}
|
||||
|
||||
override async run(accessor: ServicesAccessor, ...args: any[]) {
|
||||
return moveToSidebar(accessor);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function moveToSidebar(accessor: ServicesAccessor): Promise<void> {
|
||||
const viewsService = accessor.get(IViewsService);
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const chatContribService = accessor.get(IChatContributionService);
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
|
||||
const chatEditorInput = editorService.activeEditor;
|
||||
if (chatEditorInput instanceof ChatEditorInput && chatEditorInput.sessionId && chatEditorInput.providerId) {
|
||||
await editorService.closeEditor({ editor: chatEditorInput, groupId: editorGroupService.activeGroup.id });
|
||||
const viewId = chatContribService.getViewIdForProvider(chatEditorInput.providerId);
|
||||
const view = await viewsService.openView(viewId) as ChatViewPane;
|
||||
view.loadSession(chatEditorInput.sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
export function registerMoveActions() {
|
||||
registerAction2(class GlobalMoveToEditorAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.action.chat.openInEditor`,
|
||||
title: {
|
||||
value: localize('interactiveSession.openInEditor.label', "Open Session In Editor"),
|
||||
original: 'Open Session In Editor'
|
||||
},
|
||||
category: CHAT_CATEGORY,
|
||||
precondition: CONTEXT_PROVIDER_EXISTS,
|
||||
f1: true
|
||||
});
|
||||
}
|
||||
|
||||
async run(accessor: ServicesAccessor, ...args: any[]) {
|
||||
const widgetService = accessor.get(IChatWidgetService);
|
||||
const viewService = accessor.get(IViewsService);
|
||||
const editorService = accessor.get(IEditorService);
|
||||
|
||||
const widget = widgetService.lastFocusedWidget;
|
||||
if (!widget || !('viewId' in widget.viewContext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewModel = widget.viewModel;
|
||||
if (!viewModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
const view = await viewService.openView(widget.viewContext.viewId) as ChatViewPane;
|
||||
await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: <IChatEditorOptions>{ target: { sessionId: viewModel.sessionId }, pinned: true } });
|
||||
view.clear();
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class GlobalMoveToSidebarAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.action.chat.openInSidebar`,
|
||||
title: {
|
||||
value: localize('interactiveSession.openInSidebar.label', "Open Session In Sidebar"),
|
||||
original: 'Open Session In Sidebar'
|
||||
},
|
||||
category: CHAT_CATEGORY,
|
||||
precondition: CONTEXT_PROVIDER_EXISTS,
|
||||
f1: true
|
||||
});
|
||||
}
|
||||
|
||||
async run(accessor: ServicesAccessor, ...args: any[]) {
|
||||
return moveToSidebar(accessor);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -9,11 +9,12 @@ import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
|
||||
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
|
||||
import { CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
||||
import { CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
||||
import { IChatService, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
|
||||
import { isRequestVM, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
|
||||
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||
import { CellEditType, CellKind, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
|
||||
@@ -33,9 +34,10 @@ export function registerChatTitleActions() {
|
||||
icon: Codicon.thumbsup,
|
||||
toggled: CONTEXT_RESPONSE_VOTE.isEqualTo('up'),
|
||||
menu: {
|
||||
id: MenuId.ChatTitle,
|
||||
id: MenuId.ChatMessageTitle,
|
||||
group: 'navigation',
|
||||
order: 1
|
||||
order: 1,
|
||||
when: CONTEXT_RESPONSE
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -72,9 +74,10 @@ export function registerChatTitleActions() {
|
||||
icon: Codicon.thumbsdown,
|
||||
toggled: CONTEXT_RESPONSE_VOTE.isEqualTo('down'),
|
||||
menu: {
|
||||
id: MenuId.ChatTitle,
|
||||
id: MenuId.ChatMessageTitle,
|
||||
group: 'navigation',
|
||||
order: 2
|
||||
order: 2,
|
||||
when: CONTEXT_RESPONSE
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -110,10 +113,10 @@ export function registerChatTitleActions() {
|
||||
category: CHAT_CATEGORY,
|
||||
icon: Codicon.insert,
|
||||
menu: {
|
||||
id: MenuId.ChatTitle,
|
||||
id: MenuId.ChatMessageTitle,
|
||||
group: 'navigation',
|
||||
isHiddenByDefault: true,
|
||||
when: NOTEBOOK_IS_ACTIVE_EDITOR
|
||||
when: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, CONTEXT_RESPONSE)
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -172,6 +175,40 @@ export function registerChatTitleActions() {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
registerAction2(class RemoveAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.action.chat.remove',
|
||||
title: {
|
||||
value: localize('chat.remove.label', "Remove Request and Response"),
|
||||
original: 'Remove Request and Response'
|
||||
},
|
||||
f1: false,
|
||||
category: CHAT_CATEGORY,
|
||||
icon: Codicon.x,
|
||||
menu: {
|
||||
id: MenuId.ChatMessageTitle,
|
||||
group: 'navigation',
|
||||
order: 2,
|
||||
when: CONTEXT_REQUEST
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor, ...args: any[]) {
|
||||
const item = args[0];
|
||||
if (!isRequestVM(item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const chatService = accessor.get(IChatService);
|
||||
if (item.providerRequestId) {
|
||||
chatService.removeRequest(item.sessionId, item.providerRequestId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
interface MarkdownContent {
|
||||
|
||||
@@ -35,6 +35,7 @@ import { ChatWidgetHistoryService, IChatWidgetHistoryService } from 'vs/workbenc
|
||||
import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import '../common/chatColors';
|
||||
import { registerMoveActions } from 'vs/workbench/contrib/chat/browser/actions/chatMoveActions';
|
||||
|
||||
// Register configuration
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
|
||||
@@ -128,6 +129,7 @@ registerChatTitleActions();
|
||||
registerChatExecuteActions();
|
||||
registerChatQuickQuestionActions();
|
||||
registerChatExportActions();
|
||||
registerMoveActions();
|
||||
|
||||
registerSingleton(IChatService, ChatService, InstantiationType.Delayed);
|
||||
registerSingleton(IChatContributionService, ChatContributionService, InstantiationType.Delayed);
|
||||
|
||||
@@ -16,7 +16,9 @@ import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, ViewContainerLocation, Extensions as ViewExtensions } from 'vs/workbench/common/views';
|
||||
import { getClearAction, getHistoryAction, getOpenChatEditorAction } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
|
||||
import { getHistoryAction, getOpenChatEditorAction } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
|
||||
import { getClearAction } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions';
|
||||
import { getMoveToEditorAction, getMoveToSidebarAction } from 'vs/workbench/contrib/chat/browser/actions/chatMoveActions';
|
||||
import { IChatViewOptions, CHAT_SIDEBAR_PANEL_ID, ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane';
|
||||
import { IChatContributionService, IChatProviderContribution, IRawChatProviderContribution } from 'vs/workbench/contrib/chat/common/chatContributionService';
|
||||
import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
@@ -136,20 +138,23 @@ export class ChatContributionService implements IChatContributionService {
|
||||
}];
|
||||
Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews(viewDescriptor, viewContainer);
|
||||
|
||||
// Per-provider actions
|
||||
|
||||
// Actions in view title
|
||||
const historyAction = registerAction2(getHistoryAction(viewId, providerDescriptor.id));
|
||||
const clearAction = registerAction2(getClearAction(viewId, providerDescriptor.id));
|
||||
const disposables = new DisposableStore();
|
||||
disposables.add(registerAction2(getHistoryAction(viewId, providerDescriptor.id)));
|
||||
disposables.add(registerAction2(getClearAction(viewId, providerDescriptor.id)));
|
||||
disposables.add(registerAction2(getMoveToEditorAction(viewId, providerDescriptor.id)));
|
||||
disposables.add(registerAction2(getMoveToSidebarAction(viewId, providerDescriptor.id)));
|
||||
|
||||
// "Open Chat Editor" Action
|
||||
const openEditor = registerAction2(getOpenChatEditorAction(providerDescriptor.id, providerDescriptor.label, providerDescriptor.when));
|
||||
disposables.add(registerAction2(getOpenChatEditorAction(providerDescriptor.id, providerDescriptor.label, providerDescriptor.when)));
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).deregisterViews(viewDescriptor, viewContainer);
|
||||
Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry).deregisterViewContainer(viewContainer);
|
||||
clearAction.dispose();
|
||||
historyAction.dispose();
|
||||
openEditor.dispose();
|
||||
disposables.dispose();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ export class ChatEditorInput extends EditorInput {
|
||||
}
|
||||
|
||||
this.sessionId = this.model.sessionId;
|
||||
this.providerId = this.model.providerId;
|
||||
await this.model.waitForInitialization();
|
||||
this._register(this.model.onDidChange(() => this._onDidChangeLabel.fire()));
|
||||
|
||||
|
||||
@@ -54,12 +54,13 @@ import { IChatCodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/a
|
||||
import { IChatCodeBlockInfo } from 'vs/workbench/contrib/chat/browser/chat';
|
||||
import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups';
|
||||
import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions';
|
||||
import { CONTEXT_RESPONSE_HAS_PROVIDER_ID, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
||||
import { CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_HAS_PROVIDER_ID, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
||||
import { IChatReplyFollowup, IChatService, ISlashCommand, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
import { IChatRequestViewModel, IChatResponseViewModel, IChatWelcomeMessageViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
|
||||
import { IWordCountResult, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter';
|
||||
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution';
|
||||
import { marked } from 'vs/base/common/marked/marked';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
@@ -116,7 +117,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IChatService private readonly chatService: IChatService,
|
||||
@IChatService private readonly chatService: IChatService
|
||||
) {
|
||||
super();
|
||||
this.renderer = this.instantiationService.createInstance(MarkdownRenderer, {});
|
||||
@@ -192,12 +193,12 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
|
||||
|
||||
const contextKeyService = templateDisposables.add(this.contextKeyService.createScoped(rowContainer));
|
||||
const scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyService]));
|
||||
const titleToolbar = templateDisposables.add(scopedInstantiationService.createInstance(MenuWorkbenchToolBar, header, MenuId.ChatTitle, {
|
||||
const titleToolbar = templateDisposables.add(scopedInstantiationService.createInstance(MenuWorkbenchToolBar, header, MenuId.ChatMessageTitle, {
|
||||
menuOptions: {
|
||||
shouldForwardArgs: true
|
||||
},
|
||||
actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => {
|
||||
if (action instanceof MenuItemAction) {
|
||||
if (action instanceof MenuItemAction && (action.item.id === 'workbench.action.chat.voteDown' || action.item.id === 'workbench.action.chat.voteUp')) {
|
||||
return scopedInstantiationService.createInstance(ChatVoteButton, action, options as IMenuEntryActionViewItemOptions);
|
||||
}
|
||||
|
||||
@@ -217,6 +218,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
|
||||
'welcome';
|
||||
this.traceLayout('renderElement', `${kind}, index=${index}`);
|
||||
|
||||
CONTEXT_RESPONSE.bindTo(templateData.contextKeyService).set(isResponseVM(element));
|
||||
CONTEXT_REQUEST.bindTo(templateData.contextKeyService).set(isRequestVM(element));
|
||||
CONTEXT_RESPONSE_HAS_PROVIDER_ID.bindTo(templateData.contextKeyService).set(isResponseVM(element) && !!element.providerResponseId);
|
||||
if (isResponseVM(element)) {
|
||||
CONTEXT_RESPONSE_VOTE.bindTo(templateData.contextKeyService).set(element.vote === InteractiveSessionVoteDirection.Up ? 'up' : element.vote === InteractiveSessionVoteDirection.Down ? 'down' : '');
|
||||
@@ -515,7 +518,6 @@ export class ChatListDelegate implements IListVirtualDelegate<ChatTreeItem> {
|
||||
}
|
||||
|
||||
export class ChatAccessibilityProvider implements IListAccessibilityProvider<ChatTreeItem> {
|
||||
|
||||
getWidgetRole(): AriaRole {
|
||||
return 'list';
|
||||
}
|
||||
@@ -534,7 +536,7 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider<Cha
|
||||
}
|
||||
|
||||
if (isResponseVM(element)) {
|
||||
return element.response.value;
|
||||
return this._getLabelWithCodeBlockCount(element);
|
||||
}
|
||||
|
||||
if (isWelcomeVM(element)) {
|
||||
@@ -543,6 +545,18 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider<Cha
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private _getLabelWithCodeBlockCount(element: IChatResponseViewModel): string {
|
||||
const codeBlockCount = marked.lexer(element.response.value).filter(token => token.type === 'code')?.length ?? 0;
|
||||
switch (codeBlockCount) {
|
||||
case 0:
|
||||
return element.response.value;
|
||||
case 1:
|
||||
return localize('singleCodeBlock', "1 code block, {0}", element.response.value);
|
||||
default:
|
||||
return localize('multiCodeBlock', "{0} code blocks, {1}", codeBlockCount, element.response.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface IChatResultCodeBlockData {
|
||||
@@ -634,7 +648,6 @@ class CodeBlockPart extends Disposable implements IChatResultCodeBlockPart {
|
||||
])
|
||||
}));
|
||||
|
||||
|
||||
this._register(this.options.onDidChange(() => {
|
||||
this.editor.updateOptions(this.getEditorOptionsFromConfig());
|
||||
}));
|
||||
@@ -723,7 +736,7 @@ class CodeBlockPart extends Disposable implements IChatResultCodeBlockPart {
|
||||
this.setLanguage(vscodeLanguageId);
|
||||
|
||||
this.layout(width);
|
||||
|
||||
this.editor.updateOptions({ ariaLabel: localize('chat.codeBlockLabel', "Code block {0}", data.codeBlockIndex + 1) });
|
||||
this.toolbar.context = <IChatCodeBlockActionContext>{
|
||||
code: data.text,
|
||||
codeBlockIndex: data.codeBlockIndex,
|
||||
|
||||
@@ -115,8 +115,17 @@ export class ChatViewPane extends ViewPane implements IChatViewPane {
|
||||
async clear(): Promise<void> {
|
||||
if (this.widget.viewModel) {
|
||||
this.chatService.clearSession(this.widget.viewModel.sessionId);
|
||||
this.updateModel();
|
||||
}
|
||||
this.updateModel();
|
||||
}
|
||||
|
||||
loadSession(sessionId: string): void {
|
||||
if (this.widget.viewModel) {
|
||||
this.chatService.clearSession(this.widget.viewModel.sessionId);
|
||||
}
|
||||
|
||||
const newModel = this.chatService.getOrRestoreSession(sessionId);
|
||||
this.updateModel(newModel);
|
||||
}
|
||||
|
||||
focusInput(): void {
|
||||
|
||||
@@ -65,8 +65,8 @@
|
||||
color: var(--vscode-badge-foreground) !important;
|
||||
}
|
||||
|
||||
.interactive-item-container:not(.interactive-response:hover) .header .monaco-toolbar,
|
||||
.interactive-item-container:not(.interactive-response:hover) .header .monaco-toolbar .action-label {
|
||||
.monaco-list-row:not(.focused) .interactive-item-container:not(:hover) .header .monaco-toolbar,
|
||||
.monaco-list-row:not(.focused) .interactive-item-container:not(:hover) .header .monaco-toolbar .action-label {
|
||||
/* Also apply this rule to the .action-label directly to work around a strange issue- when the
|
||||
toolbar is hidden without that second rule, tabbing from the list container into a list item doesn't work
|
||||
and the tab key doesn't do anything. */
|
||||
|
||||
@@ -10,6 +10,9 @@ export const CONTEXT_RESPONSE_HAS_PROVIDER_ID = new RawContextKey<boolean>('chat
|
||||
export const CONTEXT_RESPONSE_VOTE = new RawContextKey<string>('chatSessionResponseVote', '', { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") });
|
||||
export const CONTEXT_CHAT_REQUEST_IN_PROGRESS = new RawContextKey<boolean>('chatSessionRequestInProgress', false, { type: 'boolean', description: localize('interactiveSessionRequestInProgress', "True when the current request is still in progress.") });
|
||||
|
||||
export const CONTEXT_RESPONSE = new RawContextKey<boolean>('chatResponse', false, { type: 'boolean', description: localize('chatResponse', "The chat item is a response.") });
|
||||
export const CONTEXT_REQUEST = new RawContextKey<boolean>('chatRequest', false, { type: 'boolean', description: localize('chatRequest', "The chat item is a request") });
|
||||
|
||||
export const CONTEXT_CHAT_INPUT_HAS_TEXT = new RawContextKey<boolean>('chatInputHasText', false, { type: 'boolean', description: localize('interactiveInputHasText', "True when the chat input has text.") });
|
||||
export const CONTEXT_IN_CHAT_INPUT = new RawContextKey<boolean>('inChatInput', false, { type: 'boolean', description: localize('inInteractiveInput', "True when focus is in the chat input, false otherwise.") });
|
||||
export const CONTEXT_IN_CHAT_SESSION = new RawContextKey<boolean>('inChat', false, { type: 'boolean', description: localize('inChat', "True when focus is in the chat widget, false otherwise.") });
|
||||
|
||||
@@ -14,6 +14,7 @@ import { IChat, IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponse,
|
||||
|
||||
export interface IChatRequestModel {
|
||||
readonly id: string;
|
||||
readonly providerRequestId: string | undefined;
|
||||
readonly username: string;
|
||||
readonly avatarIconUri?: URI;
|
||||
readonly session: IChatModel;
|
||||
@@ -56,6 +57,10 @@ export class ChatRequestModel implements IChatRequestModel {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public get providerRequestId(): string | undefined {
|
||||
return this._providerRequestId;
|
||||
}
|
||||
|
||||
public get username(): string {
|
||||
return this.session.requesterUsername;
|
||||
}
|
||||
@@ -66,9 +71,14 @@ export class ChatRequestModel implements IChatRequestModel {
|
||||
|
||||
constructor(
|
||||
public readonly session: ChatModel,
|
||||
public readonly message: string | IChatReplyFollowup) {
|
||||
public readonly message: string | IChatReplyFollowup,
|
||||
private _providerRequestId?: string) {
|
||||
this._id = 'request_' + ChatRequestModel.nextId++;
|
||||
}
|
||||
|
||||
setProviderRequestId(providerRequestId: string) {
|
||||
this._providerRequestId = providerRequestId;
|
||||
}
|
||||
}
|
||||
|
||||
export class ChatResponseModel extends Disposable implements IChatResponseModel {
|
||||
@@ -189,7 +199,7 @@ export interface ISerializableChatsData {
|
||||
}
|
||||
|
||||
export interface ISerializableChatRequestData {
|
||||
providerResponseId: string | undefined;
|
||||
providerRequestId: string | undefined;
|
||||
message: string;
|
||||
response: string | undefined;
|
||||
responseErrorDetails: IChatResponseErrorDetails | undefined;
|
||||
@@ -230,7 +240,7 @@ export function isSerializableSessionData(obj: unknown): obj is ISerializableCha
|
||||
typeof data.sessionId === 'string';
|
||||
}
|
||||
|
||||
export type IChatChangeEvent = IChatAddRequestEvent | IChatAddResponseEvent | IChatInitEvent;
|
||||
export type IChatChangeEvent = IChatAddRequestEvent | IChatAddResponseEvent | IChatInitEvent | IChatRemoveRequestEvent;
|
||||
|
||||
export interface IChatAddRequestEvent {
|
||||
kind: 'addRequest';
|
||||
@@ -242,6 +252,12 @@ export interface IChatAddResponseEvent {
|
||||
response: IChatResponseModel;
|
||||
}
|
||||
|
||||
export interface IChatRemoveRequestEvent {
|
||||
kind: 'removeRequest';
|
||||
requestId: string;
|
||||
responseId?: string;
|
||||
}
|
||||
|
||||
export interface IChatInitEvent {
|
||||
kind: 'initialize';
|
||||
}
|
||||
@@ -360,9 +376,9 @@ export class ChatModel extends Disposable implements IChatModel {
|
||||
}
|
||||
|
||||
return requests.map((raw: ISerializableChatRequestData) => {
|
||||
const request = new ChatRequestModel(this, raw.message);
|
||||
const request = new ChatRequestModel(this, raw.message, raw.providerRequestId);
|
||||
if (raw.response || raw.responseErrorDetails) {
|
||||
request.response = new ChatResponseModel(new MarkdownString(raw.response), this, true, raw.isCanceled, raw.vote, raw.providerResponseId, raw.responseErrorDetails, raw.followups);
|
||||
request.response = new ChatResponseModel(new MarkdownString(raw.response), this, true, raw.isCanceled, raw.vote, raw.providerRequestId, raw.responseErrorDetails, raw.followups);
|
||||
}
|
||||
return request;
|
||||
});
|
||||
@@ -433,7 +449,22 @@ export class ChatModel extends Disposable implements IChatModel {
|
||||
if ('content' in progress) {
|
||||
request.response.updateContent(progress.content);
|
||||
} else {
|
||||
request.response.setProviderResponseId(progress.responseId);
|
||||
request.setProviderRequestId(progress.requestId);
|
||||
request.response.setProviderResponseId(progress.requestId);
|
||||
}
|
||||
}
|
||||
|
||||
removeRequest(requestId: string): void {
|
||||
const index = this._requests.findIndex(request => request.providerRequestId === requestId);
|
||||
const request = this._requests[index];
|
||||
if (!request.providerRequestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (index !== -1) {
|
||||
this._onDidChange.fire({ kind: 'removeRequest', requestId: request.providerRequestId, responseId: request.response?.providerResponseId });
|
||||
this._requests.splice(index, 1);
|
||||
request.response?.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,7 +515,7 @@ export class ChatModel extends Disposable implements IChatModel {
|
||||
}),
|
||||
requests: this._requests.map((r): ISerializableChatRequestData => {
|
||||
return {
|
||||
providerResponseId: r.response?.providerResponseId,
|
||||
providerRequestId: r.providerRequestId,
|
||||
message: typeof r.message === 'string' ? r.message : r.message.message,
|
||||
response: r.response ? r.response.response.value : undefined,
|
||||
responseErrorDetails: r.response?.errorDetails,
|
||||
|
||||
@@ -43,7 +43,7 @@ export interface IChatResponse {
|
||||
}
|
||||
|
||||
export type IChatProgress =
|
||||
{ content: string } | { responseId: string };
|
||||
{ content: string } | { requestId: string };
|
||||
|
||||
export interface IPersistedChatState { }
|
||||
export interface IChatProvider {
|
||||
@@ -56,6 +56,7 @@ export interface IChatProvider {
|
||||
provideFollowups?(session: IChat, token: CancellationToken): ProviderResult<IChatFollowup[] | undefined>;
|
||||
provideReply(request: IChatRequest, progress: (progress: IChatProgress) => void, token: CancellationToken): ProviderResult<IChatResponse>;
|
||||
provideSlashCommands?(session: IChat, token: CancellationToken): ProviderResult<ISlashCommand[]>;
|
||||
removeRequest?(session: IChat, requestId: string): void;
|
||||
}
|
||||
|
||||
export interface ISlashCommandProvider {
|
||||
@@ -186,6 +187,7 @@ export interface IChatService {
|
||||
* Returns whether the request was accepted.
|
||||
*/
|
||||
sendRequest(sessionId: string, message: string | IChatReplyFollowup): Promise<{ responseCompletePromise: Promise<void> } | undefined>;
|
||||
removeRequest(sessionid: string, requestId: string): Promise<void>;
|
||||
cancelCurrentRequestForSession(sessionId: string): void;
|
||||
getSlashCommands(sessionId: string, token: CancellationToken): Promise<ISlashCommand[] | undefined>;
|
||||
clearSession(sessionId: string): void;
|
||||
|
||||
@@ -357,7 +357,7 @@ export class ChatService extends Disposable implements IChatService {
|
||||
if ('content' in progress) {
|
||||
this.trace('sendRequest', `Provider returned progress for session ${model.sessionId}, ${progress.content.length} chars`);
|
||||
} else {
|
||||
this.trace('sendRequest', `Provider returned id for session ${model.sessionId}, ${progress.responseId}`);
|
||||
this.trace('sendRequest', `Provider returned id for session ${model.sessionId}, ${progress.requestId}`);
|
||||
}
|
||||
|
||||
model.acceptResponseProgress(request, progress);
|
||||
@@ -414,6 +414,22 @@ export class ChatService extends Disposable implements IChatService {
|
||||
return rawResponsePromise;
|
||||
}
|
||||
|
||||
async removeRequest(sessionId: string, requestId: string): Promise<void> {
|
||||
const model = this._sessionModels.get(sessionId);
|
||||
if (!model) {
|
||||
throw new Error(`Unknown session: ${sessionId}`);
|
||||
}
|
||||
|
||||
await model.waitForInitialization();
|
||||
const provider = this._providers.get(model.providerId);
|
||||
if (!provider) {
|
||||
throw new Error(`Unknown provider: ${model.providerId}`);
|
||||
}
|
||||
|
||||
model.removeRequest(requestId);
|
||||
provider.removeRequest?.(model.session!, requestId);
|
||||
}
|
||||
|
||||
private async handleSlashCommand(sessionId: string, command: string): Promise<string> {
|
||||
const slashCommands = await this.getSlashCommands(sessionId, CancellationToken.None);
|
||||
for (const slashCommand of slashCommands ?? []) {
|
||||
|
||||
@@ -39,6 +39,8 @@ export interface IChatViewModel {
|
||||
|
||||
export interface IChatRequestViewModel {
|
||||
readonly id: string;
|
||||
readonly providerRequestId: string | undefined;
|
||||
readonly sessionId: string;
|
||||
/** This ID updates every time the underlying data changes */
|
||||
readonly dataId: string;
|
||||
readonly username: string;
|
||||
@@ -64,6 +66,7 @@ export interface IChatLiveUpdateData {
|
||||
export interface IChatResponseViewModel {
|
||||
readonly onDidChange: Event<void>;
|
||||
readonly id: string;
|
||||
readonly sessionId: string;
|
||||
/** This ID updates every time the underlying data changes */
|
||||
readonly dataId: string;
|
||||
readonly providerId: string;
|
||||
@@ -91,7 +94,7 @@ export class ChatViewModel extends Disposable implements IChatViewModel {
|
||||
private readonly _onDidChange = this._register(new Emitter<void>());
|
||||
readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
private readonly _items: (IChatRequestViewModel | IChatResponseViewModel)[] = [];
|
||||
private readonly _items: (ChatRequestViewModel | ChatResponseViewModel)[] = [];
|
||||
|
||||
get inputPlaceholder(): string | undefined {
|
||||
return this._model.inputPlaceholder;
|
||||
@@ -135,6 +138,20 @@ export class ChatViewModel extends Disposable implements IChatViewModel {
|
||||
}
|
||||
} else if (e.kind === 'addResponse') {
|
||||
this.onAddResponse(e.response);
|
||||
} else if (e.kind === 'removeRequest') {
|
||||
const requestIdx = this._items.findIndex(item => isRequestVM(item) && item.providerRequestId === e.requestId);
|
||||
if (requestIdx >= 0) {
|
||||
this._items.splice(requestIdx, 1);
|
||||
}
|
||||
|
||||
const responseIdx = e.responseId && this._items.findIndex(item => isResponseVM(item) && item.providerResponseId === e.responseId);
|
||||
if (typeof responseIdx === 'number' && responseIdx >= 0) {
|
||||
const items = this._items.splice(responseIdx, 1);
|
||||
const item = items[0];
|
||||
if (isResponseVM(item)) {
|
||||
item.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._onDidChange.fire();
|
||||
@@ -147,7 +164,7 @@ export class ChatViewModel extends Disposable implements IChatViewModel {
|
||||
this._items.push(response);
|
||||
}
|
||||
|
||||
getItems() {
|
||||
getItems(): (IChatRequestViewModel | IChatResponseViewModel | IChatWelcomeMessageViewModel)[] {
|
||||
return [...(this._model.welcomeMessage ? [this._model.welcomeMessage] : []), ...this._items];
|
||||
}
|
||||
|
||||
@@ -164,10 +181,18 @@ export class ChatRequestViewModel implements IChatRequestViewModel {
|
||||
return this._model.id;
|
||||
}
|
||||
|
||||
get providerRequestId() {
|
||||
return this._model.providerRequestId;
|
||||
}
|
||||
|
||||
get dataId() {
|
||||
return this.id + (this._model.session.isInitialized ? '' : '_initializing');
|
||||
}
|
||||
|
||||
get sessionId() {
|
||||
return this._model.session.sessionId;
|
||||
}
|
||||
|
||||
get username() {
|
||||
return this._model.username;
|
||||
}
|
||||
@@ -211,6 +236,10 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi
|
||||
return this._model.providerResponseId;
|
||||
}
|
||||
|
||||
get sessionId() {
|
||||
return this._model.session.sessionId;
|
||||
}
|
||||
|
||||
get username() {
|
||||
return this._model.username;
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ export class ExperimentService extends Disposable implements IExperimentService
|
||||
return []; // TODO@sbatten add CLI argument (https://github.com/microsoft/vscode-internalbacklog/issues/2855)
|
||||
}
|
||||
|
||||
const experimentsUrl = this.configurationService.getValue<string>('_workbench.experimentsUrl') || this.productService.experimentsUrl;
|
||||
const experimentsUrl = this.productService.experimentsUrl;
|
||||
if (!experimentsUrl || this.configurationService.getValue('workbench.enableExperiments') === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { InteractiveEditorController, InteractiveEditorRunOptions } from 'vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController';
|
||||
import { CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_HAS_ACTIVE_REQUEST, CTX_INTERACTIVE_EDITOR_HAS_PROVIDER, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_FIRST, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_LAST, CTX_INTERACTIVE_EDITOR_EMPTY, CTX_INTERACTIVE_EDITOR_OUTER_CURSOR_POSITION, CTX_INTERACTIVE_EDITOR_VISIBLE, MENU_INTERACTIVE_EDITOR_WIDGET, MENU_INTERACTIVE_EDITOR_WIDGET_DISCARD, MENU_INTERACTIVE_EDITOR_WIDGET_STATUS, CTX_INTERACTIVE_EDITOR_LAST_FEEDBACK, CTX_INTERACTIVE_EDITOR_SHOWING_DIFF, CTX_INTERACTIVE_EDITOR_EDIT_MODE, EditMode, CTX_INTERACTIVE_EDITOR_LAST_RESPONSE_TYPE, MENU_INTERACTIVE_EDITOR_WIDGET_MARKDOWN_MESSAGE, CTX_INTERACTIVE_EDITOR_MESSAGE_CROP_STATE, CTX_INTERACTIVE_EDITOR_DOCUMENT_CHANGED, CTX_INTERACTIVE_EDITOR_DID_EDIT } from 'vs/workbench/contrib/interactiveEditor/common/interactiveEditor';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IAction2Options } from 'vs/platform/actions/common/actions';
|
||||
import { IAction2Options, MenuRegistry } from 'vs/platform/actions/common/actions';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -25,6 +25,7 @@ import { Range } from 'vs/editor/common/core/range';
|
||||
import { fromNow } from 'vs/base/common/date';
|
||||
import { IInteractiveEditorSessionService, Recording } from 'vs/workbench/contrib/interactiveEditor/browser/interactiveEditorSession';
|
||||
import { runAccessibilityHelpAction } from 'vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp';
|
||||
import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility';
|
||||
|
||||
|
||||
export class StartSessionAction extends EditorAction2 {
|
||||
@@ -157,7 +158,7 @@ export class ArrowOutUpAction extends AbstractInteractiveEditorAction {
|
||||
super({
|
||||
id: 'interactiveEditor.arrowOutUp',
|
||||
title: localize('arrowUp', 'Cursor Up'),
|
||||
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_FIRST, EditorContextKeys.isEmbeddedDiffEditor.negate()),
|
||||
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_FIRST, EditorContextKeys.isEmbeddedDiffEditor.negate(), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()),
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.EditorCore,
|
||||
primary: KeyCode.UpArrow
|
||||
@@ -175,7 +176,7 @@ export class ArrowOutDownAction extends AbstractInteractiveEditorAction {
|
||||
super({
|
||||
id: 'interactiveEditor.arrowOutDown',
|
||||
title: localize('arrowDown', 'Cursor Down'),
|
||||
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_LAST, EditorContextKeys.isEmbeddedDiffEditor.negate()),
|
||||
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_LAST, EditorContextKeys.isEmbeddedDiffEditor.negate(), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()),
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.EditorCore,
|
||||
primary: KeyCode.DownArrow
|
||||
@@ -195,7 +196,7 @@ export class FocusInteractiveEditor extends EditorAction2 {
|
||||
id: 'interactiveEditor.focus',
|
||||
title: localize('focus', 'Focus'),
|
||||
category: AbstractInteractiveEditorAction.category,
|
||||
precondition: ContextKeyExpr.and(EditorContextKeys.editorTextFocus, CTX_INTERACTIVE_EDITOR_VISIBLE, CTX_INTERACTIVE_EDITOR_FOCUSED.negate()),
|
||||
precondition: ContextKeyExpr.and(EditorContextKeys.editorTextFocus, CTX_INTERACTIVE_EDITOR_VISIBLE, CTX_INTERACTIVE_EDITOR_FOCUSED.negate(), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()),
|
||||
keybinding: [{
|
||||
weight: KeybindingWeight.EditorCore + 10, // win against core_command
|
||||
when: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_OUTER_CURSOR_POSITION.isEqualTo('above'), EditorContextKeys.isEmbeddedDiffEditor.negate()),
|
||||
@@ -251,6 +252,17 @@ export class NextFromHistory extends AbstractInteractiveEditorAction {
|
||||
}
|
||||
}
|
||||
|
||||
MenuRegistry.appendMenuItem(MENU_INTERACTIVE_EDITOR_WIDGET_STATUS, {
|
||||
submenu: MENU_INTERACTIVE_EDITOR_WIDGET_DISCARD,
|
||||
title: localize('discardMenu', "Discard..."),
|
||||
icon: Codicon.discard,
|
||||
group: '0_main',
|
||||
order: 2,
|
||||
when: CTX_INTERACTIVE_EDITOR_EDIT_MODE.notEqualsTo(EditMode.Preview),
|
||||
rememberDefaultAction: true
|
||||
});
|
||||
|
||||
|
||||
export class DicardAction extends AbstractInteractiveEditorAction {
|
||||
|
||||
constructor() {
|
||||
@@ -261,7 +273,7 @@ export class DicardAction extends AbstractInteractiveEditorAction {
|
||||
precondition: CTX_INTERACTIVE_EDITOR_VISIBLE,
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.EditorContrib,
|
||||
primary: KeyMod.Shift + KeyCode.Escape
|
||||
primary: KeyMod.CtrlCmd + KeyCode.Escape
|
||||
},
|
||||
menu: {
|
||||
id: MENU_INTERACTIVE_EDITOR_WIDGET_DISCARD,
|
||||
|
||||
@@ -276,7 +276,7 @@ export class InteractiveEditorController implements IEditorContribution {
|
||||
this._activeSession!.recordExternalEditOccurred(editIsOutsideOfWholeRange);
|
||||
|
||||
if (editIsOutsideOfWholeRange) {
|
||||
this._logService.trace('[IE] text changed outside of whole range, FINISH session');
|
||||
this._logService.info('[IE] text changed outside of whole range, FINISH session');
|
||||
this._finishExistingSession();
|
||||
}
|
||||
}));
|
||||
|
||||
+17
-1
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Dimension, h } from 'vs/base/browser/dom';
|
||||
import { MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { assertType } from 'vs/base/common/types';
|
||||
import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget';
|
||||
@@ -38,6 +38,7 @@ export class InteractiveEditorLivePreviewWidget extends ZoneWidget {
|
||||
|
||||
private readonly _elements = h('div.interactive-editor-diff-widget@domNode');
|
||||
|
||||
private readonly _sessionStore = this._disposables.add(new DisposableStore());
|
||||
private readonly _diffEditor: IDiffEditor;
|
||||
private readonly _inlineDiffDecorations: IEditorDecorationsCollection;
|
||||
private _dim: Dimension | undefined;
|
||||
@@ -123,12 +124,27 @@ export class InteractiveEditorLivePreviewWidget extends ZoneWidget {
|
||||
override hide(): void {
|
||||
this._cleanupFullDiff();
|
||||
this._cleanupInlineDiff();
|
||||
this._sessionStore.clear();
|
||||
super.hide();
|
||||
this._isVisible = false;
|
||||
}
|
||||
|
||||
override show(): void {
|
||||
assertType(this.editor.hasModel());
|
||||
this._sessionStore.clear();
|
||||
|
||||
this._sessionStore.add(this._diffEditor.onDidUpdateDiff(() => {
|
||||
const result = this._diffEditor.getDiffComputationResult();
|
||||
const hasFocus = this._diffEditor.hasTextFocus();
|
||||
this._updateFromChanges(this._session.wholeRange, result?.changes2 ?? []);
|
||||
// TODO@jrieken find a better fix for this. this is the challenge:
|
||||
// the _doShowForChanges method invokes show of the zone widget which removes and adds the
|
||||
// zone and overlay parts. this dettaches and reattaches the dom nodes which means they lose
|
||||
// focus
|
||||
if (hasFocus) {
|
||||
this._diffEditor.focus();
|
||||
}
|
||||
}));
|
||||
this._updateFromChanges(this._session.wholeRange, this._session.lastTextModelChanges);
|
||||
this._isVisible = true;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ModifierKeyEmitter } from 'vs/base/browser/dom';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import 'vs/css!./interactiveEditor';
|
||||
@@ -29,7 +30,7 @@ import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/
|
||||
|
||||
export abstract class EditModeStrategy {
|
||||
|
||||
dispose(): void { }
|
||||
abstract dispose(): void;
|
||||
|
||||
abstract checkChanges(response: EditResponse): boolean;
|
||||
|
||||
@@ -67,7 +68,6 @@ export class PreviewStrategy extends EditModeStrategy {
|
||||
override dispose(): void {
|
||||
this._listener.dispose();
|
||||
this._ctxDocumentChanged.reset();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
checkChanges(response: EditResponse): boolean {
|
||||
@@ -201,10 +201,11 @@ class InlineDiffDecorations {
|
||||
export class LiveStrategy extends EditModeStrategy {
|
||||
|
||||
private static _inlineDiffStorageKey: string = 'interactiveEditor.storage.inlineDiff';
|
||||
private _inlineDiffEnabled: boolean = false;
|
||||
protected _diffEnabled: boolean = false;
|
||||
|
||||
private readonly _inlineDiffDecorations: InlineDiffDecorations;
|
||||
protected readonly _ctxShowingDiff: IContextKey<boolean>;
|
||||
private readonly _ctxShowingDiff: IContextKey<boolean>;
|
||||
private readonly _diffToggleListener: IDisposable;
|
||||
private _lastResponse?: EditResponse;
|
||||
private _editCount: number = 0;
|
||||
|
||||
@@ -219,28 +220,36 @@ export class LiveStrategy extends EditModeStrategy {
|
||||
@IInstantiationService private readonly _instaService: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
this._inlineDiffDecorations = new InlineDiffDecorations(this._editor, this._inlineDiffEnabled);
|
||||
this._inlineDiffDecorations = new InlineDiffDecorations(this._editor, this._diffEnabled);
|
||||
this._ctxShowingDiff = CTX_INTERACTIVE_EDITOR_SHOWING_DIFF.bindTo(contextKeyService);
|
||||
|
||||
this._inlineDiffEnabled = _storageService.getBoolean(LiveStrategy._inlineDiffStorageKey, StorageScope.PROFILE, false);
|
||||
this._ctxShowingDiff.set(this._inlineDiffEnabled);
|
||||
this._inlineDiffDecorations.visible = this._inlineDiffEnabled;
|
||||
this._diffEnabled = _storageService.getBoolean(LiveStrategy._inlineDiffStorageKey, StorageScope.PROFILE, false);
|
||||
this._ctxShowingDiff.set(this._diffEnabled);
|
||||
this._inlineDiffDecorations.visible = this._diffEnabled;
|
||||
this._diffToggleListener = ModifierKeyEmitter.getInstance().event(e => {
|
||||
if (e.altKey || e.lastKeyReleased === 'alt') {
|
||||
this.toggleDiff();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
this._inlineDiffEnabled = this._inlineDiffDecorations.visible;
|
||||
this._storageService.store(LiveStrategy._inlineDiffStorageKey, this._inlineDiffEnabled, StorageScope.PROFILE, StorageTarget.USER);
|
||||
this._diffToggleListener.dispose();
|
||||
this._diffEnabled = this._inlineDiffDecorations.visible;
|
||||
this._storageService.store(LiveStrategy._inlineDiffStorageKey, this._diffEnabled, StorageScope.PROFILE, StorageTarget.USER);
|
||||
this._inlineDiffDecorations.clear();
|
||||
this._ctxShowingDiff.reset();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
toggleDiff(): void {
|
||||
this._inlineDiffEnabled = !this._inlineDiffEnabled;
|
||||
this._ctxShowingDiff.set(this._inlineDiffEnabled);
|
||||
this._inlineDiffDecorations.visible = this._inlineDiffEnabled;
|
||||
this._storageService.store(LiveStrategy._inlineDiffStorageKey, this._inlineDiffEnabled, StorageScope.PROFILE, StorageTarget.USER);
|
||||
this._diffEnabled = !this._diffEnabled;
|
||||
this._ctxShowingDiff.set(this._diffEnabled);
|
||||
this._storageService.store(LiveStrategy._inlineDiffStorageKey, this._diffEnabled, StorageScope.PROFILE, StorageTarget.USER);
|
||||
this._doToggleDiff();
|
||||
}
|
||||
|
||||
protected _doToggleDiff(): void {
|
||||
this._inlineDiffDecorations.visible = this._diffEnabled;
|
||||
}
|
||||
|
||||
checkChanges(response: EditResponse): boolean {
|
||||
@@ -364,9 +373,10 @@ export class LivePreviewStrategy extends LiveStrategy {
|
||||
|
||||
override async renderChanges(response: EditResponse) {
|
||||
|
||||
this._diffZone.show();
|
||||
this._updateSummaryMessage();
|
||||
this._ctxShowingDiff.set(true);
|
||||
if (this._diffEnabled) {
|
||||
this._diffZone.show();
|
||||
}
|
||||
|
||||
if (response.singleCreateFileEdit) {
|
||||
this._previewZone.showCreation(this._session.wholeRange, response.singleCreateFileEdit.uri, await Promise.all(response.singleCreateFileEdit.edits));
|
||||
@@ -375,17 +385,16 @@ export class LivePreviewStrategy extends LiveStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
override toggleDiff(): void {
|
||||
// TODO@jrieken should this be persisted like we do in live-mode?
|
||||
const scrollState = StableEditorScrollState.capture(this._editor);
|
||||
if (this._diffZone.isVisible) {
|
||||
this._diffZone.hide();
|
||||
this._ctxShowingDiff.set(false);
|
||||
} else {
|
||||
this._diffZone.show();
|
||||
this._ctxShowingDiff.set(true);
|
||||
protected override _doToggleDiff(): void {
|
||||
if (this._diffEnabled !== this._diffZone.isVisible) {
|
||||
const scrollState = StableEditorScrollState.capture(this._editor);
|
||||
if (this._diffEnabled) {
|
||||
this._diffZone.show();
|
||||
} else {
|
||||
this._diffZone.hide();
|
||||
}
|
||||
scrollState.restore(this._editor);
|
||||
}
|
||||
scrollState.restore(this._editor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -289,7 +289,7 @@ export class InteractiveEditorWidget {
|
||||
return createActionViewItem(this._instantiationService, action, options);
|
||||
}
|
||||
};
|
||||
const statusToolbar = this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.statusToolbar, MENU_INTERACTIVE_EDITOR_WIDGET_STATUS, workbenchToolbarOptions);
|
||||
const statusToolbar = this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.statusToolbar, MENU_INTERACTIVE_EDITOR_WIDGET_STATUS, { ...workbenchToolbarOptions, hiddenItemStrategy: HiddenItemStrategy.Ignore });
|
||||
this._store.add(statusToolbar.onDidChangeMenuItems(() => this._onDidChangeHeight.fire()));
|
||||
this._store.add(statusToolbar);
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
@@ -12,7 +11,7 @@ import { ISelection } from 'vs/editor/common/core/selection';
|
||||
import { ProviderResult, TextEdit, WorkspaceEdit } from 'vs/editor/common/languages';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { localize } from 'vs/nls';
|
||||
import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
|
||||
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -123,15 +122,6 @@ export const MENU_INTERACTIVE_EDITOR_WIDGET = MenuId.for('interactiveEditorWidge
|
||||
export const MENU_INTERACTIVE_EDITOR_WIDGET_MARKDOWN_MESSAGE = MenuId.for('interactiveEditorWidget.markdownMessage');
|
||||
export const MENU_INTERACTIVE_EDITOR_WIDGET_STATUS = MenuId.for('interactiveEditorWidget.status');
|
||||
export const MENU_INTERACTIVE_EDITOR_WIDGET_DISCARD = MenuId.for('interactiveEditorWidget.undo');
|
||||
MenuRegistry.appendMenuItem(MENU_INTERACTIVE_EDITOR_WIDGET_STATUS, {
|
||||
submenu: MENU_INTERACTIVE_EDITOR_WIDGET_DISCARD,
|
||||
title: localize('discard', "Discard..."),
|
||||
icon: Codicon.discard,
|
||||
group: '0_main',
|
||||
order: 2,
|
||||
when: CTX_INTERACTIVE_EDITOR_EDIT_MODE.notEqualsTo(EditMode.Preview),
|
||||
rememberDefaultAction: true
|
||||
});
|
||||
|
||||
// --- colors
|
||||
|
||||
|
||||
@@ -59,15 +59,15 @@ export class KeybindingsSearchWidget extends SearchWidget {
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
) {
|
||||
super(parent, options, contextViewService, instantiationService, contextKeyService, keybindingService);
|
||||
|
||||
this._register(toDisposable(() => this.stopRecordingKeys()));
|
||||
|
||||
this._chords = null;
|
||||
this._inputValue = '';
|
||||
|
||||
this._reset();
|
||||
}
|
||||
|
||||
override clear(): void {
|
||||
this._reset();
|
||||
this._chords = null;
|
||||
super.clear();
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ export class KeybindingsSearchWidget extends SearchWidget {
|
||||
}
|
||||
|
||||
stopRecordingKeys(): void {
|
||||
this._reset();
|
||||
this._chords = null;
|
||||
this.recordDisposables.clear();
|
||||
}
|
||||
|
||||
@@ -90,10 +90,6 @@ export class KeybindingsSearchWidget extends SearchWidget {
|
||||
this.inputBox.value = this._inputValue;
|
||||
}
|
||||
|
||||
private _reset() {
|
||||
this._chords = null;
|
||||
}
|
||||
|
||||
private _onKeyDown(keyboardEvent: IKeyboardEvent): void {
|
||||
keyboardEvent.preventDefault();
|
||||
keyboardEvent.stopPropagation();
|
||||
@@ -103,10 +99,6 @@ export class KeybindingsSearchWidget extends SearchWidget {
|
||||
return;
|
||||
}
|
||||
if (keyboardEvent.equals(KeyCode.Escape)) {
|
||||
if (this._chords !== null) {
|
||||
this.clear();
|
||||
}
|
||||
|
||||
this._onEscape.fire();
|
||||
return;
|
||||
}
|
||||
@@ -185,8 +177,8 @@ export class DefineKeybindingWidget extends Widget {
|
||||
this._keybindingInputWidget.startRecordingKeys();
|
||||
this._register(this._keybindingInputWidget.onKeybinding(keybinding => this.onKeybinding(keybinding)));
|
||||
this._register(this._keybindingInputWidget.onEnter(() => this.hide()));
|
||||
this._register(this._keybindingInputWidget.onEscape(() => this.onEscape()));
|
||||
this._register(this._keybindingInputWidget.onBlur(() => this.onBlur()));
|
||||
this._register(this._keybindingInputWidget.onEscape(() => this.clearOrHide()));
|
||||
this._register(this._keybindingInputWidget.onBlur(() => this.onCancel()));
|
||||
|
||||
this._outputNode = dom.append(this._domNode.domNode, dom.$('.output'));
|
||||
this._showExistingKeybindingsNode = dom.append(this._domNode.domNode, dom.$('.existing'));
|
||||
@@ -251,19 +243,15 @@ export class DefineKeybindingWidget extends Widget {
|
||||
dom.clearNode(this._outputNode);
|
||||
dom.clearNode(this._showExistingKeybindingsNode);
|
||||
|
||||
|
||||
|
||||
const firstLabel = new KeybindingLabel(this._outputNode, OS, defaultKeybindingLabelStyles);
|
||||
firstLabel.set(withNullAsUndefined(this._chords?.[0]));
|
||||
|
||||
|
||||
if (this._chords) {
|
||||
for (let i = 1; i < this._chords.length; i++) {
|
||||
this._outputNode.appendChild(document.createTextNode(nls.localize('defineKeybinding.chordsTo', "chord to")));
|
||||
const chordLabel = new KeybindingLabel(this._outputNode, OS, defaultKeybindingLabelStyles);
|
||||
chordLabel.set(this._chords[i]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const label = this.getUserSettingsLabel();
|
||||
@@ -280,19 +268,20 @@ export class DefineKeybindingWidget extends Widget {
|
||||
return label;
|
||||
}
|
||||
|
||||
private onBlur(): void {
|
||||
private onCancel(): void {
|
||||
this._chords = null;
|
||||
this.hide();
|
||||
}
|
||||
|
||||
private onEscape(): void {
|
||||
if (this._chords !== null) {
|
||||
private clearOrHide(): void {
|
||||
if (this._chords === null) {
|
||||
this.hide();
|
||||
} else {
|
||||
this._chords = null;
|
||||
this._keybindingInputWidget.clear();
|
||||
dom.clearNode(this._outputNode);
|
||||
dom.clearNode(this._showExistingKeybindingsNode);
|
||||
return;
|
||||
}
|
||||
this.hide();
|
||||
}
|
||||
|
||||
private hide(): void {
|
||||
|
||||
@@ -162,12 +162,12 @@ export class RemoteStartEntry extends Disposable implements IWorkbenchContributi
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.extensionManagementService.onDidInstallExtensions(async (result) => {
|
||||
for (const ext of result) {
|
||||
const index = this.remoteExtensionMetadata.findIndex(value => ExtensionIdentifier.equals(value.id, ext.identifier.id));
|
||||
this._register(this.extensionService.onDidChangeExtensions(async (result) => {
|
||||
for (const ext of result.added) {
|
||||
const index = this.remoteExtensionMetadata.findIndex(value => ExtensionIdentifier.equals(value.id, ext.identifier));
|
||||
if (index > -1) {
|
||||
this.remoteExtensionMetadata[index].installed = true;
|
||||
this.remoteExtensionMetadata[index].remoteCommands = await this.getRemoteCommands(ext.identifier.id);
|
||||
this.remoteExtensionMetadata[index].remoteCommands = await this.getRemoteCommands(ext.identifier.value);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -1148,7 +1148,7 @@ class ViewModel {
|
||||
this._onDidChangeMode.fire(mode);
|
||||
this.modeContextKey.set(mode);
|
||||
|
||||
this.storageService.store(`scm.viewMode`, mode, StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
this.storageService.store(`scm.viewMode`, mode, StorageScope.WORKSPACE, StorageTarget.USER);
|
||||
}
|
||||
|
||||
get sortKey(): ViewModelSortKey { return this._sortKey; }
|
||||
@@ -1164,7 +1164,7 @@ class ViewModel {
|
||||
this.sortKeyContextKey.set(sortKey);
|
||||
|
||||
if (this._mode === ViewModelMode.List) {
|
||||
this.storageService.store(`scm.viewSortKey`, sortKey, StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
this.storageService.store(`scm.viewSortKey`, sortKey, StorageScope.WORKSPACE, StorageTarget.USER);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1243,6 +1243,20 @@ class ViewModel {
|
||||
if (e.reason === WillSaveStateReason.SHUTDOWN) {
|
||||
this.storageService.store(`scm.viewState`, JSON.stringify(this.treeViewState), StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
this.mode = this.getViewModelMode();
|
||||
this.sortKey = this.getViewModelSortKey();
|
||||
});
|
||||
|
||||
this.storageService.onDidChangeValue(e => {
|
||||
switch (e.key) {
|
||||
case 'scm.viewMode':
|
||||
this.mode = this.getViewModelMode();
|
||||
break;
|
||||
case 'scm.viewSortKey':
|
||||
this.sortKey = this.getViewModelSortKey();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./share';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
@@ -78,19 +80,23 @@ class ShareWorkbenchContribution {
|
||||
|
||||
const uri = await shareService.provideShare({ resourceUri }, new CancellationTokenSource().token);
|
||||
if (uri) {
|
||||
await clipboardService.writeText(uri.toString());
|
||||
const result = await dialogService.input(
|
||||
const uriText = uri.toString();
|
||||
await clipboardService.writeText(uriText);
|
||||
dialogService.prompt(
|
||||
{
|
||||
type: Severity.Info,
|
||||
inputs: [{ type: 'text', value: uri.toString() }],
|
||||
message: localize('shareSuccess', 'Copied link to clipboard!'),
|
||||
custom: { icon: Codicon.check },
|
||||
primaryButton: localize('open link', 'Open Link')
|
||||
custom: {
|
||||
icon: Codicon.check,
|
||||
markdownDetails: [{
|
||||
markdown: new MarkdownString(`<div aria-label='${uriText}'>${uriText}</div>`, { supportHtml: true }),
|
||||
classes: ['share-dialog-input']
|
||||
}]
|
||||
},
|
||||
cancelButton: localize('close', 'Close'),
|
||||
buttons: [{ label: localize('open link', 'Open Link'), run: () => { urlService.open(uri, { openExternal: true }); } }]
|
||||
}
|
||||
);
|
||||
if (result.confirmed) {
|
||||
urlService.open(uri, { openExternal: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
div.share-dialog-input {
|
||||
border: 1px solid var(--vscode-input-border, transparent);
|
||||
border-radius: 2px;
|
||||
color: var(--vscode-input-foreground);
|
||||
background-color: var(--vscode-input-background);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
padding: 2px;
|
||||
user-select: all;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { score } from 'vs/editor/common/languageSelector';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ISubmenuItem } from 'vs/platform/actions/common/actions';
|
||||
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
@@ -46,7 +47,9 @@ export class ShareService implements IShareService {
|
||||
}
|
||||
|
||||
async provideShare(item: IShareableItem, token: CancellationToken): Promise<URI | undefined> {
|
||||
const providers = [...this._providers.values()];
|
||||
const providers = [...this._providers.values()]
|
||||
.filter((p) => score(p.selector, item.resourceUri, '', true, undefined, undefined) > 0)
|
||||
.sort((a, b) => a.priority - b.priority);
|
||||
|
||||
if (providers.length === 0) {
|
||||
return undefined;
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface IShareableItem {
|
||||
export interface IShareProvider {
|
||||
readonly id: string;
|
||||
readonly label: string;
|
||||
readonly priority: number;
|
||||
readonly selector: LanguageSelector;
|
||||
prepareShare?(item: IShareableItem, token: CancellationToken): Thenable<boolean | undefined>;
|
||||
provideShare(item: IShareableItem, token: CancellationToken): Thenable<URI | undefined>;
|
||||
|
||||
@@ -105,7 +105,9 @@ function getMergedDescription(collection: IMergedEnvironmentVariableCollection,
|
||||
}
|
||||
const workspaceDescription = workspaceDescriptions.get(ext);
|
||||
if (workspaceDescription) {
|
||||
message.push(`\n- \`${getExtensionName(ext, extensionService)} (${localize('ScopedEnvironmentContributionInfo', 'workspace')})\``);
|
||||
// Only show '(workspace)' suffix if there is already a description for the extension.
|
||||
const workspaceSuffix = globalDescription ? ` (${localize('ScopedEnvironmentContributionInfo', 'workspace')})` : '';
|
||||
message.push(`\n- \`${getExtensionName(ext, extensionService)}${workspaceSuffix}\``);
|
||||
message.push(`: ${workspaceDescription}`);
|
||||
}
|
||||
if (!globalDescription && !workspaceDescription) {
|
||||
|
||||
@@ -735,7 +735,8 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [
|
||||
'workbench.action.togglePanel',
|
||||
'workbench.action.quickOpenView',
|
||||
'workbench.action.toggleMaximizedPanel',
|
||||
'notification.acceptPrimaryAction'
|
||||
'notification.acceptPrimaryAction',
|
||||
'runCommands'
|
||||
];
|
||||
|
||||
export const terminalContributionsDescriptor: IExtensionPointDescriptor<ITerminalContributions> = {
|
||||
|
||||
+4
-1
@@ -87,7 +87,10 @@ export class HierarchicalByLocationProjection extends Disposable implements ITes
|
||||
// when test states change, reflect in the tree
|
||||
this._register(results.onTestChanged(ev => {
|
||||
let result = ev.item;
|
||||
if (result.ownComputedState === TestResultState.Unset) {
|
||||
// if the state is unset, or the latest run is not making the change,
|
||||
// double check that it's valid. Retire calls might cause previous
|
||||
// emit a state change for a test run that's already long completed.
|
||||
if (result.ownComputedState === TestResultState.Unset || ev.result !== results.results[0]) {
|
||||
const fallback = results.getStateById(result.item.extId);
|
||||
if (fallback) {
|
||||
result = fallback[1];
|
||||
|
||||
@@ -449,6 +449,18 @@ export class LiveTestResult implements ITestResult {
|
||||
this.completeEmitter.fire();
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the test and all of its children in the run as retired.
|
||||
*/
|
||||
public markRetired(testId: string) {
|
||||
for (const [id, test] of this.testById) {
|
||||
if (!test.retired && id === testId || TestId.isChild(testId, id)) {
|
||||
test.retired = true;
|
||||
this.changeEmitter.fire({ reason: TestResultItemChangeReason.ComputedStateChange, item: test, result: this });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
|
||||
@@ -469,12 +469,14 @@ export interface TestResultItem extends InternalTestItem {
|
||||
}
|
||||
|
||||
export namespace TestResultItem {
|
||||
/** Serialized version of the TestResultItem */
|
||||
/**
|
||||
* Serialized version of the TestResultItem. Note that 'retired' is not
|
||||
* included since all hydrated items are automatically retired.
|
||||
*/
|
||||
export interface Serialized extends InternalTestItem.Serialized {
|
||||
tasks: ITestTaskState.Serialized[];
|
||||
ownComputedState: TestResultState;
|
||||
computedState: TestResultState;
|
||||
retired?: boolean;
|
||||
}
|
||||
|
||||
export const serializeWithoutMessages = (original: TestResultItem): Serialized => ({
|
||||
@@ -482,7 +484,6 @@ export namespace TestResultItem {
|
||||
ownComputedState: original.ownComputedState,
|
||||
computedState: original.computedState,
|
||||
tasks: original.tasks.map(ITestTaskState.serializeWithoutMessages),
|
||||
retired: original.retired,
|
||||
});
|
||||
|
||||
export const serialize = (original: TestResultItem): Serialized => ({
|
||||
@@ -490,7 +491,6 @@ export namespace TestResultItem {
|
||||
ownComputedState: original.ownComputedState,
|
||||
computedState: original.computedState,
|
||||
tasks: original.tasks.map(ITestTaskState.serialize),
|
||||
retired: original.retired,
|
||||
});
|
||||
|
||||
export const deserialize = (serialized: Serialized): TestResultItem => ({
|
||||
|
||||
+3
-1
@@ -23,6 +23,7 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => {
|
||||
setup(() => {
|
||||
onTestChanged = new Emitter();
|
||||
resultsService = {
|
||||
results: [],
|
||||
onResultsChanged: () => undefined,
|
||||
onTestChanged: onTestChanged.event,
|
||||
getStateById: () => ({ state: { state: 0 }, computedState: 0 }),
|
||||
@@ -102,7 +103,6 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => {
|
||||
|
||||
test('applies state changes', async () => {
|
||||
harness.flush();
|
||||
resultsService.getStateById = () => [undefined, resultInState(TestResultState.Failed)];
|
||||
|
||||
const resultInState = (state: TestResultState): TestResultItem => ({
|
||||
item: {
|
||||
@@ -124,6 +124,7 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => {
|
||||
});
|
||||
|
||||
// Applies the change:
|
||||
resultsService.getStateById = () => [undefined, resultInState(TestResultState.Queued)];
|
||||
onTestChanged.fire({
|
||||
reason: TestResultItemChangeReason.OwnStateChange,
|
||||
result: null as any,
|
||||
@@ -139,6 +140,7 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => {
|
||||
]);
|
||||
|
||||
// Falls back if moved into unset state:
|
||||
resultsService.getStateById = () => [undefined, resultInState(TestResultState.Failed)];
|
||||
onTestChanged.fire({
|
||||
reason: TestResultItemChangeReason.OwnStateChange,
|
||||
result: null as any,
|
||||
|
||||
@@ -73,7 +73,6 @@ import { IFeaturedExtensionsService } from 'vs/workbench/contrib/welcomeGettingS
|
||||
import { IFeaturedExtension } from 'vs/base/common/product';
|
||||
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
|
||||
const SLIDE_TRANSITION_TIME_MS = 250;
|
||||
const configurationKey = 'workbench.startupEditor';
|
||||
@@ -190,9 +189,7 @@ export class GettingStartedPage extends EditorPane {
|
||||
@IWebviewService private readonly webviewService: IWebviewService,
|
||||
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
|
||||
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
@ILifecycleService private readonly lifecycleService: ILifecycleService,
|
||||
) {
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService) {
|
||||
|
||||
super(GettingStartedPage.ID, telemetryService, themeService, storageService);
|
||||
|
||||
@@ -305,7 +302,6 @@ export class GettingStartedPage extends EditorPane {
|
||||
this.container.classList.remove('animatable');
|
||||
this.editorInput = newInput;
|
||||
await super.setInput(newInput, options, context, token);
|
||||
await this.lifecycleService.when(LifecyclePhase.Restored);
|
||||
await this.buildCategoriesSlide();
|
||||
if (this.shouldAnimate()) {
|
||||
setTimeout(() => this.container.classList.add('animatable'), 0);
|
||||
@@ -729,6 +725,9 @@ export class GettingStartedPage extends EditorPane {
|
||||
|
||||
private async buildCategoriesSlide() {
|
||||
|
||||
// Delay fetching welcome page content on startup until all extensions are ready.
|
||||
await this.extensionService.whenInstalledExtensionsRegistered();
|
||||
|
||||
this.recentlyOpened = await this.workspacesService.getRecentlyOpened();
|
||||
this.gettingStartedCategories = await this.gettingStartedService.getWalkthroughs();
|
||||
this.featuredExtensions = await this.featuredExtensionService.getExtensions();
|
||||
|
||||
+54
-15
@@ -45,6 +45,15 @@ import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/use
|
||||
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
|
||||
|
||||
type GalleryExtensionInfo = { readonly id: string; preRelease?: boolean; migrateStorageFrom?: string };
|
||||
interface HostedExtensionInfo {
|
||||
readonly location: UriComponents;
|
||||
readonly preRelease?: boolean;
|
||||
readonly packageJSON?: IExtensionManifest;
|
||||
readonly defaultPackageTranslations?: ITranslations | null;
|
||||
readonly packageNLSUris?: Map<string, UriComponents>;
|
||||
readonly readmeUri?: UriComponents;
|
||||
readonly changelogUri?: UriComponents;
|
||||
}
|
||||
type ExtensionInfo = { readonly id: string; preRelease: boolean };
|
||||
|
||||
function isGalleryExtensionInfo(obj: unknown): obj is GalleryExtensionInfo {
|
||||
@@ -54,6 +63,24 @@ function isGalleryExtensionInfo(obj: unknown): obj is GalleryExtensionInfo {
|
||||
&& (galleryExtensionInfo.migrateStorageFrom === undefined || typeof galleryExtensionInfo.migrateStorageFrom === 'string');
|
||||
}
|
||||
|
||||
function isHostedExtensionInfo(obj: unknown): obj is HostedExtensionInfo {
|
||||
const hostedExtensionInfo = obj as HostedExtensionInfo | undefined;
|
||||
return isUriComponents(hostedExtensionInfo?.location)
|
||||
&& (hostedExtensionInfo?.preRelease === undefined || typeof hostedExtensionInfo.preRelease === 'boolean')
|
||||
&& (hostedExtensionInfo?.packageJSON === undefined || typeof hostedExtensionInfo.packageJSON === 'object')
|
||||
&& (hostedExtensionInfo?.defaultPackageTranslations === undefined || hostedExtensionInfo?.defaultPackageTranslations === null || typeof hostedExtensionInfo.defaultPackageTranslations === 'object')
|
||||
&& (hostedExtensionInfo?.changelogUri === undefined || isUriComponents(hostedExtensionInfo?.changelogUri))
|
||||
&& (hostedExtensionInfo?.readmeUri === undefined || isUriComponents(hostedExtensionInfo?.readmeUri));
|
||||
}
|
||||
|
||||
function isUriComponents(thing: unknown): thing is UriComponents {
|
||||
if (!thing) {
|
||||
return false;
|
||||
}
|
||||
return isString((<any>thing).path) &&
|
||||
isString((<any>thing).scheme);
|
||||
}
|
||||
|
||||
interface IStoredWebExtension {
|
||||
readonly identifier: IExtensionIdentifier;
|
||||
readonly version: string;
|
||||
@@ -117,12 +144,12 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
||||
}
|
||||
}
|
||||
|
||||
private _customBuiltinExtensionsInfoPromise: Promise<{ extensions: ExtensionInfo[]; extensionsToMigrate: [string, string][]; extensionLocations: URI[] }> | undefined;
|
||||
private readCustomBuiltinExtensionsInfoFromEnv(): Promise<{ extensions: ExtensionInfo[]; extensionsToMigrate: [string, string][]; extensionLocations: URI[] }> {
|
||||
private _customBuiltinExtensionsInfoPromise: Promise<{ extensions: ExtensionInfo[]; extensionsToMigrate: [string, string][]; extensionLocations: HostedExtensionInfo[] }> | undefined;
|
||||
private readCustomBuiltinExtensionsInfoFromEnv(): Promise<{ extensions: ExtensionInfo[]; extensionsToMigrate: [string, string][]; extensionLocations: HostedExtensionInfo[] }> {
|
||||
if (!this._customBuiltinExtensionsInfoPromise) {
|
||||
this._customBuiltinExtensionsInfoPromise = (async () => {
|
||||
let extensions: ExtensionInfo[] = [];
|
||||
const extensionLocations: URI[] = [];
|
||||
const extensionLocations: HostedExtensionInfo[] = [];
|
||||
const extensionsToMigrate: [string, string][] = [];
|
||||
const customBuiltinExtensionsInfo = this.environmentService.options && Array.isArray(this.environmentService.options.additionalBuiltinExtensions)
|
||||
? this.environmentService.options.additionalBuiltinExtensions.map(additionalBuiltinExtension => isString(additionalBuiltinExtension) ? { id: additionalBuiltinExtension } : additionalBuiltinExtension)
|
||||
@@ -134,7 +161,11 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
||||
extensionsToMigrate.push([e.migrateStorageFrom, e.id]);
|
||||
}
|
||||
} else {
|
||||
extensionLocations.push(URI.revive(e));
|
||||
if (isHostedExtensionInfo(e)) {
|
||||
extensionLocations.push(e);
|
||||
} else {
|
||||
extensionLocations.push({ location: e });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (extensions.length) {
|
||||
@@ -212,9 +243,15 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
||||
return [];
|
||||
}
|
||||
const result: IScannedExtension[] = [];
|
||||
await Promise.allSettled(extensionLocations.map(async location => {
|
||||
await Promise.allSettled(extensionLocations.map(async ({ location, preRelease, packageNLSUris, packageJSON, defaultPackageTranslations, readmeUri, changelogUri }) => {
|
||||
try {
|
||||
const webExtension = await this.toWebExtension(location);
|
||||
const webExtension = await this.toWebExtension(URI.revive(location), undefined,
|
||||
packageJSON,
|
||||
packageNLSUris ? [...packageNLSUris.entries()].reduce((result, [key, value]) => { result.set(key, URI.revive(value)); return result; }, new Map<string, URI>()) : undefined,
|
||||
defaultPackageTranslations,
|
||||
URI.revive(readmeUri),
|
||||
URI.revive(changelogUri),
|
||||
{ isPreReleaseVersion: preRelease });
|
||||
const extension = await this.toScannedExtension(webExtension, true);
|
||||
if (extension.isValid || !scanOptions?.skipInvalidExtensions) {
|
||||
result.push(extension);
|
||||
@@ -438,7 +475,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
||||
}
|
||||
|
||||
async addExtension(location: URI, metadata: Metadata, profileLocation: URI): Promise<IScannedExtension> {
|
||||
const webExtension = await this.toWebExtension(location, undefined, undefined, undefined, undefined, undefined, metadata);
|
||||
const webExtension = await this.toWebExtension(location, undefined, undefined, undefined, undefined, undefined, undefined, metadata);
|
||||
const extension = await this.toScannedExtension(webExtension, false);
|
||||
await this.addToInstalledExtensions([webExtension], profileLocation);
|
||||
return extension;
|
||||
@@ -577,6 +614,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
||||
return this.toWebExtension(
|
||||
extensionLocation,
|
||||
galleryExtension.identifier,
|
||||
undefined,
|
||||
packageNLSResources,
|
||||
fallbackPackageNLSResource ? URI.parse(fallbackPackageNLSResource) : null,
|
||||
galleryExtension.assets.readme ? URI.parse(galleryExtension.assets.readme.uri) : undefined,
|
||||
@@ -596,12 +634,13 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
||||
return packageNLSResources;
|
||||
}
|
||||
|
||||
private async toWebExtension(extensionLocation: URI, identifier?: IExtensionIdentifier, packageNLSUris?: Map<string, URI>, fallbackPackageNLSUri?: URI | null, readmeUri?: URI, changelogUri?: URI, metadata?: Metadata): Promise<IWebExtension> {
|
||||
let manifest: IExtensionManifest;
|
||||
try {
|
||||
manifest = await this.getExtensionManifest(extensionLocation);
|
||||
} catch (error) {
|
||||
throw new Error(`Error while fetching manifest from the location '${extensionLocation.toString()}'. ${getErrorMessage(error)}`);
|
||||
private async toWebExtension(extensionLocation: URI, identifier?: IExtensionIdentifier, manifest?: IExtensionManifest, packageNLSUris?: Map<string, URI>, fallbackPackageNLSUri?: URI | ITranslations | null, readmeUri?: URI, changelogUri?: URI, metadata?: Metadata): Promise<IWebExtension> {
|
||||
if (!manifest) {
|
||||
try {
|
||||
manifest = await this.getExtensionManifest(extensionLocation);
|
||||
} catch (error) {
|
||||
throw new Error(`Error while fetching manifest from the location '${extensionLocation.toString()}'. ${getErrorMessage(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.extensionManifestPropertiesService.canExecuteOnWeb(manifest)) {
|
||||
@@ -616,7 +655,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
||||
fallbackPackageNLSUri = undefined;
|
||||
}
|
||||
}
|
||||
const defaultManifestTranslations: ITranslations | null | undefined = fallbackPackageNLSUri ? await this.getTranslations(fallbackPackageNLSUri) : null;
|
||||
const defaultManifestTranslations: ITranslations | null | undefined = fallbackPackageNLSUri ? URI.isUri(fallbackPackageNLSUri) ? await this.getTranslations(fallbackPackageNLSUri) : fallbackPackageNLSUri : null;
|
||||
|
||||
return {
|
||||
identifier: { id: getGalleryExtensionId(manifest.publisher, manifest.name), uuid: identifier?.uuid },
|
||||
@@ -626,7 +665,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
||||
readmeUri,
|
||||
changelogUri,
|
||||
packageNLSUris,
|
||||
fallbackPackageNLSUri: fallbackPackageNLSUri ? fallbackPackageNLSUri : undefined,
|
||||
fallbackPackageNLSUri: URI.isUri(fallbackPackageNLSUri) ? fallbackPackageNLSUri : undefined,
|
||||
defaultManifestTranslations,
|
||||
metadata,
|
||||
};
|
||||
|
||||
@@ -83,6 +83,7 @@ export const allApiProposals = Object.freeze({
|
||||
terminalDimensions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDimensions.d.ts',
|
||||
terminalQuickFixProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalQuickFixProvider.d.ts',
|
||||
testCoverage: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testCoverage.d.ts',
|
||||
testInvalidateResults: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testInvalidateResults.d.ts',
|
||||
testObserver: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testObserver.d.ts',
|
||||
textSearchProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts',
|
||||
timeline: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.timeline.d.ts',
|
||||
|
||||
@@ -507,7 +507,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
|
||||
let sourceTextModel: ITextModel | undefined = undefined;
|
||||
if (sourceModel instanceof BaseTextEditorModel) {
|
||||
if (sourceModel.isResolved()) {
|
||||
sourceTextModel = sourceModel.textEditorModel;
|
||||
sourceTextModel = withNullAsUndefined(sourceModel.textEditorModel);
|
||||
}
|
||||
} else {
|
||||
sourceTextModel = sourceModel as ITextModel;
|
||||
|
||||
@@ -27,6 +27,7 @@ suite('DomActivityTracker', () => {
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
|
||||
test('marks inactive on no input', () => {
|
||||
assert.equal(uas.isActive, true);
|
||||
clock.tick(maxTimeToBecomeIdle);
|
||||
|
||||
@@ -151,6 +151,9 @@ declare module 'vscode' {
|
||||
prepareSession(initialState: InteractiveSessionState | undefined, token: CancellationToken): ProviderResult<S>;
|
||||
resolveRequest(session: S, context: InteractiveSessionRequestArgs | string, token: CancellationToken): ProviderResult<InteractiveRequest>;
|
||||
provideResponseWithProgress(request: InteractiveRequest, progress: Progress<InteractiveProgress>, token: CancellationToken): ProviderResult<InteractiveResponseForProgress>;
|
||||
|
||||
// eslint-disable-next-line local/vscode-dts-provider-naming
|
||||
removeRequest(session: S, requestId: string): void;
|
||||
}
|
||||
|
||||
export interface InteractiveSessionDynamicRequest {
|
||||
|
||||
+8
-10
@@ -7,6 +7,9 @@ declare module 'vscode' {
|
||||
|
||||
// https://github.com/microsoft/vscode/issues/115616 @alexr00
|
||||
|
||||
/**
|
||||
* The action that should be taken when a port is discovered through automatic port forwarding discovery.
|
||||
*/
|
||||
export enum PortAutoForwardAction {
|
||||
/**
|
||||
* Notify the user that the port is being forwarded. This is the default action.
|
||||
@@ -67,21 +70,16 @@ declare module 'vscode' {
|
||||
providePortAttributes(port: number, pid: number | undefined, commandLine: string | undefined, token: CancellationToken): ProviderResult<PortAttributes>;
|
||||
}
|
||||
|
||||
export interface PortAttributesProviderSelector {
|
||||
/**
|
||||
* TODO: @alexr00 no one is currently using this. Should we delete it?
|
||||
* If your {@link PortAttributesProvider PortAttributesProvider} is registered after your process has started then already know the process id of port you are listening on.
|
||||
* Specifying a pid will cause your provider to only be called for ports that match the pid.
|
||||
*/
|
||||
pid?: number;
|
||||
|
||||
/**
|
||||
* A selector that will be used to filter which {@link PortAttributesProvider} should be called for each port.
|
||||
*/
|
||||
export interface PortAttributesSelector {
|
||||
/**
|
||||
* Specifying a port range will cause your provider to only be called for ports within the range.
|
||||
*/
|
||||
portRange?: [number, number];
|
||||
|
||||
/**
|
||||
* TODO: @alexr00 no one is currently using this. Should we delete it?
|
||||
* Specifying a command pattern will cause your provider to only be called for processes whose command line matches the pattern.
|
||||
*/
|
||||
commandPattern?: RegExp;
|
||||
@@ -100,6 +98,6 @@ declare module 'vscode' {
|
||||
* If you don't specify a port selector your provider will be called for every port, which will result in slower port forwarding for the user.
|
||||
* @param provider The {@link PortAttributesProvider PortAttributesProvider}.
|
||||
*/
|
||||
export function registerPortAttributesProvider(portSelector: PortAttributesProviderSelector, provider: PortAttributesProvider): Disposable;
|
||||
export function registerPortAttributesProvider(portSelector: PortAttributesSelector, provider: PortAttributesProvider): Disposable;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// https://github.com/microsoft/vscode/issues/134970
|
||||
|
||||
declare module 'vscode' {
|
||||
|
||||
export interface TestController {
|
||||
|
||||
/**
|
||||
* Marks an item's results as being outdated. This is commonly called when
|
||||
* code or configuration changes and previous results should no longer
|
||||
* be considered relevant. The same logic used to mark results as outdated
|
||||
* may be used to drive {@link TestRunRequest.continuous continuous test runs}.
|
||||
*
|
||||
* If an item is passed to this method, test results for the item and all of
|
||||
* its children will be marked as outdated. If no item is passed, then all
|
||||
* test owned by the TestController will be marked as outdated.
|
||||
*
|
||||
* Any test runs started before the moment this method is called, including
|
||||
* runs which may still be ongoing, will be marked as outdated and deprioritized
|
||||
* in the editor's UI.
|
||||
*
|
||||
* @param item Item to mark as outdated. If undefined, all the controller's items are marked outdated.
|
||||
*/
|
||||
invalidateTestResults(item?: TestItem): void;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user