Merge branch 'main' into ulugbekna/implement-code-mappers-context

This commit is contained in:
Ulugbek Abdullaev 2023-09-22 14:43:25 +02:00
commit 39dc875c4e
132 changed files with 1751 additions and 1212 deletions

View file

@ -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).

View file

@ -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)]

View file

@ -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(),
};

View file

@ -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(&current_version) {
ctx.log.result(format!(

View file

@ -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)

View file

@ -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,

View file

@ -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>>()?;

View file

@ -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> {

View file

@ -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
}

View file

@ -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.

View file

@ -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",

View file

@ -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);

View file

@ -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;

View file

@ -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();
}
}

View file

@ -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();
}
}
/**

View file

@ -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 {

View file

@ -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 {

View file

@ -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>>();

View file

@ -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 {

View file

@ -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;

View file

@ -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);

View file

@ -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();

View file

@ -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)&nbsp;$(not&nbsp;a&nbsp;theme&nbsp;icon)&nbsp;$(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)&nbsp;$(not&nbsp;a&nbsp;theme&nbsp;icon)&nbsp;$(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&lt;b&gt;b&lt;/b&gt;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">`);
});
});

View file

@ -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);
}

View file

@ -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

View file

@ -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;

View file

@ -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);

View file

@ -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'
)

View file

@ -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");
}

View file

@ -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);
}

View file

@ -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

View file

@ -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);
});

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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,

View file

@ -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();
}

View file

@ -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);

View file

@ -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()));
}
}

View file

@ -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> {

View file

@ -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);

View file

@ -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;

View file

@ -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) {

View file

@ -659,6 +659,8 @@ export class ResourceListDnDHandler<T> implements IListDragAndDrop<T> {
}
drop(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): void { }
dispose(): void { }
}
//#endregion

View file

@ -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 {

View file

@ -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
},

View file

@ -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 {

View file

@ -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({

View file

@ -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';

View file

@ -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));
}

View file

@ -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

View file

@ -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;

View file

@ -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 {

View file

@ -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();
}
}

View file

@ -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 {

View file

@ -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([]);

View file

@ -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) {

View file

@ -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();

View file

@ -1834,4 +1834,6 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop<ITreeItem> {
this.dragCancellationToken?.cancel();
}
}
dispose(): void { }
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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) {

View file

@ -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;
}

View file

@ -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) => {

View file

@ -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);
});
}

View file

@ -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,

View file

@ -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;

View file

@ -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,

View 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}`;
}
}

View file

@ -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) { }
}

View file

@ -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;

View file

@ -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 {

View file

@ -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);

View file

@ -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;

View file

@ -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"
}
]

View file

@ -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"
}
]

View file

@ -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"
}

View file

@ -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?"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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?"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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?"
}

View file

@ -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?"
}

View file

@ -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);

View file

@ -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);

View file

@ -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');

View file

@ -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();

View file

@ -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 {

View file

@ -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) => {

View file

@ -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);

View file

@ -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';

View file

@ -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> {

View file

@ -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()));
}

View file

@ -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());
}));

View file

@ -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 {

View file

@ -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