mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 04:49:35 +00:00
Merge branch 'main' into ulugbekna/implement-code-mappers-context
This commit is contained in:
commit
39dc875c4e
|
@ -93,7 +93,7 @@ Once submitted, your report will go into the [issue tracking](https://github.com
|
|||
|
||||
We use GitHub Actions to help us manage issues. These Actions and their descriptions can be [viewed here](https://github.com/microsoft/vscode-github-triage-actions). Some examples of what these Actions do are:
|
||||
|
||||
* Automatically closes any issue marked `needs-more-info` if there has been no response in the past 7 days.
|
||||
* Automatically closes any issue marked `info-needed` if there has been no response in the past 7 days.
|
||||
* Automatically lock issues 45 days after they are closed.
|
||||
* Automatically implement the VS Code [feature request pipeline](https://github.com/microsoft/vscode/wiki/Issues-Triaging#managing-feature-requests).
|
||||
|
||||
|
|
|
@ -225,6 +225,9 @@ pub struct CommandShellArgs {
|
|||
/// Require the given token string to be given in the handshake.
|
||||
#[clap(long)]
|
||||
pub require_token: Option<String>,
|
||||
/// Optional parent process id. If provided, the server will be stopped when the process of the given pid no longer exists
|
||||
#[clap(long, hide = true)]
|
||||
pub parent_process_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
|
|
|
@ -136,6 +136,11 @@ impl ServiceContainer for TunnelServiceContainer {
|
|||
|
||||
pub async fn command_shell(ctx: CommandContext, args: CommandShellArgs) -> Result<i32, AnyError> {
|
||||
let platform = PreReqChecker::new().verify().await?;
|
||||
let mut shutdown_reqs = vec![ShutdownRequest::CtrlC];
|
||||
if let Some(p) = args.parent_process_id.and_then(|p| Pid::from_str(&p).ok()) {
|
||||
shutdown_reqs.push(ShutdownRequest::ParentProcessKilled(p));
|
||||
}
|
||||
|
||||
let mut params = ServeStreamParams {
|
||||
log: ctx.log,
|
||||
launcher_paths: ctx.paths,
|
||||
|
@ -144,7 +149,7 @@ pub async fn command_shell(ctx: CommandContext, args: CommandShellArgs) -> Resul
|
|||
.require_token
|
||||
.map(AuthRequired::VSDAWithToken)
|
||||
.unwrap_or(AuthRequired::VSDA),
|
||||
exit_barrier: ShutdownRequest::create_rx([ShutdownRequest::CtrlC]),
|
||||
exit_barrier: ShutdownRequest::create_rx(shutdown_reqs),
|
||||
code_server_args: (&ctx.args).into(),
|
||||
};
|
||||
|
||||
|
|
|
@ -23,6 +23,8 @@ pub async fn update(ctx: CommandContext, args: StandaloneUpdateArgs) -> Result<i
|
|||
);
|
||||
let update_service = SelfUpdate::new(&update_service)?;
|
||||
|
||||
let _ = update_service.cleanup_old_update();
|
||||
|
||||
let current_version = update_service.get_current_release().await?;
|
||||
if update_service.is_up_to_date_with(¤t_version) {
|
||||
ctx.log.result(format!(
|
||||
|
|
|
@ -24,6 +24,8 @@ pub struct SelfUpdate<'a> {
|
|||
update_service: &'a UpdateService,
|
||||
}
|
||||
|
||||
static OLD_UPDATE_EXTENSION: &str = "Updating CLI";
|
||||
|
||||
impl<'a> SelfUpdate<'a> {
|
||||
pub fn new(update_service: &'a UpdateService) -> Result<Self, AnyError> {
|
||||
let commit = VSCODE_CLI_COMMIT
|
||||
|
@ -59,6 +61,18 @@ impl<'a> SelfUpdate<'a> {
|
|||
release.commit == self.commit
|
||||
}
|
||||
|
||||
/// Cleans up old self-updated binaries. Should be called with regularity.
|
||||
/// May fail if old versions are still running.
|
||||
pub fn cleanup_old_update(&self) -> Result<(), std::io::Error> {
|
||||
let current_path = std::env::current_exe()?;
|
||||
let old_path = current_path.with_extension(OLD_UPDATE_EXTENSION);
|
||||
if old_path.exists() {
|
||||
fs::remove_file(old_path)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates the CLI to the given release.
|
||||
pub async fn do_update(
|
||||
&self,
|
||||
|
@ -89,8 +103,11 @@ impl<'a> SelfUpdate<'a> {
|
|||
// OS later. However, this can fail if the tempdir is on a different drive
|
||||
// than the installation dir. In this case just rename it to ".old".
|
||||
if fs::rename(&target_path, tempdir.path().join("old-code-cli")).is_err() {
|
||||
fs::rename(&target_path, target_path.with_extension(".old"))
|
||||
.map_err(|e| wrap(e, "failed to rename old CLI"))?;
|
||||
fs::rename(
|
||||
&target_path,
|
||||
target_path.with_extension(OLD_UPDATE_EXTENSION),
|
||||
)
|
||||
.map_err(|e| wrap(e, "failed to rename old CLI"))?;
|
||||
}
|
||||
|
||||
fs::rename(&staging_path, &target_path)
|
||||
|
|
|
@ -777,6 +777,8 @@ async fn handle_update(
|
|||
let latest_release = updater.get_current_release().await?;
|
||||
let up_to_date = updater.is_up_to_date_with(&latest_release);
|
||||
|
||||
let _ = updater.cleanup_old_update();
|
||||
|
||||
if !params.do_update || up_to_date {
|
||||
return Ok(UpdateResult {
|
||||
up_to_date,
|
||||
|
|
|
@ -10,6 +10,7 @@ use std::io::Seek;
|
|||
use std::path::{Path, PathBuf};
|
||||
use tar::Archive;
|
||||
|
||||
use super::errors::wrapdbg;
|
||||
use super::io::ReportCopyProgress;
|
||||
|
||||
fn should_skip_first_segment(file: &fs::File) -> Result<bool, WrappedError> {
|
||||
|
@ -93,7 +94,7 @@ where
|
|||
|
||||
entry
|
||||
.unpack(&path)
|
||||
.map_err(|e| wrap(e, format!("error unpacking {}", path.display())))?;
|
||||
.map_err(|e| wrapdbg(e, format!("error unpacking {}", path.display())))?;
|
||||
Ok(path)
|
||||
})
|
||||
.collect::<Result<Vec<PathBuf>, WrappedError>>()?;
|
||||
|
|
|
@ -122,7 +122,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
|
|||
});
|
||||
|
||||
// History item change decoration
|
||||
const fileDecoration = this.historyItemChangeFileDecoration(change.status);
|
||||
const fileDecoration = this.getHistoryItemChangeFileDecoration(change.status);
|
||||
this.historyItemDecorations.set(historyItemUri.toString(), fileDecoration);
|
||||
|
||||
historyItemChangesUri.push(historyItemUri);
|
||||
|
@ -161,15 +161,12 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
|
|||
return this.historyItemDecorations.get(uri.toString());
|
||||
}
|
||||
|
||||
private historyItemChangeFileDecoration(status: Status): FileDecoration {
|
||||
private getHistoryItemChangeFileDecoration(status: Status): FileDecoration {
|
||||
const letter = Resource.getStatusLetter(status);
|
||||
const tooltip = Resource.getStatusText(status);
|
||||
const color = Resource.getStatusColor(status);
|
||||
|
||||
const fileDecoration = new FileDecoration(letter, tooltip, color);
|
||||
fileDecoration.propagate = status !== Status.DELETED && status !== Status.INDEX_DELETED;
|
||||
|
||||
return fileDecoration;
|
||||
return new FileDecoration(letter, tooltip, color);
|
||||
}
|
||||
|
||||
private async getSummaryHistoryItem(ref1: string, ref2: string): Promise<SourceControlHistoryItem> {
|
||||
|
|
|
@ -524,13 +524,6 @@ export class AzureActiveDirectoryService {
|
|||
throw e;
|
||||
}
|
||||
|
||||
let label;
|
||||
if (claims.name && claims.email) {
|
||||
label = `${claims.name} - ${claims.email}`;
|
||||
} else {
|
||||
label = claims.email ?? claims.unique_name ?? claims.preferred_username ?? 'user@example.com';
|
||||
}
|
||||
|
||||
const id = `${claims.tid}/${(claims.oid ?? (claims.altsecid ?? '' + claims.ipd ?? ''))}`;
|
||||
const sessionId = existingId || `${id}/${randomUUID()}`;
|
||||
this._logger.trace(`[${scopeData.scopeStr}] '${sessionId}' Token response parsed successfully.`);
|
||||
|
@ -543,7 +536,7 @@ export class AzureActiveDirectoryService {
|
|||
scope: scopeData.scopeStr,
|
||||
sessionId,
|
||||
account: {
|
||||
label,
|
||||
label: claims.email ?? claims.preferred_username ?? claims.unique_name ?? 'user@example.com',
|
||||
id,
|
||||
type: claims.tid === MSA_TID || claims.tid === MSA_PASSTHRU_TID ? MicrosoftAccountType.MSA : MicrosoftAccountType.AAD
|
||||
}
|
||||
|
|
|
@ -387,7 +387,8 @@ import { assertNoRpc, poll } from '../utils';
|
|||
});
|
||||
|
||||
suite('window.onDidWriteTerminalData', () => {
|
||||
test('should listen to all future terminal data events', function (done) {
|
||||
// still flaky with retries, skipping https://github.com/microsoft/vscode/issues/193505
|
||||
test.skip('should listen to all future terminal data events', function (done) {
|
||||
// This test has been flaky in the past but it's not clear why, possibly because
|
||||
// events from previous tests polluting the event recording in this test. Retries
|
||||
// was added so we continue to have coverage of the onDidWriteTerminalData API.
|
||||
|
|
|
@ -219,7 +219,7 @@
|
|||
"webpack-cli": "^5.0.1",
|
||||
"webpack-stream": "^7.0.0",
|
||||
"xml2js": "^0.5.0",
|
||||
"yaserver": "^0.2.0"
|
||||
"yaserver": "^0.4.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -278,7 +278,9 @@ function configureCommandlineSwitchesSync(cliArgs) {
|
|||
|
||||
// Following features are disabled from the runtime:
|
||||
// `CalculateNativeWinOcclusion` - Disable native window occlusion tracker (https://groups.google.com/a/chromium.org/g/embedder-dev/c/ZF3uHHyWLKw/m/VDN2hDXMAAAJ)
|
||||
app.commandLine.appendSwitch('disable-features', 'CalculateNativeWinOcclusion');
|
||||
const featuresToDisable =
|
||||
`CalculateNativeWinOcclusion,${app.commandLine.getSwitchValue('disable-features')}`;
|
||||
app.commandLine.appendSwitch('disable-features', featuresToDisable);
|
||||
|
||||
// Support JS Flags
|
||||
const jsFlags = getJSFlags(cliArgs);
|
||||
|
|
|
@ -7,6 +7,7 @@ import { IDragAndDropData } from 'vs/base/browser/dnd';
|
|||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { GestureEvent } from 'vs/base/browser/touch';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface IListVirtualDelegate<T> {
|
||||
getHeight(element: T): number;
|
||||
|
@ -99,7 +100,11 @@ export const ListDragOverReactions = {
|
|||
accept(): IListDragOverReaction { return { accept: true }; },
|
||||
};
|
||||
|
||||
export interface IListDragAndDrop<T> {
|
||||
/**
|
||||
* Warning: Once passed to a list, that list takes up
|
||||
* the responsibility of disposing it.
|
||||
*/
|
||||
export interface IListDragAndDrop<T> extends IDisposable {
|
||||
getDragURI(element: T): string | null;
|
||||
getDragLabel?(elements: T[], originalEvent: DragEvent): string | undefined;
|
||||
onDragStart?(data: IDragAndDropData, originalEvent: DragEvent): void;
|
||||
|
|
|
@ -13,7 +13,7 @@ import { distinct, equals } from 'vs/base/common/arrays';
|
|||
import { Delayer, disposableTimeout } from 'vs/base/common/async';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IRange, Range } from 'vs/base/common/range';
|
||||
import { INewScrollDimensions, Scrollable, ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
|
@ -90,7 +90,8 @@ const DefaultOptions = {
|
|||
getDragURI() { return null; },
|
||||
onDragStart(): void { },
|
||||
onDragOver() { return false; },
|
||||
drop() { }
|
||||
drop() { },
|
||||
dispose() { }
|
||||
},
|
||||
horizontalScrolling: false,
|
||||
transformOptimization: true,
|
||||
|
@ -436,7 +437,7 @@ export class ListView<T> implements IListView<T> {
|
|||
this.setRowLineHeight = options.setRowLineHeight ?? DefaultOptions.setRowLineHeight;
|
||||
this.setRowHeight = options.setRowHeight ?? DefaultOptions.setRowHeight;
|
||||
this.supportDynamicHeights = options.supportDynamicHeights ?? DefaultOptions.supportDynamicHeights;
|
||||
this.dnd = options.dnd ?? DefaultOptions.dnd;
|
||||
this.dnd = options.dnd ?? this.disposables.add(DefaultOptions.dnd);
|
||||
|
||||
this.layout(options.initialSize?.height, options.initialSize?.width);
|
||||
}
|
||||
|
@ -1241,7 +1242,7 @@ export class ListView<T> implements IListView<T> {
|
|||
|
||||
private onDragLeave(event: IListDragEvent<T>): void {
|
||||
this.onDragLeaveTimeout.dispose();
|
||||
this.onDragLeaveTimeout = disposableTimeout(() => this.clearDragOverFeedback(), 100);
|
||||
this.onDragLeaveTimeout = disposableTimeout(() => this.clearDragOverFeedback(), 100, this.disposables);
|
||||
if (this.currentDragData) {
|
||||
this.dnd.onDragLeave?.(this.currentDragData, event.element, event.index, event.browserEvent);
|
||||
}
|
||||
|
@ -1299,7 +1300,7 @@ export class ListView<T> implements IListView<T> {
|
|||
this.dragOverAnimationDisposable.dispose();
|
||||
this.dragOverAnimationDisposable = undefined;
|
||||
}
|
||||
}, 1000);
|
||||
}, 1000, this.disposables);
|
||||
|
||||
this.dragOverMouseY = event.pageY;
|
||||
}
|
||||
|
@ -1544,6 +1545,7 @@ export class ListView<T> implements IListView<T> {
|
|||
this.domNode.parentNode.removeChild(this.domNode);
|
||||
}
|
||||
|
||||
dispose(this.disposables);
|
||||
this.dragOverAnimationDisposable?.dispose();
|
||||
this.disposables.dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1097,7 +1097,8 @@ const DefaultOptions: IListOptions<any> = {
|
|||
getDragURI() { return null; },
|
||||
onDragStart(): void { },
|
||||
onDragOver() { return false; },
|
||||
drop() { }
|
||||
drop() { },
|
||||
dispose() { }
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1298,6 +1299,10 @@ class ListViewDragAndDrop<T> implements IListViewDragAndDrop<T> {
|
|||
drop(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): void {
|
||||
this.dnd.drop(data, targetElement, targetIndex, originalEvent);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.dnd.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -61,6 +61,7 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
|
|||
|
||||
private autoExpandNode: ITreeNode<T, TFilterData> | undefined;
|
||||
private autoExpandDisposable: IDisposable = Disposable.None;
|
||||
private disposables = new DisposableStore();
|
||||
|
||||
constructor(private modelProvider: () => ITreeModel<T, TFilterData, TRef>, private dnd: ITreeDragAndDrop<T>) { }
|
||||
|
||||
|
@ -103,7 +104,7 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
|
|||
}
|
||||
|
||||
this.autoExpandNode = undefined;
|
||||
}, 500);
|
||||
}, 500, this.disposables);
|
||||
}
|
||||
|
||||
if (typeof result === 'boolean' || !result.accept || typeof result.bubble === 'undefined' || result.feedback) {
|
||||
|
@ -144,6 +145,11 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
|
|||
onDragEnd(originalEvent: DragEvent): void {
|
||||
this.dnd.onDragEnd?.(originalEvent);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables.dispose();
|
||||
this.dnd.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
function asListOptions<T, TFilterData, TRef>(modelProvider: () => ITreeModel<T, TFilterData, TRef>, options?: IAbstractTreeOptions<T, TFilterData>): IListOptions<ITreeNode<T, TFilterData>> | undefined {
|
||||
|
|
|
@ -208,6 +208,10 @@ class AsyncDataTreeNodeListDragAndDrop<TInput, T> implements IListDragAndDrop<IA
|
|||
onDragEnd(originalEvent: DragEvent): void {
|
||||
this.dnd.onDragEnd?.(originalEvent);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.dnd.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOptions<T, TFilterData>): IObjectTreeOptions<IAsyncDataTreeNode<TInput, T>, TFilterData> | undefined {
|
||||
|
|
|
@ -63,6 +63,24 @@ export function diffMaps<K, V>(before: Map<K, V>, after: Map<K, V>): { removed:
|
|||
}
|
||||
return { removed, added };
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the intersection of two sets.
|
||||
*
|
||||
* @param setA - The first set.
|
||||
* @param setB - The second iterable.
|
||||
* @returns A new set containing the elements that are in both `setA` and `setB`.
|
||||
*/
|
||||
export function intersection<T>(setA: Set<T>, setB: Iterable<T>): Set<T> {
|
||||
const result = new Set<T>();
|
||||
for (const elem of setB) {
|
||||
if (setA.has(elem)) {
|
||||
result.add(elem);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export class SetMap<K, V> {
|
||||
|
||||
private map = new Map<K, Set<V>>();
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { once as onceFn } from 'vs/base/common/functional';
|
||||
import { createSingleCallFunction } from 'vs/base/common/functional';
|
||||
import { combinedDisposable, Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { LinkedList } from 'vs/base/common/linkedList';
|
||||
import { IObservable, IObserver } from 'vs/base/common/observable';
|
||||
|
@ -1449,7 +1449,7 @@ export class EventMultiplexer<T> implements IDisposable {
|
|||
this.events.splice(idx, 1);
|
||||
};
|
||||
|
||||
return toDisposable(onceFn(dispose));
|
||||
return toDisposable(createSingleCallFunction(dispose));
|
||||
}
|
||||
|
||||
private onFirstListenerAdd(): void {
|
||||
|
|
|
@ -3,7 +3,10 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export function once<T extends Function>(this: unknown, fn: T): T {
|
||||
/**
|
||||
* Given a function, returns a function that is only calling that function once.
|
||||
*/
|
||||
export function createSingleCallFunction<T extends Function>(this: unknown, fn: T): T {
|
||||
const _this = this;
|
||||
let didCall = false;
|
||||
let result: unknown;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { compareBy, numberComparator } from 'vs/base/common/arrays';
|
||||
import { SetMap, groupBy } from 'vs/base/common/collections';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { createSingleCallFunction } from 'vs/base/common/functional';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
|
||||
// #region Disposable Tracking
|
||||
|
@ -345,7 +345,7 @@ export function combinedDisposable(...disposables: IDisposable[]): IDisposable {
|
|||
*/
|
||||
export function toDisposable(fn: () => void): IDisposable {
|
||||
const self = trackDisposable({
|
||||
dispose: once(() => {
|
||||
dispose: createSingleCallFunction(() => {
|
||||
markAsDisposed(self);
|
||||
fn();
|
||||
})
|
||||
|
@ -623,7 +623,7 @@ export abstract class ReferenceCollection<T> {
|
|||
}
|
||||
|
||||
const { object } = reference;
|
||||
const dispose = once(() => {
|
||||
const dispose = createSingleCallFunction(() => {
|
||||
if (--reference!.counter === 0) {
|
||||
this.destroyReferencedObject(key, reference!.object);
|
||||
this.references.delete(key);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import * as crypto from 'crypto';
|
||||
import * as fs from 'fs';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { createSingleCallFunction } from 'vs/base/common/functional';
|
||||
|
||||
export async function checksum(path: string, sha1hash: string | undefined): Promise<void> {
|
||||
const checksumPromise = new Promise<string | undefined>((resolve, reject) => {
|
||||
|
@ -13,7 +13,7 @@ export async function checksum(path: string, sha1hash: string | undefined): Prom
|
|||
const hash = crypto.createHash('sha1');
|
||||
input.pipe(hash);
|
||||
|
||||
const done = once((err?: Error, result?: string) => {
|
||||
const done = createSingleCallFunction((err?: Error, result?: string) => {
|
||||
input.removeAllListeners();
|
||||
hash.removeAllListeners();
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import { marked } from 'vs/base/common/marked/marked';
|
|||
import { parse } from 'vs/base/common/marshalling';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
|
||||
|
||||
function strToNode(str: string): HTMLElement {
|
||||
return new DOMParser().parseFromString(str, 'text/html').body.firstChild as HTMLElement;
|
||||
|
@ -23,10 +24,13 @@ function assertNodeEquals(actualNode: HTMLElement, expectedHtml: string) {
|
|||
}
|
||||
|
||||
suite('MarkdownRenderer', () => {
|
||||
|
||||
const store = ensureNoDisposablesAreLeakedInTestSuite();
|
||||
|
||||
suite('Sanitization', () => {
|
||||
test('Should not render images with unknown schemes', () => {
|
||||
const markdown = { value: `![image](no-such://example.com/cat.gif)` };
|
||||
const result: HTMLElement = renderMarkdown(markdown).element;
|
||||
const result: HTMLElement = store.add(renderMarkdown(markdown)).element;
|
||||
assert.strictEqual(result.innerHTML, '<p><img alt="image"></p>');
|
||||
});
|
||||
});
|
||||
|
@ -34,28 +38,28 @@ suite('MarkdownRenderer', () => {
|
|||
suite('Images', () => {
|
||||
test('image rendering conforms to default', () => {
|
||||
const markdown = { value: `![image](http://example.com/cat.gif 'caption')` };
|
||||
const result: HTMLElement = renderMarkdown(markdown).element;
|
||||
const result: HTMLElement = store.add(renderMarkdown(markdown)).element;
|
||||
assertNodeEquals(result, '<div><p><img title="caption" alt="image" src="http://example.com/cat.gif"></p></div>');
|
||||
});
|
||||
|
||||
test('image rendering conforms to default without title', () => {
|
||||
const markdown = { value: `![image](http://example.com/cat.gif)` };
|
||||
const result: HTMLElement = renderMarkdown(markdown).element;
|
||||
const result: HTMLElement = store.add(renderMarkdown(markdown)).element;
|
||||
assertNodeEquals(result, '<div><p><img alt="image" src="http://example.com/cat.gif"></p></div>');
|
||||
});
|
||||
|
||||
test('image width from title params', () => {
|
||||
const result: HTMLElement = renderMarkdown({ value: `![image](http://example.com/cat.gif|width=100px 'caption')` }).element;
|
||||
const result: HTMLElement = store.add(renderMarkdown({ value: `![image](http://example.com/cat.gif|width=100px 'caption')` })).element;
|
||||
assertNodeEquals(result, `<div><p><img width="100" title="caption" alt="image" src="http://example.com/cat.gif"></p></div>`);
|
||||
});
|
||||
|
||||
test('image height from title params', () => {
|
||||
const result: HTMLElement = renderMarkdown({ value: `![image](http://example.com/cat.gif|height=100 'caption')` }).element;
|
||||
const result: HTMLElement = store.add(renderMarkdown({ value: `![image](http://example.com/cat.gif|height=100 'caption')` })).element;
|
||||
assertNodeEquals(result, `<div><p><img height="100" title="caption" alt="image" src="http://example.com/cat.gif"></p></div>`);
|
||||
});
|
||||
|
||||
test('image width and height from title params', () => {
|
||||
const result: HTMLElement = renderMarkdown({ value: `![image](http://example.com/cat.gif|height=200,width=100 'caption')` }).element;
|
||||
const result: HTMLElement = store.add(renderMarkdown({ value: `![image](http://example.com/cat.gif|height=200,width=100 'caption')` })).element;
|
||||
assertNodeEquals(result, `<div><p><img height="200" width="100" title="caption" alt="image" src="http://example.com/cat.gif"></p></div>`);
|
||||
});
|
||||
|
||||
|
@ -63,7 +67,7 @@ suite('MarkdownRenderer', () => {
|
|||
if (isWeb) {
|
||||
return;
|
||||
}
|
||||
const result: HTMLElement = renderMarkdown({ value: `![image](file:///images/cat.gif)` }).element;
|
||||
const result: HTMLElement = store.add(renderMarkdown({ value: `![image](file:///images/cat.gif)` })).element;
|
||||
assertNodeEquals(result, '<div><p><img src="vscode-file://vscode-app/images/cat.gif" alt="image"></p></div>');
|
||||
});
|
||||
});
|
||||
|
@ -78,10 +82,10 @@ suite('MarkdownRenderer', () => {
|
|||
test('asyncRenderCallback should be invoked for code blocks', () => {
|
||||
const markdown = { value: '```js\n1 + 1;\n```' };
|
||||
return new Promise<void>(resolve => {
|
||||
renderMarkdown(markdown, {
|
||||
store.add(renderMarkdown(markdown, {
|
||||
asyncRenderCallback: resolve,
|
||||
codeBlockRenderer: simpleCodeBlockRenderer
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -120,12 +124,12 @@ suite('MarkdownRenderer', () => {
|
|||
test('Code blocks should use leading language id (#157793)', async () => {
|
||||
const markdown = { value: '```js some other stuff\n1 + 1;\n```' };
|
||||
const lang = await new Promise<string>(resolve => {
|
||||
renderMarkdown(markdown, {
|
||||
store.add(renderMarkdown(markdown, {
|
||||
codeBlockRenderer: async (lang, value) => {
|
||||
resolve(lang);
|
||||
return simpleCodeBlockRenderer(lang, value);
|
||||
}
|
||||
});
|
||||
}));
|
||||
});
|
||||
assert.strictEqual(lang, 'js');
|
||||
});
|
||||
|
@ -137,7 +141,7 @@ suite('MarkdownRenderer', () => {
|
|||
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
|
||||
mds.appendText('$(zap) $(not a theme icon) $(add)');
|
||||
|
||||
const result: HTMLElement = renderMarkdown(mds).element;
|
||||
const result: HTMLElement = store.add(renderMarkdown(mds)).element;
|
||||
assert.strictEqual(result.innerHTML, `<p>$(zap) $(not a theme icon) $(add)</p>`);
|
||||
});
|
||||
|
||||
|
@ -145,7 +149,7 @@ suite('MarkdownRenderer', () => {
|
|||
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
|
||||
mds.appendMarkdown('$(zap) $(not a theme icon) $(add)');
|
||||
|
||||
const result: HTMLElement = renderMarkdown(mds).element;
|
||||
const result: HTMLElement = store.add(renderMarkdown(mds)).element;
|
||||
assert.strictEqual(result.innerHTML, `<p><span class="codicon codicon-zap"></span> $(not a theme icon) <span class="codicon codicon-add"></span></p>`);
|
||||
});
|
||||
|
||||
|
@ -153,7 +157,7 @@ suite('MarkdownRenderer', () => {
|
|||
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
|
||||
mds.appendMarkdown('\\$(zap) $(not a theme icon) $(add)');
|
||||
|
||||
const result: HTMLElement = renderMarkdown(mds).element;
|
||||
const result: HTMLElement = store.add(renderMarkdown(mds)).element;
|
||||
assert.strictEqual(result.innerHTML, `<p>$(zap) $(not a theme icon) <span class="codicon codicon-add"></span></p>`);
|
||||
});
|
||||
|
||||
|
@ -161,7 +165,7 @@ suite('MarkdownRenderer', () => {
|
|||
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
|
||||
mds.appendMarkdown(`[$(zap)-link](#link)`);
|
||||
|
||||
const result: HTMLElement = renderMarkdown(mds).element;
|
||||
const result: HTMLElement = store.add(renderMarkdown(mds)).element;
|
||||
assert.strictEqual(result.innerHTML, `<p><a data-href="#link" href="" title="#link"><span class="codicon codicon-zap"></span>-link</a></p>`);
|
||||
});
|
||||
|
||||
|
@ -172,7 +176,7 @@ suite('MarkdownRenderer', () => {
|
|||
|--------|----------------------|
|
||||
| $(zap) | [$(zap)-link](#link) |`);
|
||||
|
||||
const result: HTMLElement = renderMarkdown(mds).element;
|
||||
const result: HTMLElement = store.add(renderMarkdown(mds)).element;
|
||||
assert.strictEqual(result.innerHTML, `<table>
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -192,7 +196,7 @@ suite('MarkdownRenderer', () => {
|
|||
const mds = new MarkdownString(undefined, { supportThemeIcons: true, supportHtml: true });
|
||||
mds.appendMarkdown(`<a>$(sync)</a>`);
|
||||
|
||||
const result: HTMLElement = renderMarkdown(mds).element;
|
||||
const result: HTMLElement = store.add(renderMarkdown(mds)).element;
|
||||
assert.strictEqual(result.innerHTML, `<p><span class="codicon codicon-sync"></span></p>`);
|
||||
});
|
||||
});
|
||||
|
@ -203,7 +207,7 @@ suite('MarkdownRenderer', () => {
|
|||
const mds = new MarkdownString(undefined, { supportThemeIcons: false });
|
||||
mds.appendText('$(zap) $(not a theme icon) $(add)');
|
||||
|
||||
const result: HTMLElement = renderMarkdown(mds).element;
|
||||
const result: HTMLElement = store.add(renderMarkdown(mds)).element;
|
||||
assert.strictEqual(result.innerHTML, `<p>$(zap) $(not a theme icon) $(add)</p>`);
|
||||
});
|
||||
|
||||
|
@ -211,7 +215,7 @@ suite('MarkdownRenderer', () => {
|
|||
const mds = new MarkdownString(undefined, { supportThemeIcons: false });
|
||||
mds.appendMarkdown('\\$(zap) $(not a theme icon) $(add)');
|
||||
|
||||
const result: HTMLElement = renderMarkdown(mds).element;
|
||||
const result: HTMLElement = store.add(renderMarkdown(mds)).element;
|
||||
assert.strictEqual(result.innerHTML, `<p>$(zap) $(not a theme icon) $(add)</p>`);
|
||||
});
|
||||
});
|
||||
|
@ -219,7 +223,7 @@ suite('MarkdownRenderer', () => {
|
|||
test('npm Hover Run Script not working #90855', function () {
|
||||
|
||||
const md: IMarkdownString = JSON.parse('{"value":"[Run Script](command:npm.runScriptFromHover?%7B%22documentUri%22%3A%7B%22%24mid%22%3A1%2C%22fsPath%22%3A%22c%3A%5C%5CUsers%5C%5Cjrieken%5C%5CCode%5C%5C_sample%5C%5Cfoo%5C%5Cpackage.json%22%2C%22_sep%22%3A1%2C%22external%22%3A%22file%3A%2F%2F%2Fc%253A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22path%22%3A%22%2Fc%3A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22scheme%22%3A%22file%22%7D%2C%22script%22%3A%22echo%22%7D \\"Run the script as a task\\")","supportThemeIcons":false,"isTrusted":true,"uris":{"__uri_e49443":{"$mid":1,"fsPath":"c:\\\\Users\\\\jrieken\\\\Code\\\\_sample\\\\foo\\\\package.json","_sep":1,"external":"file:///c%3A/Users/jrieken/Code/_sample/foo/package.json","path":"/c:/Users/jrieken/Code/_sample/foo/package.json","scheme":"file"},"command:npm.runScriptFromHover?%7B%22documentUri%22%3A%7B%22%24mid%22%3A1%2C%22fsPath%22%3A%22c%3A%5C%5CUsers%5C%5Cjrieken%5C%5CCode%5C%5C_sample%5C%5Cfoo%5C%5Cpackage.json%22%2C%22_sep%22%3A1%2C%22external%22%3A%22file%3A%2F%2F%2Fc%253A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22path%22%3A%22%2Fc%3A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22scheme%22%3A%22file%22%7D%2C%22script%22%3A%22echo%22%7D":{"$mid":1,"path":"npm.runScriptFromHover","scheme":"command","query":"{\\"documentUri\\":\\"__uri_e49443\\",\\"script\\":\\"echo\\"}"}}}');
|
||||
const element = renderMarkdown(md).element;
|
||||
const element = store.add(renderMarkdown(md)).element;
|
||||
|
||||
const anchor = element.querySelector('a')!;
|
||||
assert.ok(anchor);
|
||||
|
@ -238,7 +242,7 @@ suite('MarkdownRenderer', () => {
|
|||
supportHtml: true
|
||||
});
|
||||
|
||||
const result: HTMLElement = renderMarkdown(md).element;
|
||||
const result: HTMLElement = store.add(renderMarkdown(md)).element;
|
||||
assert.strictEqual(result.innerHTML, `<p>command1 command2</p>`);
|
||||
});
|
||||
|
||||
|
@ -248,7 +252,7 @@ suite('MarkdownRenderer', () => {
|
|||
supportHtml: true,
|
||||
});
|
||||
|
||||
const result: HTMLElement = renderMarkdown(md).element;
|
||||
const result: HTMLElement = store.add(renderMarkdown(md)).element;
|
||||
assert.strictEqual(result.innerHTML, `<p><a data-href="command:doFoo" href="" title="command:doFoo">command1</a> <a data-href="command:doFoo" href="">command2</a></p>`);
|
||||
});
|
||||
|
||||
|
@ -274,7 +278,7 @@ suite('MarkdownRenderer', () => {
|
|||
const mds = new MarkdownString(undefined, {});
|
||||
mds.appendMarkdown('a<b>b</b>c');
|
||||
|
||||
const result = renderMarkdown(mds).element;
|
||||
const result = store.add(renderMarkdown(mds)).element;
|
||||
assert.strictEqual(result.innerHTML, `<p>abc</p>`);
|
||||
});
|
||||
|
||||
|
@ -282,7 +286,7 @@ suite('MarkdownRenderer', () => {
|
|||
const mds = new MarkdownString(undefined, { supportHtml: true });
|
||||
mds.appendMarkdown('a<b>b</b>c');
|
||||
|
||||
const result = renderMarkdown(mds).element;
|
||||
const result = store.add(renderMarkdown(mds)).element;
|
||||
assert.strictEqual(result.innerHTML, `<p>a<b>b</b>c</p>`);
|
||||
});
|
||||
|
||||
|
@ -290,7 +294,7 @@ suite('MarkdownRenderer', () => {
|
|||
const mds = new MarkdownString(undefined, { supportHtml: true });
|
||||
mds.appendMarkdown('a<b onclick="alert(1)">b</b><script>alert(2)</script>c');
|
||||
|
||||
const result = renderMarkdown(mds).element;
|
||||
const result = store.add(renderMarkdown(mds)).element;
|
||||
assert.strictEqual(result.innerHTML, `<p>a<b>b</b>c</p>`);
|
||||
});
|
||||
|
||||
|
@ -298,7 +302,7 @@ suite('MarkdownRenderer', () => {
|
|||
const mds = new MarkdownString(undefined, { supportHtml: true });
|
||||
mds.appendText('a<b>b</b>c');
|
||||
|
||||
const result = renderMarkdown(mds).element;
|
||||
const result = store.add(renderMarkdown(mds)).element;
|
||||
assert.strictEqual(result.innerHTML, `<p>a<b>b</b>c</p>`);
|
||||
});
|
||||
|
||||
|
@ -310,7 +314,7 @@ suite('MarkdownRenderer', () => {
|
|||
const mds = new MarkdownString(undefined, { supportHtml: true });
|
||||
mds.appendMarkdown(`<img src="http://example.com/cat.gif">`);
|
||||
|
||||
const result = renderMarkdown(mds).element;
|
||||
const result = store.add(renderMarkdown(mds)).element;
|
||||
assert.strictEqual(result.innerHTML, `<img src="http://example.com/cat.gif">`);
|
||||
});
|
||||
|
||||
|
@ -322,7 +326,7 @@ suite('MarkdownRenderer', () => {
|
|||
const mds = new MarkdownString(undefined, { supportHtml: true });
|
||||
mds.appendMarkdown(`<img src="file:///images/cat.gif">`);
|
||||
|
||||
const result = renderMarkdown(mds).element;
|
||||
const result = store.add(renderMarkdown(mds)).element;
|
||||
assert.strictEqual(result.innerHTML, `<img src="vscode-file://vscode-app/images/cat.gif">`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,7 +11,7 @@ import { VSBuffer } from 'vs/base/common/buffer';
|
|||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { isSigPipeError, onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors';
|
||||
import { isEqualOrParent } from 'vs/base/common/extpath';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { stripComments } from 'vs/base/common/json';
|
||||
import { getPathLabel } from 'vs/base/common/labels';
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
|
@ -1310,7 +1310,7 @@ export class CodeApplication extends Disposable {
|
|||
try {
|
||||
const WindowsMutex = await import('@vscode/windows-mutex');
|
||||
const mutex = new WindowsMutex.Mutex(win32MutexName);
|
||||
once(this.lifecycleMainService.onWillShutdown)(() => mutex.release());
|
||||
Event.once(this.lifecycleMainService.onWillShutdown)(() => mutex.release());
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import { Promises } from 'vs/base/common/async';
|
|||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { ExpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors';
|
||||
import { IPathWithLineAndColumn, isValidBasename, parseLineAndColumnAware, sanitizeFilePath } from 'vs/base/common/extpath';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { getPathLabel } from 'vs/base/common/labels';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { basename, resolve } from 'vs/base/common/path';
|
||||
|
@ -135,7 +135,7 @@ class CodeMain {
|
|||
bufferLogService.logger = loggerService.createLogger('main', { name: localize('mainLog', "Main") });
|
||||
|
||||
// Lifecycle
|
||||
once(lifecycleMainService.onWillShutdown)(evt => {
|
||||
Event.once(lifecycleMainService.onWillShutdown)(evt => {
|
||||
fileService.dispose();
|
||||
configurationService.dispose();
|
||||
evt.join('instanceLockfile', FSPromises.unlink(environmentMainService.mainLockfile).catch(() => { /* ignored */ }));
|
||||
|
@ -279,7 +279,7 @@ class CodeMain {
|
|||
mark('code/willStartMainServer');
|
||||
mainProcessNodeIpcServer = await nodeIPCServe(environmentMainService.mainIPCHandle);
|
||||
mark('code/didStartMainServer');
|
||||
once(lifecycleMainService.onWillShutdown)(() => mainProcessNodeIpcServer.dispose());
|
||||
Event.once(lifecycleMainService.onWillShutdown)(() => mainProcessNodeIpcServer.dispose());
|
||||
} catch (error) {
|
||||
|
||||
// Handle unexpected errors (the only expected error is EADDRINUSE that
|
||||
|
|
|
@ -672,7 +672,7 @@ export class IssueReporter extends Disposable {
|
|||
}
|
||||
|
||||
if (issueType !== IssueType.FeatureRequest) {
|
||||
sourceSelect.append(this.makeOption('', localize('unknown', "Don't know"), false));
|
||||
sourceSelect.append(this.makeOption('unknown', localize('unknown', "Don't know"), false));
|
||||
}
|
||||
|
||||
if (selected !== -1 && selected < sourceSelect.options.length) {
|
||||
|
@ -1056,7 +1056,10 @@ export class IssueReporter extends Disposable {
|
|||
if (extensionsSelector) {
|
||||
const { selectedExtension } = this.issueReporterModel.getData();
|
||||
reset(extensionsSelector, this.makeOption('', localize('selectExtension', "Select extension"), true), ...extensionOptions.map(extension => makeOption(extension, selectedExtension)));
|
||||
extensionsSelector.selectedIndex = 0;
|
||||
|
||||
if (!selectedExtension) {
|
||||
extensionsSelector.selectedIndex = 0;
|
||||
}
|
||||
|
||||
this.addEventListener('extension-selector', 'change', (e: Event) => {
|
||||
const selectedExtensionId = (<HTMLInputElement>e.target).value;
|
||||
|
|
|
@ -34,7 +34,7 @@ import { Color } from 'vs/base/common/color';
|
|||
import { GestureEvent, EventType, Gesture } from 'vs/base/browser/touch';
|
||||
import { MinimapCharRendererFactory } from 'vs/editor/browser/viewParts/minimap/minimapCharRendererFactory';
|
||||
import { MinimapPosition, TextModelResolvedOptions } from 'vs/editor/common/model';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { createSingleCallFunction } from 'vs/base/common/functional';
|
||||
|
||||
/**
|
||||
* The orthogonal distance to the slider at which dragging "resets". This implements "snapping"
|
||||
|
@ -133,7 +133,7 @@ class MinimapOptions {
|
|||
this.minimapLineHeight = minimapLayout.minimapLineHeight;
|
||||
this.minimapCharWidth = Constants.BASE_CHAR_WIDTH * this.fontScale;
|
||||
|
||||
this.charRenderer = once(() => MinimapCharRendererFactory.create(this.fontScale, fontInfo.fontFamily));
|
||||
this.charRenderer = createSingleCallFunction(() => MinimapCharRendererFactory.create(this.fontScale, fontInfo.fontFamily));
|
||||
this.defaultBackgroundColor = tokensColorTracker.getColor(ColorId.DefaultBackground);
|
||||
this.backgroundColor = MinimapOptions._getMinimapBackground(theme, this.defaultBackgroundColor);
|
||||
this.foregroundAlpha = MinimapOptions._getMinimapForegroundOpacity(theme);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { createSingleCallFunction } from 'vs/base/common/functional';
|
||||
|
||||
const charTable: { [hex: string]: number } = {
|
||||
'0': 0,
|
||||
|
@ -50,12 +50,12 @@ const encodeData = (data: Uint8ClampedArray, length: string) => {
|
|||
* is use-configurable.
|
||||
*/
|
||||
export const prebakedMiniMaps: { [scale: number]: () => Uint8ClampedArray } = {
|
||||
1: once(() =>
|
||||
1: createSingleCallFunction(() =>
|
||||
decodeData(
|
||||
'0000511D6300CF609C709645A78432005642574171487021003C451900274D35D762755E8B629C5BA856AF57BA649530C167D1512A272A3F6038604460398526BCA2A968DB6F8957C768BE5FBE2FB467CF5D8D5B795DC7625B5DFF50DE64C466DB2FC47CD860A65E9A2EB96CB54CE06DA763AB2EA26860524D3763536601005116008177A8705E53AB738E6A982F88BAA35B5F5B626D9C636B449B737E5B7B678598869A662F6B5B8542706C704C80736A607578685B70594A49715A4522E792'
|
||||
)
|
||||
),
|
||||
2: once(() =>
|
||||
2: createSingleCallFunction(() =>
|
||||
decodeData(
|
||||
'000000000000000055394F383D2800008B8B1F210002000081B1CBCBCC820000847AAF6B9AAF2119BE08B8881AD60000A44FD07DCCF107015338130C00000000385972265F390B406E2437634B4B48031B12B8A0847000001E15B29A402F0000000000004B33460B00007A752C2A0000000000004D3900000084394B82013400ABA5CFC7AD9C0302A45A3E5A98AB000089A43382D97900008BA54AA087A70A0248A6A7AE6DBE0000BF6F94987EA40A01A06DCFA7A7A9030496C32F77891D0000A99FB1A0AFA80603B29AB9CA75930D010C0948354D3900000C0948354F37460D0028BE673D8400000000AF9D7B6E00002B007AA8933400007AA642675C2700007984CFB9C3985B768772A8A6B7B20000CAAECAAFC4B700009F94A6009F840009D09F9BA4CA9C0000CC8FC76DC87F0000C991C472A2000000A894A48CA7B501079BA2C9C69BA20000B19A5D3FA89000005CA6009DA2960901B0A7F0669FB200009D009E00B7890000DAD0F5D092820000D294D4C48BD10000B5A7A4A3B1A50402CAB6CBA6A2000000B5A7A4A3B1A8044FCDADD19D9CB00000B7778F7B8AAE0803C9AB5D3F5D3F00009EA09EA0BAB006039EA0989A8C7900009B9EF4D6B7C00000A9A7816CACA80000ABAC84705D3F000096DA635CDC8C00006F486F266F263D4784006124097B00374F6D2D6D2D6D4A3A95872322000000030000000000008D8939130000000000002E22A5C9CBC70600AB25C0B5C9B400061A2DB04CA67001082AA6BEBEBFC606002321DACBC19E03087AA08B6768380000282FBAC0B8CA7A88AD25BBA5A29900004C396C5894A6000040485A6E356E9442A32CD17EADA70000B4237923628600003E2DE9C1D7B500002F25BBA5A2990000231DB6AFB4A804023025C0B5CAB588062B2CBDBEC0C706882435A75CA20000002326BD6A82A908048B4B9A5A668000002423A09CB4BB060025259C9D8A7900001C1FCAB2C7C700002A2A9387ABA200002626A4A47D6E9D14333163A0C87500004B6F9C2D643A257049364936493647358A34438355497F1A0000A24C1D590000D38DFFBDD4CD3126'
|
||||
)
|
||||
|
|
|
@ -7,22 +7,22 @@ import * as nls from 'vs/nls';
|
|||
|
||||
export namespace AccessibilityHelpNLS {
|
||||
export const accessibilityHelpTitle = nls.localize('accessibilityHelpTitle', "Accessibility Help");
|
||||
export const openingDocs = nls.localize("openingDocs", "Now opening the Accessibility documentation page.");
|
||||
export const openingDocs = nls.localize("openingDocs", "Opening the Accessibility documentation page.");
|
||||
export const readonlyDiffEditor = nls.localize("readonlyDiffEditor", "You are in a read-only pane of a diff editor.");
|
||||
export const editableDiffEditor = nls.localize("editableDiffEditor", "You are in a pane of a diff editor.");
|
||||
export const readonlyEditor = nls.localize("readonlyEditor", "You are in a read-only code editor.");
|
||||
export const editableEditor = nls.localize("editableEditor", "You are in a code editor.");
|
||||
export const changeConfigToOnMac = nls.localize("changeConfigToOnMac", "To configure the application to be optimized for usage with a Screen Reader press Command+E now.");
|
||||
export const changeConfigToOnWinLinux = nls.localize("changeConfigToOnWinLinux", "To configure the application to be optimized for usage with a Screen Reader press Control+E now.");
|
||||
export const changeConfigToOnMac = nls.localize("changeConfigToOnMac", "Configure the application to be optimized for usage with a Screen Reader (Command+E).");
|
||||
export const changeConfigToOnWinLinux = nls.localize("changeConfigToOnWinLinux", "Configure the application to be optimized for usage with a Screen Reader (Control+E).");
|
||||
export const auto_on = nls.localize("auto_on", "The application is configured to be optimized for usage with a Screen Reader.");
|
||||
export const auto_off = nls.localize("auto_off", "The application is configured to never be optimized for usage with a Screen Reader.");
|
||||
export const screenReaderModeEnabled = nls.localize("screenReaderModeEnabled", "Screen Reader Optimized Mode enabled.");
|
||||
export const screenReaderModeDisabled = nls.localize("screenReaderModeDisabled", "Screen Reader Optimized Mode disabled.");
|
||||
export const tabFocusModeOnMsg = nls.localize("tabFocusModeOnMsg", "Pressing Tab in the current editor will move focus to the next focusable element. Toggle this behavior by pressing {0}.");
|
||||
export const tabFocusModeOnMsg = nls.localize("tabFocusModeOnMsg", "Pressing Tab in the current editor will move focus to the next focusable element. Toggle this behavior {0}.");
|
||||
export const tabFocusModeOnMsgNoKb = nls.localize("tabFocusModeOnMsgNoKb", "Pressing Tab in the current editor will move focus to the next focusable element. The command {0} is currently not triggerable by a keybinding.");
|
||||
export const stickScrollKb = nls.localize("stickScrollKb", "Run the command: Focus Sticky Scroll ({0}) to focus the currently nested scopes.");
|
||||
export const stickScrollNoKb = nls.localize("stickScrollNoKb", "Run the command: Focus Sticky Scroll to focus the currently nested scopes. It is currently not triggerable by a keybinding.");
|
||||
export const tabFocusModeOffMsg = nls.localize("tabFocusModeOffMsg", "Pressing Tab in the current editor will insert the tab character. Toggle this behavior by pressing {0}.");
|
||||
export const tabFocusModeOffMsg = nls.localize("tabFocusModeOffMsg", "Pressing Tab in the current editor will insert the tab character. Toggle this behavior {0}.");
|
||||
export const tabFocusModeOffMsgNoKb = nls.localize("tabFocusModeOffMsgNoKb", "Pressing Tab in the current editor will insert the tab character. The command {0} is currently not triggerable by a keybinding.");
|
||||
export const showAccessibilityHelpAction = nls.localize("showAccessibilityHelpAction", "Show Accessibility Help");
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { runWhenIdle } from 'vs/base/common/async';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { LRUCache } from 'vs/base/common/map';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
|
@ -60,7 +60,7 @@ export class CodeLensCache implements ICodeLensCache {
|
|||
this._deserialize(raw);
|
||||
|
||||
// store lens data on shutdown
|
||||
once(storageService.onWillSaveState)(e => {
|
||||
Event.once(storageService.onWillSaveState)(e => {
|
||||
if (e.reason === WillSaveStateReason.SHUTDOWN) {
|
||||
storageService.store(key, this._serialize(), StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { createSingleCallFunction } from 'vs/base/common/functional';
|
||||
import { DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { getCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
|
@ -105,7 +105,7 @@ export abstract class AbstractEditorNavigationQuickAccessProvider implements IQu
|
|||
}
|
||||
};
|
||||
|
||||
disposables.add(once(token.onCancellationRequested)(() => context.restoreViewState?.()));
|
||||
disposables.add(createSingleCallFunction(token.onCancellationRequested)(() => context.restoreViewState?.()));
|
||||
}
|
||||
|
||||
// Clean up decorations on dispose
|
||||
|
|
|
@ -16,7 +16,7 @@ import { EditorScopedLayoutService } from 'vs/editor/standalone/browser/standalo
|
|||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { QuickInputController, IQuickInputControllerHost } from 'vs/platform/quickinput/browser/quickInputController';
|
||||
import { QuickInputService } from 'vs/platform/quickinput/browser/quickInputService';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { createSingleCallFunction } from 'vs/base/common/functional';
|
||||
import { IQuickAccessController } from 'vs/platform/quickinput/common/quickAccess';
|
||||
|
||||
class EditorScopedQuickInputService extends QuickInputService {
|
||||
|
@ -73,7 +73,7 @@ export class StandaloneQuickInputService implements IQuickInputService {
|
|||
const newQuickInputService = quickInputService = this.instantiationService.createInstance(EditorScopedQuickInputService, editor);
|
||||
this.mapEditorToService.set(editor, quickInputService);
|
||||
|
||||
once(editor.onDidDispose)(() => {
|
||||
createSingleCallFunction(editor.onDidDispose)(() => {
|
||||
newQuickInputService.dispose();
|
||||
this.mapEditorToService.delete(editor);
|
||||
});
|
||||
|
|
|
@ -8,8 +8,10 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
|||
import { IToolBarOptions, ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
|
||||
import { IAction, Separator, SubmenuAction, toAction, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions';
|
||||
import { coalesceInPlace } from 'vs/base/common/arrays';
|
||||
import { intersection } from 'vs/base/common/collections';
|
||||
import { BugIndicatingError } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { localize } from 'vs/nls';
|
||||
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
|
@ -63,9 +65,11 @@ export type IWorkbenchToolBarOptions = IToolBarOptions & {
|
|||
allowContextMenu?: never;
|
||||
|
||||
/**
|
||||
* Maximun number of items that can shown. Extra items will be shown in the overflow menu.
|
||||
* Controls the overflow behavior of the primary group of toolbar. This isthe maximum number of items and id of
|
||||
* items that should never overflow
|
||||
*
|
||||
*/
|
||||
maxNumberOfItems?: number;
|
||||
overflowBehavior?: { maxItems: number; exempted?: string[] };
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -150,14 +154,22 @@ export class WorkbenchToolBar extends ToolBar {
|
|||
}
|
||||
|
||||
// count for max
|
||||
if (this._options?.maxNumberOfItems !== undefined) {
|
||||
if (this._options?.overflowBehavior !== undefined) {
|
||||
|
||||
const exemptedIds = intersection(new Set(this._options.overflowBehavior.exempted), Iterable.map(primary, a => a.id));
|
||||
const maxItems = this._options.overflowBehavior.maxItems - exemptedIds.size;
|
||||
|
||||
let count = 0;
|
||||
for (let i = 0; i < primary.length; i++) {
|
||||
const action = primary[i];
|
||||
if (!action) {
|
||||
continue;
|
||||
}
|
||||
if (++count >= this._options.maxNumberOfItems) {
|
||||
count++;
|
||||
if (exemptedIds.has(action.id)) {
|
||||
continue;
|
||||
}
|
||||
if (count >= maxItems) {
|
||||
primary[i] = undefined!;
|
||||
extraSecondary[i] = action;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IConfigBasedExtensionTip as IRawConfigBasedExtensionTip } from 'vs/base/common/product';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
@ -154,11 +154,11 @@ export abstract class AbstractNativeExtensionTipsService extends ExtensionTipsSe
|
|||
3s has come out to be the good number to fetch and prompt important exe based recommendations
|
||||
Also fetch important exe based recommendations for reporting telemetry
|
||||
*/
|
||||
this._register(disposableTimeout(async () => {
|
||||
disposableTimeout(async () => {
|
||||
await this.collectTips();
|
||||
this.promptHighImportanceExeBasedTip();
|
||||
this.promptMediumImportanceExeBasedTip();
|
||||
}, 3000));
|
||||
}, 3000, this._store);
|
||||
}
|
||||
|
||||
override async getImportantExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]> {
|
||||
|
@ -243,7 +243,8 @@ export abstract class AbstractNativeExtensionTipsService extends ExtensionTipsSe
|
|||
}
|
||||
case RecommendationsNotificationResult.TooMany: {
|
||||
// Too many notifications. Schedule the prompt after one hour
|
||||
const disposable = this._register(disposableTimeout(() => { disposable.dispose(); this.promptHighImportanceExeBasedTip(); }, 60 * 60 * 1000 /* 1 hour */));
|
||||
const disposable = this._register(new MutableDisposable());
|
||||
disposable.value = disposableTimeout(() => { disposable.dispose(); this.promptHighImportanceExeBasedTip(); }, 60 * 60 * 1000 /* 1 hour */);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -263,7 +264,8 @@ export abstract class AbstractNativeExtensionTipsService extends ExtensionTipsSe
|
|||
const promptInterval = 7 * 24 * 60 * 60 * 1000; // 7 Days
|
||||
if (timeSinceLastPrompt < promptInterval) {
|
||||
// Wait until interval and prompt
|
||||
const disposable = this._register(disposableTimeout(() => { disposable.dispose(); this.promptMediumImportanceExeBasedTip(); }, promptInterval - timeSinceLastPrompt));
|
||||
const disposable = this._register(new MutableDisposable());
|
||||
disposable.value = disposableTimeout(() => { disposable.dispose(); this.promptMediumImportanceExeBasedTip(); }, promptInterval - timeSinceLastPrompt);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -278,7 +280,8 @@ export abstract class AbstractNativeExtensionTipsService extends ExtensionTipsSe
|
|||
this.addToRecommendedExecutables(tips[0].exeName, tips);
|
||||
|
||||
// Schedule the next recommendation for next internval
|
||||
const disposable1 = this._register(disposableTimeout(() => { disposable1.dispose(); this.promptMediumImportanceExeBasedTip(); }, promptInterval));
|
||||
const disposable1 = this._register(new MutableDisposable());
|
||||
disposable1.value = disposableTimeout(() => { disposable1.dispose(); this.promptMediumImportanceExeBasedTip(); }, promptInterval);
|
||||
break;
|
||||
}
|
||||
case RecommendationsNotificationResult.Ignored:
|
||||
|
@ -295,7 +298,8 @@ export abstract class AbstractNativeExtensionTipsService extends ExtensionTipsSe
|
|||
}
|
||||
case RecommendationsNotificationResult.TooMany: {
|
||||
// Too many notifications. Schedule the prompt after one hour
|
||||
const disposable2 = this._register(disposableTimeout(() => { disposable2.dispose(); this.promptMediumImportanceExeBasedTip(); }, 60 * 60 * 1000 /* 1 hour */));
|
||||
const disposable2 = this._register(new MutableDisposable());
|
||||
disposable2.value = disposableTimeout(() => { disposable2.dispose(); this.promptMediumImportanceExeBasedTip(); }, 60 * 60 * 1000 /* 1 hour */);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
|
|||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { isCancellationError } from 'vs/base/common/errors';
|
||||
import { matchesContiguousSubString, matchesPrefix, matchesWords, or } from 'vs/base/common/filters';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { createSingleCallFunction } from 'vs/base/common/functional';
|
||||
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { LRUCache } from 'vs/base/common/map';
|
||||
import { TfIdfCalculator, normalizeTfIdfScores } from 'vs/base/common/tfIdf';
|
||||
|
@ -71,7 +71,7 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc
|
|||
return [];
|
||||
}
|
||||
|
||||
const runTfidf = once(() => {
|
||||
const runTfidf = createSingleCallFunction(() => {
|
||||
const tfidf = new TfIdfCalculator();
|
||||
tfidf.updateDocuments(allCommandPicks.map(commandPick => ({
|
||||
key: commandPick.commandId,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { DeferredPromise } from 'vs/base/common/async';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { DefaultQuickAccessFilterValue, Extensions, IQuickAccessController, IQuickAccessOptions, IQuickAccessProvider, IQuickAccessProviderDescriptor, IQuickAccessProviderRunOptions, IQuickAccessRegistry } from 'vs/platform/quickinput/common/quickAccess';
|
||||
|
@ -112,7 +112,7 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon
|
|||
let pickPromise: DeferredPromise<IQuickPickItem[]> | undefined = undefined;
|
||||
if (pick) {
|
||||
pickPromise = new DeferredPromise<IQuickPickItem[]>();
|
||||
disposables.add(once(picker.onWillAccept)(e => {
|
||||
disposables.add(Event.once(picker.onWillAccept)(e => {
|
||||
e.veto();
|
||||
picker.hide();
|
||||
}));
|
||||
|
@ -131,7 +131,7 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon
|
|||
|
||||
// Finally, trigger disposal and cancellation when the picker
|
||||
// hides depending on items selected or not.
|
||||
once(picker.onDidHide)(() => {
|
||||
Event.once(picker.onDidHide)(() => {
|
||||
if (picker.selectedItems.length === 0) {
|
||||
cts.cancel();
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IStorage } from 'vs/base/parts/storage/common/storage';
|
||||
|
@ -171,7 +170,7 @@ export class StorageMainService extends Disposable implements IStorageMainServic
|
|||
|
||||
const applicationStorage = new ApplicationStorageMain(this.getStorageOptions(), this.userDataProfilesService, this.logService, this.fileService);
|
||||
|
||||
this._register(once(applicationStorage.onDidCloseStorage)(() => {
|
||||
this._register(Event.once(applicationStorage.onDidCloseStorage)(() => {
|
||||
this.logService.trace(`StorageMainService: closed application storage`);
|
||||
}));
|
||||
|
||||
|
@ -202,7 +201,7 @@ export class StorageMainService extends Disposable implements IStorageMainServic
|
|||
profile
|
||||
})));
|
||||
|
||||
this._register(once(profileStorage.onDidCloseStorage)(() => {
|
||||
this._register(Event.once(profileStorage.onDidCloseStorage)(() => {
|
||||
this.logService.trace(`StorageMainService: closed profile storage (${profile.name})`);
|
||||
|
||||
this.mapProfileToStorage.delete(profile.id);
|
||||
|
@ -241,7 +240,7 @@ export class StorageMainService extends Disposable implements IStorageMainServic
|
|||
workspaceStorage = this._register(this.createWorkspaceStorage(workspace));
|
||||
this.mapWorkspaceToStorage.set(workspace.id, workspaceStorage);
|
||||
|
||||
this._register(once(workspaceStorage.onDidCloseStorage)(() => {
|
||||
this._register(Event.once(workspaceStorage.onDidCloseStorage)(() => {
|
||||
this.logService.trace(`StorageMainService: closed workspace storage (${workspace.id})`);
|
||||
|
||||
this.mapWorkspaceToStorage.delete(workspace.id);
|
||||
|
|
|
@ -85,7 +85,7 @@ export class ElectronURLListener extends Disposable {
|
|||
} else {
|
||||
logService.trace('ElectronURLListener: waiting for window to be ready to handle URLs...');
|
||||
|
||||
this._register(Event.once(windowsMainService.onDidSignalReadyWindow)(this.flush));
|
||||
this._register(Event.once(windowsMainService.onDidSignalReadyWindow)(() => this.flush()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -424,7 +424,10 @@ class AutoSync extends Disposable {
|
|||
}
|
||||
|
||||
private waitUntilNextIntervalAndSync(): void {
|
||||
this.intervalHandler.value = disposableTimeout(() => this.sync(AutoSync.INTERVAL_SYNCING, false), this.interval);
|
||||
this.intervalHandler.value = disposableTimeout(() => {
|
||||
this.sync(AutoSync.INTERVAL_SYNCING, false);
|
||||
this.intervalHandler.value = undefined;
|
||||
}, this.interval);
|
||||
}
|
||||
|
||||
sync(reason: string, disableCache: boolean): Promise<void> {
|
||||
|
|
|
@ -12,7 +12,6 @@ import { CancellationToken } from 'vs/base/common/cancellation';
|
|||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { isWindowsDriveLetter, parseLineAndColumnAware, sanitizeFilePath, toSlashes } from 'vs/base/common/extpath';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { getPathLabel } from 'vs/base/common/labels';
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
@ -1464,9 +1463,9 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
|
|||
this._onDidChangeWindowsCount.fire({ oldCount: this.getWindowCount() - 1, newCount: this.getWindowCount() });
|
||||
|
||||
// Window Events
|
||||
once(createdWindow.onDidSignalReady)(() => this._onDidSignalReadyWindow.fire(createdWindow));
|
||||
once(createdWindow.onDidClose)(() => this.onWindowClosed(createdWindow));
|
||||
once(createdWindow.onDidDestroy)(() => this._onDidDestroyWindow.fire(createdWindow));
|
||||
Event.once(createdWindow.onDidSignalReady)(() => this._onDidSignalReadyWindow.fire(createdWindow));
|
||||
Event.once(createdWindow.onDidClose)(() => this.onWindowClosed(createdWindow));
|
||||
Event.once(createdWindow.onDidDestroy)(() => this._onDidDestroyWindow.fire(createdWindow));
|
||||
createdWindow.onDidTriggerSystemContextMenu(({ x, y }) => this._onDidTriggerSystemContextMenu.fire({ window: createdWindow, x, y }));
|
||||
|
||||
const webContents = assertIsDefined(createdWindow.win?.webContents);
|
||||
|
|
|
@ -10,7 +10,7 @@ import { RunOnceScheduler } from 'vs/base/common/async';
|
|||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { createSingleCallFunction } from 'vs/base/common/functional';
|
||||
import { hash } from 'vs/base/common/hash';
|
||||
import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { MarshalledId } from 'vs/base/common/marshallingIds';
|
||||
|
@ -979,7 +979,7 @@ class TestObservers {
|
|||
return {
|
||||
onDidChangeTest: current.tests.onDidChangeTests,
|
||||
get tests() { return [...current.tests.rootTests].map(t => t.revived); },
|
||||
dispose: once(() => {
|
||||
dispose: createSingleCallFunction(() => {
|
||||
if (--current.observers === 0) {
|
||||
this.proxy.$unsubscribeFromDiffs();
|
||||
this.current = undefined;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import { asArray, coalesce, isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { VSBuffer, encodeBase64 } from 'vs/base/common/buffer';
|
||||
import { IDataTransferFile, IDataTransferItem, UriList } from 'vs/base/common/dataTransfer';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { createSingleCallFunction } from 'vs/base/common/functional';
|
||||
import * as htmlContent from 'vs/base/common/htmlContent';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { ResourceMap, ResourceSet } from 'vs/base/common/map';
|
||||
|
@ -2076,7 +2076,7 @@ export namespace DataTransferItem {
|
|||
const file = item.fileData;
|
||||
if (file) {
|
||||
return new types.InternalFileDataTransferItem(
|
||||
new types.DataTransferFile(file.name, URI.revive(file.uri), file.id, once(() => resolveFileData(file.id))));
|
||||
new types.DataTransferFile(file.name, URI.revive(file.uri), file.id, createSingleCallFunction(() => resolveFileData(file.id))));
|
||||
}
|
||||
|
||||
if (mime === Mimes.uriList && item.uriListData) {
|
||||
|
|
|
@ -659,6 +659,8 @@ export class ResourceListDnDHandler<T> implements IListDragAndDrop<T> {
|
|||
}
|
||||
|
||||
drop(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): void { }
|
||||
|
||||
dispose(): void { }
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
|
|
@ -351,7 +351,7 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
|
|||
if (account.canSignOut) {
|
||||
const signOutAction = disposables.add(new Action('signOut', localize('signOut', "Sign Out"), undefined, true, async () => {
|
||||
const allSessions = await this.authenticationService.getSessions(providerId);
|
||||
const sessionsForAccount = allSessions.filter(s => s.account.id === account.id);
|
||||
const sessionsForAccount = allSessions.filter(s => s.account.label === account.label);
|
||||
return await this.authenticationService.removeAccountSessions(providerId, account.label, sessionsForAccount);
|
||||
}));
|
||||
providerSubMenuActions.push(signOutAction);
|
||||
|
@ -399,34 +399,35 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
|
|||
|
||||
private async addOrUpdateAccount(providerId: string, account: AuthenticationSessionAccount): Promise<void> {
|
||||
let accounts = this.groupedAccounts.get(providerId);
|
||||
if (accounts) {
|
||||
const existingAccount = accounts.find(a => a.id === account.id);
|
||||
if (existingAccount) {
|
||||
// Update the label if it has changed
|
||||
if (existingAccount.label !== account.label) {
|
||||
existingAccount.label = account.label;
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!accounts) {
|
||||
accounts = [];
|
||||
this.groupedAccounts.set(providerId, accounts);
|
||||
}
|
||||
|
||||
const sessionFromEmbedder = await this.sessionFromEmbedder.value;
|
||||
// If the session stored from the embedder allows sign out, then we can treat it and all others as sign out-able
|
||||
let canSignOut = !!sessionFromEmbedder?.canSignOut;
|
||||
if (!canSignOut) {
|
||||
if (sessionFromEmbedder?.id) {
|
||||
const sessions = (await this.authenticationService.getSessions(providerId)).filter(s => s.account.id === account.id);
|
||||
canSignOut = !sessions.some(s => s.id === sessionFromEmbedder.id);
|
||||
} else {
|
||||
// The default if we don't have a session from the embedder is to allow sign out
|
||||
canSignOut = true;
|
||||
}
|
||||
let canSignOut = true;
|
||||
if (
|
||||
sessionFromEmbedder // if we have a session from the embedder
|
||||
&& !sessionFromEmbedder.canSignOut // and that session says we can't sign out
|
||||
&& (await this.authenticationService.getSessions(providerId)) // and that session is associated with the account we are adding/updating
|
||||
.some(s =>
|
||||
s.id === sessionFromEmbedder.id
|
||||
&& s.account.id === account.id
|
||||
)
|
||||
) {
|
||||
canSignOut = false;
|
||||
}
|
||||
|
||||
accounts.push({ ...account, canSignOut });
|
||||
const existingAccount = accounts.find(a => a.label === account.label);
|
||||
if (existingAccount) {
|
||||
// if we have an existing account and we discover that we
|
||||
// can't sign out of it, update the account to mark it as "can't sign out"
|
||||
if (!canSignOut) {
|
||||
existingAccount.canSignOut = canSignOut;
|
||||
}
|
||||
} else {
|
||||
accounts.push({ ...account, canSignOut });
|
||||
}
|
||||
}
|
||||
|
||||
private removeAccount(providerId: string, account: AuthenticationSessionAccount): void {
|
||||
|
|
|
@ -47,7 +47,7 @@ import {
|
|||
CLOSE_EDITORS_AND_GROUP_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_EDITOR_GROUP_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID,
|
||||
CLOSE_PINNED_EDITOR_COMMAND_ID, CLOSE_SAVED_EDITORS_COMMAND_ID, GOTO_NEXT_CHANGE, GOTO_PREVIOUS_CHANGE, KEEP_EDITOR_COMMAND_ID, PIN_EDITOR_COMMAND_ID, SHOW_EDITORS_IN_GROUP, SPLIT_EDITOR_DOWN, SPLIT_EDITOR_LEFT,
|
||||
SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, TOGGLE_DIFF_SIDE_BY_SIDE, TOGGLE_KEEP_EDITORS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, setup as registerEditorCommands, REOPEN_WITH_COMMAND_ID,
|
||||
TOGGLE_LOCK_GROUP_COMMAND_ID, UNLOCK_GROUP_COMMAND_ID, SPLIT_EDITOR_IN_GROUP, JOIN_EDITOR_IN_GROUP, FOCUS_FIRST_SIDE_EDITOR, FOCUS_SECOND_SIDE_EDITOR, TOGGLE_SPLIT_EDITOR_IN_GROUP_LAYOUT
|
||||
TOGGLE_LOCK_GROUP_COMMAND_ID, UNLOCK_GROUP_COMMAND_ID, SPLIT_EDITOR_IN_GROUP, JOIN_EDITOR_IN_GROUP, FOCUS_FIRST_SIDE_EDITOR, FOCUS_SECOND_SIDE_EDITOR, TOGGLE_SPLIT_EDITOR_IN_GROUP_LAYOUT, SPLIT_EDITOR
|
||||
} from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { inQuickPickContext, getQuickNavigateHandler } from 'vs/workbench/browser/quickaccess';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
|
@ -411,7 +411,7 @@ const CLOSE_ORDER = 1000000; // towards the far end
|
|||
// Editor Title Menu: Split Editor
|
||||
appendEditorToolItem(
|
||||
{
|
||||
id: SplitEditorAction.ID,
|
||||
id: SPLIT_EDITOR,
|
||||
title: localize('splitEditorRight', "Split Editor Right"),
|
||||
icon: Codicon.splitHorizontal
|
||||
},
|
||||
|
@ -426,7 +426,7 @@ appendEditorToolItem(
|
|||
|
||||
appendEditorToolItem(
|
||||
{
|
||||
id: SplitEditorAction.ID,
|
||||
id: SPLIT_EDITOR,
|
||||
title: localize('splitEditorDown', "Split Editor Down"),
|
||||
icon: Codicon.splitVertical
|
||||
},
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { GroupIdentifier, IWorkbenchEditorConfiguration, IEditorIdentifier, IEditorCloseEvent, IEditorPartOptions, IEditorPartOptionsChangeEvent, SideBySideEditor, EditorCloseContext } from 'vs/workbench/common/editor';
|
||||
import { GroupIdentifier, IWorkbenchEditorConfiguration, IEditorIdentifier, IEditorCloseEvent, IEditorPartOptions, IEditorPartOptionsChangeEvent, SideBySideEditor, EditorCloseContext, IEditorPane } from 'vs/workbench/common/editor';
|
||||
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
|
||||
import { IEditorGroup, GroupDirection, IMergeGroupOptions, GroupsOrder, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
@ -82,7 +82,7 @@ export function getEditorPartOptions(configurationService: IConfigurationService
|
|||
return options;
|
||||
}
|
||||
|
||||
export interface IEditorGroupsAccessor {
|
||||
export interface IEditorGroupsView {
|
||||
|
||||
readonly groups: IEditorGroupView[];
|
||||
readonly activeGroup: IEditorGroupView;
|
||||
|
@ -148,6 +148,8 @@ export interface IEditorGroupView extends IDisposable, ISerializableView, IEdito
|
|||
|
||||
notifyIndexChanged(newIndex: number): void;
|
||||
|
||||
openEditor(editor: EditorInput, options?: IEditorOptions, internalOptions?: IInternalEditorOpenOptions): Promise<IEditorPane | undefined>;
|
||||
|
||||
relayout(): void;
|
||||
}
|
||||
|
||||
|
@ -200,6 +202,11 @@ export interface IInternalEditorOpenOptions extends IInternalEditorTitleControlO
|
|||
* opened in one of the sides.
|
||||
*/
|
||||
supportSideBySide?: SideBySideEditor.ANY | SideBySideEditor.BOTH;
|
||||
|
||||
/**
|
||||
* When set to `true`, pass DOM focus into the tab control.
|
||||
*/
|
||||
focusTabControl?: boolean;
|
||||
}
|
||||
|
||||
export interface IInternalEditorCloseOptions extends IInternalEditorTitleControlOptions {
|
||||
|
|
|
@ -13,7 +13,7 @@ import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/bro
|
|||
import { GoFilter, IHistoryService } from 'vs/workbench/services/history/common/history';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveCopyArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, COPY_ACTIVE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveCopyArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, COPY_ACTIVE_EDITOR_COMMAND_ID, SPLIT_EDITOR } from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { IEditorGroupsService, IEditorGroup, GroupsArrangement, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
@ -68,7 +68,7 @@ abstract class AbstractSplitEditorAction extends Action2 {
|
|||
|
||||
export class SplitEditorAction extends AbstractSplitEditorAction {
|
||||
|
||||
static readonly ID = 'workbench.action.splitEditor';
|
||||
static readonly ID = SPLIT_EDITOR;
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
|
|
|
@ -73,6 +73,7 @@ export const DIFF_FOCUS_OTHER_SIDE = 'workbench.action.compareEditor.focusOtherS
|
|||
export const DIFF_OPEN_SIDE = 'workbench.action.compareEditor.openSide';
|
||||
export const TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE = 'toggle.diff.ignoreTrimWhitespace';
|
||||
|
||||
export const SPLIT_EDITOR = 'workbench.action.splitEditor';
|
||||
export const SPLIT_EDITOR_UP = 'workbench.action.splitEditorUp';
|
||||
export const SPLIT_EDITOR_DOWN = 'workbench.action.splitEditorDown';
|
||||
export const SPLIT_EDITOR_LEFT = 'workbench.action.splitEditorLeft';
|
||||
|
@ -98,6 +99,13 @@ export const API_OPEN_EDITOR_COMMAND_ID = '_workbench.open';
|
|||
export const API_OPEN_DIFF_EDITOR_COMMAND_ID = '_workbench.diff';
|
||||
export const API_OPEN_WITH_EDITOR_COMMAND_ID = '_workbench.openWith';
|
||||
|
||||
export const EDITOR_CORE_NAVIGATION_COMMANDS = [
|
||||
SPLIT_EDITOR,
|
||||
CLOSE_EDITOR_COMMAND_ID,
|
||||
UNPIN_EDITOR_COMMAND_ID,
|
||||
UNLOCK_GROUP_COMMAND_ID
|
||||
];
|
||||
|
||||
export interface ActiveEditorMoveCopyArguments {
|
||||
to: 'first' | 'last' | 'left' | 'right' | 'up' | 'down' | 'center' | 'position' | 'previous' | 'next';
|
||||
by: 'tab' | 'group';
|
||||
|
|
|
@ -20,7 +20,7 @@ import { IThemeService, Themable } from 'vs/platform/theme/common/themeService';
|
|||
import { isTemporaryWorkspace, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { CodeDataTransfers, containsDragType, Extensions as DragAndDropExtensions, IDragAndDropContributionRegistry, LocalSelectionTransfer } from 'vs/platform/dnd/browser/dnd';
|
||||
import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, extractTreeDropData, ResourcesDropHandler } from 'vs/workbench/browser/dnd';
|
||||
import { fillActiveEditorViewState, IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { fillActiveEditorViewState, IEditorGroupsView, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { EditorInputCapabilities, IEditorIdentifier, IUntypedEditorInput } from 'vs/workbench/common/editor';
|
||||
import { EDITOR_DRAG_AND_DROP_BACKGROUND, EDITOR_DROP_INTO_PROMPT_BACKGROUND, EDITOR_DROP_INTO_PROMPT_BORDER, EDITOR_DROP_INTO_PROMPT_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { GroupDirection, IEditorGroupsService, IMergeGroupOptions, MergeGroupMode } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
|
@ -62,7 +62,7 @@ class DropOverlay extends Themable {
|
|||
private readonly enableDropIntoEditor: boolean;
|
||||
|
||||
constructor(
|
||||
private accessor: IEditorGroupsAccessor,
|
||||
private groupsView: IEditorGroupsView,
|
||||
private groupView: IEditorGroupView,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
|
@ -237,7 +237,7 @@ class DropOverlay extends Themable {
|
|||
if (this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype)) {
|
||||
const data = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype);
|
||||
if (Array.isArray(data)) {
|
||||
return this.accessor.getGroup(data[0].identifier);
|
||||
return this.groupsView.getGroup(data[0].identifier);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -245,7 +245,7 @@ class DropOverlay extends Themable {
|
|||
else if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) {
|
||||
const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype);
|
||||
if (Array.isArray(data)) {
|
||||
return this.accessor.getGroup(data[0].identifier.groupId);
|
||||
return this.groupsView.getGroup(data[0].identifier.groupId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -258,7 +258,7 @@ class DropOverlay extends Themable {
|
|||
const ensureTargetGroup = () => {
|
||||
let targetGroup: IEditorGroupView;
|
||||
if (typeof splitDirection === 'number') {
|
||||
targetGroup = this.accessor.addGroup(this.groupView, splitDirection);
|
||||
targetGroup = this.groupsView.addGroup(this.groupView, splitDirection);
|
||||
} else {
|
||||
targetGroup = this.groupView;
|
||||
}
|
||||
|
@ -270,7 +270,7 @@ class DropOverlay extends Themable {
|
|||
if (this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype)) {
|
||||
const data = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype);
|
||||
if (Array.isArray(data)) {
|
||||
const sourceGroup = this.accessor.getGroup(data[0].identifier);
|
||||
const sourceGroup = this.groupsView.getGroup(data[0].identifier);
|
||||
if (sourceGroup) {
|
||||
if (typeof splitDirection !== 'number' && sourceGroup === this.groupView) {
|
||||
return;
|
||||
|
@ -280,9 +280,9 @@ class DropOverlay extends Themable {
|
|||
let targetGroup: IEditorGroupView | undefined;
|
||||
if (typeof splitDirection === 'number') {
|
||||
if (this.isCopyOperation(event)) {
|
||||
targetGroup = this.accessor.copyGroup(sourceGroup, this.groupView, splitDirection);
|
||||
targetGroup = this.groupsView.copyGroup(sourceGroup, this.groupView, splitDirection);
|
||||
} else {
|
||||
targetGroup = this.accessor.moveGroup(sourceGroup, this.groupView, splitDirection);
|
||||
targetGroup = this.groupsView.moveGroup(sourceGroup, this.groupView, splitDirection);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -293,11 +293,11 @@ class DropOverlay extends Themable {
|
|||
mergeGroupOptions = { mode: MergeGroupMode.COPY_EDITORS };
|
||||
}
|
||||
|
||||
this.accessor.mergeGroup(sourceGroup, this.groupView, mergeGroupOptions);
|
||||
this.groupsView.mergeGroup(sourceGroup, this.groupView, mergeGroupOptions);
|
||||
}
|
||||
|
||||
if (targetGroup) {
|
||||
this.accessor.activateGroup(targetGroup);
|
||||
this.groupsView.activateGroup(targetGroup);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -311,7 +311,7 @@ class DropOverlay extends Themable {
|
|||
if (Array.isArray(data)) {
|
||||
const draggedEditor = data[0].identifier;
|
||||
|
||||
const sourceGroup = this.accessor.getGroup(draggedEditor.groupId);
|
||||
const sourceGroup = this.groupsView.getGroup(draggedEditor.groupId);
|
||||
if (sourceGroup) {
|
||||
const copyEditor = this.isCopyOperation(event, draggedEditor);
|
||||
let targetGroup: IEditorGroupView | undefined = undefined;
|
||||
|
@ -320,7 +320,7 @@ class DropOverlay extends Themable {
|
|||
// and we are configured to close empty editor groups, we can
|
||||
// rather move the entire editor group according to the direction
|
||||
if (this.editorGroupService.partOptions.closeEmptyGroups && sourceGroup.count === 1 && typeof splitDirection === 'number' && !copyEditor) {
|
||||
targetGroup = this.accessor.moveGroup(sourceGroup, this.groupView, splitDirection);
|
||||
targetGroup = this.groupsView.moveGroup(sourceGroup, this.groupView, splitDirection);
|
||||
}
|
||||
|
||||
// In any other case do a normal move/copy operation
|
||||
|
@ -391,7 +391,7 @@ class DropOverlay extends Themable {
|
|||
}
|
||||
|
||||
private positionOverlay(mousePosX: number, mousePosY: number, isDraggingGroup: boolean, enableSplitting: boolean): void {
|
||||
const preferSplitVertically = this.accessor.partOptions.openSideBySideDirection === 'right';
|
||||
const preferSplitVertically = this.groupsView.partOptions.openSideBySideDirection === 'right';
|
||||
|
||||
const editorControlWidth = this.groupView.element.clientWidth;
|
||||
const editorControlHeight = this.groupView.element.clientHeight - this.getOverlayOffsetHeight();
|
||||
|
@ -531,7 +531,7 @@ class DropOverlay extends Themable {
|
|||
private getOverlayOffsetHeight(): number {
|
||||
|
||||
// With tabs and opened editors: use the area below tabs as drop target
|
||||
if (!this.groupView.isEmpty && this.accessor.partOptions.showTabs) {
|
||||
if (!this.groupView.isEmpty && this.groupsView.partOptions.showTabs) {
|
||||
return this.groupView.titleHeight.offset;
|
||||
}
|
||||
|
||||
|
@ -587,7 +587,7 @@ export class EditorDropTarget extends Themable {
|
|||
private readonly groupTransfer = LocalSelectionTransfer.getInstance<DraggedEditorGroupIdentifier>();
|
||||
|
||||
constructor(
|
||||
private accessor: IEditorGroupsAccessor,
|
||||
private groupsView: IEditorGroupsView,
|
||||
private container: HTMLElement,
|
||||
private readonly delegate: IEditorDropTargetDelegate,
|
||||
@IThemeService themeService: IThemeService,
|
||||
|
@ -649,7 +649,7 @@ export class EditorDropTarget extends Themable {
|
|||
if (!this.overlay) {
|
||||
const targetGroupView = this.findTargetGroupView(target);
|
||||
if (targetGroupView) {
|
||||
this._overlay = this.instantiationService.createInstance(DropOverlay, this.accessor, targetGroupView);
|
||||
this._overlay = this.instantiationService.createInstance(DropOverlay, this.groupsView, targetGroupView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -671,7 +671,7 @@ export class EditorDropTarget extends Themable {
|
|||
}
|
||||
|
||||
private findTargetGroupView(child: HTMLElement): IEditorGroupView | undefined {
|
||||
const groups = this.accessor.groups;
|
||||
const groups = this.groupsView.groups;
|
||||
|
||||
return groups.find(groupView => isAncestor(child, groupView.element) || this.delegate.containsGroup?.(groupView));
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ import { MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
|||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { DeferredPromise, Promises, RunOnceWorker } from 'vs/base/common/async';
|
||||
import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch';
|
||||
import { IEditorGroupsAccessor, IEditorGroupView, fillActiveEditorViewState, EditorServiceImpl, IEditorGroupTitleHeight, IInternalEditorOpenOptions, IInternalMoveCopyOptions, IInternalEditorCloseOptions, IInternalEditorTitleControlOptions } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { IEditorGroupsView, IEditorGroupView, fillActiveEditorViewState, EditorServiceImpl, IEditorGroupTitleHeight, IInternalEditorOpenOptions, IInternalMoveCopyOptions, IInternalEditorCloseOptions, IInternalEditorTitleControlOptions } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
|
@ -58,16 +58,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
|
||||
//#region factory
|
||||
|
||||
static createNew(accessor: IEditorGroupsAccessor, index: number, instantiationService: IInstantiationService): IEditorGroupView {
|
||||
return instantiationService.createInstance(EditorGroupView, accessor, null, index);
|
||||
static createNew(groupsView: IEditorGroupsView, index: number, instantiationService: IInstantiationService): IEditorGroupView {
|
||||
return instantiationService.createInstance(EditorGroupView, groupsView, null, index);
|
||||
}
|
||||
|
||||
static createFromSerialized(serialized: ISerializedEditorGroupModel, accessor: IEditorGroupsAccessor, index: number, instantiationService: IInstantiationService): IEditorGroupView {
|
||||
return instantiationService.createInstance(EditorGroupView, accessor, serialized, index);
|
||||
static createFromSerialized(serialized: ISerializedEditorGroupModel, groupsView: IEditorGroupsView, index: number, instantiationService: IInstantiationService): IEditorGroupView {
|
||||
return instantiationService.createInstance(EditorGroupView, groupsView, serialized, index);
|
||||
}
|
||||
|
||||
static createCopy(copyFrom: IEditorGroupView, accessor: IEditorGroupsAccessor, index: number, instantiationService: IInstantiationService): IEditorGroupView {
|
||||
return instantiationService.createInstance(EditorGroupView, accessor, copyFrom, index);
|
||||
static createCopy(copyFrom: IEditorGroupView, groupsView: IEditorGroupsView, index: number, instantiationService: IInstantiationService): IEditorGroupView {
|
||||
return instantiationService.createInstance(EditorGroupView, groupsView, copyFrom, index);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
@ -133,7 +133,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
readonly whenRestored = this.whenRestoredPromise.p;
|
||||
|
||||
constructor(
|
||||
private accessor: IEditorGroupsAccessor,
|
||||
private groupsView: IEditorGroupsView,
|
||||
from: IEditorGroupView | ISerializedEditorGroupModel | null,
|
||||
private _index: number,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
|
@ -198,7 +198,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
this.element.appendChild(this.titleContainer);
|
||||
|
||||
// Title control
|
||||
this.titleControl = this._register(this.scopedInstantiationService.createInstance(EditorTitleControl, this.titleContainer, this.accessor, this, this.model));
|
||||
this.titleControl = this._register(this.scopedInstantiationService.createInstance(EditorTitleControl, this.titleContainer, this.groupsView, this, this.model));
|
||||
|
||||
// Editor container
|
||||
this.editorContainer = document.createElement('div');
|
||||
|
@ -318,7 +318,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
if (this.isEmpty && e.button === 1 /* Middle Button */) {
|
||||
EventHelper.stop(e, true);
|
||||
|
||||
this.accessor.removeGroup(this);
|
||||
this.groupsView.removeGroup(this);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
@ -452,8 +452,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
}
|
||||
|
||||
private updateTitleContainer(): void {
|
||||
this.titleContainer.classList.toggle('tabs', this.accessor.partOptions.showTabs);
|
||||
this.titleContainer.classList.toggle('show-file-icons', this.accessor.partOptions.showIcons);
|
||||
this.titleContainer.classList.toggle('tabs', this.groupsView.partOptions.showTabs);
|
||||
this.titleContainer.classList.toggle('show-file-icons', this.groupsView.partOptions.showIcons);
|
||||
}
|
||||
|
||||
private restoreEditors(from: IEditorGroupView | ISerializedEditorGroupModel | null): Promise<void> | undefined {
|
||||
|
@ -489,7 +489,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
// stolen accidentally on startup when the user already
|
||||
// clicked somewhere.
|
||||
|
||||
if (this.accessor.activeGroup === this && activeElement === document.activeElement) {
|
||||
if (this.groupsView.activeGroup === this && activeElement === document.activeElement) {
|
||||
this.focus();
|
||||
}
|
||||
});
|
||||
|
@ -503,10 +503,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
this._register(this.model.onDidModelChange(e => this.onDidGroupModelChange(e)));
|
||||
|
||||
// Option Changes
|
||||
this._register(this.accessor.onDidChangeEditorPartOptions(e => this.onDidChangeEditorPartOptions(e)));
|
||||
this._register(this.groupsView.onDidChangeEditorPartOptions(e => this.onDidChangeEditorPartOptions(e)));
|
||||
|
||||
// Visibility
|
||||
this._register(this.accessor.onDidVisibilityChange(e => this.onDidVisibilityChange(e)));
|
||||
this._register(this.groupsView.onDidVisibilityChange(e => this.onDidVisibilityChange(e)));
|
||||
}
|
||||
|
||||
private onDidGroupModelChange(e: IGroupModelChangeEvent): void {
|
||||
|
@ -600,7 +600,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
}
|
||||
|
||||
private canDispose(editor: EditorInput): boolean {
|
||||
for (const groupView of this.accessor.groups) {
|
||||
for (const groupView of this.groupsView.groups) {
|
||||
if (groupView instanceof EditorGroupView && groupView.model.contains(editor, {
|
||||
strictEquals: true, // only if this input is not shared across editor groups
|
||||
supportSideBySide: SideBySideEditor.ANY // include any side of an opened side by side editor
|
||||
|
@ -946,8 +946,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
|
||||
//#region openEditor()
|
||||
|
||||
async openEditor(editor: EditorInput, options?: IEditorOptions): Promise<IEditorPane | undefined> {
|
||||
async openEditor(editor: EditorInput, options?: IEditorOptions, internalOptions?: IInternalEditorOpenOptions): Promise<IEditorPane | undefined> {
|
||||
return this.doOpenEditor(editor, options, {
|
||||
// Appply given internal open options
|
||||
...internalOptions,
|
||||
// Allow to match on a side-by-side editor when same
|
||||
// editor is opened on both sides. In that case we
|
||||
// do not want to open a new editor but reuse that one.
|
||||
|
@ -969,7 +971,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
|
||||
// Determine options
|
||||
const pinned = options?.sticky
|
||||
|| !this.accessor.partOptions.enablePreview
|
||||
|| !this.groupsView.partOptions.enablePreview
|
||||
|| editor.isDirty()
|
||||
|| (options?.pinned ?? typeof options?.index === 'number' /* unless specified, prefer to pin when opening with index */)
|
||||
|| (typeof options?.index === 'number' && this.model.isSticky(options.index))
|
||||
|
@ -1030,10 +1032,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
if (
|
||||
isNew && // only if this editor was new for the group
|
||||
this.count === 1 && // only when this editor was the first editor in the group
|
||||
this.accessor.groups.length > 1 // only when there are more than one groups open
|
||||
this.groupsView.groups.length > 1 // only when there are more than one groups open
|
||||
) {
|
||||
// only when the editor identifier is configured as such
|
||||
if (openedEditor.editorId && this.accessor.partOptions.autoLockGroups?.has(openedEditor.editorId)) {
|
||||
if (openedEditor.editorId && this.groupsView.partOptions.autoLockGroups?.has(openedEditor.editorId)) {
|
||||
this.lock(true);
|
||||
}
|
||||
}
|
||||
|
@ -1043,9 +1045,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
|
||||
// Finally make sure the group is active or restored as instructed
|
||||
if (activateGroup) {
|
||||
this.accessor.activateGroup(this);
|
||||
this.groupsView.activateGroup(this);
|
||||
} else if (restoreGroup) {
|
||||
this.accessor.restoreGroup(this);
|
||||
this.groupsView.restoreGroup(this);
|
||||
}
|
||||
|
||||
return showEditorResult;
|
||||
|
@ -1090,7 +1092,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
// Show in title control after editor control because some actions depend on it
|
||||
// but respect the internal options in case title control updates should skip.
|
||||
if (!internalOptions?.skipTitleUpdate) {
|
||||
this.titleControl.openEditor(editor);
|
||||
this.titleControl.openEditor(editor, internalOptions);
|
||||
}
|
||||
|
||||
return openEditorPromise;
|
||||
|
@ -1322,7 +1324,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
return true;
|
||||
}
|
||||
|
||||
private doCloseEditor(editor: EditorInput, focusNext = (this.accessor.activeGroup === this), internalOptions?: IInternalEditorCloseOptions): void {
|
||||
private doCloseEditor(editor: EditorInput, focusNext = (this.groupsView.activeGroup === this), internalOptions?: IInternalEditorCloseOptions): void {
|
||||
|
||||
// Forward to title control unless skipped via internal options
|
||||
if (!internalOptions?.skipTitleUpdate) {
|
||||
|
@ -1345,7 +1347,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
}
|
||||
}
|
||||
|
||||
private doCloseActiveEditor(focusNext = (this.accessor.activeGroup === this), internalOptions?: IInternalEditorCloseOptions): void {
|
||||
private doCloseActiveEditor(focusNext = (this.groupsView.activeGroup === this), internalOptions?: IInternalEditorCloseOptions): void {
|
||||
const editorToClose = this.activeEditor;
|
||||
const restoreFocus = this.shouldRestoreFocus(this.element);
|
||||
|
||||
|
@ -1356,15 +1358,15 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
// optimization, this group (if active) would first trigger a active editor change
|
||||
// event because it became empty, only to then trigger another one when the next
|
||||
// group gets active.
|
||||
const closeEmptyGroup = this.accessor.partOptions.closeEmptyGroups;
|
||||
const closeEmptyGroup = this.groupsView.partOptions.closeEmptyGroups;
|
||||
if (closeEmptyGroup && this.active && this.count === 1) {
|
||||
const mostRecentlyActiveGroups = this.accessor.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE);
|
||||
const mostRecentlyActiveGroups = this.groupsView.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE);
|
||||
const nextActiveGroup = mostRecentlyActiveGroups[1]; // [0] will be the current one, so take [1]
|
||||
if (nextActiveGroup) {
|
||||
if (restoreFocus) {
|
||||
nextActiveGroup.focus();
|
||||
} else {
|
||||
this.accessor.activateGroup(nextActiveGroup);
|
||||
this.groupsView.activateGroup(nextActiveGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1380,7 +1382,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
const preserveFocus = !focusNext;
|
||||
|
||||
let activation: EditorActivation | undefined = undefined;
|
||||
if (preserveFocus && this.accessor.activeGroup !== this) {
|
||||
if (preserveFocus && this.groupsView.activeGroup !== this) {
|
||||
// If we are opening the next editor in an inactive group
|
||||
// without focussing it, ensure we preserve the editor
|
||||
// group sizes in case that group is minimized.
|
||||
|
@ -1420,7 +1422,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
|
||||
// Remove empty group if we should
|
||||
if (closeEmptyGroup) {
|
||||
this.accessor.removeGroup(this);
|
||||
this.groupsView.removeGroup(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1489,7 +1491,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
// The only exception is when the same editor is opened on both sides of a side
|
||||
// by side editor (https://github.com/microsoft/vscode/issues/138442)
|
||||
|
||||
if (this.accessor.groups.some(groupView => {
|
||||
if (this.groupsView.groups.some(groupView => {
|
||||
if (groupView === this) {
|
||||
return false; // skip (we already handled our group above)
|
||||
}
|
||||
|
@ -1705,8 +1707,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
// If the group is empty and the request is to close all editors, we still close
|
||||
// the editor group is the related setting to close empty groups is enabled for
|
||||
// a convenient way of removing empty editor groups for the user.
|
||||
if (this.accessor.partOptions.closeEmptyGroups) {
|
||||
this.accessor.removeGroup(this);
|
||||
if (this.groupsView.partOptions.closeEmptyGroups) {
|
||||
this.groupsView.removeGroup(this);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -1826,7 +1828,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
//#region Locking
|
||||
|
||||
get isLocked(): boolean {
|
||||
if (this.accessor.groups.length === 1) {
|
||||
if (this.groupsView.groups.length === 1) {
|
||||
// Special case: if only 1 group is opened, never report it as locked
|
||||
// to ensure editors can always open in the "default" editor group
|
||||
return false;
|
||||
|
@ -1836,7 +1838,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
}
|
||||
|
||||
lock(locked: boolean): void {
|
||||
if (this.accessor.groups.length === 1) {
|
||||
if (this.groupsView.groups.length === 1) {
|
||||
// Special case: if only 1 group is opened, never allow to lock
|
||||
// to ensure editors can always open in the "default" editor group
|
||||
locked = false;
|
||||
|
@ -1869,7 +1871,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
this.titleContainer.style.removeProperty('--title-border-bottom-color');
|
||||
}
|
||||
|
||||
const { showTabs } = this.accessor.partOptions;
|
||||
const { showTabs } = this.groupsView.partOptions;
|
||||
this.titleContainer.style.backgroundColor = this.getColor(showTabs ? EDITOR_GROUP_HEADER_TABS_BACKGROUND : EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND) || '';
|
||||
|
||||
// Editor container
|
||||
|
|
|
@ -14,7 +14,7 @@ import { IView, orthogonal, LayoutPriority, IViewSize, Direction, SerializableGr
|
|||
import { GroupIdentifier, EditorInputWithOptions, IEditorPartOptions, IEditorPartOptionsChangeEvent, GroupModelChangeKind } from 'vs/workbench/common/editor';
|
||||
import { EDITOR_GROUP_BORDER, EDITOR_PANE_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { distinct, coalesce, firstOrDefault } from 'vs/base/common/arrays';
|
||||
import { IEditorGroupsAccessor, IEditorGroupView, getEditorPartOptions, impactsEditorPartOptions, IEditorPartCreationOptions } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { IEditorGroupsView, IEditorGroupView, getEditorPartOptions, impactsEditorPartOptions, IEditorPartCreationOptions } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupView';
|
||||
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
|
@ -80,7 +80,7 @@ class GridWidgetView<T extends IView> implements IView {
|
|||
}
|
||||
}
|
||||
|
||||
export class EditorPart extends Part implements IEditorGroupsService, IEditorGroupsAccessor, IEditorDropService {
|
||||
export class EditorPart extends Part implements IEditorGroupsService, IEditorGroupsView, IEditorDropService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ import { listActiveSelectionBackground, listActiveSelectionForeground } from 'vs
|
|||
import { IThemeService, Themable } from 'vs/platform/theme/common/themeService';
|
||||
import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, fillEditorsDragData } from 'vs/workbench/browser/dnd';
|
||||
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
|
||||
import { IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { IEditorGroupsView, IEditorGroupView, IInternalEditorOpenOptions } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { IEditorCommandsContext, EditorResourceAccessor, IEditorPartOptions, SideBySideEditor, EditorsOrder, EditorInputCapabilities } from 'vs/workbench/common/editor';
|
||||
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
|
||||
import { ResourceContextKey, ActiveEditorPinnedContext, ActiveEditorStickyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, ActiveEditorLastInGroupContext, ActiveEditorFirstInGroupContext, ActiveEditorAvailableEditorIdsContext, applyAvailableEditorIds } from 'vs/workbench/common/contextkeys';
|
||||
|
@ -39,6 +39,7 @@ import { DraggedTreeItemsIdentifier } from 'vs/editor/common/services/treeViewsD
|
|||
import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService';
|
||||
import { IEditorTitleControlDimensions } from 'vs/workbench/browser/parts/editor/editorTitleControl';
|
||||
import { IReadonlyEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel';
|
||||
import { EDITOR_CORE_NAVIGATION_COMMANDS } from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
|
||||
export interface IToolbarActions {
|
||||
readonly primary: IAction[];
|
||||
|
@ -73,7 +74,7 @@ export class EditorCommandsContextActionRunner extends ActionRunner {
|
|||
|
||||
export interface IEditorTabsControl extends IDisposable {
|
||||
updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void;
|
||||
openEditor(editor: EditorInput): boolean;
|
||||
openEditor(editor: EditorInput, options?: IInternalEditorOpenOptions): boolean;
|
||||
openEditors(editors: EditorInput[]): boolean;
|
||||
beforeCloseEditor(editor: EditorInput): void;
|
||||
closeEditor(editor: EditorInput): void;
|
||||
|
@ -121,8 +122,8 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC
|
|||
|
||||
constructor(
|
||||
private parent: HTMLElement,
|
||||
protected accessor: IEditorGroupsAccessor,
|
||||
protected groupViewer: IEditorGroupView,
|
||||
protected groupsView: IEditorGroupsView,
|
||||
protected groupView: IEditorGroupView,
|
||||
protected tabsModel: IReadonlyEditorGroupModel,
|
||||
@IContextMenuService protected readonly contextMenuService: IContextMenuService,
|
||||
@IInstantiationService protected instantiationService: IInstantiationService,
|
||||
|
@ -159,7 +160,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC
|
|||
}
|
||||
|
||||
protected createEditorActionsToolBar(container: HTMLElement): void {
|
||||
const context: IEditorCommandsContext = { groupId: this.groupViewer.id };
|
||||
const context: IEditorCommandsContext = { groupId: this.groupView.id };
|
||||
|
||||
// Toolbar Widget
|
||||
|
||||
|
@ -173,7 +174,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC
|
|||
renderDropdownAsChildElement: this.renderDropdownAsChildElement,
|
||||
telemetrySource: 'editorPart',
|
||||
resetMenu: MenuId.EditorTitle,
|
||||
maxNumberOfItems: 9,
|
||||
overflowBehavior: { maxItems: 9, exempted: EDITOR_CORE_NAVIGATION_COMMANDS },
|
||||
highlightToggledItems: true,
|
||||
}));
|
||||
|
||||
|
@ -191,7 +192,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC
|
|||
}
|
||||
|
||||
private actionViewItemProvider(action: IAction): IActionViewItem | undefined {
|
||||
const activeEditorPane = this.groupViewer.activeEditorPane;
|
||||
const activeEditorPane = this.groupView.activeEditorPane;
|
||||
|
||||
// Check Active Editor
|
||||
if (activeEditorPane instanceof EditorPane) {
|
||||
|
@ -224,24 +225,24 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC
|
|||
|
||||
// Update contexts
|
||||
this.contextKeyService.bufferChangeEvents(() => {
|
||||
const activeEditor = this.groupViewer.activeEditor;
|
||||
const activeEditor = this.groupView.activeEditor;
|
||||
|
||||
this.resourceContext.set(EditorResourceAccessor.getOriginalUri(activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY } ?? null));
|
||||
|
||||
this.editorPinnedContext.set(activeEditor ? this.groupViewer.isPinned(activeEditor) : false);
|
||||
this.editorIsFirstContext.set(activeEditor ? this.groupViewer.isFirst(activeEditor) : false);
|
||||
this.editorIsLastContext.set(activeEditor ? this.groupViewer.isLast(activeEditor) : false);
|
||||
this.editorStickyContext.set(activeEditor ? this.groupViewer.isSticky(activeEditor) : false);
|
||||
this.editorPinnedContext.set(activeEditor ? this.groupView.isPinned(activeEditor) : false);
|
||||
this.editorIsFirstContext.set(activeEditor ? this.groupView.isFirst(activeEditor) : false);
|
||||
this.editorIsLastContext.set(activeEditor ? this.groupView.isLast(activeEditor) : false);
|
||||
this.editorStickyContext.set(activeEditor ? this.groupView.isSticky(activeEditor) : false);
|
||||
applyAvailableEditorIds(this.editorAvailableEditorIds, activeEditor, this.editorResolverService);
|
||||
|
||||
this.editorCanSplitInGroupContext.set(activeEditor ? activeEditor.hasCapability(EditorInputCapabilities.CanSplitInGroup) : false);
|
||||
this.sideBySideEditorContext.set(activeEditor?.typeId === SideBySideEditorInput.ID);
|
||||
|
||||
this.groupLockedContext.set(this.groupViewer.isLocked);
|
||||
this.groupLockedContext.set(this.groupView.isLocked);
|
||||
});
|
||||
|
||||
// Editor actions require the editor control to be there, so we retrieve it via service
|
||||
const activeEditorPane = this.groupViewer.activeEditorPane;
|
||||
const activeEditorPane = this.groupView.activeEditorPane;
|
||||
if (activeEditorPane instanceof EditorPane) {
|
||||
const scopedContextKeyService = this.getEditorPaneAwareContextKeyService();
|
||||
const titleBarMenu = this.menuService.createMenu(MenuId.EditorTitle, scopedContextKeyService, { emitEventsForSubmenuChanges: true, eventDebounceDelay: 0 });
|
||||
|
@ -265,7 +266,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC
|
|||
}
|
||||
|
||||
private getEditorPaneAwareContextKeyService(): IContextKeyService {
|
||||
return this.groupViewer.activeEditorPane?.scopedContextKeyService ?? this.contextKeyService;
|
||||
return this.groupView.activeEditorPane?.scopedContextKeyService ?? this.contextKeyService;
|
||||
}
|
||||
|
||||
protected clearEditorActionsToolbar(): void {
|
||||
|
@ -281,34 +282,34 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC
|
|||
}
|
||||
|
||||
// Set editor group as transfer
|
||||
this.groupTransfer.setData([new DraggedEditorGroupIdentifier(this.groupViewer.id)], DraggedEditorGroupIdentifier.prototype);
|
||||
this.groupTransfer.setData([new DraggedEditorGroupIdentifier(this.groupView.id)], DraggedEditorGroupIdentifier.prototype);
|
||||
if (e.dataTransfer) {
|
||||
e.dataTransfer.effectAllowed = 'copyMove';
|
||||
}
|
||||
|
||||
// Drag all tabs of the group if tabs are enabled
|
||||
let hasDataTransfer = false;
|
||||
if (this.accessor.partOptions.showTabs) {
|
||||
hasDataTransfer = this.doFillResourceDataTransfers(this.groupViewer.getEditors(EditorsOrder.SEQUENTIAL), e);
|
||||
if (this.groupsView.partOptions.showTabs) {
|
||||
hasDataTransfer = this.doFillResourceDataTransfers(this.groupView.getEditors(EditorsOrder.SEQUENTIAL), e);
|
||||
}
|
||||
|
||||
// Otherwise only drag the active editor
|
||||
else {
|
||||
if (this.groupViewer.activeEditor) {
|
||||
hasDataTransfer = this.doFillResourceDataTransfers([this.groupViewer.activeEditor], e);
|
||||
if (this.groupView.activeEditor) {
|
||||
hasDataTransfer = this.doFillResourceDataTransfers([this.groupView.activeEditor], e);
|
||||
}
|
||||
}
|
||||
|
||||
// Firefox: requires to set a text data transfer to get going
|
||||
if (!hasDataTransfer && isFirefox) {
|
||||
e.dataTransfer?.setData(DataTransfers.TEXT, String(this.groupViewer.label));
|
||||
e.dataTransfer?.setData(DataTransfers.TEXT, String(this.groupView.label));
|
||||
}
|
||||
|
||||
// Drag Image
|
||||
if (this.groupViewer.activeEditor) {
|
||||
let label = this.groupViewer.activeEditor.getName();
|
||||
if (this.accessor.partOptions.showTabs && this.groupViewer.count > 1) {
|
||||
label = localize('draggedEditorGroup', "{0} (+{1})", label, this.groupViewer.count - 1);
|
||||
if (this.groupView.activeEditor) {
|
||||
let label = this.groupView.activeEditor.getName();
|
||||
if (this.groupsView.partOptions.showTabs && this.groupView.count > 1) {
|
||||
label = localize('draggedEditorGroup', "{0} (+{1})", label, this.groupView.count - 1);
|
||||
}
|
||||
|
||||
applyDragImage(e, label, 'monaco-editor-group-drag-image', this.getColor(listActiveSelectionBackground), this.getColor(listActiveSelectionForeground));
|
||||
|
@ -323,7 +324,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC
|
|||
|
||||
protected doFillResourceDataTransfers(editors: readonly EditorInput[], e: DragEvent): boolean {
|
||||
if (editors.length) {
|
||||
this.instantiationService.invokeFunction(fillEditorsDragData, editors.map(editor => ({ editor, groupId: this.groupViewer.id })), e);
|
||||
this.instantiationService.invokeFunction(fillEditorsDragData, editors.map(editor => ({ editor, groupId: this.groupView.id })), e);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -365,7 +366,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC
|
|||
menuId: MenuId.EditorTitleContext,
|
||||
menuActionOptions: { shouldForwardArgs: true, arg: this.resourceContext.get() },
|
||||
contextKeyService: this.contextKeyService,
|
||||
getActionsContext: () => ({ groupId: this.groupViewer.id, editorIndex: this.groupViewer.getIndexOfEditor(editor) }),
|
||||
getActionsContext: () => ({ groupId: this.groupView.id, editorIndex: this.groupView.getIndexOfEditor(editor) }),
|
||||
getKeyBinding: action => this.getKeybinding(action),
|
||||
onHide: () => {
|
||||
|
||||
|
@ -381,7 +382,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC
|
|||
this.editorAvailableEditorIds.set(currentEditorAvailableEditorIds);
|
||||
|
||||
// restore focus to active group
|
||||
this.accessor.activeGroup.focus();
|
||||
this.groupsView.activeGroup.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -397,7 +398,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC
|
|||
}
|
||||
|
||||
protected get tabHeight() {
|
||||
return this.accessor.partOptions.tabHeight !== 'compact' ? EditorTabsControl.EDITOR_TAB_HEIGHT.normal : EditorTabsControl.EDITOR_TAB_HEIGHT.compact;
|
||||
return this.groupsView.partOptions.tabHeight !== 'compact' ? EditorTabsControl.EDITOR_TAB_HEIGHT.normal : EditorTabsControl.EDITOR_TAB_HEIGHT.compact;
|
||||
}
|
||||
|
||||
protected updateTabHeight(): void {
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Dimension, clearNode } from 'vs/base/browser/dom';
|
|||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IThemeService, Themable } from 'vs/platform/theme/common/themeService';
|
||||
import { BreadcrumbsControl, BreadcrumbsControlFactory } from 'vs/workbench/browser/parts/editor/breadcrumbsControl';
|
||||
import { IEditorGroupsAccessor, IEditorGroupTitleHeight, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { IEditorGroupsView, IEditorGroupTitleHeight, IEditorGroupView, IInternalEditorOpenOptions } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { IEditorTabsControl } from 'vs/workbench/browser/parts/editor/editorTabsControl';
|
||||
import { MultiEditorTabsControl } from 'vs/workbench/browser/parts/editor/multiEditorTabsControl';
|
||||
import { SingleEditorTabsControl } from 'vs/workbench/browser/parts/editor/singleEditorTabsControl';
|
||||
|
@ -43,8 +43,8 @@ export class EditorTitleControl extends Themable {
|
|||
|
||||
constructor(
|
||||
private parent: HTMLElement,
|
||||
private accessor: IEditorGroupsAccessor,
|
||||
private groupViewer: IEditorGroupView,
|
||||
private groupsView: IEditorGroupsView,
|
||||
private groupView: IEditorGroupView,
|
||||
private model: IReadonlyEditorGroupModel,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService
|
||||
|
@ -57,21 +57,21 @@ export class EditorTitleControl extends Themable {
|
|||
|
||||
private createEditorTabsControl(): IEditorTabsControl {
|
||||
let control: IEditorTabsControl;
|
||||
if (this.accessor.partOptions.showTabs) {
|
||||
if (this.accessor.partOptions.pinnedTabsOnSeparateRow) {
|
||||
control = this.instantiationService.createInstance(MultiRowEditorControl, this.parent, this.accessor, this.groupViewer, this.model);
|
||||
if (this.groupsView.partOptions.showTabs) {
|
||||
if (this.groupsView.partOptions.pinnedTabsOnSeparateRow) {
|
||||
control = this.instantiationService.createInstance(MultiRowEditorControl, this.parent, this.groupsView, this.groupView, this.model);
|
||||
} else {
|
||||
control = this.instantiationService.createInstance(MultiEditorTabsControl, this.parent, this.accessor, this.groupViewer, this.model);
|
||||
control = this.instantiationService.createInstance(MultiEditorTabsControl, this.parent, this.groupsView, this.groupView, this.model);
|
||||
}
|
||||
} else {
|
||||
control = this.instantiationService.createInstance(SingleEditorTabsControl, this.parent, this.accessor, this.groupViewer, this.model);
|
||||
control = this.instantiationService.createInstance(SingleEditorTabsControl, this.parent, this.groupsView, this.groupView, this.model);
|
||||
}
|
||||
|
||||
return this.editorTabsControlDisposable.add(control);
|
||||
}
|
||||
|
||||
private createBreadcrumbsControl(): BreadcrumbsControlFactory | undefined {
|
||||
if (!this.accessor.partOptions.showTabs) {
|
||||
if (!this.groupsView.partOptions.showTabs) {
|
||||
return undefined; // single tabs have breadcrumbs inlined
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,7 @@ export class EditorTitleControl extends Themable {
|
|||
breadcrumbsContainer.classList.add('breadcrumbs-below-tabs');
|
||||
this.parent.appendChild(breadcrumbsContainer);
|
||||
|
||||
const breadcrumbsControlFactory = this.breadcrumbsControlDisposables.add(this.instantiationService.createInstance(BreadcrumbsControlFactory, breadcrumbsContainer, this.groupViewer, {
|
||||
const breadcrumbsControlFactory = this.breadcrumbsControlDisposables.add(this.instantiationService.createInstance(BreadcrumbsControlFactory, breadcrumbsContainer, this.groupView, {
|
||||
showFileIcons: true,
|
||||
showSymbolIcons: true,
|
||||
showDecorationColors: false,
|
||||
|
@ -92,11 +92,11 @@ export class EditorTitleControl extends Themable {
|
|||
}
|
||||
|
||||
private handleBreadcrumbsEnablementChange(): void {
|
||||
this.groupViewer.relayout(); // relayout when breadcrumbs are enable/disabled
|
||||
this.groupView.relayout(); // relayout when breadcrumbs are enable/disabled
|
||||
}
|
||||
|
||||
openEditor(editor: EditorInput): void {
|
||||
const didChange = this.editorTabsControl.openEditor(editor);
|
||||
openEditor(editor: EditorInput, options?: IInternalEditorOpenOptions): void {
|
||||
const didChange = this.editorTabsControl.openEditor(editor, options);
|
||||
|
||||
this.handleOpenedEditors(didChange);
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ export class EditorTitleControl extends Themable {
|
|||
}
|
||||
|
||||
private handleClosedEditors(): void {
|
||||
if (!this.groupViewer.activeEditor) {
|
||||
if (!this.groupView.activeEditor) {
|
||||
this.breadcrumbsControl?.update();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
|
|||
import { MergeGroupMode, IMergeGroupOptions, GroupsArrangement, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { addDisposableListener, EventType, EventHelper, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode, DragAndDropObserver } from 'vs/base/browser/dom';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IEditorGroupsAccessor, EditorServiceImpl, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { IEditorGroupsView, EditorServiceImpl, IEditorGroupView, IInternalEditorOpenOptions } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { CloseOneEditorAction, UnpinEditorAction } from 'vs/workbench/browser/parts/editor/editorActions';
|
||||
import { assertAllDefined, assertIsDefined } from 'vs/base/common/types';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
@ -134,8 +134,8 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
|
||||
constructor(
|
||||
parent: HTMLElement,
|
||||
accessor: IEditorGroupsAccessor,
|
||||
groupViewer: IEditorGroupView,
|
||||
groupsView: IEditorGroupsView,
|
||||
groupView: IEditorGroupView,
|
||||
tabsModel: IReadonlyEditorGroupModel,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
|
@ -151,7 +151,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
@ITreeViewsDnDService private readonly treeViewsDragAndDropService: ITreeViewsDnDService,
|
||||
@IEditorResolverService editorResolverService: IEditorResolverService
|
||||
) {
|
||||
super(parent, accessor, groupViewer, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, menuService, quickInputService, themeService, editorResolverService);
|
||||
super(parent, groupsView, groupView, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, menuService, quickInputService, themeService, editorResolverService);
|
||||
|
||||
// Resolve the correct path library for the OS we are on
|
||||
// If we are connected to remote, this accounts for the
|
||||
|
@ -230,7 +230,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
|
||||
tabSizingFixedDisposables.clear();
|
||||
|
||||
const options = this.accessor.partOptions;
|
||||
const options = this.groupsView.partOptions;
|
||||
if (options.tabSizing === 'fixed') {
|
||||
tabsContainer.style.setProperty('--tab-sizing-fixed-min-width', `${options.tabSizingFixedMinWidth}px`);
|
||||
tabsContainer.style.setProperty('--tab-sizing-fixed-max-width', `${options.tabSizingFixedMaxWidth}px`);
|
||||
|
@ -266,7 +266,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
}
|
||||
|
||||
private getTabsScrollbarSizing(): number {
|
||||
if (this.accessor.partOptions.titleScrollbarSizing !== 'large') {
|
||||
if (this.groupsView.partOptions.titleScrollbarSizing !== 'large') {
|
||||
return MultiEditorTabsControl.SCROLLBAR_SIZES.default;
|
||||
}
|
||||
|
||||
|
@ -310,10 +310,10 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
resource: undefined,
|
||||
options: {
|
||||
pinned: true,
|
||||
index: this.groupViewer.count, // always at the end
|
||||
index: this.groupView.count, // always at the end
|
||||
override: DEFAULT_EDITOR_ASSOCIATION.id
|
||||
}
|
||||
}, this.groupViewer.id);
|
||||
}, this.groupView.id);
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -354,7 +354,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype);
|
||||
if (Array.isArray(data)) {
|
||||
const localDraggedEditor = data[0].identifier;
|
||||
if (this.groupViewer.id === localDraggedEditor.groupId && this.tabsModel.isLast(localDraggedEditor.editor)) {
|
||||
if (this.groupView.id === localDraggedEditor.groupId && this.tabsModel.isLast(localDraggedEditor.editor)) {
|
||||
if (e.dataTransfer) {
|
||||
e.dataTransfer.dropEffect = 'none';
|
||||
}
|
||||
|
@ -397,13 +397,13 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
|
||||
// Mouse-wheel support to switch to tabs optionally
|
||||
this._register(addDisposableListener(tabsContainer, EventType.MOUSE_WHEEL, (e: WheelEvent) => {
|
||||
const activeEditor = this.groupViewer.activeEditor;
|
||||
if (!activeEditor || this.groupViewer.count < 2) {
|
||||
const activeEditor = this.groupView.activeEditor;
|
||||
if (!activeEditor || this.groupView.count < 2) {
|
||||
return; // need at least 2 open editors
|
||||
}
|
||||
|
||||
// Shift-key enables or disables this behaviour depending on the setting
|
||||
if (this.accessor.partOptions.scrollToSwitchTabs === true) {
|
||||
if (this.groupsView.partOptions.scrollToSwitchTabs === true) {
|
||||
if (e.shiftKey) {
|
||||
return; // 'on': only enable this when Shift-key is not pressed
|
||||
}
|
||||
|
@ -433,13 +433,13 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
return;
|
||||
}
|
||||
|
||||
const nextEditor = this.groupViewer.getEditorByIndex(this.groupViewer.getIndexOfEditor(activeEditor) + tabSwitchDirection);
|
||||
const nextEditor = this.groupView.getEditorByIndex(this.groupView.getIndexOfEditor(activeEditor) + tabSwitchDirection);
|
||||
if (!nextEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Open it
|
||||
this.groupViewer.openEditor(nextEditor);
|
||||
this.groupView.openEditor(nextEditor);
|
||||
|
||||
// Disable normal scrolling, opening the editor will already reveal it properly
|
||||
EventHelper.stop(e, true);
|
||||
|
@ -461,9 +461,9 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
menuId: MenuId.EditorTabsBarContext,
|
||||
contextKeyService: this.contextKeyService,
|
||||
menuActionOptions: { shouldForwardArgs: true },
|
||||
getActionsContext: () => ({ groupId: this.groupViewer.id }),
|
||||
getActionsContext: () => ({ groupId: this.groupView.id }),
|
||||
getKeyBinding: action => this.getKeybinding(action),
|
||||
onHide: () => this.groupViewer.focus()
|
||||
onHide: () => this.groupView.focus()
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -486,8 +486,15 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
this.layout(this.dimensions);
|
||||
}
|
||||
|
||||
openEditor(editor: EditorInput): boolean {
|
||||
return this.handleOpenedEditors();
|
||||
openEditor(editor: EditorInput, options?: IInternalEditorOpenOptions): boolean {
|
||||
const changed = this.handleOpenedEditors();
|
||||
|
||||
// Respect option to focus tab control if provided
|
||||
if (options?.focusTabControl) {
|
||||
this.withTab(editor, (editor, tabIndex, tabContainer) => tabContainer.focus());
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
openEditors(editors: EditorInput[]): boolean {
|
||||
|
@ -568,7 +575,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
// This helps keeping the close button stable under
|
||||
// the mouse and allows for rapid closing of tabs.
|
||||
|
||||
if (this.isMouseOverTabs && this.accessor.partOptions.tabSizing === 'fixed') {
|
||||
if (this.isMouseOverTabs && this.groupsView.partOptions.tabSizing === 'fixed') {
|
||||
const closingLastTab = this.tabsModel.isLast(editor);
|
||||
this.updateTabsFixedWidth(!closingLastTab);
|
||||
}
|
||||
|
@ -707,7 +714,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
}
|
||||
|
||||
updateEditorDirty(editor: EditorInput): void {
|
||||
this.withTab(editor, (editor, tabIndex, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => this.redrawTabActiveAndDirty(this.accessor.activeGroup === this.groupViewer, editor, tabContainer, tabActionBar));
|
||||
this.withTab(editor, (editor, tabIndex, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => this.redrawTabActiveAndDirty(this.groupsView.activeGroup === this.groupView, editor, tabContainer, tabActionBar));
|
||||
}
|
||||
|
||||
override updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void {
|
||||
|
@ -807,12 +814,8 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
|
||||
const that = this;
|
||||
const tabActionRunner = new EditorCommandsContextActionRunner({
|
||||
groupId: this.groupViewer.id,
|
||||
get editorIndex() {
|
||||
const editor = assertIsDefined(that.tabsModel.getEditorByIndex(tabIndex));
|
||||
|
||||
return that.groupViewer.getIndexOfEditor(editor);
|
||||
},
|
||||
groupId: this.groupView.id,
|
||||
get editorIndex() { return that.toEditorIndex(tabIndex); }
|
||||
});
|
||||
|
||||
const tabActionBar = new ActionBar(tabActionsContainer, { ariaLabel: localize('ariaLabelTabActions', "Tab actions"), actionRunner: tabActionRunner });
|
||||
|
@ -837,6 +840,16 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
return tabContainer;
|
||||
}
|
||||
|
||||
private toEditorIndex(tabIndex: number): number {
|
||||
|
||||
// Given a `tabIndex` that is relative to the tabs model
|
||||
// returns the `editorIndex` relative to the entire group
|
||||
|
||||
const editor = assertIsDefined(this.tabsModel.getEditorByIndex(tabIndex));
|
||||
|
||||
return this.groupView.getIndexOfEditor(editor);
|
||||
}
|
||||
|
||||
private registerTabListeners(tab: HTMLElement, tabIndex: number, tabsContainer: HTMLElement, tabsScrollbar: ScrollableElement): IDisposable {
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
|
@ -859,7 +872,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
const editor = this.tabsModel.getEditorByIndex(tabIndex);
|
||||
if (editor) {
|
||||
// Even if focus is preserved make sure to activate the group.
|
||||
this.groupViewer.openEditor(editor, { preserveFocus, activation: EditorActivation.ACTIVATE });
|
||||
this.groupView.openEditor(editor, { preserveFocus, activation: EditorActivation.ACTIVATE });
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
@ -897,12 +910,12 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
|
||||
const editor = this.tabsModel.getEditorByIndex(tabIndex);
|
||||
if (editor) {
|
||||
if (preventEditorClose(this.tabsModel, editor, EditorCloseMethod.MOUSE, this.accessor.partOptions)) {
|
||||
if (preventEditorClose(this.tabsModel, editor, EditorCloseMethod.MOUSE, this.groupsView.partOptions)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.blockRevealActiveTabOnce();
|
||||
this.closeEditorAction.run({ groupId: this.groupViewer.id, editorIndex: this.groupViewer.getIndexOfEditor(editor) });
|
||||
this.closeEditorAction.run({ groupId: this.groupView.id, editorIndex: this.groupView.getIndexOfEditor(editor) });
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
@ -930,28 +943,27 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
handled = true;
|
||||
const editor = this.tabsModel.getEditorByIndex(tabIndex);
|
||||
if (editor) {
|
||||
this.groupViewer.openEditor(editor);
|
||||
this.groupView.openEditor(editor);
|
||||
}
|
||||
}
|
||||
|
||||
// Navigate in editors
|
||||
else if ([KeyCode.LeftArrow, KeyCode.RightArrow, KeyCode.UpArrow, KeyCode.DownArrow, KeyCode.Home, KeyCode.End].some(kb => event.equals(kb))) {
|
||||
let tabTargetIndex: number;
|
||||
let editorIndex = this.toEditorIndex(tabIndex);
|
||||
if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.UpArrow)) {
|
||||
tabTargetIndex = tabIndex - 1;
|
||||
editorIndex = editorIndex - 1;
|
||||
} else if (event.equals(KeyCode.RightArrow) || event.equals(KeyCode.DownArrow)) {
|
||||
tabTargetIndex = tabIndex + 1;
|
||||
editorIndex = editorIndex + 1;
|
||||
} else if (event.equals(KeyCode.Home)) {
|
||||
tabTargetIndex = 0;
|
||||
editorIndex = 0;
|
||||
} else {
|
||||
tabTargetIndex = this.tabsModel.count - 1;
|
||||
editorIndex = this.groupView.count - 1;
|
||||
}
|
||||
|
||||
const target = this.tabsModel.getEditorByIndex(tabTargetIndex);
|
||||
const target = this.groupView.getEditorByIndex(editorIndex);
|
||||
if (target) {
|
||||
handled = true;
|
||||
this.groupViewer.openEditor(target, { preserveFocus: true });
|
||||
(<HTMLElement>tabsContainer.childNodes[tabTargetIndex]).focus();
|
||||
this.groupView.openEditor(target, { preserveFocus: true }, { focusTabControl: true });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -976,11 +988,11 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
|
||||
const editor = this.tabsModel.getEditorByIndex(tabIndex);
|
||||
if (editor && this.tabsModel.isPinned(editor)) {
|
||||
if (this.accessor.partOptions.doubleClickTabToToggleEditorGroupSizes) {
|
||||
this.accessor.arrangeGroups(GroupsArrangement.TOGGLE, this.groupViewer);
|
||||
if (this.groupsView.partOptions.doubleClickTabToToggleEditorGroupSizes) {
|
||||
this.groupsView.arrangeGroups(GroupsArrangement.TOGGLE, this.groupView);
|
||||
}
|
||||
} else {
|
||||
this.groupViewer.pinEditor(editor);
|
||||
this.groupView.pinEditor(editor);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
@ -1002,7 +1014,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
return;
|
||||
}
|
||||
|
||||
this.editorTransfer.setData([new DraggedEditorIdentifier({ editor, groupId: this.groupViewer.id })], DraggedEditorIdentifier.prototype);
|
||||
this.editorTransfer.setData([new DraggedEditorIdentifier({ editor, groupId: this.groupView.id })], DraggedEditorIdentifier.prototype);
|
||||
|
||||
if (e.dataTransfer) {
|
||||
e.dataTransfer.effectAllowed = 'copyMove';
|
||||
|
@ -1040,7 +1052,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype);
|
||||
if (Array.isArray(data)) {
|
||||
const localDraggedEditor = data[0].identifier;
|
||||
if (localDraggedEditor.editor === this.tabsModel.getEditorByIndex(tabIndex) && localDraggedEditor.groupId === this.groupViewer.id) {
|
||||
if (localDraggedEditor.editor === this.tabsModel.getEditorByIndex(tabIndex) && localDraggedEditor.groupId === this.groupView.id) {
|
||||
if (e.dataTransfer) {
|
||||
e.dataTransfer.dropEffect = 'none';
|
||||
}
|
||||
|
@ -1065,7 +1077,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
if (dragDuration >= MultiEditorTabsControl.DRAG_OVER_OPEN_TAB_THRESHOLD) {
|
||||
const draggedOverTab = this.tabsModel.getEditorByIndex(tabIndex);
|
||||
if (draggedOverTab && this.tabsModel.activeEditor !== draggedOverTab) {
|
||||
this.groupViewer.openEditor(draggedOverTab, { preserveFocus: true });
|
||||
this.groupView.openEditor(draggedOverTab, { preserveFocus: true });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1098,7 +1110,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
const data = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype);
|
||||
if (Array.isArray(data)) {
|
||||
const group = data[0];
|
||||
if (group.identifier === this.groupViewer.id) {
|
||||
if (group.identifier === this.groupView.id) {
|
||||
return false; // groups cannot be dropped on group it originates from
|
||||
}
|
||||
}
|
||||
|
@ -1142,7 +1154,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
}
|
||||
|
||||
private computeTabLabels(): void {
|
||||
const { labelFormat } = this.accessor.partOptions;
|
||||
const { labelFormat } = this.groupsView.partOptions;
|
||||
const { verbosity, shortenDuplicates } = this.getLabelConfigFlags(labelFormat);
|
||||
|
||||
// Build labels and descriptions for each editor
|
||||
|
@ -1155,7 +1167,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
description: editor.getDescription(verbosity),
|
||||
forceDescription: editor.hasCapability(EditorInputCapabilities.ForceDescription),
|
||||
title: editor.getTitle(Verbosity.LONG),
|
||||
ariaLabel: computeEditorAriaLabel(editor, tabIndex, this.groupViewer, this.editorGroupService.count)
|
||||
ariaLabel: computeEditorAriaLabel(editor, tabIndex, this.groupView, this.editorGroupService.count)
|
||||
});
|
||||
|
||||
if (editor === this.tabsModel.activeEditor) {
|
||||
|
@ -1292,7 +1304,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
|
||||
private redrawTab(editor: EditorInput, tabIndex: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel, tabActionBar: ActionBar): void {
|
||||
const isTabSticky = this.tabsModel.isSticky(tabIndex);
|
||||
const options = this.accessor.partOptions;
|
||||
const options = this.groupsView.partOptions;
|
||||
|
||||
// Label
|
||||
this.redrawTabLabel(editor, tabIndex, tabContainer, tabLabelWidget, tabLabel);
|
||||
|
@ -1347,11 +1359,11 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
this.redrawTabBorders(tabIndex, tabContainer);
|
||||
|
||||
// Active / dirty state
|
||||
this.redrawTabActiveAndDirty(this.accessor.activeGroup === this.groupViewer, editor, tabContainer, tabActionBar);
|
||||
this.redrawTabActiveAndDirty(this.groupsView.activeGroup === this.groupView, editor, tabContainer, tabActionBar);
|
||||
}
|
||||
|
||||
private redrawTabLabel(editor: EditorInput, tabIndex: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel): void {
|
||||
const options = this.accessor.partOptions;
|
||||
const options = this.groupsView.partOptions;
|
||||
|
||||
// Unless tabs are sticky compact, show the full label and description
|
||||
// Sticky compact tabs will only show an icon if icons are enabled
|
||||
|
@ -1474,7 +1486,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
tabContainer.classList.add('dirty');
|
||||
|
||||
// Highlight modified tabs with a border if configured
|
||||
if (this.accessor.partOptions.highlightModifiedTabs) {
|
||||
if (this.groupsView.partOptions.highlightModifiedTabs) {
|
||||
let modifiedBorderColor: string | null;
|
||||
if (isGroupActive && isTabActive) {
|
||||
modifiedBorderColor = this.getColor(TAB_ACTIVE_MODIFIED_BORDER);
|
||||
|
@ -1510,15 +1522,16 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
private redrawTabBorders(tabIndex: number, tabContainer: HTMLElement): void {
|
||||
const isTabSticky = this.tabsModel.isSticky(tabIndex);
|
||||
const isTabLastSticky = isTabSticky && this.tabsModel.stickyCount === tabIndex + 1;
|
||||
const showLastStickyTabBorderColor = this.tabsModel.stickyCount !== this.tabsModel.count;
|
||||
|
||||
// Borders / Outline
|
||||
const borderRightColor = ((isTabLastSticky ? this.getColor(TAB_LAST_PINNED_BORDER) : undefined) || this.getColor(TAB_BORDER) || this.getColor(contrastBorder));
|
||||
const borderRightColor = ((isTabLastSticky && showLastStickyTabBorderColor ? this.getColor(TAB_LAST_PINNED_BORDER) : undefined) || this.getColor(TAB_BORDER) || this.getColor(contrastBorder));
|
||||
tabContainer.style.borderRight = borderRightColor ? `1px solid ${borderRightColor}` : '';
|
||||
tabContainer.style.outlineColor = this.getColor(activeContrastBorder) || '';
|
||||
}
|
||||
|
||||
protected override prepareEditorActions(editorActions: IToolbarActions): IToolbarActions {
|
||||
const isGroupActive = this.accessor.activeGroup === this.groupViewer;
|
||||
const isGroupActive = this.groupsView.activeGroup === this.groupView;
|
||||
|
||||
// Active: allow all actions
|
||||
if (isGroupActive) {
|
||||
|
@ -1552,7 +1565,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
|
||||
if (!this.visible) {
|
||||
height = 0;
|
||||
} else if (this.accessor.partOptions.wrapTabs && this.tabsAndActionsContainer?.classList.contains('wrapping')) {
|
||||
} else if (this.groupsView.partOptions.wrapTabs && this.tabsAndActionsContainer?.classList.contains('wrapping')) {
|
||||
// Wrap: we need to ask `offsetHeight` to get
|
||||
// the real height of the title area with wrapping.
|
||||
height = this.tabsAndActionsContainer.offsetHeight;
|
||||
|
@ -1617,7 +1630,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
// to signal this to the outside via a `relayout` call so that
|
||||
// e.g. the editor control can be adjusted accordingly.
|
||||
if (oldDimension && oldDimension.height !== newDimension.height) {
|
||||
this.groupViewer.relayout();
|
||||
this.groupView.relayout();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1657,7 +1670,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
}
|
||||
|
||||
// Setting enabled: selectively enable wrapping if possible
|
||||
if (this.accessor.partOptions.wrapTabs) {
|
||||
if (this.groupsView.partOptions.wrapTabs) {
|
||||
const visibleTabsWidth = tabsContainer.offsetWidth;
|
||||
const allTabsWidth = tabsContainer.scrollWidth;
|
||||
const lastTabFitsWrapped = () => {
|
||||
|
@ -1792,7 +1805,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
let stickyTabsWidth = 0;
|
||||
if (this.tabsModel.stickyCount > 0) {
|
||||
let stickyTabWidth = 0;
|
||||
switch (this.accessor.partOptions.pinnedTabSizing) {
|
||||
switch (this.groupsView.partOptions.pinnedTabSizing) {
|
||||
case 'compact':
|
||||
stickyTabWidth = MultiEditorTabsControl.TAB_WIDTH.compact;
|
||||
break;
|
||||
|
@ -1809,7 +1822,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
|
||||
// Figure out if active tab is positioned static which has an
|
||||
// impact on whether to reveal the tab or not later
|
||||
let activeTabPositionStatic = this.accessor.partOptions.pinnedTabSizing !== 'normal' && typeof activeTabIndex === 'number' && this.tabsModel.isSticky(activeTabIndex);
|
||||
let activeTabPositionStatic = this.groupsView.partOptions.pinnedTabSizing !== 'normal' && typeof activeTabIndex === 'number' && this.tabsModel.isSticky(activeTabIndex);
|
||||
|
||||
// Special case: we have sticky tabs but the available space for showing tabs
|
||||
// is little enough that we need to disable sticky tabs sticky positioning
|
||||
|
@ -1972,7 +1985,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
this.updateDropFeedback(tabsContainer, false);
|
||||
tabsContainer.classList.remove('scroll');
|
||||
|
||||
const targetEditorIndex = this.tabsModel instanceof UnstickyEditorGroupModel ? targetTabIndex + this.groupViewer.stickyCount : targetTabIndex;
|
||||
const targetEditorIndex = this.tabsModel instanceof UnstickyEditorGroupModel ? targetTabIndex + this.groupView.stickyCount : targetTabIndex;
|
||||
const options: IEditorOptions = {
|
||||
sticky: this.tabsModel instanceof StickyEditorGroupModel && this.tabsModel.stickyCount === targetEditorIndex,
|
||||
index: targetEditorIndex
|
||||
|
@ -1982,17 +1995,17 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
if (this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype)) {
|
||||
const data = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype);
|
||||
if (Array.isArray(data)) {
|
||||
const sourceGroup = this.accessor.getGroup(data[0].identifier);
|
||||
const sourceGroup = this.groupsView.getGroup(data[0].identifier);
|
||||
if (sourceGroup) {
|
||||
const mergeGroupOptions: IMergeGroupOptions = { index: targetEditorIndex };
|
||||
if (!this.isMoveOperation(e, sourceGroup.id)) {
|
||||
mergeGroupOptions.mode = MergeGroupMode.COPY_EDITORS;
|
||||
}
|
||||
|
||||
this.accessor.mergeGroup(sourceGroup, this.groupViewer, mergeGroupOptions);
|
||||
this.groupsView.mergeGroup(sourceGroup, this.groupView, mergeGroupOptions);
|
||||
}
|
||||
|
||||
this.groupViewer.focus();
|
||||
this.groupView.focus();
|
||||
this.groupTransfer.clearData(DraggedEditorGroupIdentifier.prototype);
|
||||
}
|
||||
}
|
||||
|
@ -2002,21 +2015,21 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype);
|
||||
if (Array.isArray(data)) {
|
||||
const draggedEditor = data[0].identifier;
|
||||
const sourceGroup = this.accessor.getGroup(draggedEditor.groupId);
|
||||
const sourceGroup = this.groupsView.getGroup(draggedEditor.groupId);
|
||||
if (sourceGroup) {
|
||||
|
||||
// Move editor to target position and index
|
||||
if (this.isMoveOperation(e, draggedEditor.groupId, draggedEditor.editor)) {
|
||||
sourceGroup.moveEditor(draggedEditor.editor, this.groupViewer, options);
|
||||
sourceGroup.moveEditor(draggedEditor.editor, this.groupView, options);
|
||||
}
|
||||
|
||||
// Copy editor to target position and index
|
||||
else {
|
||||
sourceGroup.copyEditor(draggedEditor.editor, this.groupViewer, options);
|
||||
sourceGroup.copyEditor(draggedEditor.editor, this.groupView, options);
|
||||
}
|
||||
}
|
||||
|
||||
this.groupViewer.focus();
|
||||
this.groupView.focus();
|
||||
this.editorTransfer.clearData(DraggedEditorIdentifier.prototype);
|
||||
}
|
||||
}
|
||||
|
@ -2034,7 +2047,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
}
|
||||
}
|
||||
|
||||
this.editorService.openEditors(editors, this.groupViewer, { validateTrust: true });
|
||||
this.editorService.openEditors(editors, this.groupView, { validateTrust: true });
|
||||
}
|
||||
|
||||
this.treeItemsTransfer.clearData(DraggedTreeItemsIdentifier.prototype);
|
||||
|
@ -2043,7 +2056,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
// Check for URI transfer
|
||||
else {
|
||||
const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: false });
|
||||
dropHandler.handleDrop(e, () => this.groupViewer, () => this.groupViewer.focus(), options);
|
||||
dropHandler.handleDrop(e, () => this.groupView, () => this.groupView.focus(), options);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2054,7 +2067,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
|
|||
|
||||
const isCopy = (e.ctrlKey && !isMacintosh) || (e.altKey && isMacintosh);
|
||||
|
||||
return (!isCopy || sourceGroup === this.groupViewer.id);
|
||||
return (!isCopy || sourceGroup === this.groupView.id);
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { Dimension } from 'vs/base/browser/dom';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { IEditorGroupsView, IEditorGroupView, IInternalEditorOpenOptions } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { IEditorTabsControl } from 'vs/workbench/browser/parts/editor/editorTabsControl';
|
||||
import { MultiEditorTabsControl } from 'vs/workbench/browser/parts/editor/multiEditorTabsControl';
|
||||
import { IEditorPartOptions } from 'vs/workbench/common/editor';
|
||||
|
@ -22,8 +22,8 @@ export class MultiRowEditorControl extends Disposable implements IEditorTabsCont
|
|||
|
||||
constructor(
|
||||
private parent: HTMLElement,
|
||||
private accessor: IEditorGroupsAccessor,
|
||||
private groupViewer: IEditorGroupView,
|
||||
private groupsView: IEditorGroupsView,
|
||||
private groupView: IEditorGroupView,
|
||||
private model: IReadonlyEditorGroupModel,
|
||||
@IInstantiationService protected instantiationService: IInstantiationService
|
||||
) {
|
||||
|
@ -32,19 +32,19 @@ export class MultiRowEditorControl extends Disposable implements IEditorTabsCont
|
|||
const stickyModel = this._register(new StickyEditorGroupModel(this.model));
|
||||
const unstickyModel = this._register(new UnstickyEditorGroupModel(this.model));
|
||||
|
||||
this.stickyEditorTabsControl = this._register(this.instantiationService.createInstance(MultiEditorTabsControl, this.parent, this.accessor, this.groupViewer, stickyModel));
|
||||
this.unstickyEditorTabsControl = this._register(this.instantiationService.createInstance(MultiEditorTabsControl, this.parent, this.accessor, this.groupViewer, unstickyModel));
|
||||
this.stickyEditorTabsControl = this._register(this.instantiationService.createInstance(MultiEditorTabsControl, this.parent, this.groupsView, this.groupView, stickyModel));
|
||||
this.unstickyEditorTabsControl = this._register(this.instantiationService.createInstance(MultiEditorTabsControl, this.parent, this.groupsView, this.groupView, unstickyModel));
|
||||
|
||||
this.handlePinnedTabsSeparateRowToolbars();
|
||||
}
|
||||
|
||||
private handlePinnedTabsSeparateRowToolbars(): void {
|
||||
if (this.groupViewer.count === 0) {
|
||||
if (this.groupView.count === 0) {
|
||||
// Do nothing as no tab bar is visible
|
||||
return;
|
||||
}
|
||||
// Ensure action toolbar is only visible once
|
||||
if (this.groupViewer.count === this.groupViewer.stickyCount) {
|
||||
if (this.groupView.count === this.groupView.stickyCount) {
|
||||
this.parent.classList.toggle('two-tab-bars', false);
|
||||
} else {
|
||||
this.parent.classList.toggle('two-tab-bars', true);
|
||||
|
@ -55,9 +55,9 @@ export class MultiRowEditorControl extends Disposable implements IEditorTabsCont
|
|||
return this.model.isSticky(editor) ? this.stickyEditorTabsControl : this.unstickyEditorTabsControl;
|
||||
}
|
||||
|
||||
openEditor(editor: EditorInput): boolean {
|
||||
openEditor(editor: EditorInput, options: IInternalEditorOpenOptions): boolean {
|
||||
const [editorTabController, otherTabController] = this.model.isSticky(editor) ? [this.stickyEditorTabsControl, this.unstickyEditorTabsControl] : [this.unstickyEditorTabsControl, this.stickyEditorTabsControl];
|
||||
const didChange = editorTabController.openEditor(editor);
|
||||
const didChange = editorTabController.openEditor(editor, options);
|
||||
if (didChange) {
|
||||
// HACK: To render all editor tabs on startup, otherwise only one row gets rendered
|
||||
otherTabController.openEditors([]);
|
||||
|
|
|
@ -55,7 +55,7 @@ export class SingleEditorTabsControl extends EditorTabsControl {
|
|||
this._register(addDisposableListener(this.editorLabel.element, EventType.CLICK, e => this.onTitleLabelClick(e)));
|
||||
|
||||
// Breadcrumbs
|
||||
this.breadcrumbsControlFactory = this._register(this.instantiationService.createInstance(BreadcrumbsControlFactory, labelContainer, this.groupViewer, {
|
||||
this.breadcrumbsControlFactory = this._register(this.instantiationService.createInstance(BreadcrumbsControlFactory, labelContainer, this.groupView, {
|
||||
showFileIcons: false,
|
||||
showSymbolIcons: true,
|
||||
showDecorationColors: false,
|
||||
|
@ -109,15 +109,15 @@ export class SingleEditorTabsControl extends EditorTabsControl {
|
|||
private onTitleDoubleClick(e: MouseEvent): void {
|
||||
EventHelper.stop(e);
|
||||
|
||||
this.groupViewer.pinEditor();
|
||||
this.groupView.pinEditor();
|
||||
}
|
||||
|
||||
private onTitleAuxClick(e: MouseEvent): void {
|
||||
if (e.button === 1 /* Middle Button */ && this.tabsModel.activeEditor) {
|
||||
EventHelper.stop(e, true /* for https://github.com/microsoft/vscode/issues/56715 */);
|
||||
|
||||
if (!preventEditorClose(this.tabsModel, this.tabsModel.activeEditor, EditorCloseMethod.MOUSE, this.accessor.partOptions)) {
|
||||
this.groupViewer.closeEditor(this.tabsModel.activeEditor);
|
||||
if (!preventEditorClose(this.tabsModel, this.tabsModel.activeEditor, EditorCloseMethod.MOUSE, this.groupsView.partOptions)) {
|
||||
this.groupView.closeEditor(this.tabsModel.activeEditor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -261,10 +261,10 @@ export class SingleEditorTabsControl extends EditorTabsControl {
|
|||
|
||||
private redraw(): void {
|
||||
const editor = this.tabsModel.activeEditor ?? undefined;
|
||||
const options = this.accessor.partOptions;
|
||||
const options = this.groupsView.partOptions;
|
||||
|
||||
const isEditorPinned = editor ? this.tabsModel.isPinned(editor) : false;
|
||||
const isGroupActive = this.accessor.activeGroup === this.groupViewer;
|
||||
const isGroupActive = this.groupsView.activeGroup === this.groupView;
|
||||
|
||||
this.activeLabel = { editor, pinned: isEditorPinned };
|
||||
|
||||
|
@ -293,7 +293,7 @@ export class SingleEditorTabsControl extends EditorTabsControl {
|
|||
this.updateEditorDirty(editor);
|
||||
|
||||
// Editor Label
|
||||
const { labelFormat } = this.accessor.partOptions;
|
||||
const { labelFormat } = this.groupsView.partOptions;
|
||||
let description: string;
|
||||
if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) {
|
||||
description = ''; // hide description when showing breadcrumbs
|
||||
|
@ -345,7 +345,7 @@ export class SingleEditorTabsControl extends EditorTabsControl {
|
|||
}
|
||||
|
||||
protected override prepareEditorActions(editorActions: IToolbarActions): IToolbarActions {
|
||||
const isGroupActive = this.accessor.activeGroup === this.groupViewer;
|
||||
const isGroupActive = this.groupsView.activeGroup === this.groupView;
|
||||
|
||||
// Active: allow all actions
|
||||
if (isGroupActive) {
|
||||
|
|
|
@ -93,7 +93,6 @@ export function registerNotificationCommands(center: INotificationsCenterControl
|
|||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: SHOW_NOTIFICATIONS_CENTER,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: NotificationsCenterVisibleContext.negate(),
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyN),
|
||||
handler: () => {
|
||||
toasts.hide();
|
||||
|
|
|
@ -1834,4 +1834,6 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop<ITreeItem> {
|
|||
this.dragCancellationToken?.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void { }
|
||||
}
|
||||
|
|
|
@ -174,14 +174,14 @@ export interface IReadonlyEditorGroupModel {
|
|||
readonly activeEditor: EditorInput | null;
|
||||
readonly previewEditor: EditorInput | null;
|
||||
|
||||
getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): readonly EditorInput[];
|
||||
getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): EditorInput[];
|
||||
getEditorByIndex(index: number): EditorInput | undefined;
|
||||
indexOf(editor: EditorInput | IUntypedEditorInput | null, editors?: EditorInput[], options?: IMatchEditorOptions): number;
|
||||
isActive(editor: EditorInput | IUntypedEditorInput): boolean;
|
||||
isPinned(editorOrIndex: EditorInput | number): boolean;
|
||||
isSticky(editorOrIndex: EditorInput | number): boolean;
|
||||
isFirst(editor: EditorInput): boolean;
|
||||
isLast(editor: EditorInput): boolean;
|
||||
isFirst(editor: EditorInput, editors?: EditorInput[]): boolean;
|
||||
isLast(editor: EditorInput, editors?: EditorInput[]): boolean;
|
||||
findEditor(editor: EditorInput | null, options?: IMatchEditorOptions): [EditorInput, number /* index */] | undefined;
|
||||
contains(editor: EditorInput | IUntypedEditorInput, options?: IMatchEditorOptions): boolean;
|
||||
}
|
||||
|
@ -937,19 +937,19 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel {
|
|||
return [this.editors[index], index];
|
||||
}
|
||||
|
||||
isFirst(candidate: EditorInput | null): boolean {
|
||||
return this.matches(this.editors[0], candidate);
|
||||
isFirst(candidate: EditorInput | null, editors = this.editors): boolean {
|
||||
return this.matches(editors[0], candidate);
|
||||
}
|
||||
|
||||
isLast(candidate: EditorInput | null): boolean {
|
||||
return this.matches(this.editors[this.editors.length - 1], candidate);
|
||||
isLast(candidate: EditorInput | null, editors = this.editors): boolean {
|
||||
return this.matches(editors[editors.length - 1], candidate);
|
||||
}
|
||||
|
||||
contains(candidate: EditorInput | IUntypedEditorInput, options?: IMatchEditorOptions): boolean {
|
||||
return this.indexOf(candidate, this.editors, options) !== -1;
|
||||
}
|
||||
|
||||
private matches(editor: EditorInput | null, candidate: EditorInput | IUntypedEditorInput | null, options?: IMatchEditorOptions): boolean {
|
||||
private matches(editor: EditorInput | null | undefined, candidate: EditorInput | IUntypedEditorInput | null, options?: IMatchEditorOptions): boolean {
|
||||
if (!editor || !candidate) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -41,7 +41,15 @@ abstract class FilteredEditorGroupModel extends Disposable implements IReadonlyE
|
|||
isSticky(editorOrIndex: EditorInput | number): boolean { return this.model.isSticky(editorOrIndex); }
|
||||
isActive(editor: EditorInput | IUntypedEditorInput): boolean { return this.model.isActive(editor); }
|
||||
|
||||
getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): readonly EditorInput[] {
|
||||
isFirst(editor: EditorInput): boolean {
|
||||
return this.model.isFirst(editor, this.getEditors(EditorsOrder.SEQUENTIAL));
|
||||
}
|
||||
|
||||
isLast(editor: EditorInput): boolean {
|
||||
return this.model.isLast(editor, this.getEditors(EditorsOrder.SEQUENTIAL));
|
||||
}
|
||||
|
||||
getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): EditorInput[] {
|
||||
const editors = this.model.getEditors(order, options);
|
||||
return editors.filter(e => this.filter(e));
|
||||
}
|
||||
|
@ -56,8 +64,6 @@ abstract class FilteredEditorGroupModel extends Disposable implements IReadonlyE
|
|||
|
||||
abstract get count(): number;
|
||||
|
||||
abstract isFirst(editor: EditorInput): boolean;
|
||||
abstract isLast(editor: EditorInput): boolean;
|
||||
abstract getEditorByIndex(index: number): EditorInput | undefined;
|
||||
abstract indexOf(editor: EditorInput | IUntypedEditorInput | null, editors?: EditorInput[], options?: IMatchEditorOptions): number;
|
||||
abstract contains(editor: EditorInput | IUntypedEditorInput, options?: IMatchEditorOptions): boolean;
|
||||
|
@ -68,7 +74,7 @@ abstract class FilteredEditorGroupModel extends Disposable implements IReadonlyE
|
|||
export class StickyEditorGroupModel extends FilteredEditorGroupModel {
|
||||
get count(): number { return this.model.stickyCount; }
|
||||
|
||||
override getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): readonly EditorInput[] {
|
||||
override getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): EditorInput[] {
|
||||
if (options?.excludeSticky) {
|
||||
return [];
|
||||
}
|
||||
|
@ -82,14 +88,6 @@ export class StickyEditorGroupModel extends FilteredEditorGroupModel {
|
|||
return true;
|
||||
}
|
||||
|
||||
isFirst(editor: EditorInput): boolean {
|
||||
return this.model.isFirst(editor);
|
||||
}
|
||||
|
||||
isLast(editor: EditorInput): boolean {
|
||||
return this.model.indexOf(editor) === this.model.stickyCount - 1;
|
||||
}
|
||||
|
||||
getEditorByIndex(index: number): EditorInput | undefined {
|
||||
return index < this.count ? this.model.getEditorByIndex(index) : undefined;
|
||||
}
|
||||
|
@ -120,21 +118,13 @@ export class UnstickyEditorGroupModel extends FilteredEditorGroupModel {
|
|||
return false;
|
||||
}
|
||||
|
||||
override getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): readonly EditorInput[] {
|
||||
override getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): EditorInput[] {
|
||||
if (order === EditorsOrder.SEQUENTIAL) {
|
||||
return this.model.getEditors(EditorsOrder.SEQUENTIAL).slice(this.model.stickyCount);
|
||||
}
|
||||
return super.getEditors(order, options);
|
||||
}
|
||||
|
||||
isFirst(editor: EditorInput): boolean {
|
||||
return this.model.indexOf(editor) === this.model.stickyCount;
|
||||
}
|
||||
|
||||
isLast(editor: EditorInput): boolean {
|
||||
return this.model.isLast(editor);
|
||||
}
|
||||
|
||||
getEditorByIndex(index: number): EditorInput | undefined {
|
||||
return index >= 0 ? this.model.getEditorByIndex(index + this.model.stickyCount) : undefined;
|
||||
}
|
||||
|
|
|
@ -102,11 +102,14 @@ class AccessibilityHelpProvider implements IAccessibleContentProvider {
|
|||
|
||||
const editorContext = this._contextKeyService.getContext(this._editor.getDomNode()!);
|
||||
if (editorContext.getValue<boolean>(CommentContextKeys.activeEditorHasCommentingRange.key)) {
|
||||
content.push(this._descriptionForCommand(CommentCommandId.Add, CommentAccessibilityHelpNLS.addComment, CommentAccessibilityHelpNLS.addCommentNoKb));
|
||||
content.push(this._descriptionForCommand(CommentCommandId.NextThread, CommentAccessibilityHelpNLS.nextCommentThreadKb, CommentAccessibilityHelpNLS.nextCommentThreadNoKb));
|
||||
content.push(this._descriptionForCommand(CommentCommandId.PreviousThread, CommentAccessibilityHelpNLS.previousCommentThreadKb, CommentAccessibilityHelpNLS.previousCommentThreadNoKb));
|
||||
content.push(this._descriptionForCommand(CommentCommandId.NextRange, CommentAccessibilityHelpNLS.nextRange, CommentAccessibilityHelpNLS.nextRangeNoKb));
|
||||
content.push(this._descriptionForCommand(CommentCommandId.PreviousRange, CommentAccessibilityHelpNLS.previousRange, CommentAccessibilityHelpNLS.previousRangeNoKb));
|
||||
const commentCommandInfo = [];
|
||||
commentCommandInfo.push(CommentAccessibilityHelpNLS.intro);
|
||||
commentCommandInfo.push(this._descriptionForCommand(CommentCommandId.Add, CommentAccessibilityHelpNLS.addComment, CommentAccessibilityHelpNLS.addCommentNoKb));
|
||||
commentCommandInfo.push(this._descriptionForCommand(CommentCommandId.NextThread, CommentAccessibilityHelpNLS.nextCommentThreadKb, CommentAccessibilityHelpNLS.nextCommentThreadNoKb));
|
||||
commentCommandInfo.push(this._descriptionForCommand(CommentCommandId.PreviousThread, CommentAccessibilityHelpNLS.previousCommentThreadKb, CommentAccessibilityHelpNLS.previousCommentThreadNoKb));
|
||||
commentCommandInfo.push(this._descriptionForCommand(CommentCommandId.NextRange, CommentAccessibilityHelpNLS.nextRange, CommentAccessibilityHelpNLS.nextRangeNoKb));
|
||||
commentCommandInfo.push(this._descriptionForCommand(CommentCommandId.PreviousRange, CommentAccessibilityHelpNLS.previousRange, CommentAccessibilityHelpNLS.previousRangeNoKb));
|
||||
content.push(commentCommandInfo.join('\n'));
|
||||
}
|
||||
|
||||
if (options.get(EditorOption.stickyScroll).enabled) {
|
||||
|
|
|
@ -363,7 +363,7 @@ export class AccessibleView extends Disposable {
|
|||
this._accessibleViewCurrentProviderId.set(provider.verbositySettingKey.replaceAll('accessibility.verbosity.', ''));
|
||||
}
|
||||
const value = this._configurationService.getValue(provider.verbositySettingKey);
|
||||
const readMoreLink = provider.options.readMoreUrl ? localize("openDoc", "\n\nPress H now to open a browser window with more information related to accessibility.\n\n") : '';
|
||||
const readMoreLink = provider.options.readMoreUrl ? localize("openDoc", "\n\nOpen a browser window with more information related to accessibility (H).") : '';
|
||||
let disableHelpHint = '';
|
||||
if (provider.options.type === AccessibleViewType.Help && !!value) {
|
||||
disableHelpHint = this._getDisableVerbosityHint(provider.verbositySettingKey);
|
||||
|
@ -384,7 +384,8 @@ export class AccessibleView extends Disposable {
|
|||
message += '\n';
|
||||
}
|
||||
}
|
||||
this._currentContent = message + provider.provideContent() + readMoreLink + disableHelpHint;
|
||||
const exitThisDialogHint = localize('exit', '\n\nExit this dialog (Escape).');
|
||||
this._currentContent = message + provider.provideContent() + readMoreLink + disableHelpHint + exitThisDialogHint;
|
||||
this._updateContextKeys(provider, true);
|
||||
|
||||
this._getTextModel(URI.from({ path: `accessible-view-${provider.verbositySettingKey}`, scheme: 'accessible-view', fragment: this._currentContent })).then((model) => {
|
||||
|
@ -402,7 +403,7 @@ export class AccessibleView extends Disposable {
|
|||
const verbose = this._configurationService.getValue(provider.verbositySettingKey);
|
||||
const hasActions = this._accessibleViewSupportsNavigation.get() || this._accessibleViewVerbosityEnabled.get() || this._accessibleViewGoToSymbolSupported.get() || this._currentProvider?.actions;
|
||||
if (verbose && !showAccessibleViewHelp && hasActions) {
|
||||
actionsHint = localize('ariaAccessibleViewActions', "Use Shift+Tab to explore actions such as disabling this hint.");
|
||||
actionsHint = localize('ariaAccessibleViewActions', 'Explore actions such as disabling this hint (Shift+Tab).');
|
||||
}
|
||||
let ariaLabel = provider.options.type === AccessibleViewType.Help ? localize('accessibility-help', "Accessibility Help") : localize('accessible-view', "Accessible View");
|
||||
this._title.textContent = ariaLabel;
|
||||
|
@ -520,7 +521,7 @@ export class AccessibleView extends Disposable {
|
|||
private _getAccessibleViewHelpDialogContent(providerHasSymbols?: boolean): string {
|
||||
const navigationHint = this._getNavigationHint();
|
||||
const goToSymbolHint = this._getGoToSymbolHint(providerHasSymbols);
|
||||
const toolbarHint = localize('toolbar', "Navigate to the toolbar (Shift+Tab))");
|
||||
const toolbarHint = localize('toolbar', "Navigate to the toolbar (Shift+Tab)).");
|
||||
|
||||
let hint = localize('intro', "In the accessible view, you can:\n");
|
||||
if (navigationHint) {
|
||||
|
@ -540,9 +541,9 @@ export class AccessibleView extends Disposable {
|
|||
const nextKeybinding = this._keybindingService.lookupKeybinding(AccessibilityCommandId.ShowNext)?.getAriaLabel();
|
||||
const previousKeybinding = this._keybindingService.lookupKeybinding(AccessibilityCommandId.ShowPrevious)?.getAriaLabel();
|
||||
if (nextKeybinding && previousKeybinding) {
|
||||
hint = localize('accessibleViewNextPreviousHint', "Show the next ({0}) or previous ({1}) item", nextKeybinding, previousKeybinding);
|
||||
hint = localize('accessibleViewNextPreviousHint', "Show the next ({0}) or previous ({1}) item.", nextKeybinding, previousKeybinding);
|
||||
} else {
|
||||
hint = localize('chatAccessibleViewNextPreviousHintNoKb', "Show the next or previous item by configuring keybindings for the Show Next & Previous in Accessible View commands");
|
||||
hint = localize('chatAccessibleViewNextPreviousHintNoKb', "Show the next or previous item by configuring keybindings for the Show Next & Previous in Accessible View commands.");
|
||||
}
|
||||
return hint;
|
||||
}
|
||||
|
@ -553,9 +554,9 @@ export class AccessibleView extends Disposable {
|
|||
let hint = '';
|
||||
const disableKeybinding = this._keybindingService.lookupKeybinding(AccessibilityCommandId.DisableVerbosityHint, this._contextKeyService)?.getAriaLabel();
|
||||
if (disableKeybinding) {
|
||||
hint = localize('acessibleViewDisableHint', "Disable accessibility verbosity for this feature ({0}). This will disable the hint to open the accessible view for example.\n", disableKeybinding);
|
||||
hint = localize('acessibleViewDisableHint', "\n\nDisable accessibility verbosity for this feature ({0}).", disableKeybinding);
|
||||
} else {
|
||||
hint = localize('accessibleViewDisableHintNoKb', "Add a keybinding for the command Disable Accessible View Hint, which disables accessibility verbosity for this feature.\n");
|
||||
hint = localize('accessibleViewDisableHintNoKb', "\n\nAdd a keybinding for the command Disable Accessible View Hint, which disables accessibility verbosity for this feature.s");
|
||||
}
|
||||
return hint;
|
||||
}
|
||||
|
|
|
@ -62,8 +62,8 @@ import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/brows
|
|||
import { IChatCodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatCodeblockActions';
|
||||
import { ChatTreeItem, IChatCodeBlockInfo, IChatFileTreeInfo } from 'vs/workbench/contrib/chat/browser/chat';
|
||||
import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups';
|
||||
import { convertParsedRequestToMarkdown, walkTreeAndAnnotateResourceLinks } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer';
|
||||
import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions';
|
||||
import { fixVariableReferences, walkTreeAndAnnotateResourceLinks } from 'vs/workbench/contrib/chat/browser/chatVariableReferenceRenderer';
|
||||
import { CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_HAS_PROVIDER_ID, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
||||
import { IPlaceholderMarkdownString } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||
import { IChatReplyFollowup, IChatResponseProgressFileTreeData, IChatService, ISlashCommand, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
|
@ -314,7 +314,10 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
|
|||
} else if (isResponseVM(element)) {
|
||||
this.basicRenderElement(element.response.value, element, index, templateData);
|
||||
} else if (isRequestVM(element)) {
|
||||
this.basicRenderElement([new MarkdownString(element.messageText)], element, index, templateData);
|
||||
const markdown = 'kind' in element.message ?
|
||||
element.message.message :
|
||||
convertParsedRequestToMarkdown(element.message);
|
||||
this.basicRenderElement([new MarkdownString(markdown)], element, index, templateData);
|
||||
} else {
|
||||
this.renderWelcomeMessage(element, templateData);
|
||||
}
|
||||
|
@ -608,11 +611,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
|
|||
});
|
||||
|
||||
const codeblocks: IChatCodeBlockInfo[] = [];
|
||||
|
||||
if (isRequestVM(element)) {
|
||||
markdown = fixVariableReferences(markdown);
|
||||
}
|
||||
|
||||
const result = this.renderer.render(markdown, {
|
||||
fillInIncompleteTokens,
|
||||
codeBlockRendererSync: (languageId, text) => {
|
||||
|
|
|
@ -4,27 +4,33 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { IParsedChatRequest, ChatRequestTextPart } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||
|
||||
const variableRefUrlPrefix = 'http://vscodeVar_';
|
||||
const variableRefUrl = 'http://_vscodeDecoration_';
|
||||
|
||||
export function fixVariableReferences(markdown: IMarkdownString): IMarkdownString {
|
||||
const fixedMarkdownSource = markdown.value.replace(/\]\(values:(.*)/g, `](${variableRefUrlPrefix}_$1`);
|
||||
return new MarkdownString(fixedMarkdownSource, { isTrusted: markdown.isTrusted, supportThemeIcons: markdown.supportThemeIcons, supportHtml: markdown.supportHtml });
|
||||
export function convertParsedRequestToMarkdown(parsedRequest: IParsedChatRequest): string {
|
||||
let result = '';
|
||||
for (const part of parsedRequest.parts) {
|
||||
if (part instanceof ChatRequestTextPart) {
|
||||
result += part.text;
|
||||
} else {
|
||||
result += `[${part.text}](${variableRefUrl})`;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function walkTreeAndAnnotateResourceLinks(element: HTMLElement): void {
|
||||
element.querySelectorAll('a').forEach(a => {
|
||||
const href = a.getAttribute('data-href');
|
||||
if (href) {
|
||||
if (href.startsWith(variableRefUrlPrefix)) {
|
||||
if (href.startsWith(variableRefUrl)) {
|
||||
a.parentElement!.replaceChild(
|
||||
renderResourceWidget(a.textContent!),
|
||||
a);
|
||||
}
|
||||
}
|
||||
|
||||
walkTreeAndAnnotateResourceLinks(a as HTMLElement);
|
||||
});
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ import { IChatWidgetService, IQuickChatService } from 'vs/workbench/contrib/chat
|
|||
import { IChatViewOptions } from 'vs/workbench/contrib/chat/browser/chatViewPane';
|
||||
import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget';
|
||||
import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||
import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
|
||||
export class QuickChatService extends Disposable implements IQuickChatService {
|
||||
|
@ -271,7 +272,7 @@ class QuickChat extends Disposable {
|
|||
for (const request of this.model.getRequests()) {
|
||||
if (request.response?.response.value || request.response?.errorDetails) {
|
||||
this.chatService.addCompleteRequest(widget.viewModel.sessionId,
|
||||
request.message as string,
|
||||
request.message as IParsedChatRequest,
|
||||
{
|
||||
message: request.response.response.value,
|
||||
errorDetails: request.response.errorDetails,
|
||||
|
|
|
@ -26,7 +26,7 @@ import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart';
|
|||
import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget';
|
||||
import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||
import { chatSlashCommandBackground, chatSlashCommandForeground } from 'vs/workbench/contrib/chat/common/chatColors';
|
||||
import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart } from '../../common/chatRequestParser';
|
||||
import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||
import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser';
|
||||
import { IChatService, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
|
||||
|
@ -127,7 +127,7 @@ class InputEditorDecorations extends Disposable {
|
|||
return;
|
||||
}
|
||||
|
||||
const parsedRequest = await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(viewModel.sessionId, inputValue);
|
||||
const parsedRequest = (await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(viewModel.sessionId, inputValue)).parts;
|
||||
|
||||
let placeholderDecoration: IDecorationOptions[] | undefined;
|
||||
const agentPart = parsedRequest.find((p): p is ChatRequestAgentPart => p instanceof ChatRequestAgentPart);
|
||||
|
@ -252,7 +252,7 @@ class SlashCommandCompletions extends Disposable {
|
|||
return null;
|
||||
}
|
||||
|
||||
const parsedRequest = await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue());
|
||||
const parsedRequest = (await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue())).parts;
|
||||
const usedAgent = parsedRequest.find(p => p instanceof ChatRequestAgentPart);
|
||||
if (usedAgent) {
|
||||
// No (classic) global slash commands when an agent is used
|
||||
|
@ -303,7 +303,7 @@ class AgentCompletions extends Disposable {
|
|||
return null;
|
||||
}
|
||||
|
||||
const parsedRequest = await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue());
|
||||
const parsedRequest = (await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue())).parts;
|
||||
const usedAgent = parsedRequest.find(p => p instanceof ChatRequestAgentPart);
|
||||
if (usedAgent && !Range.containsPosition(usedAgent.editorRange, position)) {
|
||||
// Only one agent allowed
|
||||
|
@ -340,7 +340,7 @@ class AgentCompletions extends Disposable {
|
|||
return;
|
||||
}
|
||||
|
||||
const parsedRequest = await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue());
|
||||
const parsedRequest = (await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue())).parts;
|
||||
const usedAgent = parsedRequest.find((p): p is ChatRequestAgentPart => p instanceof ChatRequestAgentPart);
|
||||
if (!usedAgent) {
|
||||
return;
|
||||
|
|
|
@ -10,7 +10,8 @@ import { Disposable } from 'vs/base/common/lifecycle';
|
|||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||
import { IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||
import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||
import { IChat, IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IUsedContext, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
|
||||
export interface IChatRequestModel {
|
||||
|
@ -19,7 +20,7 @@ export interface IChatRequestModel {
|
|||
readonly username: string;
|
||||
readonly avatarIconUri?: URI;
|
||||
readonly session: IChatModel;
|
||||
readonly message: string | IChatReplyFollowup;
|
||||
readonly message: IParsedChatRequest | IChatReplyFollowup;
|
||||
readonly response: IChatResponseModel | undefined;
|
||||
}
|
||||
|
||||
|
@ -92,7 +93,7 @@ export class ChatRequestModel implements IChatRequestModel {
|
|||
|
||||
constructor(
|
||||
public readonly session: ChatModel,
|
||||
public readonly message: string | IChatReplyFollowup,
|
||||
public readonly message: IParsedChatRequest | IChatReplyFollowup,
|
||||
private _providerRequestId?: string) {
|
||||
this._id = 'request_' + ChatRequestModel.nextId++;
|
||||
}
|
||||
|
@ -483,8 +484,9 @@ export class ChatModel extends Disposable implements IChatModel {
|
|||
}
|
||||
|
||||
get title(): string {
|
||||
const firstRequestMessage = this._requests[0]?.message;
|
||||
const message = typeof firstRequestMessage === 'string' ? firstRequestMessage : firstRequestMessage?.message ?? '';
|
||||
// const firstRequestMessage = this._requests[0]?.message;
|
||||
// const message = typeof firstRequestMessage === 'string' ? firstRequestMessage : firstRequestMessage?.message ?? '';
|
||||
const message = '';
|
||||
return message.split('\n')[0].substring(0, 50);
|
||||
}
|
||||
|
||||
|
@ -492,7 +494,6 @@ export class ChatModel extends Disposable implements IChatModel {
|
|||
public readonly providerId: string,
|
||||
private readonly initialData: ISerializableChatData | IExportableChatData | undefined,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IChatAgentService private readonly chatAgentService: IChatAgentService,
|
||||
) {
|
||||
super();
|
||||
|
||||
|
@ -518,14 +519,15 @@ export class ChatModel extends Disposable implements IChatModel {
|
|||
this._welcomeMessage = new ChatWelcomeMessageModel(this, content);
|
||||
}
|
||||
|
||||
return requests.map((raw: ISerializableChatRequestData) => {
|
||||
const request = new ChatRequestModel(this, raw.message, raw.providerRequestId);
|
||||
if (raw.response || raw.responseErrorDetails) {
|
||||
const agent = raw.agent && this.chatAgentService.getAgents().find(a => a.id === raw.agent!.id); // TODO do something reasonable if this agent has disappeared since the last session
|
||||
request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, true, raw.isCanceled, raw.vote, raw.providerRequestId, raw.responseErrorDetails, raw.followups);
|
||||
}
|
||||
return request;
|
||||
});
|
||||
return [];
|
||||
// return requests.map((raw: ISerializableChatRequestData) => {
|
||||
// const request = new ChatRequestModel(this, raw.message, raw.providerRequestId);
|
||||
// if (raw.response || raw.responseErrorDetails) {
|
||||
// const agent = raw.agent && this.chatAgentService.getAgents().find(a => a.id === raw.agent!.id); // TODO do something reasonable if this agent has disappeared since the last session
|
||||
// request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, true, raw.isCanceled, raw.vote, raw.providerRequestId, raw.responseErrorDetails, raw.followups);
|
||||
// }
|
||||
// return request;
|
||||
// });
|
||||
}
|
||||
|
||||
startReinitialize(): void {
|
||||
|
@ -569,7 +571,7 @@ export class ChatModel extends Disposable implements IChatModel {
|
|||
return this._requests;
|
||||
}
|
||||
|
||||
addRequest(message: string | IChatReplyFollowup, chatAgent?: IChatAgentData): ChatRequestModel {
|
||||
addRequest(message: IParsedChatRequest | IChatReplyFollowup, chatAgent?: IChatAgentData): ChatRequestModel {
|
||||
if (!this._session) {
|
||||
throw new Error('addRequest: No session');
|
||||
}
|
||||
|
@ -677,7 +679,7 @@ export class ChatModel extends Disposable implements IChatModel {
|
|||
requests: this._requests.map((r): ISerializableChatRequestData => {
|
||||
return {
|
||||
providerRequestId: r.providerRequestId,
|
||||
message: typeof r.message === 'string' ? r.message : r.message.message,
|
||||
message: typeof r.message === 'string' ? r.message : '',
|
||||
response: r.response ? r.response.response.value : undefined,
|
||||
responseErrorDetails: r.response?.errorDetails,
|
||||
followups: r.response?.followups,
|
||||
|
|
73
src/vs/workbench/contrib/chat/common/chatParserTypes.ts
Normal file
73
src/vs/workbench/contrib/chat/common/chatParserTypes.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { OffsetRange } from 'vs/editor/common/core/offsetRange';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { IChatAgentData, IChatAgentCommand } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||
import { ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
|
||||
// These are in a separate file to avoid circular dependencies with the dependencies of the parser
|
||||
|
||||
export interface IParsedChatRequest {
|
||||
readonly parts: ReadonlyArray<IParsedChatRequestPart>;
|
||||
readonly text: string;
|
||||
}
|
||||
|
||||
export interface IParsedChatRequestPart {
|
||||
readonly range: OffsetRange;
|
||||
readonly editorRange: IRange;
|
||||
readonly text: string;
|
||||
}
|
||||
|
||||
// TODO rename to tokens
|
||||
|
||||
export class ChatRequestTextPart implements IParsedChatRequestPart {
|
||||
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly text: string) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* An invocation of a static variable that can be resolved by the variable service
|
||||
*/
|
||||
export class ChatRequestVariablePart implements IParsedChatRequestPart {
|
||||
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly variableName: string, readonly variableArg: string) { }
|
||||
|
||||
get text(): string {
|
||||
const argPart = this.variableArg ? `:${this.variableArg}` : '';
|
||||
return `@${this.variableName}${argPart}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An invocation of an agent that can be resolved by the agent service
|
||||
*/
|
||||
export class ChatRequestAgentPart implements IParsedChatRequestPart {
|
||||
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly agent: IChatAgentData) { }
|
||||
|
||||
get text(): string {
|
||||
return `@${this.agent.id}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An invocation of an agent's subcommand
|
||||
*/
|
||||
export class ChatRequestAgentSubcommandPart implements IParsedChatRequestPart {
|
||||
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly command: IChatAgentCommand) { }
|
||||
|
||||
get text(): string {
|
||||
return `/${this.command.name}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An invocation of a standalone slash command
|
||||
*/
|
||||
export class ChatRequestSlashCommandPart implements IParsedChatRequestPart {
|
||||
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly slashCommand: ISlashCommand) { }
|
||||
|
||||
get text(): string {
|
||||
return `/${this.slashCommand.command}`;
|
||||
}
|
||||
}
|
|
@ -6,13 +6,14 @@
|
|||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { OffsetRange } from 'vs/editor/common/core/offsetRange';
|
||||
import { IPosition, Position } from 'vs/editor/common/core/position';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||
import { IChatService, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||
import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart, IParsedChatRequest, IParsedChatRequestPart } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
|
||||
|
||||
const variableOrAgentReg = /^@([\w_\-]+)(:\d+)?(?=(\s|$))/i; // An @-variable with an optional numeric : arg (@response:2)
|
||||
const slashReg = /\/([\w_-]+)(?=(\s|$))/i; // A / command
|
||||
const variableOrAgentReg = /^@([\w_\-]+)(:\d+)?(?=(\s|$|\b))/i; // An @-variable with an optional numeric : arg (@response:2)
|
||||
const slashReg = /\/([\w_-]+)(?=(\s|$|\b))/i; // A / command
|
||||
|
||||
export class ChatRequestParser {
|
||||
constructor(
|
||||
|
@ -21,7 +22,7 @@ export class ChatRequestParser {
|
|||
@IChatService private readonly chatService: IChatService,
|
||||
) { }
|
||||
|
||||
async parseChatRequest(sessionId: string, message: string): Promise<IParsedChatRequestPart[]> {
|
||||
async parseChatRequest(sessionId: string, message: string): Promise<IParsedChatRequest> {
|
||||
const parts: IParsedChatRequestPart[] = [];
|
||||
|
||||
let lineNumber = 1;
|
||||
|
@ -68,7 +69,10 @@ export class ChatRequestParser {
|
|||
new Range(lastPart?.editorRange.endLineNumber ?? 1, lastPart?.editorRange.endColumn ?? 1, lineNumber, column),
|
||||
message.slice(lastPartEnd, message.length)));
|
||||
|
||||
return parts;
|
||||
return {
|
||||
parts,
|
||||
text: message,
|
||||
};
|
||||
}
|
||||
|
||||
private tryToParseVariableOrAgent(message: string, offset: number, position: IPosition, parts: ReadonlyArray<IParsedChatRequestPart>): ChatRequestAgentPart | ChatRequestVariablePart | undefined {
|
||||
|
@ -131,40 +135,3 @@ export class ChatRequestParser {
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IParsedChatRequestPart {
|
||||
readonly range: OffsetRange;
|
||||
readonly editorRange: IRange;
|
||||
}
|
||||
|
||||
export class ChatRequestTextPart implements IParsedChatRequestPart {
|
||||
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly text: string) { }
|
||||
}
|
||||
/**
|
||||
* An invocation of a static variable that can be resolved by the variable service
|
||||
*/
|
||||
|
||||
export class ChatRequestVariablePart implements IParsedChatRequestPart {
|
||||
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly variableName: string, readonly variableArg: string) { }
|
||||
}
|
||||
/**
|
||||
* An invocation of an agent that can be resolved by the agent service
|
||||
*/
|
||||
|
||||
export class ChatRequestAgentPart implements IParsedChatRequestPart {
|
||||
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly agent: IChatAgentData) { }
|
||||
}
|
||||
/**
|
||||
* An invocation of an agent's subcommand
|
||||
*/
|
||||
|
||||
export class ChatRequestAgentSubcommandPart implements IParsedChatRequestPart {
|
||||
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly command: IChatAgentCommand) { }
|
||||
}
|
||||
/**
|
||||
* An invocation of a standalone slash command
|
||||
*/
|
||||
|
||||
export class ChatRequestSlashCommandPart implements IParsedChatRequestPart {
|
||||
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly slashCommand: ISlashCommand) { }
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import { IRange } from 'vs/editor/common/core/range';
|
|||
import { ProviderResult } from 'vs/editor/common/languages';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IChatModel, ChatModel, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||
import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||
import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables';
|
||||
|
||||
export interface IChat {
|
||||
|
@ -242,7 +243,7 @@ export interface IChatService {
|
|||
getSlashCommands(sessionId: string, token: CancellationToken): Promise<ISlashCommand[]>;
|
||||
clearSession(sessionId: string): void;
|
||||
addRequest(context: any): void;
|
||||
addCompleteRequest(sessionId: string, message: string, response: IChatCompleteResponse): void;
|
||||
addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, response: IChatCompleteResponse): void;
|
||||
sendRequestToProvider(sessionId: string, message: IChatDynamicRequest): void;
|
||||
getHistory(): IChatDetail[];
|
||||
removeHistoryEntry(sessionId: string): void;
|
||||
|
|
|
@ -21,10 +21,12 @@ import { Progress } from 'vs/platform/progress/common/progress';
|
|||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||
import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||
import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
||||
import { ChatModel, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, ISerializableChatData, ISerializableChatsData, isCompleteInteractiveProgressTreeData } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||
import { ChatRequestAgentPart, ChatRequestSlashCommandPart, IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||
import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider';
|
||||
import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser';
|
||||
import { IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatReplyFollowup, IChatRequest, IChatResponse, IChatService, IChatTransferredSessionData, IChatUserActionEvent, ISlashCommand, InteractiveSessionCopyKind, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
import { IChatSlashCommandService, IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
|
||||
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
|
||||
|
@ -431,18 +433,21 @@ export class ChatService extends Disposable implements IChatService {
|
|||
}
|
||||
|
||||
// This method is only returning whether the request was accepted - don't block on the actual request
|
||||
return { responseCompletePromise: this._sendRequestAsync(model, provider, request, usedSlashCommand) };
|
||||
return { responseCompletePromise: this._sendRequestAsync(model, sessionId, provider, request, usedSlashCommand) };
|
||||
}
|
||||
|
||||
private async _sendRequestAsync(model: ChatModel, provider: IChatProvider, message: string | IChatReplyFollowup, usedSlashCommand?: ISlashCommand): Promise<void> {
|
||||
const resolvedAgent = typeof message === 'string' ? this.resolveAgent(message) : undefined;
|
||||
let request: ChatRequestModel;
|
||||
private async _sendRequestAsync(model: ChatModel, sessionId: string, provider: IChatProvider, message: string | IChatReplyFollowup, usedSlashCommand?: ISlashCommand): Promise<void> {
|
||||
const parsedRequest = typeof message === 'string' ?
|
||||
await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, message) :
|
||||
message; // Handle the followup type along with the response
|
||||
|
||||
const resolvedCommand = typeof message === 'string' && message.startsWith('/') ? await this.handleSlashCommand(model.sessionId, message) : message;
|
||||
let request: ChatRequestModel;
|
||||
const agentPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart);
|
||||
const commandPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestSlashCommandPart => r instanceof ChatRequestSlashCommandPart);
|
||||
|
||||
let gotProgress = false;
|
||||
const requestType = typeof message === 'string' ?
|
||||
(message.startsWith('/') ? 'slashCommand' : 'string') :
|
||||
commandPart ? 'slashCommand' : 'string' :
|
||||
'followup';
|
||||
|
||||
const rawResponsePromise = createCancelablePromise<void>(async token => {
|
||||
|
@ -493,8 +498,8 @@ export class ChatService extends Disposable implements IChatService {
|
|||
let rawResponse: IChatResponse | null | undefined;
|
||||
let slashCommandFollowups: IChatFollowup[] | void = [];
|
||||
|
||||
if (typeof message === 'string' && resolvedAgent) {
|
||||
request = model.addRequest(message);
|
||||
if (typeof message === 'string' && agentPart) {
|
||||
request = model.addRequest(parsedRequest);
|
||||
const history: IChatMessage[] = [];
|
||||
for (const request of model.getRequests()) {
|
||||
if (typeof request.message !== 'string' || !request.response) {
|
||||
|
@ -505,15 +510,15 @@ export class ChatService extends Disposable implements IChatService {
|
|||
history.push({ role: ChatMessageRole.Assistant, content: request.response.response.value.value });
|
||||
}
|
||||
}
|
||||
const agentResult = await this.chatAgentService.invokeAgent(resolvedAgent.id, message.substring(resolvedAgent.id.length + 1).trimStart(), new Progress<IChatSlashFragment>(p => {
|
||||
const agentResult = await this.chatAgentService.invokeAgent(agentPart.agent.id, message.substring(agentPart.agent.id.length + 1).trimStart(), new Progress<IChatSlashFragment>(p => {
|
||||
const { content } = p;
|
||||
const data = isCompleteInteractiveProgressTreeData(content) ? content : { content };
|
||||
progressCallback(data);
|
||||
}), history, token);
|
||||
slashCommandFollowups = agentResult?.followUp;
|
||||
rawResponse = { session: model.session! };
|
||||
} else if ((typeof resolvedCommand === 'string' && typeof message === 'string' && this.chatSlashCommandService.hasCommand(resolvedCommand))) {
|
||||
request = model.addRequest(message);
|
||||
} else if (commandPart && typeof message === 'string' && this.chatSlashCommandService.hasCommand(commandPart.slashCommand.command)) {
|
||||
request = model.addRequest(parsedRequest);
|
||||
// contributed slash commands
|
||||
// TODO: spell this out in the UI
|
||||
const history: IChatMessage[] = [];
|
||||
|
@ -526,7 +531,7 @@ export class ChatService extends Disposable implements IChatService {
|
|||
history.push({ role: ChatMessageRole.Assistant, content: request.response.response.value.value });
|
||||
}
|
||||
}
|
||||
const commandResult = await this.chatSlashCommandService.executeCommand(resolvedCommand, message.substring(resolvedCommand.length + 1).trimStart(), new Progress<IChatSlashFragment>(p => {
|
||||
const commandResult = await this.chatSlashCommandService.executeCommand(commandPart.slashCommand.command, message.substring(commandPart.slashCommand.command.length + 1).trimStart(), new Progress<IChatSlashFragment>(p => {
|
||||
const { content } = p;
|
||||
const data = isCompleteInteractiveProgressTreeData(content) ? content : { content };
|
||||
progressCallback(data);
|
||||
|
@ -535,19 +540,18 @@ export class ChatService extends Disposable implements IChatService {
|
|||
rawResponse = { session: model.session! };
|
||||
|
||||
} else {
|
||||
request = model.addRequest(parsedRequest);
|
||||
const requestProps: IChatRequest = {
|
||||
session: model.session!,
|
||||
message: resolvedCommand,
|
||||
message,
|
||||
variables: {}
|
||||
};
|
||||
|
||||
if (typeof requestProps.message === 'string') {
|
||||
const varResult = await this.chatVariablesService.resolveVariables(requestProps.message, model, token);
|
||||
if ('parts' in parsedRequest) {
|
||||
const varResult = await this.chatVariablesService.resolveVariables(parsedRequest, model, token);
|
||||
requestProps.variables = varResult.variables;
|
||||
requestProps.message = varResult.prompt;
|
||||
}
|
||||
request = model.addRequest(requestProps.message);
|
||||
|
||||
rawResponse = await provider.provideReply(requestProps, progressCallback, token);
|
||||
}
|
||||
|
||||
|
@ -615,26 +619,6 @@ export class ChatService extends Disposable implements IChatService {
|
|||
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 ?? []) {
|
||||
if (command.startsWith(`/${slashCommand.command}`) && this.chatSlashCommandService.hasCommand(slashCommand.command)) {
|
||||
return slashCommand.command;
|
||||
}
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
private resolveAgent(prompt: string): IChatAgentData | undefined {
|
||||
prompt = prompt.trim();
|
||||
const agents = this.chatAgentService.getAgents();
|
||||
if (!prompt.startsWith('@')) {
|
||||
return;
|
||||
}
|
||||
|
||||
return agents.find(a => prompt.match(new RegExp(`@${a.id}($|\\s)`)));
|
||||
}
|
||||
|
||||
async getSlashCommands(sessionId: string, token: CancellationToken): Promise<ISlashCommand[]> {
|
||||
const model = this._sessionModels.get(sessionId);
|
||||
if (!model) {
|
||||
|
@ -709,7 +693,7 @@ export class ChatService extends Disposable implements IChatService {
|
|||
return Array.from(this._providers.keys());
|
||||
}
|
||||
|
||||
async addCompleteRequest(sessionId: string, message: string, response: IChatCompleteResponse): Promise<void> {
|
||||
async addCompleteRequest(sessionId: string, message: string | IParsedChatRequest, response: IChatCompleteResponse): Promise<void> {
|
||||
this.trace('addCompleteRequest', `message: ${message}`);
|
||||
|
||||
const model = this._sessionModels.get(sessionId);
|
||||
|
@ -718,7 +702,8 @@ export class ChatService extends Disposable implements IChatService {
|
|||
}
|
||||
|
||||
await model.waitForInitialization();
|
||||
const request = model.addRequest(message, undefined);
|
||||
const parsedRequest = typeof message === 'string' ? await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, message) : message;
|
||||
const request = model.addRequest(parsedRequest);
|
||||
if (typeof response.message === 'string') {
|
||||
model.acceptResponseProgress(request, { content: response.message });
|
||||
} else {
|
||||
|
|
|
@ -9,6 +9,7 @@ import { Iterable } from 'vs/base/common/iterator';
|
|||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||
import { ChatRequestVariablePart, IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||
|
||||
export interface IChatVariableData {
|
||||
name: string;
|
||||
|
@ -39,7 +40,7 @@ export interface IChatVariablesService {
|
|||
/**
|
||||
* Resolves all variables that occur in `prompt`
|
||||
*/
|
||||
resolveVariables(prompt: string, model: IChatModel, token: CancellationToken): Promise<IChatVariableResolveResult>;
|
||||
resolveVariables(prompt: IParsedChatRequest, model: IChatModel, token: CancellationToken): Promise<IChatVariableResolveResult>;
|
||||
}
|
||||
|
||||
interface IChatData {
|
||||
|
@ -60,40 +61,29 @@ export class ChatVariablesService implements IChatVariablesService {
|
|||
constructor() {
|
||||
}
|
||||
|
||||
async resolveVariables(prompt: string, model: IChatModel, token: CancellationToken): Promise<IChatVariableResolveResult> {
|
||||
async resolveVariables(prompt: IParsedChatRequest, model: IChatModel, token: CancellationToken): Promise<IChatVariableResolveResult> {
|
||||
const resolvedVariables: Record<string, IChatRequestVariableValue[]> = {};
|
||||
const jobs: Promise<any>[] = [];
|
||||
|
||||
// TODO have a separate parser that is also used for decorations
|
||||
const regex = /(^|\s)@(\w+)(:\w+)?(?=\s|$|\b)/ig;
|
||||
|
||||
let lastMatch = 0;
|
||||
const parsedPrompt: string[] = [];
|
||||
let match: RegExpMatchArray | null;
|
||||
while (match = regex.exec(prompt)) {
|
||||
const [fullMatch, leading, varName, arg] = match;
|
||||
const data = this._resolver.get(varName.toLowerCase());
|
||||
if (data) {
|
||||
if (!arg || data.data.canTakeArgument) {
|
||||
parsedPrompt.push(prompt.substring(lastMatch, match.index!));
|
||||
parsedPrompt.push('');
|
||||
lastMatch = match.index! + fullMatch.length;
|
||||
const varIndex = parsedPrompt.length - 1;
|
||||
const argWithoutColon = arg?.slice(1);
|
||||
const fullVarName = varName + (arg ?? '');
|
||||
jobs.push(data.resolver(prompt, argWithoutColon, model, token).then(value => {
|
||||
if (value) {
|
||||
resolvedVariables[fullVarName] = value;
|
||||
parsedPrompt[varIndex] = `${leading}[@${fullVarName}](values:${fullVarName})`;
|
||||
} else {
|
||||
parsedPrompt[varIndex] = fullMatch;
|
||||
}
|
||||
}).catch(onUnexpectedExternalError));
|
||||
prompt.parts
|
||||
.forEach((varPart, i) => {
|
||||
if (varPart instanceof ChatRequestVariablePart) {
|
||||
const data = this._resolver.get(varPart.variableName.toLowerCase());
|
||||
if (data) {
|
||||
jobs.push(data.resolver(prompt.text, varPart.variableArg, model, token).then(value => {
|
||||
if (value) {
|
||||
resolvedVariables[varPart.variableName] = value;
|
||||
parsedPrompt[i] = `[@${varPart.variableName}](values:${varPart.variableName})`;
|
||||
} else {
|
||||
parsedPrompt[i] = varPart.text;
|
||||
}
|
||||
}).catch(onUnexpectedExternalError));
|
||||
}
|
||||
} else {
|
||||
parsedPrompt[i] = varPart.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parsedPrompt.push(prompt.substring(lastMatch));
|
||||
});
|
||||
|
||||
await Promise.allSettled(jobs);
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import { localize } from 'vs/nls';
|
|||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IChatModel, IChatRequestModel, IChatResponseModel, IChatWelcomeMessageContent, IResponse, Response } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||
import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||
import { IChatReplyFollowup, IChatResponseCommandFollowup, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter';
|
||||
|
||||
|
@ -51,7 +52,7 @@ export interface IChatRequestViewModel {
|
|||
readonly dataId: string;
|
||||
readonly username: string;
|
||||
readonly avatarIconUri?: URI;
|
||||
readonly message: string | IChatReplyFollowup;
|
||||
readonly message: IParsedChatRequest | IChatReplyFollowup;
|
||||
readonly messageText: string;
|
||||
currentRenderedHeight: number | undefined;
|
||||
}
|
||||
|
@ -215,7 +216,7 @@ export class ChatRequestViewModel implements IChatRequestViewModel {
|
|||
}
|
||||
|
||||
get messageText() {
|
||||
return typeof this.message === 'string' ? this.message : this.message.message;
|
||||
return 'kind' in this.message ? this.message.message : this.message.text;
|
||||
}
|
||||
|
||||
currentRenderedHeight: number | undefined;
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
[
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 6
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 7
|
||||
},
|
||||
agent: {
|
||||
id: "agent",
|
||||
metadata: {
|
||||
description: "",
|
||||
subCommands: [ { name: "subCommand" } ]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 6,
|
||||
endExclusive: 18
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 7,
|
||||
endLineNumber: 2,
|
||||
endColumn: 4
|
||||
},
|
||||
text: " Please \ndo "
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 18,
|
||||
endExclusive: 29
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 2,
|
||||
startColumn: 4,
|
||||
endLineNumber: 2,
|
||||
endColumn: 15
|
||||
},
|
||||
command: { name: "subCommand" }
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 29,
|
||||
endExclusive: 63
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 2,
|
||||
startColumn: 15,
|
||||
endLineNumber: 3,
|
||||
endColumn: 18
|
||||
},
|
||||
text: " with @selection\nand @debugConsole"
|
||||
}
|
||||
]
|
|
@ -1,15 +0,0 @@
|
|||
[
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 21
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 3,
|
||||
endColumn: 7
|
||||
},
|
||||
text: "line 1\nline 2\r\nline 3"
|
||||
}
|
||||
]
|
|
@ -1,73 +1,76 @@
|
|||
[
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 10
|
||||
{
|
||||
parts: [
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 10
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 11
|
||||
},
|
||||
text: "Hello Mr. "
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 11
|
||||
},
|
||||
text: "Hello Mr. "
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 10,
|
||||
endExclusive: 16
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 11,
|
||||
endLineNumber: 1,
|
||||
endColumn: 17
|
||||
},
|
||||
agent: {
|
||||
id: "agent",
|
||||
metadata: {
|
||||
description: "",
|
||||
subCommands: [ { name: "subCommand" } ]
|
||||
{
|
||||
range: {
|
||||
start: 10,
|
||||
endExclusive: 16
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 11,
|
||||
endLineNumber: 1,
|
||||
endColumn: 17
|
||||
},
|
||||
agent: {
|
||||
id: "agent",
|
||||
metadata: {
|
||||
description: "",
|
||||
subCommands: [ { name: "subCommand" } ]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 16,
|
||||
endExclusive: 17
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 17,
|
||||
endLineNumber: 1,
|
||||
endColumn: 18
|
||||
},
|
||||
text: " "
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 17,
|
||||
endExclusive: 28
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 18,
|
||||
endLineNumber: 1,
|
||||
endColumn: 29
|
||||
},
|
||||
command: { name: "subCommand" }
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 28,
|
||||
endExclusive: 35
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 29,
|
||||
endLineNumber: 1,
|
||||
endColumn: 36
|
||||
},
|
||||
text: " thanks"
|
||||
}
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 16,
|
||||
endExclusive: 17
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 17,
|
||||
endLineNumber: 1,
|
||||
endColumn: 18
|
||||
},
|
||||
text: " "
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 17,
|
||||
endExclusive: 28
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 18,
|
||||
endLineNumber: 1,
|
||||
endColumn: 29
|
||||
},
|
||||
command: { name: "subCommand" }
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 28,
|
||||
endExclusive: 35
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 29,
|
||||
endLineNumber: 1,
|
||||
endColumn: 36
|
||||
},
|
||||
text: " thanks"
|
||||
}
|
||||
]
|
||||
],
|
||||
text: "Hello Mr. @agent /subCommand thanks"
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
parts: [
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 14
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 15
|
||||
},
|
||||
text: "Are you there "
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 14,
|
||||
endExclusive: 20
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 15,
|
||||
endLineNumber: 1,
|
||||
endColumn: 21
|
||||
},
|
||||
agent: {
|
||||
id: "agent",
|
||||
metadata: {
|
||||
description: "",
|
||||
subCommands: [ { name: "subCommand" } ]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 20,
|
||||
endExclusive: 21
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 21,
|
||||
endLineNumber: 1,
|
||||
endColumn: 22
|
||||
},
|
||||
text: "?"
|
||||
}
|
||||
],
|
||||
text: "Are you there @agent?"
|
||||
}
|
|
@ -1,60 +1,63 @@
|
|||
[
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 6
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 7
|
||||
},
|
||||
agent: {
|
||||
id: "agent",
|
||||
metadata: {
|
||||
description: "",
|
||||
subCommands: [ { name: "subCommand" } ]
|
||||
{
|
||||
parts: [
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 6
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 7
|
||||
},
|
||||
agent: {
|
||||
id: "agent",
|
||||
metadata: {
|
||||
description: "",
|
||||
subCommands: [ { name: "subCommand" } ]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 6,
|
||||
endExclusive: 17
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 7,
|
||||
endLineNumber: 1,
|
||||
endColumn: 18
|
||||
},
|
||||
text: " Please do "
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 17,
|
||||
endExclusive: 28
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 18,
|
||||
endLineNumber: 1,
|
||||
endColumn: 29
|
||||
},
|
||||
command: { name: "subCommand" }
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 28,
|
||||
endExclusive: 35
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 29,
|
||||
endLineNumber: 1,
|
||||
endColumn: 36
|
||||
},
|
||||
text: " thanks"
|
||||
}
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 6,
|
||||
endExclusive: 17
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 7,
|
||||
endLineNumber: 1,
|
||||
endColumn: 18
|
||||
},
|
||||
text: " Please do "
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 17,
|
||||
endExclusive: 28
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 18,
|
||||
endLineNumber: 1,
|
||||
endColumn: 29
|
||||
},
|
||||
command: { name: "subCommand" }
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 28,
|
||||
endExclusive: 35
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 29,
|
||||
endLineNumber: 1,
|
||||
endColumn: 36
|
||||
},
|
||||
text: " thanks"
|
||||
}
|
||||
]
|
||||
],
|
||||
text: "@agent Please do /subCommand thanks"
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
parts: [
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 6
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 7
|
||||
},
|
||||
agent: {
|
||||
id: "agent",
|
||||
metadata: {
|
||||
description: "",
|
||||
subCommands: [ { name: "subCommand" } ]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 6,
|
||||
endExclusive: 18
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 7,
|
||||
endLineNumber: 2,
|
||||
endColumn: 4
|
||||
},
|
||||
text: " Please \ndo "
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 18,
|
||||
endExclusive: 29
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 2,
|
||||
startColumn: 4,
|
||||
endLineNumber: 2,
|
||||
endColumn: 15
|
||||
},
|
||||
command: { name: "subCommand" }
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 29,
|
||||
endExclusive: 63
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 2,
|
||||
startColumn: 15,
|
||||
endLineNumber: 3,
|
||||
endColumn: 18
|
||||
},
|
||||
text: " with @selection\nand @debugConsole"
|
||||
}
|
||||
],
|
||||
text: "@agent Please \ndo /subCommand with @selection\nand @debugConsole"
|
||||
}
|
|
@ -1,15 +1,18 @@
|
|||
[
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 13
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 14
|
||||
},
|
||||
text: "/explain this"
|
||||
}
|
||||
]
|
||||
{
|
||||
parts: [
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 13
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 14
|
||||
},
|
||||
text: "/explain this"
|
||||
}
|
||||
],
|
||||
text: "/explain this"
|
||||
}
|
|
@ -1,15 +1,18 @@
|
|||
[
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 26
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 27
|
||||
},
|
||||
text: "What does @selection mean?"
|
||||
}
|
||||
]
|
||||
{
|
||||
parts: [
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 26
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 27
|
||||
},
|
||||
text: "What does @selection mean?"
|
||||
}
|
||||
],
|
||||
text: "What does @selection mean?"
|
||||
}
|
|
@ -1,28 +1,31 @@
|
|||
[
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 4
|
||||
{
|
||||
parts: [
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 4
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 5
|
||||
},
|
||||
slashCommand: { command: "fix" }
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 5
|
||||
},
|
||||
slashCommand: { command: "fix" }
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 4,
|
||||
endExclusive: 9
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 5,
|
||||
endLineNumber: 1,
|
||||
endColumn: 10
|
||||
},
|
||||
text: " /fix"
|
||||
}
|
||||
]
|
||||
{
|
||||
range: {
|
||||
start: 4,
|
||||
endExclusive: 9
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 5,
|
||||
endLineNumber: 1,
|
||||
endColumn: 10
|
||||
},
|
||||
text: " /fix"
|
||||
}
|
||||
],
|
||||
text: "/fix /fix"
|
||||
}
|
|
@ -1,15 +1,18 @@
|
|||
[
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 4
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 5
|
||||
},
|
||||
text: "test"
|
||||
}
|
||||
]
|
||||
{
|
||||
parts: [
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 4
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 5
|
||||
},
|
||||
text: "test"
|
||||
}
|
||||
],
|
||||
text: "test"
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
parts: [
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 21
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 3,
|
||||
endColumn: 7
|
||||
},
|
||||
text: "line 1\nline 2\r\nline 3"
|
||||
}
|
||||
],
|
||||
text: "line 1\nline 2\r\nline 3"
|
||||
}
|
|
@ -1,28 +1,31 @@
|
|||
[
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 4
|
||||
{
|
||||
parts: [
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 4
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 5
|
||||
},
|
||||
slashCommand: { command: "fix" }
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 5
|
||||
},
|
||||
slashCommand: { command: "fix" }
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 4,
|
||||
endExclusive: 9
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 5,
|
||||
endLineNumber: 1,
|
||||
endColumn: 10
|
||||
},
|
||||
text: " this"
|
||||
}
|
||||
]
|
||||
{
|
||||
range: {
|
||||
start: 4,
|
||||
endExclusive: 9
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 5,
|
||||
endLineNumber: 1,
|
||||
endColumn: 10
|
||||
},
|
||||
text: " this"
|
||||
}
|
||||
],
|
||||
text: "/fix this"
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
parts: [
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 8
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 9
|
||||
},
|
||||
text: "What is "
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 8,
|
||||
endExclusive: 18
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 9,
|
||||
endLineNumber: 1,
|
||||
endColumn: 19
|
||||
},
|
||||
variableName: "selection",
|
||||
variableArg: ""
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 18,
|
||||
endExclusive: 19
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 19,
|
||||
endLineNumber: 1,
|
||||
endColumn: 20
|
||||
},
|
||||
text: "?"
|
||||
}
|
||||
],
|
||||
text: "What is @selection?"
|
||||
}
|
|
@ -1,42 +1,45 @@
|
|||
[
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 10
|
||||
{
|
||||
parts: [
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 10
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 11
|
||||
},
|
||||
text: "What does "
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 11
|
||||
{
|
||||
range: {
|
||||
start: 10,
|
||||
endExclusive: 20
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 11,
|
||||
endLineNumber: 1,
|
||||
endColumn: 21
|
||||
},
|
||||
variableName: "selection",
|
||||
variableArg: ""
|
||||
},
|
||||
text: "What does "
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 10,
|
||||
endExclusive: 20
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 11,
|
||||
endLineNumber: 1,
|
||||
endColumn: 21
|
||||
},
|
||||
variableName: "selection",
|
||||
variableArg: ""
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 20,
|
||||
endExclusive: 26
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 21,
|
||||
endLineNumber: 1,
|
||||
endColumn: 27
|
||||
},
|
||||
text: " mean?"
|
||||
}
|
||||
]
|
||||
{
|
||||
range: {
|
||||
start: 20,
|
||||
endExclusive: 26
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 21,
|
||||
endLineNumber: 1,
|
||||
endColumn: 27
|
||||
},
|
||||
text: " mean?"
|
||||
}
|
||||
],
|
||||
text: "What does @selection mean?"
|
||||
}
|
|
@ -36,7 +36,7 @@ suite('ChatRequestParser', () => {
|
|||
await assertSnapshot(result);
|
||||
});
|
||||
|
||||
test('_plain text with newlines', async () => {
|
||||
test('plain text with newlines', async () => {
|
||||
parser = instantiationService.createInstance(ChatRequestParser);
|
||||
const text = 'line 1\nline 2\r\nline 3';
|
||||
const result = await parser.parseChatRequest('1', text);
|
||||
|
@ -87,6 +87,17 @@ suite('ChatRequestParser', () => {
|
|||
await assertSnapshot(result);
|
||||
});
|
||||
|
||||
test('variable with question mark', async () => {
|
||||
const variablesService = mockObject<IChatVariablesService>()({});
|
||||
variablesService.hasVariable.returns(true);
|
||||
instantiationService.stub(IChatVariablesService, variablesService as any);
|
||||
|
||||
parser = instantiationService.createInstance(ChatRequestParser);
|
||||
const text = 'What is @selection?';
|
||||
const result = await parser.parseChatRequest('1', text);
|
||||
await assertSnapshot(result);
|
||||
});
|
||||
|
||||
test('invalid variables', async () => {
|
||||
const variablesService = mockObject<IChatVariablesService>()({});
|
||||
variablesService.hasVariable.returns(false);
|
||||
|
@ -108,6 +119,16 @@ suite('ChatRequestParser', () => {
|
|||
await assertSnapshot(result);
|
||||
});
|
||||
|
||||
test('agent with question mark', async () => {
|
||||
const agentsService = mockObject<IChatAgentService>()({});
|
||||
agentsService.getAgent.returns(<IChatAgentData>{ id: 'agent', metadata: { description: '', subCommands: [{ name: 'subCommand' }] } });
|
||||
instantiationService.stub(IChatAgentService, agentsService as any);
|
||||
|
||||
parser = instantiationService.createInstance(ChatRequestParser);
|
||||
const result = await parser.parseChatRequest('1', 'Are you there @agent?');
|
||||
await assertSnapshot(result);
|
||||
});
|
||||
|
||||
test('agent not first', async () => {
|
||||
const agentsService = mockObject<IChatAgentService>()({});
|
||||
agentsService.getAgent.returns(<IChatAgentData>{ id: 'agent', metadata: { description: '', subCommands: [{ name: 'subCommand' }] } });
|
||||
|
@ -118,7 +139,7 @@ suite('ChatRequestParser', () => {
|
|||
await assertSnapshot(result);
|
||||
});
|
||||
|
||||
test('_agents and variables and multiline', async () => {
|
||||
test('agents and variables and multiline', async () => {
|
||||
const agentsService = mockObject<IChatAgentService>()({});
|
||||
agentsService.getAgent.returns(<IChatAgentData>{ id: 'agent', metadata: { description: '', subCommands: [{ name: 'subCommand' }] } });
|
||||
instantiationService.stub(IChatAgentService, agentsService as any);
|
||||
|
|
|
@ -94,11 +94,11 @@ suite('Chat', () => {
|
|||
|
||||
const session1 = testDisposables.add(testService.startSession('provider1', CancellationToken.None));
|
||||
await session1.waitForInitialization();
|
||||
session1!.addRequest('request 1');
|
||||
session1!.addRequest({ parts: [], text: 'request 1' });
|
||||
|
||||
const session2 = testDisposables.add(testService.startSession('provider2', CancellationToken.None));
|
||||
await session2.waitForInitialization();
|
||||
session2!.addRequest('request 2');
|
||||
session2!.addRequest({ parts: [], text: 'request 2' });
|
||||
|
||||
assert.strictEqual(provider1.lastInitialState, undefined);
|
||||
assert.strictEqual(provider2.lastInitialState, undefined);
|
||||
|
|
|
@ -6,58 +6,78 @@
|
|||
import * as assert from 'assert';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
|
||||
import { ChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
|
||||
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
|
||||
import { ILogService, NullLogService } from 'vs/platform/log/common/log';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||
import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser';
|
||||
import { ChatVariablesService, IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
|
||||
|
||||
suite('ChatVariables', function () {
|
||||
|
||||
let service: ChatVariablesService;
|
||||
let instantiationService: TestInstantiationService;
|
||||
const testDisposables = ensureNoDisposablesAreLeakedInTestSuite();
|
||||
|
||||
setup(function () {
|
||||
service = new ChatVariablesService();
|
||||
instantiationService = testDisposables.add(new TestInstantiationService());
|
||||
instantiationService.stub(IStorageService, testDisposables.add(new TestStorageService()));
|
||||
instantiationService.stub(ILogService, new NullLogService());
|
||||
instantiationService.stub(IExtensionService, new TestExtensionService());
|
||||
instantiationService.stub(IChatVariablesService, service);
|
||||
instantiationService.stub(IChatAgentService, testDisposables.add(instantiationService.createInstance(ChatAgentService)));
|
||||
});
|
||||
|
||||
ensureNoDisposablesAreLeakedInTestSuite();
|
||||
|
||||
test('ChatVariables - resolveVariables', async function () {
|
||||
|
||||
const v1 = service.registerVariable({ name: 'foo', description: 'bar' }, async () => ([{ level: 'full', value: 'farboo' }]));
|
||||
const v2 = service.registerVariable({ name: 'far', description: 'boo' }, async () => ([{ level: 'full', value: 'farboo' }]));
|
||||
|
||||
const parser = instantiationService.createInstance(ChatRequestParser);
|
||||
|
||||
const resolveVariables = async (text: string) => {
|
||||
const result = await parser.parseChatRequest('1', text);
|
||||
return await service.resolveVariables(result, null!, CancellationToken.None);
|
||||
};
|
||||
|
||||
{
|
||||
const data = await service.resolveVariables('Hello @foo and@far', null!, CancellationToken.None);
|
||||
const data = await resolveVariables('Hello @foo and@far');
|
||||
assert.strictEqual(Object.keys(data.variables).length, 1);
|
||||
assert.deepEqual(Object.keys(data.variables).sort(), ['foo']);
|
||||
assert.strictEqual(data.prompt, 'Hello [@foo](values:foo) and@far');
|
||||
}
|
||||
{
|
||||
const data = await service.resolveVariables('@foo Hello', null!, CancellationToken.None);
|
||||
const data = await resolveVariables('@foo Hello');
|
||||
assert.strictEqual(Object.keys(data.variables).length, 1);
|
||||
assert.deepEqual(Object.keys(data.variables).sort(), ['foo']);
|
||||
assert.strictEqual(data.prompt, '[@foo](values:foo) Hello');
|
||||
}
|
||||
{
|
||||
const data = await service.resolveVariables('Hello @foo', null!, CancellationToken.None);
|
||||
const data = await resolveVariables('Hello @foo');
|
||||
assert.strictEqual(Object.keys(data.variables).length, 1);
|
||||
assert.deepEqual(Object.keys(data.variables).sort(), ['foo']);
|
||||
}
|
||||
{
|
||||
const data = await service.resolveVariables('Hello @foo?', null!, CancellationToken.None);
|
||||
const data = await resolveVariables('Hello @foo?');
|
||||
assert.strictEqual(Object.keys(data.variables).length, 1);
|
||||
assert.deepEqual(Object.keys(data.variables).sort(), ['foo']);
|
||||
assert.strictEqual(data.prompt, 'Hello [@foo](values:foo)?');
|
||||
}
|
||||
{
|
||||
const data = await service.resolveVariables('Hello @foo and@far @foo', null!, CancellationToken.None);
|
||||
const data = await resolveVariables('Hello @foo and@far @foo');
|
||||
assert.strictEqual(Object.keys(data.variables).length, 1);
|
||||
assert.deepEqual(Object.keys(data.variables).sort(), ['foo']);
|
||||
}
|
||||
{
|
||||
const data = await service.resolveVariables('Hello @foo and @far @foo', null!, CancellationToken.None);
|
||||
const data = await resolveVariables('Hello @foo and @far @foo');
|
||||
assert.strictEqual(Object.keys(data.variables).length, 2);
|
||||
assert.deepEqual(Object.keys(data.variables).sort(), ['far', 'foo']);
|
||||
}
|
||||
{
|
||||
const data = await service.resolveVariables('Hello @foo and @far @foo @unknown', null!, CancellationToken.None);
|
||||
const data = await resolveVariables('Hello @foo and @far @foo @unknown');
|
||||
assert.strictEqual(Object.keys(data.variables).length, 2);
|
||||
assert.deepEqual(Object.keys(data.variables).sort(), ['far', 'foo']);
|
||||
assert.strictEqual(data.prompt, 'Hello [@foo](values:foo) and [@far](values:far) [@foo](values:foo) @unknown');
|
||||
|
|
|
@ -68,19 +68,21 @@ registerSingleton(ICommentService, CommentService, InstantiationType.Delayed);
|
|||
|
||||
|
||||
export namespace CommentAccessibilityHelpNLS {
|
||||
export const escape = nls.localize('escape', "Dismiss the comment widget via Escape.");
|
||||
export const nextRange = nls.localize('next', "Navigate to the next commenting range via ({0}).");
|
||||
export const nextRangeNoKb = nls.localize('nextNoKb', "Run the command: Go to Next Commenting Range, which is currently not triggerable via keybinding.");
|
||||
export const previousRange = nls.localize('previous', "Navigate to the previous comment range via ({0}).");
|
||||
export const intro = nls.localize('intro', "The editor contains a commentable range. Some useful commands include:");
|
||||
export const introWidget = nls.localize('introWidget', "Some useful comment commands include:");
|
||||
export const escape = nls.localize('escape', "- Dismiss Comment (Escape)");
|
||||
export const nextRange = nls.localize('next', "- Navigate to the next commenting range ({0})");
|
||||
export const nextRangeNoKb = nls.localize('nextNoKb', "- Go to Next Commenting Range, which is currently not triggerable via keybinding.");
|
||||
export const previousRange = nls.localize('previous', "- Navigate to the previous commenting range ({0})");
|
||||
export const previousRangeNoKb = nls.localize('previousNoKb', "Run the command: Go to Previous Commenting Range, which is currently not triggerable via keybinding.");
|
||||
export const nextCommentThreadKb = nls.localize('nextCommentThreadKb', "Navigate to the next comment thread via ({0}).");
|
||||
export const nextCommentThreadNoKb = nls.localize('nextCommentThreadNoKb', "Run the command: Go to Next Comment Thread, which is currently not triggerable via keybinding.");
|
||||
export const previousCommentThreadKb = nls.localize('previousCommentThreadKb', "Navigate to the previous comment thread via ({0}).");
|
||||
export const previousCommentThreadNoKb = nls.localize('previousCommentThreadNoKb', "Run the command: Go to Previous Comment Thread, which is currently not triggerable via keybinding.");
|
||||
export const addComment = nls.localize('addComment', "Add a comment via ({0}).");
|
||||
export const addCommentNoKb = nls.localize('addCommentNoKb', "Add a comment via the command: Add Comment on Current Selection, which is currently not triggerable via keybinding.");
|
||||
export const submitComment = nls.localize('submitComment', "Submit the comment via ({0}).");
|
||||
export const submitCommentNoKb = nls.localize('submitCommentNoKb', "Submit the comment by navigating with tab to the button, as it's currently not triggerable via keybinding.");
|
||||
export const nextCommentThreadKb = nls.localize('nextCommentThreadKb', "- Navigate to the next comment thread ({0})");
|
||||
export const nextCommentThreadNoKb = nls.localize('nextCommentThreadNoKb', "- Run the command: Go to Next Comment Thread, which is currently not triggerable via keybinding.");
|
||||
export const previousCommentThreadKb = nls.localize('previousCommentThreadKb', "- Navigate to the previous comment thread ({0})");
|
||||
export const previousCommentThreadNoKb = nls.localize('previousCommentThreadNoKb', "- Run the command: Go to Previous Comment Thread, which is currently not triggerable via keybinding.");
|
||||
export const addComment = nls.localize('addComment', "- Add Comment ({0})");
|
||||
export const addCommentNoKb = nls.localize('addCommentNoKb', "- Add Comment on Current Selection, which is currently not triggerable via keybinding.");
|
||||
export const submitComment = nls.localize('submitComment', "- Submit Comment ({0})");
|
||||
export const submitCommentNoKb = nls.localize('submitCommentNoKb', "- Submit Comment, accessible via tabbing, as it's currently not triggerable with a keybinding.");
|
||||
}
|
||||
|
||||
export class CommentsAccessibilityHelpContribution extends Disposable {
|
||||
|
@ -114,12 +116,13 @@ export class CommentsAccessibilityHelpProvider implements IAccessibleContentProv
|
|||
provideContent(): string {
|
||||
this._element = document.activeElement as HTMLElement;
|
||||
const content: string[] = [];
|
||||
content.push(CommentAccessibilityHelpNLS.introWidget);
|
||||
content.push(CommentAccessibilityHelpNLS.escape);
|
||||
content.push(this._descriptionForCommand(CommentCommandId.Add, CommentAccessibilityHelpNLS.addComment, CommentAccessibilityHelpNLS.addCommentNoKb));
|
||||
content.push(this._descriptionForCommand(CommentCommandId.Submit, CommentAccessibilityHelpNLS.submitComment, CommentAccessibilityHelpNLS.submitCommentNoKb));
|
||||
content.push(this._descriptionForCommand(CommentCommandId.NextRange, CommentAccessibilityHelpNLS.nextRange, CommentAccessibilityHelpNLS.nextRangeNoKb));
|
||||
content.push(this._descriptionForCommand(CommentCommandId.PreviousRange, CommentAccessibilityHelpNLS.previousRange, CommentAccessibilityHelpNLS.previousRangeNoKb));
|
||||
content.push(this._descriptionForCommand(CommentCommandId.Submit, CommentAccessibilityHelpNLS.submitComment, CommentAccessibilityHelpNLS.submitCommentNoKb));
|
||||
return content.join('\n\n');
|
||||
return content.join('\n');
|
||||
}
|
||||
onClose(): void {
|
||||
this._element?.focus();
|
||||
|
|
|
@ -851,7 +851,6 @@ export class CommentController implements IEditorContribution {
|
|||
if (!this._hasRespondedToEditorChange) {
|
||||
if (this._commentInfos.some(commentInfo => commentInfo.commentingRanges.ranges.length > 0 || commentInfo.commentingRanges.fileComments)) {
|
||||
this._hasRespondedToEditorChange = true;
|
||||
this._activeEditorHasCommentingRange.set(true);
|
||||
const verbose = this.configurationService.getValue(AccessibilityVerbositySettingId.Comments);
|
||||
if (verbose) {
|
||||
const keybinding = this.keybindingService.lookupKeybinding(AccessibilityCommandId.OpenAccessibilityHelp)?.getAriaLabel();
|
||||
|
@ -863,8 +862,6 @@ export class CommentController implements IEditorContribution {
|
|||
} else {
|
||||
status(nls.localize('hasCommentRanges', "Editor has commenting ranges."));
|
||||
}
|
||||
} else {
|
||||
this._activeEditorHasCommentingRange.set(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1129,7 +1126,12 @@ export class CommentController implements IEditorContribution {
|
|||
// create viewzones
|
||||
this.removeCommentWidgetsAndStoreCache();
|
||||
|
||||
let hasCommentingRanges = false;
|
||||
this._commentInfos.forEach(info => {
|
||||
if (!hasCommentingRanges && (info.commentingRanges.ranges.length > 0 || info.commentingRanges.fileComments)) {
|
||||
hasCommentingRanges = true;
|
||||
}
|
||||
|
||||
const providerCacheStore = this._pendingNewCommentCache[info.owner];
|
||||
const providerEditsCacheStore = this._pendingEditsCache[info.owner];
|
||||
info.threads = info.threads.filter(thread => !thread.isDisposed);
|
||||
|
@ -1157,6 +1159,12 @@ export class CommentController implements IEditorContribution {
|
|||
|
||||
this._commentingRangeDecorator.update(this.editor, this._commentInfos);
|
||||
this._commentThreadRangeDecorator.update(this.editor, this._commentInfos);
|
||||
|
||||
if (hasCommentingRanges) {
|
||||
this._activeEditorHasCommentingRange.set(true);
|
||||
} else {
|
||||
this._activeEditorHasCommentingRange.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
public closeWidget(): void {
|
||||
|
|
|
@ -85,6 +85,15 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
|||
weight: KeybindingWeight.EditorContrib
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
command: {
|
||||
id: CommentCommandId.NextRange,
|
||||
title: nls.localize('comments.nextCommentingRange', "Go to Next Commenting Range"),
|
||||
category: 'Comments',
|
||||
},
|
||||
when: CommentContextKeys.activeEditorHasCommentingRange
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: CommentCommandId.PreviousRange,
|
||||
handler: async (accessor, args?: { range: IRange; fileComment: boolean }) => {
|
||||
|
@ -104,6 +113,15 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
|||
weight: KeybindingWeight.EditorContrib
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
command: {
|
||||
id: CommentCommandId.PreviousRange,
|
||||
title: nls.localize('comments.previousCommentingRange', "Go to Previous Commenting Range"),
|
||||
category: 'Comments',
|
||||
},
|
||||
when: CommentContextKeys.activeEditorHasCommentingRange
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: CommentCommandId.ToggleCommenting,
|
||||
handler: (accessor) => {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { createSingleCallFunction } from 'vs/base/common/functional';
|
||||
import { IReference } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ICustomEditorModel, ICustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditor';
|
||||
|
@ -45,7 +45,7 @@ export class CustomEditorModelManager implements ICustomEditorModelManager {
|
|||
return entry.model.then(model => {
|
||||
return {
|
||||
object: model,
|
||||
dispose: once(() => {
|
||||
dispose: createSingleCallFunction(() => {
|
||||
if (--entry!.counter <= 0) {
|
||||
entry.model.then(x => x.dispose());
|
||||
this._references.delete(key);
|
||||
|
|
|
@ -12,7 +12,7 @@ import { ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
|
|||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { ThemeIcon } from 'vs/base/common/themables';
|
||||
import { createMatches, FuzzyScore } from 'vs/base/common/filters';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { createSingleCallFunction } from 'vs/base/common/functional';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { localize } from 'vs/nls';
|
||||
|
@ -214,7 +214,7 @@ export abstract class AbstractExpressionsRenderer<T = IExpression> implements IT
|
|||
inputBox.focus();
|
||||
inputBox.select();
|
||||
|
||||
const done = once((success: boolean, finishEditing: boolean) => {
|
||||
const done = createSingleCallFunction((success: boolean, finishEditing: boolean) => {
|
||||
nameElement.style.display = '';
|
||||
valueElement.style.display = '';
|
||||
inputBoxContainer.style.display = 'none';
|
||||
|
|
|
@ -417,6 +417,8 @@ class WatchExpressionsDragAndDrop implements ITreeDragAndDrop<IExpression> {
|
|||
const position = targetElement instanceof Expression ? watches.indexOf(targetElement) : watches.length - 1;
|
||||
this.debugService.moveWatchExpression(draggedElement.getId(), position);
|
||||
}
|
||||
|
||||
dispose(): void { }
|
||||
}
|
||||
|
||||
registerAction2(class Collapse extends ViewAction<WatchExpressionsView> {
|
||||
|
|
|
@ -128,7 +128,7 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
|
|||
}
|
||||
|
||||
// re-schedule this bit of the operation to be off the critical path - in case glob-match is slow
|
||||
this._register(disposableTimeout(() => this.promptImportantRecommendations(uri, model), 0));
|
||||
disposableTimeout(() => this.promptImportantRecommendations(uri, model), 0, this._store);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -232,12 +232,12 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
|
|||
const disposables = new DisposableStore();
|
||||
disposables.add(model.onDidChangeLanguage(() => {
|
||||
// re-schedule this bit of the operation to be off the critical path - in case glob-match is slow
|
||||
disposables.add(disposableTimeout(() => {
|
||||
disposableTimeout(() => {
|
||||
if (!disposables.isDisposed) {
|
||||
this.promptImportantRecommendations(uri, model, unmatchedRecommendations);
|
||||
disposables.dispose();
|
||||
}
|
||||
}, 0));
|
||||
}, 0, disposables);
|
||||
}));
|
||||
disposables.add(model.onWillDispose(() => disposables.dispose()));
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ import { FileAccess, Schemas } from 'vs/base/common/network';
|
|||
import { mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { listenStream } from 'vs/base/common/stream';
|
||||
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { createSingleCallFunction } from 'vs/base/common/functional';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { canceled } from 'vs/base/common/errors';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
@ -710,7 +710,7 @@ export class FileDownload {
|
|||
const disposables = new DisposableStore();
|
||||
disposables.add(toDisposable(() => target.close()));
|
||||
|
||||
disposables.add(once(token.onCancellationRequested)(() => {
|
||||
disposables.add(createSingleCallFunction(token.onCancellationRequested)(() => {
|
||||
disposables.dispose();
|
||||
reject(canceled());
|
||||
}));
|
||||
|
|
|
@ -23,7 +23,7 @@ import { IFilesConfiguration, UndoConfirmLevel } from 'vs/workbench/contrib/file
|
|||
import { dirname, joinPath, distinctParents } from 'vs/base/common/resources';
|
||||
import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { localize } from 'vs/nls';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { createSingleCallFunction } from 'vs/base/common/functional';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { equals, deepClone } from 'vs/base/common/objects';
|
||||
import * as path from 'vs/base/common/path';
|
||||
|
@ -559,7 +559,7 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
|
|||
inputBox.focus();
|
||||
inputBox.select({ start: 0, end: lastDot > 0 && !stat.isDirectory ? lastDot : value.length });
|
||||
|
||||
const done = once((success: boolean, finishEditing: boolean) => {
|
||||
const done = createSingleCallFunction((success: boolean, finishEditing: boolean) => {
|
||||
label.element.style.display = 'none';
|
||||
const value = inputBox.value;
|
||||
dispose(toDispose);
|
||||
|
@ -1039,7 +1039,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
|
|||
private compressedDragOverElement: HTMLElement | undefined;
|
||||
private compressedDropTargetDisposable: IDisposable = Disposable.None;
|
||||
|
||||
private toDispose: IDisposable[];
|
||||
private disposables = new DisposableStore();
|
||||
private dropEnabled = false;
|
||||
|
||||
constructor(
|
||||
|
@ -1054,15 +1054,13 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
|
|||
@IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService,
|
||||
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService
|
||||
) {
|
||||
this.toDispose = [];
|
||||
|
||||
const updateDropEnablement = (e: IConfigurationChangeEvent | undefined) => {
|
||||
if (!e || e.affectsConfiguration('explorer.enableDragAndDrop')) {
|
||||
this.dropEnabled = this.configurationService.getValue('explorer.enableDragAndDrop');
|
||||
}
|
||||
};
|
||||
updateDropEnablement(undefined);
|
||||
this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => updateDropEnablement(e)));
|
||||
this.disposables.add(this.configurationService.onDidChangeConfiguration(e => updateDropEnablement(e)));
|
||||
}
|
||||
|
||||
onDragOver(data: IDragAndDropData, target: ExplorerItem | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction {
|
||||
|
@ -1482,6 +1480,10 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
|
|||
onDragEnd(): void {
|
||||
this.compressedDropTargetDisposable.dispose();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.compressedDropTargetDisposable.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
function getIconLabelNameFromHTMLElement(target: HTMLElement | EventTarget | Element | null): { element: HTMLElement; count: number; index: number } | null {
|
||||
|
|
|
@ -711,6 +711,8 @@ class OpenEditorsDragAndDrop implements IListDragAndDrop<OpenEditor | IEditorGro
|
|||
this.dropHandler.handleDrop(originalEvent, () => group, () => group.focus(), { index });
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void { }
|
||||
}
|
||||
|
||||
class OpenEditorsAccessibilityProvider implements IListAccessibilityProvider<OpenEditor | IEditorGroup> {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue