Merge branch 'main' into joh/voluminous-lobster

This commit is contained in:
Johannes 2022-06-10 09:07:30 +02:00
commit ed9ed45794
No known key found for this signature in database
GPG key ID: 6DEF802A22264FCA
115 changed files with 2137 additions and 1993 deletions

View file

@ -318,7 +318,7 @@ steps:
targetPath: .build/logs
displayName: "Publish Log Files"
continueOnError: true
condition: failed()
condition: succeededOrFailed()
- task: PublishTestResults@2
displayName: Publish Tests Results

View file

@ -387,7 +387,7 @@ steps:
targetPath: .build/logs
displayName: "Publish Log Files"
continueOnError: true
condition: and(failed(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
condition: and(succeededOrFailed(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
- task: PublishTestResults@2
displayName: Publish Tests Results

View file

@ -339,7 +339,7 @@ steps:
targetPath: .build\logs
displayName: "Publish Log Files"
continueOnError: true
condition: and(failed(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64'))
condition: and(succeededOrFailed(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64'))
- task: PublishTestResults@2
displayName: Publish Tests Results

View file

@ -3129,9 +3129,7 @@ export class CommandCenter {
if (result) {
const resultFn = choices.get(result);
if (resultFn) {
resultFn();
}
resultFn?.();
}
});
};

View file

@ -556,9 +556,7 @@ export class Git {
private async _exec(args: string[], options: SpawnOptions = {}): Promise<IExecutionResult<string>> {
const child = this.spawn(args, options);
if (options.onSpawn) {
options.onSpawn(child);
}
options.onSpawn?.(child);
if (options.input) {
child.stdin!.end(options.input, 'utf8');

View file

@ -251,9 +251,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
// The settings have changed. Is send on server activation as well.
connection.onDidChangeConfiguration((change) => {
const settings = <Settings>change.settings;
if (runtime.configureHttpRequests) {
runtime.configureHttpRequests(settings?.http?.proxy, !!settings.http?.proxyStrictSSL);
}
runtime.configureHttpRequests?.(settings?.http?.proxy, !!settings.http?.proxyStrictSSL);
jsonConfigurationSettings = settings.json?.schemas;
validateEnabled = !!settings.json?.validate?.enable;
updateConfiguration();

View file

@ -185,12 +185,12 @@ function stripAngleBrackets(link: string) {
/**
* Matches `[text](link)`
*/
const linkPattern = /(\[((!\[[^\]]*?\]\(\s*)([^\s\(\)]+?)\s*\)\]|(?:\\\]|[^\]])*\])\(\s*)(([^\s\(\)]|\([^\s\(\)]*?\))+)\s*("[^"]*"|'[^']*'|\([^\(\)]*\))?\s*\)/g;
const linkPattern = /(\[((!\[[^\]]*?\]\(\s*)([^\s\(\)]+?)\s*\)\]|(?:\\\]|[^\]]|\][^(])*\])\(\s*)(([^\s\(\)]|\([^\s\(\)]*?\))+)\s*("[^"]*"|'[^']*'|\([^\(\)]*\))?\s*\)/g;
/**
* Matches `[text](<link>)`
*/
const linkPatternAngle = /(\[((!\[[^\]]*?\]\(\s*)([^\s\(\)]+?)\s*\)\]|(?:\\\]|[^\]])*\])\(\s*<)(([^<>]|\([^\s\(\)]*?\))+)>\s*("[^"]*"|'[^']*'|\([^\(\)]*\))?\s*\)/g;
const linkPatternAngle = /(\[((!\[[^\]]*?\]\(\s*)([^\s\(\)]+?)\s*\)\]|(?:\\\]|[^\]]|\][^(])*\])\(\s*<)(([^<>]|\([^\s\(\)]*?\))+)>\s*("[^"]*"|'[^']*'|\([^\(\)]*\))?\s*\)/g;
/**

View file

@ -101,6 +101,21 @@ suite('markdown.DocumentLinkProvider', () => {
}
});
test('Should ignore texts in brackets inside link title (#150921)', async () => {
{
const links = await getLinksForFile('[some [inner bracket pairs] in title](<link>)');
assertLinksEqual(links, [
new vscode.Range(0, 39, 0, 43),
]);
}
{
const links = await getLinksForFile('[some [inner bracket pairs] in title](link)');
assertLinksEqual(links, [
new vscode.Range(0, 38, 0, 42)
]);
}
});
test('Should handle two links without space', async () => {
const links = await getLinksForFile('a ([test](test)[test2](test2)) c');
assertLinksEqual(links, [

View file

@ -287,8 +287,9 @@ async function provideNpmScriptsForFolder(context: ExtensionContext, packageJson
result.push({ task, location: new Location(packageJsonUri, nameRange) });
}
// always add npm install (without a problem matcher)
result.push({ task: await createTask(packageManager, INSTALL_SCRIPT, [INSTALL_SCRIPT], folder, packageJsonUri, 'install dependencies from package', []) });
if (!workspace.getConfiguration('npm', folder).get<string[]>('scriptExplorerExclude', []).find(e => e.includes('install'))) {
result.push({ task: await createTask(packageManager, INSTALL_SCRIPT, [INSTALL_SCRIPT], folder, packageJsonUri, 'install dependencies from package', []) });
}
return result;
}

View file

@ -927,6 +927,80 @@ const apiTestContentProvider: vscode.NotebookContentProvider = {
assert.strictEqual(executionWasCancelled, true);
});
test('appendOutput to different cell', async function () {
const notebook = await openRandomNotebookDocument();
const editor = await vscode.window.showNotebookDocument(notebook);
const cell0 = editor.notebook.cellAt(0);
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
const cell1 = editor.notebook.cellAt(1);
const nextCellKernel = new class extends Kernel {
constructor() {
super('nextCellKernel', 'Append to cell kernel');
}
override async _runCell(cell: vscode.NotebookCell) {
const task = this.controller.createNotebookCellExecution(cell);
task.start();
await task.appendOutput([new vscode.NotebookCellOutput([
vscode.NotebookCellOutputItem.text('my output')
])], cell1);
await task.appendOutput([new vscode.NotebookCellOutput([
vscode.NotebookCellOutputItem.text('my output 2')
])], cell1);
task.end(true);
}
};
testDisposables.push(nextCellKernel.controller);
await withEvent<vscode.NotebookDocumentChangeEvent>(vscode.workspace.onDidChangeNotebookDocument, async (event) => {
await assertKernel(nextCellKernel, notebook);
await vscode.commands.executeCommand('notebook.cell.execute');
await event;
assert.strictEqual(cell0.outputs.length, 0, 'should not change cell 0');
assert.strictEqual(cell1.outputs.length, 2, 'should update cell 1');
assert.strictEqual(cell1.outputs[0].items.length, 1);
assert.deepStrictEqual(new TextDecoder().decode(cell1.outputs[0].items[0].data), 'my output');
});
});
test('replaceOutput to different cell', async function () {
const notebook = await openRandomNotebookDocument();
const editor = await vscode.window.showNotebookDocument(notebook);
const cell0 = editor.notebook.cellAt(0);
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
const cell1 = editor.notebook.cellAt(1);
const nextCellKernel = new class extends Kernel {
constructor() {
super('nextCellKernel', 'Replace to cell kernel');
}
override async _runCell(cell: vscode.NotebookCell) {
const task = this.controller.createNotebookCellExecution(cell);
task.start();
await task.replaceOutput([new vscode.NotebookCellOutput([
vscode.NotebookCellOutputItem.text('my output')
])], cell1);
await task.replaceOutput([new vscode.NotebookCellOutput([
vscode.NotebookCellOutputItem.text('my output 2')
])], cell1);
task.end(true);
}
};
testDisposables.push(nextCellKernel.controller);
await withEvent<vscode.NotebookDocumentChangeEvent>(vscode.workspace.onDidChangeNotebookDocument, async (event) => {
await assertKernel(nextCellKernel, notebook);
await vscode.commands.executeCommand('notebook.cell.execute');
await event;
assert.strictEqual(cell0.outputs.length, 0, 'should not change cell 0');
assert.strictEqual(cell1.outputs.length, 1, 'should update cell 1');
assert.strictEqual(cell1.outputs[0].items.length, 1);
assert.deepStrictEqual(new TextDecoder().decode(cell1.outputs[0].items[0].data), 'my output 2');
});
});
});
(vscode.env.uiKind === vscode.UIKind.Web ? suite.skip : suite)('statusbar', () => {

View file

@ -1,6 +1,6 @@
{
"name": "code-oss-dev",
"version": "1.68.0",
"version": "1.69.0",
"distro": "2966cd72fc1a3a5fb89bf2d85a1a66e56206961a",
"author": {
"name": "Microsoft Corporation"

View file

@ -1733,9 +1733,7 @@ export class DragAndDropObserver extends Disposable {
this._register(addDisposableListener(this.element, EventType.DRAG_OVER, (e: DragEvent) => {
e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome)
if (this.callbacks.onDragOver) {
this.callbacks.onDragOver(e, e.timeStamp - this.dragStartTime);
}
this.callbacks.onDragOver?.(e, e.timeStamp - this.dragStartTime);
}));
this._register(addDisposableListener(this.element, EventType.DRAG_LEAVE, (e: DragEvent) => {

View file

@ -220,9 +220,7 @@ export class ContextView extends Disposable {
this.doLayout();
// Focus
if (this.delegate.focus) {
this.delegate.focus();
}
this.delegate.focus?.();
}
getViewElement(): HTMLElement {

View file

@ -857,9 +857,7 @@ class LeafNode implements ISplitView<ILayoutContext>, IDisposable {
set boundarySashes(boundarySashes: IRelativeBoundarySashes) {
this._boundarySashes = boundarySashes;
if (this.view.setBoundarySashes) {
this.view.setBoundarySashes(toAbsoluteBoundarySashes(boundarySashes, this.orientation));
}
this.view.setBoundarySashes?.(toAbsoluteBoundarySashes(boundarySashes, this.orientation));
}
layout(size: number, offset: number, ctx: ILayoutContext | undefined): void {
@ -897,9 +895,7 @@ class LeafNode implements ISplitView<ILayoutContext>, IDisposable {
}
setVisible(visible: boolean): void {
if (this.view.setVisible) {
this.view.setVisible(visible);
}
this.view.setVisible?.(visible);
}
dispose(): void {

View file

@ -1032,9 +1032,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.currentDragData = new ElementsDragAndDropData(elements);
StaticDND.CurrentDragAndDropData = new ExternalElementsDragAndDropData(elements);
if (this.dnd.onDragStart) {
this.dnd.onDragStart(this.currentDragData, event);
}
this.dnd.onDragStart?.(this.currentDragData, event);
}
private onDragOver(event: IListDragEvent<T>): boolean {
@ -1169,9 +1167,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.currentDragData = undefined;
StaticDND.CurrentDragAndDropData = undefined;
if (this.dnd.onDragEnd) {
this.dnd.onDragEnd(event);
}
this.dnd.onDragEnd?.(event);
}
private clearDragOverFeedback(): void {
@ -1379,16 +1375,12 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
if (renderer) {
renderer.renderElement(item.element, index, row.templateData, undefined);
if (renderer.disposeElement) {
renderer.disposeElement(item.element, index, row.templateData, undefined);
}
renderer.disposeElement?.(item.element, index, row.templateData, undefined);
}
item.size = row.domNode.offsetHeight;
if (this.virtualDelegate.setDynamicHeight) {
this.virtualDelegate.setDynamicHeight(item.element, item.size);
}
this.virtualDelegate.setDynamicHeight?.(item.element, item.size);
item.lastDynamicHeightWidth = this.renderWidth;
this.rowsContainer.removeChild(row.domNode);
@ -1429,9 +1421,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
if (item.row) {
const renderer = this.renderers.get(item.row.templateId);
if (renderer) {
if (renderer.disposeElement) {
renderer.disposeElement(item.element, -1, item.row.templateData, undefined);
}
renderer.disposeElement?.(item.element, -1, item.row.templateData, undefined);
renderer.disposeTemplate(item.row.templateData);
}
}

View file

@ -1109,9 +1109,7 @@ class PipelineRenderer<T> implements IListRenderer<T, any> {
let i = 0;
for (const renderer of this.renderers) {
if (renderer.disposeElement) {
renderer.disposeElement(element, index, templateData[i], height);
}
renderer.disposeElement?.(element, index, templateData[i], height);
i += 1;
}
@ -1182,9 +1180,7 @@ class ListViewDragAndDrop<T> implements IListViewDragAndDrop<T> {
}
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
if (this.dnd.onDragStart) {
this.dnd.onDragStart(data, originalEvent);
}
this.dnd.onDragStart?.(data, originalEvent);
}
onDragOver(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): boolean | IListDragOverReaction {
@ -1196,9 +1192,7 @@ class ListViewDragAndDrop<T> implements IListViewDragAndDrop<T> {
}
onDragEnd(originalEvent: DragEvent): void {
if (this.dnd.onDragEnd) {
this.dnd.onDragEnd(originalEvent);
}
this.dnd.onDragEnd?.(originalEvent);
}
drop(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): void {
@ -1328,9 +1322,7 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
if (this.accessibilityProvider) {
baseRenderers.push(new AccessibiltyRenderer<T>(this.accessibilityProvider));
if (this.accessibilityProvider.onDidChangeActiveDescendant) {
this.accessibilityProvider.onDidChangeActiveDescendant(this.onDidChangeActiveDescendant, this, this.disposables);
}
this.accessibilityProvider.onDidChangeActiveDescendant?.(this.onDidChangeActiveDescendant, this, this.disposables);
}
renderers = renderers.map(r => new PipelineRenderer(r.templateId, [...baseRenderers, r]));

View file

@ -231,9 +231,7 @@ abstract class ViewItem<TLayoutContext> {
this.container.classList.toggle('visible', visible);
if (this.view.setVisible) {
this.view.setVisible(visible);
}
this.view.setVisible?.(visible);
}
get minimumSize(): number { return this.visible ? this.view.minimumSize : 0; }

View file

@ -70,9 +70,7 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
}
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
if (this.dnd.onDragStart) {
this.dnd.onDragStart(asTreeDragAndDropData(data), originalEvent);
}
this.dnd.onDragStart?.(asTreeDragAndDropData(data), originalEvent);
}
onDragOver(data: IDragAndDropData, targetNode: ITreeNode<T, TFilterData> | undefined, targetIndex: number | undefined, originalEvent: DragEvent, raw = true): boolean | IListDragOverReaction {
@ -137,9 +135,7 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
}
onDragEnd(originalEvent: DragEvent): void {
if (this.dnd.onDragEnd) {
this.dnd.onDragEnd(originalEvent);
}
this.dnd.onDragEnd?.(originalEvent);
}
}
@ -220,9 +216,7 @@ export class ComposedTreeDelegate<T, N extends { element: T }> implements IListV
}
setDynamicHeight(element: N, height: number): void {
if (this.delegate.setDynamicHeight) {
this.delegate.setDynamicHeight(element.element, height);
}
this.delegate.setDynamicHeight?.(element.element, height);
}
}
@ -350,9 +344,7 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
Event.map(onDidChangeCollapseState, e => e.node)(this.onDidChangeNodeTwistieState, this, this.disposables);
if (renderer.onDidChangeTwistieState) {
renderer.onDidChangeTwistieState(this.onDidChangeTwistieState, this, this.disposables);
}
renderer.onDidChangeTwistieState?.(this.onDidChangeTwistieState, this, this.disposables);
}
updateOptions(options: ITreeRendererOptions = {}): void {
@ -414,9 +406,7 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
disposeElement(node: ITreeNode<T, TFilterData>, index: number, templateData: ITreeListTemplateData<TTemplateData>, height: number | undefined): void {
templateData.indentGuidesDisposable.dispose();
if (this.renderer.disposeElement) {
this.renderer.disposeElement(node, index, templateData.templateData, height);
}
this.renderer.disposeElement?.(node, index, templateData.templateData, height);
if (typeof height === 'number') {
this.renderedNodes.delete(node);

View file

@ -119,9 +119,7 @@ class AsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements IT
}
disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
if (this.renderer.disposeElement) {
this.renderer.disposeElement(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
}
this.renderer.disposeElement?.(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
}
disposeTemplate(templateData: IDataTreeListTemplateData<TTemplateData>): void {
@ -196,9 +194,7 @@ class AsyncDataTreeNodeListDragAndDrop<TInput, T> implements IListDragAndDrop<IA
}
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
if (this.dnd.onDragStart) {
this.dnd.onDragStart(asAsyncDataTreeDragAndDropData(data), originalEvent);
}
this.dnd.onDragStart?.(asAsyncDataTreeDragAndDropData(data), originalEvent);
}
onDragOver(data: IDragAndDropData, targetNode: IAsyncDataTreeNode<TInput, T> | undefined, targetIndex: number | undefined, originalEvent: DragEvent, raw = true): boolean | IListDragOverReaction {
@ -210,9 +206,7 @@ class AsyncDataTreeNodeListDragAndDrop<TInput, T> implements IListDragAndDrop<IA
}
onDragEnd(originalEvent: DragEvent): void {
if (this.dnd.onDragEnd) {
this.dnd.onDragEnd(originalEvent);
}
this.dnd.onDragEnd?.(originalEvent);
}
}
@ -1086,15 +1080,11 @@ class CompressibleAsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> i
}
disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
if (this.renderer.disposeElement) {
this.renderer.disposeElement(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
}
this.renderer.disposeElement?.(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
}
disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
if (this.renderer.disposeCompressedElements) {
this.renderer.disposeCompressedElements(this.compressibleNodeMapperProvider().map(node) as ITreeNode<ICompressedTreeNode<T>, TFilterData>, index, templateData.templateData, height);
}
this.renderer.disposeCompressedElements?.(this.compressibleNodeMapperProvider().map(node) as ITreeNode<ICompressedTreeNode<T>, TFilterData>, index, templateData.templateData, height);
}
disposeTemplate(templateData: IDataTreeListTemplateData<TTemplateData>): void {

View file

@ -140,9 +140,7 @@ export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | nu
insertedElements.add(id);
this.nodesByIdentity.set(id, node);
if (outerOnDidCreateNode) {
outerOnDidCreateNode(node);
}
outerOnDidCreateNode?.(node);
};
onDidDeleteNode = (node: ITreeNode<T, TFilterData>) => {

View file

@ -554,9 +554,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
node.renderNodeCount = renderNodeCount;
}
if (onDidCreateNode) {
onDidCreateNode(node);
}
onDidCreateNode?.(node);
return node;
}

View file

@ -140,13 +140,9 @@ class CompressibleRenderer<T extends NonNullable<any>, TFilterData, TTemplateDat
disposeElement(node: ITreeNode<T, TFilterData>, index: number, templateData: CompressibleTemplateData<T, TFilterData, TTemplateData>, height: number | undefined): void {
if (templateData.compressedTreeNode) {
if (this.renderer.disposeCompressedElements) {
this.renderer.disposeCompressedElements(templateData.compressedTreeNode, index, templateData.data, height);
}
this.renderer.disposeCompressedElements?.(templateData.compressedTreeNode, index, templateData.data, height);
} else {
if (this.renderer.disposeElement) {
this.renderer.disposeElement(node, index, templateData.data, height);
}
this.renderer.disposeElement?.(node, index, templateData.data, height);
}
}

View file

@ -354,9 +354,7 @@ export class Delayer<T> implements IDisposable {
this.cancelTimeout();
if (this.completionPromise) {
if (this.doReject) {
this.doReject(new CancellationError());
}
this.doReject?.(new CancellationError());
this.completionPromise = null;
}
}
@ -885,9 +883,7 @@ export class RunOnceScheduler {
}
protected doRun(): void {
if (this.runner) {
this.runner();
}
this.runner?.();
}
}
@ -960,9 +956,7 @@ export class ProcessTimeRunOnceScheduler {
// time elapsed
clearInterval(this.intervalToken);
this.intervalToken = -1;
if (this.runner) {
this.runner();
}
this.runner?.();
}
}
@ -985,9 +979,7 @@ export class RunOnceWorker<T> extends RunOnceScheduler {
const units = this.units;
this.units = [];
if (this.runner) {
this.runner(units);
}
this.runner?.(units);
}
override dispose(): void {

View file

@ -95,6 +95,7 @@ export interface SerializedError {
readonly name: string;
readonly message: string;
readonly stack: string;
readonly noTelemetry: boolean;
}
export function transformErrorForSerialization(error: Error): SerializedError;
@ -107,7 +108,8 @@ export function transformErrorForSerialization(error: any): any {
$isError: true,
name,
message,
stack
stack,
noTelemetry: error instanceof ErrorNoTelemetry
};
}

View file

@ -690,9 +690,7 @@ export class Emitter<T> {
}
const result = listener.subscription.set(() => {
if (removeMonitor) {
removeMonitor();
}
removeMonitor?.();
if (!this._disposed) {
removeListener();
if (this._options && this._options.onLastListenerRemove) {

View file

@ -283,9 +283,7 @@ export class SimpleWorkerClient<W extends object, H extends object> extends Disp
(err: any) => {
// in Firefox, web workers fail lazily :(
// we will reject the proxy
if (lazyProxyReject) {
lazyProxyReject(err);
}
lazyProxyReject?.(err);
}
));

View file

@ -15,9 +15,7 @@ export function popup(items: IContextMenuItem[], options?: IPopupOptions, onHide
const onClickChannel = `vscode:onContextMenu${contextMenuId}`;
const onClickChannelHandler = (event: unknown, itemId: number, context: IContextMenuEvent) => {
const item = processedItems[itemId];
if (item.click) {
item.click(context);
}
item.click?.(context);
};
ipcRenderer.once(onClickChannel, onClickChannelHandler);
@ -28,9 +26,7 @@ export function popup(items: IContextMenuItem[], options?: IPopupOptions, onHide
ipcRenderer.removeListener(onClickChannel, onClickChannelHandler);
if (onHide) {
onHide();
}
onHide?.();
});
ipcRenderer.send(CONTEXT_MENU_CHANNEL, contextMenuId, items.map(item => createItem(item, processedItems)), onClickChannel, options);

View file

@ -707,9 +707,7 @@ export class ChannelClient implements IChannelClient, IDisposable {
const handler = this.handlers.get(response.id);
if (handler) {
handler(response);
}
handler?.(response);
}
@memoize

View file

@ -26,9 +26,7 @@ export class Server<TContext extends string> extends IPCServer<TContext> {
super({
send: r => {
try {
if (process.send) {
process.send((<Buffer>r.buffer).toString('base64'));
}
process.send?.((<Buffer>r.buffer).toString('base64'));
} catch (e) { /* not much to do */ }
},
onMessage: Event.fromNodeEventEmitter(process, 'message', msg => VSBuffer.wrap(Buffer.from(msg, 'base64')))

View file

@ -1406,9 +1406,7 @@ export class QuickInputController extends Disposable {
return new Promise<R>((doResolve, reject) => {
let resolve = (result: R) => {
resolve = doResolve;
if (options.onKeyMods) {
options.onKeyMods(input.keyMods);
}
options.onKeyMods?.(input.keyMods);
doResolve(result);
};
if (token.isCancellationRequested) {

View file

@ -440,14 +440,10 @@ class SQLiteStorageDatabaseLogger {
}
trace(msg: string): void {
if (this.logTrace) {
this.logTrace(msg);
}
this.logTrace?.(msg);
}
error(error: string | Error): void {
if (this.logError) {
this.logError(error);
}
this.logError?.(error);
}
}

View file

@ -47,13 +47,9 @@ export function endTrackingDisposables(): void {
}
export function beginLoggingFS(withStacks: boolean = false): void {
if ((<any>self).beginLoggingFS) {
(<any>self).beginLoggingFS(withStacks);
}
(<any>self).beginLoggingFS?.(withStacks);
}
export function endLoggingFS(): void {
if ((<any>self).endLoggingFS) {
(<any>self).endLoggingFS();
}
(<any>self).endLoggingFS?.();
}

View file

@ -33,69 +33,47 @@ export class ViewUserInputEvents {
}
public emitKeyDown(e: IKeyboardEvent): void {
if (this.onKeyDown) {
this.onKeyDown(e);
}
this.onKeyDown?.(e);
}
public emitKeyUp(e: IKeyboardEvent): void {
if (this.onKeyUp) {
this.onKeyUp(e);
}
this.onKeyUp?.(e);
}
public emitContextMenu(e: IEditorMouseEvent): void {
if (this.onContextMenu) {
this.onContextMenu(this._convertViewToModelMouseEvent(e));
}
this.onContextMenu?.(this._convertViewToModelMouseEvent(e));
}
public emitMouseMove(e: IEditorMouseEvent): void {
if (this.onMouseMove) {
this.onMouseMove(this._convertViewToModelMouseEvent(e));
}
this.onMouseMove?.(this._convertViewToModelMouseEvent(e));
}
public emitMouseLeave(e: IPartialEditorMouseEvent): void {
if (this.onMouseLeave) {
this.onMouseLeave(this._convertViewToModelMouseEvent(e));
}
this.onMouseLeave?.(this._convertViewToModelMouseEvent(e));
}
public emitMouseDown(e: IEditorMouseEvent): void {
if (this.onMouseDown) {
this.onMouseDown(this._convertViewToModelMouseEvent(e));
}
this.onMouseDown?.(this._convertViewToModelMouseEvent(e));
}
public emitMouseUp(e: IEditorMouseEvent): void {
if (this.onMouseUp) {
this.onMouseUp(this._convertViewToModelMouseEvent(e));
}
this.onMouseUp?.(this._convertViewToModelMouseEvent(e));
}
public emitMouseDrag(e: IEditorMouseEvent): void {
if (this.onMouseDrag) {
this.onMouseDrag(this._convertViewToModelMouseEvent(e));
}
this.onMouseDrag?.(this._convertViewToModelMouseEvent(e));
}
public emitMouseDrop(e: IPartialEditorMouseEvent): void {
if (this.onMouseDrop) {
this.onMouseDrop(this._convertViewToModelMouseEvent(e));
}
this.onMouseDrop?.(this._convertViewToModelMouseEvent(e));
}
public emitMouseDropCanceled(): void {
if (this.onMouseDropCanceled) {
this.onMouseDropCanceled();
}
this.onMouseDropCanceled?.();
}
public emitMouseWheel(e: IMouseWheelEvent): void {
if (this.onMouseWheel) {
this.onMouseWheel(e);
}
this.onMouseWheel?.(e);
}
private _convertViewToModelMouseEvent(e: IEditorMouseEvent): IEditorMouseEvent;

View file

@ -2136,9 +2136,7 @@ class CodeEditorWidgetFocusTracker extends Disposable {
}
public refreshState(): void {
if (this._domFocusTracker.refreshState) {
this._domFocusTracker.refreshState();
}
this._domFocusTracker.refreshState?.();
}
}

View file

@ -22,6 +22,7 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat
export const codeActionCommandId = 'editor.action.codeAction';
export const refactorCommandId = 'editor.action.refactor';
export const refactorPreviewCommandId = 'editor.action.refactor.preview';
export const sourceActionCommandId = 'editor.action.sourceAction';
export const organizeImportsCommandId = 'editor.action.organizeImports';
export const fixAllCommandId = 'editor.action.fixAll';

View file

@ -18,7 +18,7 @@ import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { CodeActionTriggerType } from 'vs/editor/common/languages';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { codeActionCommandId, CodeActionItem, CodeActionSet, fixAllCommandId, organizeImportsCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/browser/codeAction';
import { codeActionCommandId, CodeActionItem, CodeActionSet, fixAllCommandId, organizeImportsCommandId, refactorCommandId, refactorPreviewCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/browser/codeAction';
import { CodeActionUi } from 'vs/editor/contrib/codeAction/browser/codeActionUi';
import { MessageController } from 'vs/editor/contrib/message/browser/messageController';
import * as nls from 'vs/nls';
@ -27,8 +27,8 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IMarkerService } from 'vs/platform/markers/common/markers';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { CodeActionModel, CodeActionsState, SUPPORTED_CODE_ACTIONS } from './codeActionModel';
import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionFilter, CodeActionKind, CodeActionTrigger } from './types';
@ -39,6 +39,26 @@ function contextKeyForSupportedActions(kind: CodeActionKind) {
new RegExp('(\\s|^)' + escapeRegExpCharacters(kind.value) + '\\b'));
}
function RefactorTrigger(editor: ICodeEditor, userArgs: any, preview: boolean) {
const args = CodeActionCommandArgs.fromUser(userArgs, {
kind: CodeActionKind.Refactor,
apply: CodeActionAutoApply.Never
});
return triggerCodeActionsForEditorSelection(editor,
typeof userArgs?.kind === 'string'
? args.preferred
? nls.localize('editor.action.refactor.noneMessage.preferred.kind', "No preferred refactorings for '{0}' available", userArgs.kind)
: nls.localize('editor.action.refactor.noneMessage.kind', "No refactorings for '{0}' available", userArgs.kind)
: args.preferred
? nls.localize('editor.action.refactor.noneMessage.preferred', "No preferred refactorings available")
: nls.localize('editor.action.refactor.noneMessage', "No refactorings available"),
{
include: CodeActionKind.Refactor.contains(args.kind) ? args.kind : CodeActionKind.None,
onlyIncludePreferredActions: args.preferred
},
args.apply, preview);
}
const argsSchema: IJSONSchema = {
type: 'object',
defaultSnippets: [{ body: { kind: '' } }],
@ -92,11 +112,12 @@ export class QuickFixController extends Disposable implements IEditorContributio
this._model = this._register(new CodeActionModel(this._editor, languageFeaturesService.codeActionProvider, markerService, contextKeyService, progressService));
this._register(this._model.onDidChangeState(newState => this.update(newState)));
this._ui = new Lazy(() =>
this._register(new CodeActionUi(editor, QuickFixAction.Id, AutoFixAction.Id, {
applyCodeAction: async (action, retrigger) => {
applyCodeAction: async (action, retrigger, preview) => {
try {
await this._applyCodeAction(action);
await this._applyCodeAction(action, preview);
} finally {
if (retrigger) {
this._trigger({ type: CodeActionTriggerType.Auto, filter: {} });
@ -118,7 +139,8 @@ export class QuickFixController extends Disposable implements IEditorContributio
public manualTriggerAtCurrentPosition(
notAvailableMessage: string,
filter?: CodeActionFilter,
autoApply?: CodeActionAutoApply
autoApply?: CodeActionAutoApply,
preview?: boolean
): void {
if (!this._editor.hasModel()) {
return;
@ -126,22 +148,22 @@ export class QuickFixController extends Disposable implements IEditorContributio
MessageController.get(this._editor)?.closeMessage();
const triggerPosition = this._editor.getPosition();
this._trigger({ type: CodeActionTriggerType.Invoke, filter, autoApply, context: { notAvailableMessage, position: triggerPosition } });
this._trigger({ type: CodeActionTriggerType.Invoke, filter, autoApply, context: { notAvailableMessage, position: triggerPosition }, preview });
}
private _trigger(trigger: CodeActionTrigger) {
return this._model.trigger(trigger);
}
private _applyCodeAction(action: CodeActionItem): Promise<void> {
return this._instantiationService.invokeFunction(applyCodeAction, action, this._editor);
private _applyCodeAction(action: CodeActionItem, preview: boolean): Promise<void> {
return this._instantiationService.invokeFunction(applyCodeAction, action, { preview, editor: this._editor });
}
}
export async function applyCodeAction(
accessor: ServicesAccessor,
item: CodeActionItem,
editor?: ICodeEditor,
options?: { preview?: boolean; editor?: ICodeEditor }
): Promise<void> {
const bulkEditService = accessor.get(IBulkEditService);
const commandService = accessor.get(ICommandService);
@ -171,11 +193,12 @@ export async function applyCodeAction(
if (item.action.edit) {
await bulkEditService.apply(ResourceEdit.convert(item.action.edit), {
editor,
editor: options?.editor,
label: item.action.title,
quotableLabel: item.action.title,
code: 'undoredo.codeAction',
respectAutoSaveConfig: true
respectAutoSaveConfig: true,
showPreview: options?.preview,
});
}
@ -206,12 +229,13 @@ function triggerCodeActionsForEditorSelection(
editor: ICodeEditor,
notAvailableMessage: string,
filter: CodeActionFilter | undefined,
autoApply: CodeActionAutoApply | undefined
autoApply: CodeActionAutoApply | undefined,
preview: boolean = false
): void {
if (editor.hasModel()) {
const controller = QuickFixController.get(editor);
if (controller) {
controller.manualTriggerAtCurrentPosition(notAvailableMessage, filter, autoApply);
controller.manualTriggerAtCurrentPosition(notAvailableMessage, filter, autoApply, preview);
}
}
}
@ -306,23 +330,27 @@ export class RefactorAction extends EditorAction {
}
public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs: any): void {
const args = CodeActionCommandArgs.fromUser(userArgs, {
kind: CodeActionKind.Refactor,
apply: CodeActionAutoApply.Never
return RefactorTrigger(editor, userArgs, false);
}
}
export class RefactorPreview extends EditorAction {
constructor() {
super({
id: refactorPreviewCommandId,
label: nls.localize('refactor.preview.label', "Refactor with Preview..."),
alias: 'Refactor Preview...',
precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider),
description: {
description: 'Refactor Preview...',
args: [{ name: 'args', schema: argsSchema }]
}
});
return triggerCodeActionsForEditorSelection(editor,
typeof userArgs?.kind === 'string'
? args.preferred
? nls.localize('editor.action.refactor.noneMessage.preferred.kind', "No preferred refactorings for '{0}' available", userArgs.kind)
: nls.localize('editor.action.refactor.noneMessage.kind', "No refactorings for '{0}' available", userArgs.kind)
: args.preferred
? nls.localize('editor.action.refactor.noneMessage.preferred', "No preferred refactorings available")
: nls.localize('editor.action.refactor.noneMessage', "No refactorings available"),
{
include: CodeActionKind.Refactor.contains(args.kind) ? args.kind : CodeActionKind.None,
onlyIncludePreferredActions: args.preferred,
},
args.apply);
}
public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs: any): void {
return RefactorTrigger(editor, userArgs, true);
}
}

View file

@ -4,12 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import { registerEditorAction, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { AutoFixAction, CodeActionCommand, FixAllAction, OrganizeImportsAction, QuickFixAction, QuickFixController, RefactorAction, SourceAction } from 'vs/editor/contrib/codeAction/browser/codeActionCommands';
import { AutoFixAction, CodeActionCommand, FixAllAction, OrganizeImportsAction, QuickFixAction, QuickFixController, RefactorAction, RefactorPreview, SourceAction } from 'vs/editor/contrib/codeAction/browser/codeActionCommands';
registerEditorContribution(QuickFixController.ID, QuickFixController);
registerEditorAction(QuickFixAction);
registerEditorAction(RefactorAction);
registerEditorAction(RefactorPreview);
registerEditorAction(SourceAction);
registerEditorAction(OrganizeImportsAction);
registerEditorAction(AutoFixAction);

View file

@ -23,7 +23,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
interface CodeActionWidgetDelegate {
onSelectCodeAction: (action: CodeActionItem) => Promise<any>;
onSelectCodeAction: (action: CodeActionItem, trigger: CodeActionTrigger) => Promise<any>;
}
interface ResolveCodeActionKeybinding {
@ -115,7 +115,7 @@ export class CodeActionMenu extends Disposable {
actionsToShow: readonly CodeActionItem[],
documentation: readonly Command[]
): IAction[] {
const toCodeActionAction = (item: CodeActionItem): CodeActionAction => new CodeActionAction(item.action, () => this._delegate.onSelectCodeAction(item));
const toCodeActionAction = (item: CodeActionItem): CodeActionAction => new CodeActionAction(item.action, () => this._delegate.onSelectCodeAction(item, trigger));
const result: IAction[] = actionsToShow
.map(toCodeActionAction);

View file

@ -31,7 +31,7 @@ export class CodeActionUi extends Disposable {
quickFixActionId: string,
preferredFixActionId: string,
private readonly delegate: {
applyCodeAction: (action: CodeActionItem, regtriggerAfterApply: boolean) => Promise<void>;
applyCodeAction: (action: CodeActionItem, regtriggerAfterApply: boolean, preview: boolean) => Promise<void>;
},
@IInstantiationService instantiationService: IInstantiationService,
) {
@ -39,8 +39,8 @@ export class CodeActionUi extends Disposable {
this._codeActionWidget = new Lazy(() => {
return this._register(instantiationService.createInstance(CodeActionMenu, this._editor, {
onSelectCodeAction: async (action) => {
this.delegate.applyCodeAction(action, /* retrigger */ true);
onSelectCodeAction: async (action, trigger) => {
this.delegate.applyCodeAction(action, /* retrigger */ true, Boolean(trigger.preview));
}
}));
});
@ -85,7 +85,7 @@ export class CodeActionUi extends Disposable {
if (validActionToApply) {
try {
this._lightBulbWidget.getValue().hide();
await this.delegate.applyCodeAction(validActionToApply, false);
await this.delegate.applyCodeAction(validActionToApply, false, false);
} finally {
actions.dispose();
}

View file

@ -122,6 +122,7 @@ export interface CodeActionTrigger {
readonly notAvailableMessage: string;
readonly position: Position;
};
readonly preview?: boolean;
}
export class CodeActionCommandArgs {

View file

@ -53,7 +53,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi
private readonly _editor: ICodeEditor;
private _currentClipboardItem: undefined | {
private _currentClipboardItem?: {
readonly handle: string;
readonly dataTransferPromise: CancelablePromise<VSDataTransfer>;
};
@ -70,131 +70,134 @@ export class CopyPasteController extends Disposable implements IEditorContributi
this._editor = editor;
const container = editor.getContainerDomNode();
this._register(addDisposableListener(container, 'copy', (e: ClipboardEvent) => {
if (!e.clipboardData) {
return;
}
const model = editor.getModel();
const selections = this._editor.getSelections();
if (!model || !selections?.length) {
return;
}
if (!this.arePasteActionsEnabled(model)) {
return;
}
const providers = this._languageFeaturesService.documentPasteEditProvider.ordered(model).filter(x => !!x.prepareDocumentPaste);
if (!providers.length) {
return;
}
const dataTransfer = toVSDataTransfer(e.clipboardData);
// Save off a handle pointing to data that VS Code maintains.
const handle = generateUuid();
e.clipboardData.setData(vscodeClipboardMime, handle);
const promise = createCancelablePromise(async token => {
const results = await Promise.all(providers.map(provider => {
return provider.prepareDocumentPaste!(model, selections, dataTransfer, token);
}));
for (const result of results) {
result?.forEach((value, key) => {
dataTransfer.replace(key, value);
});
}
return dataTransfer;
});
this._currentClipboardItem?.dataTransferPromise.cancel();
this._currentClipboardItem = { handle: handle, dataTransferPromise: promise };
}));
this._register(addDisposableListener(container, 'paste', async (e: ClipboardEvent) => {
const selections = this._editor.getSelections();
if (!e.clipboardData || !selections?.length || !editor.hasModel()) {
return;
}
const model = editor.getModel();
if (!this.arePasteActionsEnabled(model)) {
return;
}
const handle = e.clipboardData?.getData(vscodeClipboardMime);
if (typeof handle !== 'string') {
return;
}
const providers = this._languageFeaturesService.documentPasteEditProvider.ordered(model);
if (!providers.length) {
return;
}
e.preventDefault();
e.stopImmediatePropagation();
const originalDocVersion = model.getVersionId();
const tokenSource = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection);
try {
const dataTransfer = toVSDataTransfer(e.clipboardData);
if (handle && this._currentClipboardItem?.handle === handle) {
const toMergeDataTransfer = await this._currentClipboardItem.dataTransferPromise;
toMergeDataTransfer.forEach((value, key) => {
dataTransfer.replace(key, value);
});
}
if (!dataTransfer.has(Mimes.uriList)) {
const resources = await this._clipboardService.readResources();
if (resources.length) {
const value = resources.join('\n');
dataTransfer.append(Mimes.uriList, createStringDataTransferItem(value));
}
}
dataTransfer.delete(vscodeClipboardMime);
for (const provider of [...providers, defaultPasteEditProvider]) {
if (!provider.pasteMimeTypes.some(type => {
if (type.toLowerCase() === DataTransfers.FILES.toLowerCase()) {
return [...dataTransfer.values()].some(item => item.asFile());
}
return dataTransfer.has(type);
})) {
continue;
}
const edit = await provider.provideDocumentPasteEdits(model, selections, dataTransfer, tokenSource.token);
if (originalDocVersion !== model.getVersionId()) {
return;
}
if (edit) {
performSnippetEdit(editor, typeof edit.insertText === 'string' ? SnippetParser.escape(edit.insertText) : edit.insertText.snippet, selections);
if (edit.additionalEdit) {
await this._bulkEditService.apply(ResourceEdit.convert(edit.additionalEdit), { editor });
}
return;
}
}
} finally {
tokenSource.dispose();
}
}, true));
this._register(addDisposableListener(container, 'copy', e => this.handleCopy(e)));
this._register(addDisposableListener(container, 'cut', e => this.handleCopy(e)));
this._register(addDisposableListener(container, 'paste', e => this.handlePaste(e), true));
}
public arePasteActionsEnabled(model: ITextModel): boolean {
private arePasteActionsEnabled(model: ITextModel): boolean {
return this._configurationService.getValue('editor.experimental.pasteActions.enabled', {
resource: model.uri
});
}
private handleCopy(e: ClipboardEvent) {
if (!e.clipboardData) {
return;
}
const model = this._editor.getModel();
const selections = this._editor.getSelections();
if (!model || !selections?.length) {
return;
}
if (!this.arePasteActionsEnabled(model)) {
return;
}
const providers = this._languageFeaturesService.documentPasteEditProvider.ordered(model).filter(x => !!x.prepareDocumentPaste);
if (!providers.length) {
return;
}
const dataTransfer = toVSDataTransfer(e.clipboardData);
// Save off a handle pointing to data that VS Code maintains.
const handle = generateUuid();
e.clipboardData.setData(vscodeClipboardMime, handle);
const promise = createCancelablePromise(async token => {
const results = await Promise.all(providers.map(provider => {
return provider.prepareDocumentPaste!(model, selections, dataTransfer, token);
}));
for (const result of results) {
result?.forEach((value, key) => {
dataTransfer.replace(key, value);
});
}
return dataTransfer;
});
this._currentClipboardItem?.dataTransferPromise.cancel();
this._currentClipboardItem = { handle: handle, dataTransferPromise: promise };
}
private async handlePaste(e: ClipboardEvent) {
const selections = this._editor.getSelections();
if (!e.clipboardData || !selections?.length || !this._editor.hasModel()) {
return;
}
const model = this._editor.getModel();
if (!this.arePasteActionsEnabled(model)) {
return;
}
const handle = e.clipboardData?.getData(vscodeClipboardMime);
if (typeof handle !== 'string') {
return;
}
const providers = this._languageFeaturesService.documentPasteEditProvider.ordered(model);
if (!providers.length) {
return;
}
e.preventDefault();
e.stopImmediatePropagation();
const originalDocVersion = model.getVersionId();
const tokenSource = new EditorStateCancellationTokenSource(this._editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection);
try {
const dataTransfer = toVSDataTransfer(e.clipboardData);
if (handle && this._currentClipboardItem?.handle === handle) {
const toMergeDataTransfer = await this._currentClipboardItem.dataTransferPromise;
toMergeDataTransfer.forEach((value, key) => {
dataTransfer.replace(key, value);
});
}
if (!dataTransfer.has(Mimes.uriList)) {
const resources = await this._clipboardService.readResources();
if (resources.length) {
const value = resources.join('\n');
dataTransfer.append(Mimes.uriList, createStringDataTransferItem(value));
}
}
dataTransfer.delete(vscodeClipboardMime);
for (const provider of [...providers, defaultPasteEditProvider]) {
if (!provider.pasteMimeTypes.some(type => {
if (type.toLowerCase() === DataTransfers.FILES.toLowerCase()) {
return [...dataTransfer.values()].some(item => item.asFile());
}
return dataTransfer.has(type);
})) {
continue;
}
const edit = await provider.provideDocumentPasteEdits(model, selections, dataTransfer, tokenSource.token);
if (originalDocVersion !== model.getVersionId()) {
return;
}
if (edit) {
performSnippetEdit(this._editor, typeof edit.insertText === 'string' ? SnippetParser.escape(edit.insertText) : edit.insertText.snippet, selections);
if (edit.additionalEdit) {
await this._bulkEditService.apply(ResourceEdit.convert(edit.additionalEdit), { editor: this._editor });
}
return;
}
}
} finally {
tokenSource.dispose();
}
}
}

View file

@ -1348,9 +1348,7 @@ export class SimpleButton extends Widget {
e.preventDefault();
return;
}
if (this._opts.onKeyDown) {
this._opts.onKeyDown(e);
}
this._opts.onKeyDown?.(e);
});
}

View file

@ -74,9 +74,7 @@ export class RangesCollector {
}
return new FoldingRegions(startIndexes, endIndexes);
} else {
if (this._notifyTooManyRegions) {
this._notifyTooManyRegions(this._foldingRangesLimit);
}
this._notifyTooManyRegions?.(this._foldingRangesLimit);
let entries = 0;
let maxIndent = this._indentOccurrences.length;
for (let i = 0; i < this._indentOccurrences.length; i++) {

View file

@ -121,9 +121,7 @@ export class RangesCollector {
}
return new FoldingRegions(startIndexes, endIndexes, this._types);
} else {
if (this._notifyTooManyRegions) {
this._notifyTooManyRegions(this._foldingRangesLimit);
}
this._notifyTooManyRegions?.(this._foldingRangesLimit);
let entries = 0;
let maxLevel = this._nestingLevelCounts.length;
for (let i = 0; i < this._nestingLevelCounts.length; i++) {

View file

@ -243,9 +243,7 @@ export class InlineCompletionsSession extends BaseGhostTextWidgetModel {
lastCompletionItem = currentCompletion.sourceInlineCompletion;
const provider = currentCompletion.sourceProvider;
if (provider.handleItemDidShow) {
provider.handleItemDidShow(currentCompletion.sourceInlineCompletions, lastCompletionItem);
}
provider.handleItemDidShow?.(currentCompletion.sourceInlineCompletions, lastCompletionItem);
}
}));

View file

@ -146,15 +146,11 @@ export class RenameInputField implements IContentWidget {
private _currentCancelInput?: (focusEditor: boolean) => void;
acceptInput(wantsPreview: boolean): void {
if (this._currentAcceptInput) {
this._currentAcceptInput(wantsPreview);
}
this._currentAcceptInput?.(wantsPreview);
}
cancelInput(focusEditor: boolean): void {
if (this._currentCancelInput) {
this._currentCancelInput(focusEditor);
}
this._currentCancelInput?.(focusEditor);
}
getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean, token: CancellationToken): Promise<RenameInputFieldResult | boolean> {

View file

@ -39,9 +39,7 @@ export class BannerController extends Disposable {
...item,
onClose: () => {
this.hide();
if (item.onClose) {
item.onClose();
}
item.onClose?.();
}
});
this._editor.setBanner(this.banner.element, BANNER_ELEMENT_HEIGHT);

View file

@ -129,9 +129,7 @@ export class ContextMenuHandler {
},
onHide: (didCancel?: boolean) => {
if (delegate.onHide) {
delegate.onHide(!!didCancel);
}
delegate.onHide?.(!!didCancel);
if (this.block) {
this.block.remove();

View file

@ -47,9 +47,7 @@ export class ContextMenuService extends Disposable implements IContextMenuServic
this.contextMenuHandler.showContextMenu({
...delegate,
onHide: (didCancel) => {
if (delegate.onHide) {
delegate.onHide(didCancel);
}
delegate.onHide?.(didCancel);
this._onDidHideContextMenu.fire();
}

View file

@ -816,9 +816,7 @@ export class Menubar {
const originalClick = options.click;
options.click = (item, window, event) => {
this.reportMenuActionTelemetry(commandId);
if (originalClick) {
originalClick(item, window, event);
}
originalClick?.(item, window, event);
};
return options;

View file

@ -71,9 +71,7 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
const key = uri.toString();
if (!this._canonicalURIRequests.has(key)) {
const request = new PendingPromise<URI, URI>(uri);
if (this._canonicalURIProvider) {
this._canonicalURIProvider(request.input).then((uri) => request.resolve(uri), (err) => request.reject(err));
}
this._canonicalURIProvider?.(request.input).then((uri) => request.resolve(uri), (err) => request.reject(err));
this._canonicalURIRequests.set(key, request);
}
return this._canonicalURIRequests.get(key)!.promise;

View file

@ -183,9 +183,10 @@ async function transformToTerminalProfiles(
validatedProfile.color = profile.color;
resultProfiles.push(validatedProfile);
} else {
logService?.trace('profile not validated', profileName, originalPaths);
logService?.debug('Terminal profile not validated', profileName, originalPaths);
}
}
logService?.debug('Validated terminal profiles', resultProfiles);
return resultProfiles;
}

View file

@ -74,17 +74,18 @@ export class MainThreadCommands implements MainThreadCommandsShape {
}
}
async $executeCommand<T>(id: string, args: any[] | SerializableObjectWithBuffers<any[]>, retry: boolean): Promise<T | undefined> {
async $activateByCommandEvent(id: string): Promise<void> {
const activationEvent = `onCommand:${id}`;
await this._extensionService.activateByEvent(activationEvent);
}
async $executeCommand<T>(id: string, args: any[] | SerializableObjectWithBuffers<any[]>): Promise<T | undefined> {
if (args instanceof SerializableObjectWithBuffers) {
args = args.value;
}
for (let i = 0; i < args.length; i++) {
args[i] = revive(args[i]);
}
if (retry && args.length > 0 && !CommandsRegistry.getCommand(id)) {
await this._extensionService.activateByEvent(`onCommand:${id}`);
throw new Error('$executeCommand:retry');
}
return this._commandService.executeCommand<T>(id, ...args);
}

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SerializedError, onUnexpectedError } from 'vs/base/common/errors';
import { SerializedError, onUnexpectedError, ErrorNoTelemetry } from 'vs/base/common/errors';
import { extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { MainContext, MainThreadErrorsShape } from 'vs/workbench/api/common/extHost.protocol';
@ -17,7 +17,7 @@ export class MainThreadErrors implements MainThreadErrorsShape {
$onUnexpectedError(err: any | SerializedError): void {
if (err && err.$isError) {
const { name, message, stack } = err;
err = new Error();
err = err.noTelemetry ? new ErrorNoTelemetry() : new Error();
err.message = message;
err.name = name;
err.stack = stack;

View file

@ -96,6 +96,7 @@ export namespace NotebookDto {
if (data.editType === CellExecutionUpdateType.Output) {
return {
editType: data.editType,
cellHandle: data.cellHandle,
append: data.append,
outputs: data.outputs.map(fromNotebookOutputDto)
};

View file

@ -94,9 +94,7 @@ class SearchOperation {
this.matches.set(match.resource.toString(), match);
}
if (this.progress) {
this.progress(match);
}
this.progress?.(match);
}
}

View file

@ -92,7 +92,8 @@ export interface MainThreadClipboardShape extends IDisposable {
export interface MainThreadCommandsShape extends IDisposable {
$registerCommand(id: string): void;
$unregisterCommand(id: string): void;
$executeCommand(id: string, args: any[] | SerializableObjectWithBuffers<any[]>, retry: boolean): Promise<unknown | undefined>;
$activateByCommandEvent(id: string): Promise<void>;
$executeCommand(id: string, args: any[] | SerializableObjectWithBuffers<any[]>): Promise<unknown | undefined>;
$getCommands(): Promise<string[]>;
}
@ -999,6 +1000,7 @@ export interface INotebookProxyKernelDto {
export interface ICellExecuteOutputEditDto {
editType: CellExecutionUpdateType.Output;
cellHandle: number;
append?: boolean;
outputs: NotebookOutputDto[];
}

View file

@ -163,10 +163,15 @@ export class ExtHostCommands implements ExtHostCommandsShape {
executeCommand<T>(id: string, ...args: any[]): Promise<T> {
this._logService.trace('ExtHostCommands#executeCommand', id);
return this._doExecuteCommand(id, args, true);
return this._doExecuteCommand(id, args);
}
private async _doExecuteCommand<T>(id: string, args: any[], retry: boolean): Promise<T> {
private async _doExecuteCommand<T>(id: string, args: any[]): Promise<T> {
// make sure to emit an onCommand-activation event in ALL cases
// (1) locally known command -> activation notificed bystander
// (2) unknown command -> can activate future local extension
await this.#proxy.$activateByCommandEvent(id);
if (this._commands.has(id)) {
// we stay inside the extension host and support
@ -201,17 +206,10 @@ export class ExtHostCommands implements ExtHostCommandsShape {
});
try {
const result = await this.#proxy.$executeCommand(id, hasBuffers ? new SerializableObjectWithBuffers(toArgs) : toArgs, retry);
const result = await this.#proxy.$executeCommand(id, hasBuffers ? new SerializableObjectWithBuffers(toArgs) : toArgs);
return revive<any>(result);
} catch (e) {
// Rerun the command when it wasn't known, had arguments, and when retry
// is enabled. We do this because the command might be registered inside
// the extension host now and can therfore accept the arguments as-is.
if (e instanceof Error && e.message === '$executeCommand:retry') {
return this._doExecuteCommand(id, args, false);
} else {
throw e;
}
throw e;
}
}
}

View file

@ -441,6 +441,17 @@ class NotebookCellExecutionTask extends Disposable {
}
}
private cellIndexToHandle(cellOrCellIndex: vscode.NotebookCell | undefined): number {
let cell: ExtHostCell | undefined = this._cell;
if (cellOrCellIndex) {
cell = this._cell.notebook.getCellFromApiCell(cellOrCellIndex);
}
if (!cell) {
throw new Error('INVALID cell');
}
return cell.handle;
}
private validateAndConvertOutputs(items: vscode.NotebookCellOutput[]): NotebookOutputDto[] {
return items.map(output => {
const newOutput = NotebookCellOutput.ensureUniqueMimeTypes(output.items, true);
@ -456,10 +467,12 @@ class NotebookCellExecutionTask extends Disposable {
}
private async updateOutputs(outputs: vscode.NotebookCellOutput | vscode.NotebookCellOutput[], cell: vscode.NotebookCell | undefined, append: boolean): Promise<void> {
const handle = this.cellIndexToHandle(cell);
const outputDtos = this.validateAndConvertOutputs(asArray(outputs));
return this.updateSoon(
{
editType: CellExecutionUpdateType.Output,
cellHandle: handle,
append,
outputs: outputDtos
});

View file

@ -136,9 +136,7 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx
}
$onItemSelected(handle: number): void {
if (this._onDidSelectItem) {
this._onDidSelectItem(handle);
}
this._onDidSelectItem?.(handle);
}
// ---- input

View file

@ -2473,9 +2473,9 @@ export class DataTransfer {
this.#items.set(mimeType, [value]);
}
forEach(callbackfn: (value: DataTransferItem, key: string) => void): void {
forEach(callbackfn: (value: DataTransferItem, key: string) => void, thisArg?: unknown): void {
for (const [mime, items] of this.#items) {
items.forEach(item => callbackfn(item, mime));
items.forEach(item => callbackfn(item, mime), thisArg);
}
}
}

View file

@ -546,9 +546,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
}
$handleTextSearchResult(result: IRawFileMatch2, requestId: number): void {
if (this._activeSearchCallbacks[requestId]) {
this._activeSearchCallbacks[requestId](result);
}
this._activeSearchCallbacks[requestId]?.(result);
}
saveAll(includeUntitled?: boolean): Promise<boolean> {

View file

@ -197,9 +197,7 @@ function _createExtHostProtocol(): Promise<IMessagePassingProtocol> {
// Now that we have managed to install a message listener, ask the other side to send us the socket
const req: IExtHostReadyMessage = { type: 'VSCODE_EXTHOST_IPC_READY' };
if (process.send) {
process.send(req);
}
process.send?.(req);
});
} else {

View file

@ -60,7 +60,7 @@ suite('ExtHostCommands', function () {
assert.strictEqual(unregisterCounter, 1);
});
test('execute with retry', async function () {
test('execute triggers activate', async function () {
let count = 0;
@ -68,16 +68,13 @@ suite('ExtHostCommands', function () {
override $registerCommand(id: string): void {
//
}
override async $executeCommand<T>(id: string, args: any[], retry: boolean): Promise<T | undefined> {
count++;
assert.strictEqual(retry, count === 1);
if (count === 1) {
assert.strictEqual(retry, true);
throw new Error('$executeCommand:retry');
} else {
assert.strictEqual(retry, false);
return <any>17;
}
override async $activateByCommandEvent(id: string): Promise<void> {
count += 1;
}
override async $executeCommand<T>(id: string, args: any[]): Promise<T | undefined> {
return undefined;
}
};
@ -86,8 +83,7 @@ suite('ExtHostCommands', function () {
new NullLogService()
);
const result = await commands.executeCommand('fooo', [this, true]);
assert.strictEqual(result, 17);
assert.strictEqual(count, 2);
await commands.executeCommand('fooo', [this, true]);
assert.strictEqual(count, 1);
});
});

View file

@ -5,7 +5,7 @@
import * as assert from 'assert';
import { MainThreadCommands } from 'vs/workbench/api/browser/mainThreadCommands';
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { SingleProxyRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { mock } from 'vs/base/test/common/mock';
@ -42,46 +42,4 @@ suite('MainThreadCommands', function () {
assert.strictEqual(CommandsRegistry.getCommand('foo'), undefined);
assert.strictEqual(CommandsRegistry.getCommand('bar'), undefined);
});
test('activate and throw when needed', async function () {
const activations: string[] = [];
const runs: string[] = [];
const commands = new MainThreadCommands(
SingleProxyRPCProtocol(null),
new class extends mock<ICommandService>() {
override executeCommand<T>(id: string): Promise<T | undefined> {
runs.push(id);
return Promise.resolve(undefined);
}
},
new class extends mock<IExtensionService>() {
override activateByEvent(id: string) {
activations.push(id);
return Promise.resolve();
}
}
);
// case 1: arguments and retry
try {
activations.length = 0;
await commands.$executeCommand('bazz', [1, 2, { n: 3 }], true);
assert.ok(false);
} catch (e) {
assert.deepStrictEqual(activations, ['onCommand:bazz']);
assert.strictEqual((<Error>e).message, '$executeCommand:retry');
}
// case 2: no arguments and retry
runs.length = 0;
await commands.$executeCommand('bazz', [], true);
assert.deepStrictEqual(runs, ['bazz']);
// case 3: arguments and no retry
runs.length = 0;
await commands.$executeCommand('bazz', [1, 2, true], false);
assert.deepStrictEqual(runs, ['bazz']);
});
});

View file

@ -57,9 +57,7 @@ export class TestRPCProtocol implements IExtHostContext, IExtHostRpcService {
private set _callCount(value: number) {
this._callCountValue = value;
if (this._callCountValue === 0) {
if (this._completeIdle) {
this._completeIdle();
}
this._completeIdle?.();
this._idle = undefined;
}
}

View file

@ -609,9 +609,7 @@ export class CompositeDragAndDropObserver extends Disposable {
return;
}
if (callbacks.onDragLeave) {
callbacks.onDragLeave({ eventData: e, dragAndDropData: data! });
}
callbacks.onDragLeave?.({ eventData: e, dragAndDropData: data! });
},
onDrop: e => {
if (callbacks.onDrop) {

View file

@ -6,7 +6,9 @@
import { Event } from 'vs/base/common/event';
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
export interface IObservable<T> {
export interface IObservable<T, TChange = void> {
_change: TChange;
/**
* Reads the current value.
*
@ -39,7 +41,7 @@ export interface IReader {
*
* Is called by `Observable.read`.
*/
handleBeforeReadObservable<T>(observable: IObservable<T>): void;
handleBeforeReadObservable<T>(observable: IObservable<T, any>): void;
}
export interface IObserver {
@ -61,7 +63,7 @@ export interface IObserver {
* Implementations must not call into other observables!
* The change should be processed when {@link IObserver.endUpdate} is called.
*/
handleChange<T>(observable: IObservable<T>): void;
handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void;
/**
* Indicates that an update operation has completed.
@ -69,8 +71,8 @@ export interface IObserver {
endUpdate<T>(observable: IObservable<T>): void;
}
export interface ISettable<T> {
set(value: T, transaction: ITransaction | undefined): void;
export interface ISettable<T, TChange = void> {
set(value: T, transaction: ITransaction | undefined, change: TChange): void;
}
export interface ITransaction {
@ -80,12 +82,14 @@ export interface ITransaction {
*/
updateObserver(
observer: IObserver,
observable: IObservable<any>
observable: IObservable<any, any>
): void;
}
// === Base ===
export abstract class ConvenientObservable<T> implements IObservable<T> {
export abstract class ConvenientObservable<T, TChange> implements IObservable<T, TChange> {
get _change(): TChange { return null!; }
public abstract get(): T;
public abstract subscribe(observer: IObserver): void;
public abstract unsubscribe(observer: IObserver): void;
@ -100,7 +104,7 @@ export abstract class ConvenientObservable<T> implements IObservable<T> {
}
}
export abstract class BaseObservable<T> extends ConvenientObservable<T> {
export abstract class BaseObservable<T, TChange = void> extends ConvenientObservable<T, TChange> {
protected readonly observers = new Set<IObserver>();
public subscribe(observer: IObserver): void {
@ -151,9 +155,9 @@ class TransactionImpl implements ITransaction {
}
}
export class ObservableValue<T>
extends BaseObservable<T>
implements ISettable<T>
export class ObservableValue<T, TChange = void>
extends BaseObservable<T, TChange>
implements ISettable<T, TChange>
{
private value: T;
@ -166,14 +170,14 @@ export class ObservableValue<T>
return this.value;
}
public set(value: T, tx: ITransaction | undefined): void {
public set(value: T, tx: ITransaction | undefined, change: TChange): void {
if (this.value === value) {
return;
}
if (!tx) {
transaction((tx) => {
this.set(value, tx);
this.set(value, tx, change);
});
return;
}
@ -182,7 +186,7 @@ export class ObservableValue<T>
for (const observer of this.observers) {
tx.updateObserver(observer, this);
observer.handleChange(this);
observer.handleChange(this, change);
}
}
}
@ -191,7 +195,7 @@ export function constObservable<T>(value: T): IObservable<T> {
return new ConstObservable(value);
}
class ConstObservable<T> extends ConvenientObservable<T> {
class ConstObservable<T> extends ConvenientObservable<T, void> {
constructor(private readonly value: T) {
super();
}
@ -208,11 +212,28 @@ class ConstObservable<T> extends ConvenientObservable<T> {
}
// == autorun ==
export function autorun(
fn: (reader: IReader) => void,
name: string
export function autorun(fn: (reader: IReader) => void, name: string): IDisposable {
return new AutorunObserver(fn, name, undefined);
}
interface IChangeContext {
readonly changedObservable: IObservable<any, any>;
readonly change: unknown;
didChange<T, TChange>(observable: IObservable<T, TChange>): this is { change: TChange };
}
export function autorunHandleChanges(
name: string,
options: {
/**
* Returns if this change should cause a re-run of the autorun.
*/
handleChange: (context: IChangeContext) => boolean;
},
fn: (reader: IReader) => void
): IDisposable {
return new AutorunObserver(fn, name);
return new AutorunObserver(fn, name, options.handleChange);
}
export function autorunWithStore(
@ -252,7 +273,8 @@ export class AutorunObserver implements IObserver, IReader, IDisposable {
constructor(
private readonly runFn: (reader: IReader) => void,
public readonly name: string
public readonly name: string,
private readonly _handleChange: ((context: IChangeContext) => boolean) | undefined
) {
this.runIfNeeded();
}
@ -264,8 +286,13 @@ export class AutorunObserver implements IObserver, IReader, IDisposable {
}
}
public handleChange() {
this.needsToRun = true;
public handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void {
const shouldReact = this._handleChange ? this._handleChange({
changedObservable: observable,
change,
didChange: o => o === observable as any,
}) : true;
this.needsToRun = this.needsToRun || shouldReact;
if (this.updateCount === 0) {
this.runIfNeeded();
@ -337,7 +364,7 @@ export function autorunDelta<T>(
export function derivedObservable<T>(name: string, computeFn: (reader: IReader) => T): IObservable<T> {
return new LazyDerived(computeFn, name);
}
export class LazyDerived<T> extends ConvenientObservable<T> {
export class LazyDerived<T> extends ConvenientObservable<T, void> {
private readonly observer: LazyDerivedObserver<T>;
constructor(computeFn: (reader: IReader) => T, name: string) {
@ -366,7 +393,7 @@ export class LazyDerived<T> extends ConvenientObservable<T> {
* @internal
*/
class LazyDerivedObserver<T>
extends BaseObservable<T>
extends BaseObservable<T, void>
implements IReader, IObserver {
private hadValue = false;
private hasValue = false;
@ -486,9 +513,8 @@ class LazyDerivedObserver<T>
this.hasValue = true;
if (this.hadValue && oldValue !== this.value) {
//
for (const r of this.observers) {
r.handleChange(this);
r.handleChange(this, undefined);
}
}
}
@ -508,6 +534,20 @@ export function observableFromPromise<T>(promise: Promise<T>): IObservable<{ val
return observable;
}
export function waitForState<T, TState extends T>(observable: IObservable<T>, predicate: (state: T) => state is TState): Promise<TState>;
export function waitForState<T>(observable: IObservable<T>, predicate: (state: T) => boolean): Promise<T>;
export function waitForState<T>(observable: IObservable<T>, predicate: (state: T) => boolean): Promise<T> {
return new Promise(resolve => {
const d = autorun(reader => {
const currentState = observable.read(reader);
if (predicate(currentState)) {
d.dispose();
resolve(currentState);
}
}, 'waitForState');
});
}
export function observableFromEvent<T, TArgs = unknown>(
event: Event<TArgs>,
getValue: (args: TArgs | undefined) => T
@ -540,7 +580,7 @@ class FromEventObservable<TArgs, T> extends BaseObservable<T> {
transaction(tx => {
for (const o of this.observers) {
tx.updateObserver(o, this);
o.handleChange(this);
o.handleChange(this, undefined);
}
});
}
@ -622,3 +662,12 @@ export function keepAlive(observable: IObservable<any>): IDisposable {
observable.read(reader);
}, 'keep-alive');
}
export function derivedObservableWithCache<T>(name: string, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable<T> {
let lastValue: T | undefined = undefined;
const observable = derivedObservable(name, reader => {
lastValue = computeFn(reader, lastValue);
return lastValue;
});
return observable;
}

View file

@ -272,9 +272,7 @@ export class BulkEditPane extends ViewPane {
}
private _done(accept: boolean): void {
if (this._currentResolve) {
this._currentResolve(accept ? this._currentInput?.getWorkspaceEdit() : undefined);
}
this._currentResolve?.(accept ? this._currentInput?.getWorkspaceEdit() : undefined);
this._currentInput = undefined;
this._setState(State.Message);
this._sessionDisposables.clear();

View file

@ -250,9 +250,7 @@ export class CommentReply<T extends IRange | ICellRange> extends Disposable {
}));
this._commentFormActions = new CommentFormActions(container, async (action: IAction) => {
if (this._actionRunDelegate) {
this._actionRunDelegate();
}
this._actionRunDelegate?.();
action.run({
thread: this._commentThread,

View file

@ -155,14 +155,10 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter {
switch (message.type) {
case 'event':
if (this.eventCallback) {
this.eventCallback(<DebugProtocol.Event>message);
}
this.eventCallback?.(<DebugProtocol.Event>message);
break;
case 'request':
if (this.requestCallback) {
this.requestCallback(<DebugProtocol.Request>message);
}
this.requestCallback?.(<DebugProtocol.Request>message);
break;
case 'response': {
const response = <DebugProtocol.Response>message;

View file

@ -1519,9 +1519,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
this.progressService.withProgress({ location: ProgressLocation.Extensions }, () => new Promise(resolve => this._activityCallBack = resolve));
}
} else {
if (this._activityCallBack) {
this._activityCallBack();
}
this._activityCallBack?.();
this._activityCallBack = null;
}
}

View file

@ -215,6 +215,10 @@
min-width: 0;
}
.monaco-list-row.disabled .extension-list-item .details .description {
color: var(--vscode-disabledForeground);
}
.extension-list-item .monaco-action-bar .action-label.icon {
padding: 1px 2px;
}

View file

@ -45,12 +45,14 @@ interface IMarkerCodeColumnTemplateData {
}
interface IMarkerFileColumnTemplateData {
readonly columnElement: HTMLElement;
readonly fileLabel: HighlightedLabel;
readonly positionLabel: HighlightedLabel;
}
interface IMarkerHighlightedLabelColumnTemplateData {
readonly columnElement: HTMLElement;
readonly highlightedLabel: HighlightedLabel;
}
@ -86,6 +88,7 @@ class MarkerSeverityColumnRenderer implements ITableRenderer<MarkerTableItem, IM
}
};
templateData.icon.title = MarkerSeverity.toString(element.marker.severity);
templateData.icon.className = `marker-icon codicon ${SeverityIcon.className(MarkerSeverity.toSeverity(element.marker.severity))}`;
templateData.actionBar.clear();
@ -137,9 +140,11 @@ class MarkerCodeColumnRenderer implements ITableRenderer<MarkerTableItem, IMarke
DOM.show(templateData.codeLabel.element);
if (typeof element.marker.code === 'string') {
templateData.codeColumn.title = `${element.marker.source} (${element.marker.code})`;
templateData.sourceLabel.set(element.marker.source, element.sourceMatches);
templateData.codeLabel.set(element.marker.code, element.codeMatches);
} else {
templateData.codeColumn.title = `${element.marker.source} (${element.marker.code.value})`;
templateData.sourceLabel.set(element.marker.source, element.sourceMatches);
const codeLinkLabel = new HighlightedLabel($('.code-link-label'));
@ -152,6 +157,7 @@ class MarkerCodeColumnRenderer implements ITableRenderer<MarkerTableItem, IMarke
};
}
} else {
templateData.codeColumn.title = '';
templateData.sourceLabel.set('-');
DOM.hide(templateData.codeLabel.element);
}
@ -167,13 +173,14 @@ class MarkerMessageColumnRenderer implements ITableRenderer<MarkerTableItem, IMa
readonly templateId: string = MarkerMessageColumnRenderer.TEMPLATE_ID;
renderTemplate(container: HTMLElement): IMarkerHighlightedLabelColumnTemplateData {
const fileColumn = DOM.append(container, $('.message'));
const highlightedLabel = new HighlightedLabel(fileColumn);
const columnElement = DOM.append(container, $('.message'));
const highlightedLabel = new HighlightedLabel(columnElement);
return { highlightedLabel };
return { columnElement, highlightedLabel };
}
renderElement(element: MarkerTableItem, index: number, templateData: IMarkerHighlightedLabelColumnTemplateData, height: number | undefined): void {
templateData.columnElement.title = element.marker.message;
templateData.highlightedLabel.set(element.marker.message, element.messageMatches);
}
@ -191,18 +198,21 @@ class MarkerFileColumnRenderer implements ITableRenderer<MarkerTableItem, IMarke
) { }
renderTemplate(container: HTMLElement): IMarkerFileColumnTemplateData {
const fileColumn = DOM.append(container, $('.file'));
const fileLabel = new HighlightedLabel(fileColumn);
const columnElement = DOM.append(container, $('.file'));
const fileLabel = new HighlightedLabel(columnElement);
fileLabel.element.classList.add('file-label');
const positionLabel = new HighlightedLabel(fileColumn);
const positionLabel = new HighlightedLabel(columnElement);
positionLabel.element.classList.add('file-position');
return { fileLabel, positionLabel };
return { columnElement, fileLabel, positionLabel };
}
renderElement(element: MarkerTableItem, index: number, templateData: IMarkerFileColumnTemplateData, height: number | undefined): void {
const positionLabel = Messages.MARKERS_PANEL_AT_LINE_COL_NUMBER(element.marker.startLineNumber, element.marker.startColumn);
templateData.columnElement.title = `${this.labelService.getUriLabel(element.marker.resource, { relative: false })} ${positionLabel}`;
templateData.fileLabel.set(this.labelService.getUriLabel(element.marker.resource, { relative: true }), element.fileMatches);
templateData.positionLabel.set(Messages.MARKERS_PANEL_AT_LINE_COL_NUMBER(element.marker.startLineNumber, element.marker.startColumn), undefined);
templateData.positionLabel.set(positionLabel, undefined);
}
disposeTemplate(templateData: IMarkerFileColumnTemplateData): void { }
@ -215,12 +225,13 @@ class MarkerOwnerColumnRenderer implements ITableRenderer<MarkerTableItem, IMark
readonly templateId: string = MarkerOwnerColumnRenderer.TEMPLATE_ID;
renderTemplate(container: HTMLElement): IMarkerHighlightedLabelColumnTemplateData {
const fileColumn = DOM.append(container, $('.owner'));
const highlightedLabel = new HighlightedLabel(fileColumn);
return { highlightedLabel };
const columnElement = DOM.append(container, $('.owner'));
const highlightedLabel = new HighlightedLabel(columnElement);
return { columnElement, highlightedLabel };
}
renderElement(element: MarkerTableItem, index: number, templateData: IMarkerHighlightedLabelColumnTemplateData, height: number | undefined): void {
templateData.columnElement.title = element.marker.owner;
templateData.highlightedLabel.set(element.marker.owner, element.ownerMatches);
}

View file

@ -30,6 +30,7 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/tex
import { localize } from 'vs/nls';
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@ -42,7 +43,7 @@ import { DEFAULT_EDITOR_MAX_DIMENSIONS, DEFAULT_EDITOR_MIN_DIMENSIONS } from 'vs
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { IEditorControl, IEditorOpenContext } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { autorun, derivedObservable, IObservable, ITransaction, keepAlive, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';
import { autorun, autorunWithStore, derivedObservable, IObservable, ITransaction, keepAlive, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable';
import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput';
import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorModel';
import { LineRange, ModifiedBaseRange } from 'vs/workbench/contrib/mergeEditor/browser/model';
@ -61,8 +62,8 @@ export class MergeEditor extends EditorPane {
private _grid!: Grid<IView>;
private readonly input1View = this.instantiation.createInstance(InputCodeEditorView, 1, { readonly: true });
private readonly input2View = this.instantiation.createInstance(InputCodeEditorView, 2, { readonly: true });
private readonly input1View = this.instantiation.createInstance(InputCodeEditorView, 1, { readonly: !this.inputsWritable });
private readonly input2View = this.instantiation.createInstance(InputCodeEditorView, 2, { readonly: !this.inputsWritable });
private readonly inputResultView = this.instantiation.createInstance(ResultCodeEditorView, { readonly: false });
private readonly _ctxIsMergeEditor: IContextKey<boolean>;
@ -71,6 +72,10 @@ export class MergeEditor extends EditorPane {
private _model: MergeEditorModel | undefined;
public get model(): MergeEditorModel | undefined { return this._model; }
private get inputsWritable(): boolean {
return !!this._configurationService.getValue<boolean>('mergeEditor.writableInputs');
}
constructor(
@IInstantiationService private readonly instantiation: IInstantiationService,
@ILabelService private readonly _labelService: ILabelService,
@ -80,6 +85,7 @@ export class MergeEditor extends EditorPane {
@IStorageService storageService: IStorageService,
@IThemeService themeService: IThemeService,
@ITextResourceConfigurationService private readonly textResourceConfigurationService: ITextResourceConfigurationService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
) {
super(MergeEditor.ID, telemetryService, themeService, storageService);
@ -94,7 +100,7 @@ export class MergeEditor extends EditorPane {
return undefined;
}
const resultDiffs = model.resultDiffs.read(reader);
const modifiedBaseRanges = ModifiedBaseRange.fromDiffs(model.base, model.input1, model.input1LinesDiffs, model.result, resultDiffs);
const modifiedBaseRanges = ModifiedBaseRange.fromDiffs(model.base, model.input1, model.input1LinesDiffs.read(reader), model.result, resultDiffs);
return modifiedBaseRanges;
});
const input2ResultMapping = derivedObservable('input2ResultMapping', reader => {
@ -103,7 +109,7 @@ export class MergeEditor extends EditorPane {
return undefined;
}
const resultDiffs = model.resultDiffs.read(reader);
const modifiedBaseRanges = ModifiedBaseRange.fromDiffs(model.base, model.input2, model.input2LinesDiffs, model.result, resultDiffs);
const modifiedBaseRanges = ModifiedBaseRange.fromDiffs(model.base, model.input2, model.input2LinesDiffs.read(reader), model.result, resultDiffs);
return modifiedBaseRanges;
});
@ -228,42 +234,44 @@ export class MergeEditor extends EditorPane {
// TODO: Update editor options!
const input1ViewZoneIds: string[] = [];
const input2ViewZoneIds: string[] = [];
for (const m of model.modifiedBaseRanges) {
const max = Math.max(m.input1Range.lineCount, m.input2Range.lineCount, 1);
this._sessionDisposables.add(autorunWithStore((reader, store) => {
const input1ViewZoneIds: string[] = [];
const input2ViewZoneIds: string[] = [];
for (const m of model.modifiedBaseRanges.read(reader)) {
const max = Math.max(m.input1Range.lineCount, m.input2Range.lineCount, 1);
this.input1View.editor.changeViewZones(a => {
input1ViewZoneIds.push(a.addZone({
afterLineNumber: m.input1Range.endLineNumberExclusive - 1,
heightInLines: max - m.input1Range.lineCount,
domNode: $('div.diagonal-fill'),
}));
});
this.input2View.editor.changeViewZones(a => {
input2ViewZoneIds.push(a.addZone({
afterLineNumber: m.input2Range.endLineNumberExclusive - 1,
heightInLines: max - m.input2Range.lineCount,
domNode: $('div.diagonal-fill'),
}));
});
}
this._sessionDisposables.add({
dispose: () => {
this.input1View.editor.changeViewZones(a => {
for (const zone of input1ViewZoneIds) {
a.removeZone(zone);
}
input1ViewZoneIds.push(a.addZone({
afterLineNumber: m.input1Range.endLineNumberExclusive - 1,
heightInLines: max - m.input1Range.lineCount,
domNode: $('div.diagonal-fill'),
}));
});
this.input2View.editor.changeViewZones(a => {
for (const zone of input2ViewZoneIds) {
a.removeZone(zone);
}
input2ViewZoneIds.push(a.addZone({
afterLineNumber: m.input2Range.endLineNumberExclusive - 1,
heightInLines: max - m.input2Range.lineCount,
domNode: $('div.diagonal-fill'),
}));
});
}
});
store.add({
dispose: () => {
this.input1View.editor.changeViewZones(a => {
for (const zone of input1ViewZoneIds) {
a.removeZone(zone);
}
});
this.input2View.editor.changeViewZones(a => {
for (const zone of input2ViewZoneIds) {
a.removeZone(zone);
}
});
}
});
}, 'update alignment view zones'));
}
protected override setEditorVisible(visible: boolean): void {
@ -448,7 +456,7 @@ class InputCodeEditorView extends CodeEditorView {
return [];
}
const result = new Array<IModelDeltaDecoration>();
for (const m of model.modifiedBaseRanges) {
for (const m of model.modifiedBaseRanges.read(reader)) {
const range = m.getInputRange(this.inputNumber);
if (!range.isEmpty) {
result.push({
@ -478,13 +486,14 @@ class InputCodeEditorView extends CodeEditorView {
getIntersectingGutterItems: (range, reader) => {
const model = this.model.read(reader);
if (!model) { return []; }
return model.modifiedBaseRanges
return model.modifiedBaseRanges.read(reader)
.filter((r) => r.getInputDiffs(this.inputNumber).length > 0)
.map<ModifiedBaseRangeGutterItemInfo>((baseRange, idx) => ({
id: idx.toString(),
additionalHeightInPx: 0,
offsetInPx: 0,
range: baseRange.getInputRange(this.inputNumber),
enabled: model.isUpToDate,
toggleState: derivedObservable('toggle', (reader) =>
model
.getState(baseRange)
@ -510,14 +519,19 @@ class InputCodeEditorView extends CodeEditorView {
}
interface ModifiedBaseRangeGutterItemInfo extends IGutterItemInfo {
enabled: IObservable<boolean>;
toggleState: IObservable<boolean | undefined>;
setState(value: boolean, tx: ITransaction | undefined): void;
setState(value: boolean, tx: ITransaction): void;
}
class MergeConflictGutterItemView extends Disposable implements IGutterItemView<ModifiedBaseRangeGutterItemInfo> {
constructor(private item: ModifiedBaseRangeGutterItemInfo, private readonly target: HTMLElement) {
private readonly item = new ObservableValue<ModifiedBaseRangeGutterItemInfo | undefined>(undefined, 'item');
constructor(item: ModifiedBaseRangeGutterItemInfo, private readonly target: HTMLElement) {
super();
this.item.set(item, undefined);
target.classList.add('merge-accept-gutter-marker');
// TODO: localized title
@ -526,7 +540,8 @@ class MergeConflictGutterItemView extends Disposable implements IGutterItemView<
this._register(
autorun((reader) => {
const value = this.item.toggleState.read(reader);
const item = this.item.read(reader)!;
const value = item.toggleState.read(reader);
checkBox.setIcon(
value === true
? Codicon.check
@ -535,11 +550,19 @@ class MergeConflictGutterItemView extends Disposable implements IGutterItemView<
: Codicon.circleFilled
);
checkBox.checked = value === true;
if (!item.enabled.read(reader)) {
checkBox.disable();
} else {
checkBox.enable();
}
}, 'Update Toggle State')
);
this._register(checkBox.onChange(() => {
this.item.setState(checkBox.checked, undefined);
transaction(tx => {
this.item.get()!.setState(checkBox.checked, tx);
});
}));
target.appendChild(n('div.background', [noBreakWhitespace]).root);
@ -555,7 +578,7 @@ class MergeConflictGutterItemView extends Disposable implements IGutterItemView<
}
update(baseRange: ModifiedBaseRangeGutterItemInfo): void {
this.item = baseRange;
this.item.set(baseRange, undefined);
}
}

View file

@ -14,7 +14,7 @@ import { ILabelService } from 'vs/platform/label/common/label';
import { IUntypedEditorInput, EditorInputCapabilities } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput';
import { MergeEditorModel, MergeEditorModelFactory } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorModel';
import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorModel';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
@ -39,10 +39,9 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput {
private _model?: MergeEditorModel;
private _outTextModel?: ITextFileEditorModel;
private readonly mergeEditorModelFactory = this._instaService.createInstance(MergeEditorModelFactory);
constructor(
private readonly _anchestor: URI,
private readonly _base: URI,
private readonly _input1: MergeEditorInputData,
private readonly _input2: MergeEditorInputData,
private readonly _result: URI,
@ -101,13 +100,14 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput {
if (!this._model) {
const anchestor = await this._textModelService.createModelReference(this._anchestor);
const base = await this._textModelService.createModelReference(this._base);
const input1 = await this._textModelService.createModelReference(this._input1.uri);
const input2 = await this._textModelService.createModelReference(this._input2.uri);
const result = await this._textModelService.createModelReference(this._result);
this._model = await this.mergeEditorModelFactory.create(
anchestor.object.textEditorModel,
this._model = this._instaService.createInstance(
MergeEditorModel,
base.object.textEditorModel,
input1.object.textEditorModel,
this._input1.detail,
this._input1.description,
@ -117,13 +117,13 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput {
result.object.textEditorModel
);
await this._model.onInitialized;
this._store.add(this._model);
this._store.add(anchestor);
this._store.add(base);
this._store.add(input1);
this._store.add(input2);
this._store.add(result);
// result.object.
}
return this._model;
}
@ -132,7 +132,7 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput {
if (!(otherInput instanceof MergeEditorInput)) {
return false;
}
return isEqual(this._anchestor, otherInput._anchestor)
return isEqual(this._base, otherInput._base)
&& isEqual(this._input1.uri, otherInput._input1.uri)
&& isEqual(this._input2.uri, otherInput._input2.uri)
&& isEqual(this._result, otherInput._result);
@ -140,7 +140,7 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput {
toJSON(): MergeEditorInputJSON {
return {
anchestor: this._anchestor,
anchestor: this._base,
inputOne: this._input1,
inputTwo: this._input2,
result: this._result,

View file

@ -3,96 +3,72 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter } from 'vs/base/common/event';
import { compareBy, CompareResult, equals, numberComparator } from 'vs/base/common/arrays';
import { compareBy, CompareResult, equals } from 'vs/base/common/arrays';
import { BugIndicatingError } from 'vs/base/common/errors';
import { ITextModel } from 'vs/editor/common/model';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
import { EditorModel } from 'vs/workbench/common/editor/editorModel';
import { IObservable, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable';
import { ModifiedBaseRange, LineEdit, LineDiff, ModifiedBaseRangeState, LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model';
import { leftJoin, ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/utils';
import { autorunHandleChanges, derivedObservable, derivedObservableWithCache, IObservable, ITransaction, keepAlive, ObservableValue, transaction, waitForState } from 'vs/workbench/contrib/audioCues/browser/observable';
import { LineDiff, LineEdit, LineRange, ModifiedBaseRange, ModifiedBaseRangeState } from 'vs/workbench/contrib/mergeEditor/browser/model';
import { EditorWorkerServiceDiffComputer, TextModelDiffChangeReason, TextModelDiffs, TextModelDiffState } from 'vs/workbench/contrib/mergeEditor/browser/textModelDiffs';
import { leftJoin } from 'vs/workbench/contrib/mergeEditor/browser/utils';
export class MergeEditorModelFactory {
constructor(
@IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService
) {
}
public async create(
base: ITextModel,
input1: ITextModel,
input1Detail: string | undefined,
input1Description: string | undefined,
input2: ITextModel,
input2Detail: string | undefined,
input2Description: string | undefined,
result: ITextModel,
): Promise<MergeEditorModel> {
const baseToInput1DiffPromise = this._editorWorkerService.computeDiff(
base.uri,
input1.uri,
false,
1000
);
const baseToInput2DiffPromise = this._editorWorkerService.computeDiff(
base.uri,
input2.uri,
false,
1000
);
const baseToResultDiffPromise = this._editorWorkerService.computeDiff(
base.uri,
result.uri,
false,
1000
);
const [baseToInput1Diff, baseToInput2Diff, baseToResultDiff] = await Promise.all([
baseToInput1DiffPromise,
baseToInput2DiffPromise,
baseToResultDiffPromise
]);
const changesInput1 =
baseToInput1Diff?.changes.map((c) =>
LineDiff.fromLineChange(c, base, input1)
) || [];
const changesInput2 =
baseToInput2Diff?.changes.map((c) =>
LineDiff.fromLineChange(c, base, input2)
) || [];
const changesResult =
baseToResultDiff?.changes.map((c) =>
LineDiff.fromLineChange(c, base, result)
) || [];
return new MergeEditorModel(
InternalSymbol,
base,
input1,
input1Detail,
input1Description,
input2,
input2Detail,
input2Description,
result,
changesInput1,
changesInput2,
changesResult,
this._editorWorkerService,
);
}
export const enum MergeEditorModelState {
initializing = 1,
upToDate = 2,
updating = 3,
}
const InternalSymbol: unique symbol = null!;
export class MergeEditorModel extends EditorModel {
private resultEdits: ResultEdits;
private readonly diffComputer = new EditorWorkerServiceDiffComputer(this.editorWorkerService);
private readonly input1TextModelDiffs = new TextModelDiffs(this.base, this.input1, this.diffComputer);
private readonly input2TextModelDiffs = new TextModelDiffs(this.base, this.input2, this.diffComputer);
private readonly resultTextModelDiffs = new TextModelDiffs(this.base, this.result, this.diffComputer);
public readonly state = derivedObservable('state', reader => {
const states = [
this.input1TextModelDiffs,
this.input2TextModelDiffs,
this.resultTextModelDiffs,
].map((s) => s.state.read(reader));
if (states.some((s) => s === TextModelDiffState.initializing)) {
return MergeEditorModelState.initializing;
}
if (states.some((s) => s === TextModelDiffState.updating)) {
return MergeEditorModelState.updating;
}
return MergeEditorModelState.upToDate;
});
public readonly isUpToDate = derivedObservable('isUpdating', reader => this.state.read(reader) === MergeEditorModelState.upToDate);
public readonly onInitialized = waitForState(this.state, state => state === MergeEditorModelState.upToDate);
public readonly modifiedBaseRanges = derivedObservableWithCache<ModifiedBaseRange[]>('modifiedBaseRanges', (reader, lastValue) => {
if (this.state.read(reader) !== MergeEditorModelState.upToDate) {
return lastValue || [];
}
const input1Diffs = this.input1TextModelDiffs.diffs.read(reader);
const input2Diffs = this.input2TextModelDiffs.diffs.read(reader);
return ModifiedBaseRange.fromDiffs(this.base, this.input1, input1Diffs, this.input2, input2Diffs);
});
public readonly input1LinesDiffs = this.input1TextModelDiffs.diffs;
public readonly input2LinesDiffs = this.input2TextModelDiffs.diffs;
public readonly resultDiffs = this.resultTextModelDiffs.diffs;
private readonly modifiedBaseRangeStateStores =
derivedObservable('modifiedBaseRangeStateStores', reader => {
const map = new Map(
this.modifiedBaseRanges.read(reader).map(s => ([s, new ObservableValue(ModifiedBaseRangeState.default, 'State')]))
);
return map;
});
constructor(
_symbol: typeof InternalSymbol,
readonly base: ITextModel,
readonly input1: ITextModel,
readonly input1Detail: string | undefined,
@ -101,27 +77,43 @@ export class MergeEditorModel extends EditorModel {
readonly input2Detail: string | undefined,
readonly input2Description: string | undefined,
readonly result: ITextModel,
public readonly input1LinesDiffs: readonly LineDiff[],
public readonly input2LinesDiffs: readonly LineDiff[],
resultDiffs: LineDiff[],
private readonly editorWorkerService: IEditorWorkerService
@IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService
) {
super();
this.resultEdits = new ResultEdits(resultDiffs, this.base, this.result, this.editorWorkerService);
this.resultEdits.onDidChange(() => {
this.recomputeState();
});
this.recomputeState();
this._register(keepAlive(this.modifiedBaseRangeStateStores));
this.resetUnknown();
this._register(
autorunHandleChanges(
'Recompute State',
{
handleChange: (ctx) =>
ctx.didChange(this.resultTextModelDiffs.diffs)
// Ignore non-text changes as we update the state directly
? ctx.change === TextModelDiffChangeReason.textChange
: true,
},
(reader) => {
if (!this.isUpToDate.read(reader)) {
return;
}
const resultDiffs = this.resultTextModelDiffs.diffs.read(reader);
const stores = this.modifiedBaseRangeStateStores.read(reader);
this.recomputeState(resultDiffs, stores);
}
)
);
this.onInitialized.then(() => {
this.resetUnknown();
});
}
private recomputeState(): void {
private recomputeState(resultDiffs: LineDiff[], stores: Map<ModifiedBaseRange, ObservableValue<ModifiedBaseRangeState>>): void {
transaction(tx => {
const baseRangeWithStoreAndTouchingDiffs = leftJoin(
this.modifiedBaseRangeStateStores,
this.resultEdits.diffs.get(),
stores,
resultDiffs,
(baseRange, diff) =>
baseRange[0].baseRange.touches(diff.originalRange)
? CompareResult.neitherLessOrGreaterThan
@ -132,14 +124,14 @@ export class MergeEditorModel extends EditorModel {
);
for (const row of baseRangeWithStoreAndTouchingDiffs) {
row.left[1].set(this.computeState(row.left[0], row.rights), tx);
row.left[1].set(computeState(row.left[0], row.rights), tx);
}
});
}
public resetUnknown(): void {
transaction(tx => {
for (const range of this.modifiedBaseRanges) {
for (const range of this.modifiedBaseRanges.get()) {
if (this.getState(range).get().conflicting) {
this.setState(range, ModifiedBaseRangeState.default, tx);
}
@ -149,7 +141,7 @@ export class MergeEditorModel extends EditorModel {
public mergeNonConflictingDiffs(): void {
transaction((tx) => {
for (const m of this.modifiedBaseRanges) {
for (const m of this.modifiedBaseRanges.get()) {
if (m.isConflicting) {
continue;
}
@ -164,54 +156,8 @@ export class MergeEditorModel extends EditorModel {
});
}
public get resultDiffs(): IObservable<readonly LineDiff[]> {
return this.resultEdits.diffs;
}
public readonly modifiedBaseRanges = ModifiedBaseRange.fromDiffs(
this.base,
this.input1,
this.input1LinesDiffs,
this.input2,
this.input2LinesDiffs
);
private readonly modifiedBaseRangeStateStores: ReadonlyMap<ModifiedBaseRange, ObservableValue<ModifiedBaseRangeState>> = new Map(
this.modifiedBaseRanges.map(s => ([s, new ObservableValue(ModifiedBaseRangeState.default, 'State')]))
);
private computeState(baseRange: ModifiedBaseRange, conflictingDiffs?: LineDiff[]): ModifiedBaseRangeState {
if (!conflictingDiffs) {
conflictingDiffs = this.resultEdits.findTouchingDiffs(
baseRange.baseRange
);
}
if (conflictingDiffs.length === 0) {
return ModifiedBaseRangeState.default;
}
const conflictingEdits = conflictingDiffs.map((d) => d.getLineEdit());
function editsAgreeWithDiffs(diffs: readonly LineDiff[]): boolean {
return equals(
conflictingEdits,
diffs.map((d) => d.getLineEdit()),
(a, b) => a.equals(b)
);
}
if (editsAgreeWithDiffs(baseRange.input1Diffs)) {
return ModifiedBaseRangeState.default.withInput1(true);
}
if (editsAgreeWithDiffs(baseRange.input2Diffs)) {
return ModifiedBaseRangeState.default.withInput2(true);
}
return ModifiedBaseRangeState.conflicting;
}
public getState(baseRange: ModifiedBaseRange): IObservable<ModifiedBaseRangeState> {
const existingState = this.modifiedBaseRangeStateStores.get(baseRange);
const existingState = this.modifiedBaseRangeStateStores.get().get(baseRange);
if (!existingState) {
throw new BugIndicatingError('object must be from this instance');
}
@ -221,240 +167,122 @@ export class MergeEditorModel extends EditorModel {
public setState(
baseRange: ModifiedBaseRange,
state: ModifiedBaseRangeState,
transaction: ITransaction | undefined
transaction: ITransaction
): void {
const existingState = this.modifiedBaseRangeStateStores.get(baseRange);
if (!this.isUpToDate.get()) {
throw new BugIndicatingError('Cannot set state while updating');
}
const existingState = this.modifiedBaseRangeStateStores.get().get(baseRange);
if (!existingState) {
throw new BugIndicatingError('object must be from this instance');
}
const conflictingDiffs = this.resultEdits.findTouchingDiffs(
const conflictingDiffs = this.resultTextModelDiffs.findTouchingDiffs(
baseRange.baseRange
);
if (conflictingDiffs) {
this.resultEdits.removeDiffs(conflictingDiffs, transaction);
this.resultTextModelDiffs.removeDiffs(conflictingDiffs, transaction);
}
function getEdit(baseRange: ModifiedBaseRange, state: ModifiedBaseRangeState): { edit: LineEdit | undefined; effectiveState: ModifiedBaseRangeState } {
interface LineDiffWithInputNumber {
diff: LineDiff;
inputNumber: 1 | 2;
}
const diffs = new Array<LineDiffWithInputNumber>();
if (state.input1) {
if (baseRange.input1CombinedDiff) {
diffs.push({ diff: baseRange.input1CombinedDiff, inputNumber: 1 });
}
}
if (state.input2) {
if (baseRange.input2CombinedDiff) {
diffs.push({ diff: baseRange.input2CombinedDiff, inputNumber: 2 });
}
}
if (state.input2First) {
diffs.reverse();
}
const firstDiff: LineDiffWithInputNumber | undefined = diffs[0];
const secondDiff: LineDiffWithInputNumber | undefined = diffs[1];
diffs.sort(compareBy(d => d.diff.originalRange, LineRange.compareByStart));
if (!firstDiff) {
return { edit: undefined, effectiveState: state };
}
if (!secondDiff) {
return { edit: firstDiff.diff.getLineEdit(), effectiveState: state };
}
// Two inserts
if (
firstDiff.diff.originalRange.lineCount === 0 &&
firstDiff.diff.originalRange.equals(secondDiff.diff.originalRange)
) {
return {
edit: new LineEdit(
firstDiff.diff.originalRange,
firstDiff.diff
.getLineEdit()
.newLines.concat(secondDiff.diff.getLineEdit().newLines)
),
effectiveState: state,
};
}
// Technically non-conflicting diffs
if (diffs.length === 2 && diffs[0].diff.originalRange.endLineNumberExclusive === diffs[1].diff.originalRange.startLineNumber) {
return {
edit: new LineEdit(
LineRange.join(diffs.map(d => d.diff.originalRange))!,
diffs.flatMap(d => d.diff.getLineEdit().newLines)
),
effectiveState: state,
};
}
return { edit: firstDiff.diff.getLineEdit(), effectiveState: state };
}
const { edit, effectiveState } = getEdit(baseRange, state);
const { edit, effectiveState } = getEditForBase(baseRange, state);
existingState.set(effectiveState, transaction);
if (edit) {
this.resultEdits.applyEditRelativeToOriginal(edit, transaction);
this.resultTextModelDiffs.applyEditRelativeToOriginal(edit, transaction);
}
}
public getResultRange(baseRange: LineRange): LineRange {
return this.resultEdits.getResultRange(baseRange);
}
}
class ResultEdits {
private readonly barrier = new ReentrancyBarrier();
private readonly onDidChangeEmitter = new Emitter();
public readonly onDidChange = this.onDidChangeEmitter.event;
function getEditForBase(baseRange: ModifiedBaseRange, state: ModifiedBaseRangeState): { edit: LineEdit | undefined; effectiveState: ModifiedBaseRangeState } {
interface LineDiffWithInputNumber {
diff: LineDiff;
inputNumber: 1 | 2;
}
constructor(
diffs: LineDiff[],
private readonly baseTextModel: ITextModel,
private readonly resultTextModel: ITextModel,
private readonly _editorWorkerService: IEditorWorkerService
const diffs = new Array<LineDiffWithInputNumber>();
if (state.input1) {
if (baseRange.input1CombinedDiff) {
diffs.push({ diff: baseRange.input1CombinedDiff, inputNumber: 1 });
}
}
if (state.input2) {
if (baseRange.input2CombinedDiff) {
diffs.push({ diff: baseRange.input2CombinedDiff, inputNumber: 2 });
}
}
if (state.input2First) {
diffs.reverse();
}
const firstDiff: LineDiffWithInputNumber | undefined = diffs[0];
const secondDiff: LineDiffWithInputNumber | undefined = diffs[1];
diffs.sort(compareBy(d => d.diff.originalRange, LineRange.compareByStart));
if (!firstDiff) {
return { edit: undefined, effectiveState: ModifiedBaseRangeState.default };
}
if (!secondDiff) {
return { edit: firstDiff.diff.getLineEdit(), effectiveState: ModifiedBaseRangeState.default.withInputValue(firstDiff.inputNumber, true) };
}
// Two inserts
if (
firstDiff.diff.originalRange.lineCount === 0 &&
firstDiff.diff.originalRange.equals(secondDiff.diff.originalRange)
) {
diffs.sort(compareBy((d) => d.originalRange.startLineNumber, numberComparator));
this._diffs.set(diffs, undefined);
resultTextModel.onDidChangeContent(e => {
this.barrier.runExclusively(() => {
this._editorWorkerService.computeDiff(
baseTextModel.uri,
resultTextModel.uri,
false,
1000
).then(e => {
const diffs =
e?.changes.map((c) =>
LineDiff.fromLineChange(c, baseTextModel, resultTextModel)
) || [];
this._diffs.set(diffs, undefined);
this.onDidChangeEmitter.fire(undefined);
});
});
});
return {
edit: new LineEdit(
firstDiff.diff.originalRange,
firstDiff.diff
.getLineEdit()
.newLines.concat(secondDiff.diff.getLineEdit().newLines)
),
effectiveState: state,
};
}
private readonly _diffs = new ObservableValue<LineDiff[]>([], 'diffs');
public readonly diffs: IObservable<readonly LineDiff[]> = this._diffs;
public removeDiffs(diffToRemoves: LineDiff[], transaction: ITransaction | undefined): void {
diffToRemoves.sort(compareBy((d) => d.originalRange.startLineNumber, numberComparator));
diffToRemoves.reverse();
let diffs = this._diffs.get();
for (const diffToRemove of diffToRemoves) {
// TODO improve performance
const len = diffs.length;
diffs = diffs.filter((d) => d !== diffToRemove);
if (len === diffs.length) {
throw new BugIndicatingError();
}
this.barrier.runExclusivelyOrThrow(() => {
diffToRemove.getReverseLineEdit().apply(this.resultTextModel);
});
diffs = diffs.map((d) =>
d.modifiedRange.isAfter(diffToRemove.modifiedRange)
? new LineDiff(
d.originalTextModel,
d.originalRange,
d.modifiedTextModel,
d.modifiedRange.delta(
diffToRemove.originalRange.lineCount - diffToRemove.modifiedRange.lineCount
)
)
: d
);
}
this._diffs.set(diffs, transaction);
// Technically non-conflicting diffs
if (diffs.length === 2 && diffs[0].diff.originalRange.endLineNumberExclusive === diffs[1].diff.originalRange.startLineNumber) {
return {
edit: new LineEdit(
LineRange.join(diffs.map(d => d.diff.originalRange))!,
diffs.flatMap(d => d.diff.getLineEdit().newLines)
),
effectiveState: state,
};
}
/**
* Edit must be conflict free.
*/
public applyEditRelativeToOriginal(edit: LineEdit, transaction: ITransaction | undefined): void {
let firstAfter = false;
let delta = 0;
const newDiffs = new Array<LineDiff>();
for (const diff of this._diffs.get()) {
if (diff.originalRange.touches(edit.range)) {
throw new BugIndicatingError('Edit must be conflict free.');
} else if (diff.originalRange.isAfter(edit.range)) {
if (!firstAfter) {
firstAfter = true;
newDiffs.push(new LineDiff(
this.baseTextModel,
edit.range,
this.resultTextModel,
new LineRange(edit.range.startLineNumber + delta, edit.newLines.length)
));
}
newDiffs.push(new LineDiff(
diff.originalTextModel,
diff.originalRange,
diff.modifiedTextModel,
diff.modifiedRange.delta(edit.newLines.length - edit.range.lineCount)
));
} else {
newDiffs.push(diff);
}
if (!firstAfter) {
delta += diff.modifiedRange.lineCount - diff.originalRange.lineCount;
}
}
if (!firstAfter) {
firstAfter = true;
newDiffs.push(new LineDiff(
this.baseTextModel,
edit.range,
this.resultTextModel,
new LineRange(edit.range.startLineNumber + delta, edit.newLines.length)
));
}
this.barrier.runExclusivelyOrThrow(() => {
new LineEdit(edit.range.delta(delta), edit.newLines).apply(this.resultTextModel);
});
this._diffs.set(newDiffs, transaction);
}
public findTouchingDiffs(baseRange: LineRange): LineDiff[] {
return this.diffs.get().filter(d => d.originalRange.touches(baseRange));
}
public getResultRange(baseRange: LineRange): LineRange {
let startOffset = 0;
let lengthOffset = 0;
for (const diff of this.diffs.get()) {
if (diff.originalRange.endLineNumberExclusive <= baseRange.startLineNumber) {
startOffset += diff.resultingDeltaFromOriginalToModified;
} else if (diff.originalRange.startLineNumber <= baseRange.endLineNumberExclusive) {
lengthOffset += diff.resultingDeltaFromOriginalToModified;
} else {
break;
}
}
return new LineRange(baseRange.startLineNumber + startOffset, baseRange.lineCount + lengthOffset);
}
return {
edit: secondDiff.diff.getLineEdit(),
effectiveState: ModifiedBaseRangeState.default.withInputValue(
secondDiff.inputNumber,
true
),
};
}
function computeState(baseRange: ModifiedBaseRange, conflictingDiffs: LineDiff[]): ModifiedBaseRangeState {
if (conflictingDiffs.length === 0) {
return ModifiedBaseRangeState.default;
}
const conflictingEdits = conflictingDiffs.map((d) => d.getLineEdit());
function editsAgreeWithDiffs(diffs: readonly LineDiff[]): boolean {
return equals(
conflictingEdits,
diffs.map((d) => d.getLineEdit()),
(a, b) => a.equals(b)
);
}
if (editsAgreeWithDiffs(baseRange.input1Diffs)) {
return ModifiedBaseRangeState.default.withInput1(true);
}
if (editsAgreeWithDiffs(baseRange.input2Diffs)) {
return ModifiedBaseRangeState.default.withInput2(true);
}
return ModifiedBaseRangeState.conflicting;
}

View file

@ -0,0 +1,229 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { compareBy, numberComparator } from 'vs/base/common/arrays';
import { BugIndicatingError } from 'vs/base/common/errors';
import { Disposable } from 'vs/base/common/lifecycle';
import { ITextModel } from 'vs/editor/common/model';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
import { IObservable, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable';
import { LineDiff, LineEdit, LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model';
import { ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/utils';
export class TextModelDiffs extends Disposable {
private updateCount = 0;
private readonly _state = new ObservableValue<TextModelDiffState, TextModelDiffChangeReason>(TextModelDiffState.initializing, 'LiveDiffState');
private readonly _diffs = new ObservableValue<LineDiff[], TextModelDiffChangeReason>([], 'LiveDiffs');
private readonly barrier = new ReentrancyBarrier();
constructor(
private readonly baseTextModel: ITextModel,
private readonly textModel: ITextModel,
private readonly diffComputer: IDiffComputer,
) {
super();
this.update(true);
this._register(baseTextModel.onDidChangeContent(this.barrier.makeExclusive(() => this.update())));
this._register(textModel.onDidChangeContent(this.barrier.makeExclusive(() => this.update())));
}
public get state(): IObservable<TextModelDiffState, TextModelDiffChangeReason> {
return this._state;
}
public get diffs(): IObservable<LineDiff[], TextModelDiffChangeReason> {
return this._diffs;
}
private async update(initializing: boolean = false): Promise<void> {
this.updateCount++;
const currentUpdateCount = this.updateCount;
if (this._state.get() === TextModelDiffState.initializing) {
initializing = true;
}
transaction(tx => {
this._state.set(
initializing ? TextModelDiffState.initializing : TextModelDiffState.updating,
tx,
TextModelDiffChangeReason.other
);
});
const result = await this.diffComputer.computeDiff(this.baseTextModel, this.textModel);
if (currentUpdateCount !== this.updateCount) {
// There is a newer update call
return;
}
transaction(tx => {
if (result) {
this._state.set(TextModelDiffState.upToDate, tx, TextModelDiffChangeReason.textChange);
this._diffs.set(result, tx, TextModelDiffChangeReason.textChange);
} else {
this._state.set(TextModelDiffState.error, tx, TextModelDiffChangeReason.textChange);
}
});
}
private ensureUpToDate(): void {
if (this.state.get() !== TextModelDiffState.upToDate) {
throw new BugIndicatingError('Cannot remove diffs when the model is not up to date');
}
}
public removeDiffs(diffToRemoves: LineDiff[], transaction: ITransaction | undefined): void {
this.ensureUpToDate();
diffToRemoves.sort(compareBy((d) => d.originalRange.startLineNumber, numberComparator));
diffToRemoves.reverse();
let diffs = this._diffs.get();
for (const diffToRemove of diffToRemoves) {
// TODO improve performance
const len = diffs.length;
diffs = diffs.filter((d) => d !== diffToRemove);
if (len === diffs.length) {
throw new BugIndicatingError();
}
this.barrier.runExclusivelyOrThrow(() => {
diffToRemove.getReverseLineEdit().apply(this.textModel);
});
diffs = diffs.map((d) =>
d.modifiedRange.isAfter(diffToRemove.modifiedRange)
? new LineDiff(
d.originalTextModel,
d.originalRange,
d.modifiedTextModel,
d.modifiedRange.delta(
diffToRemove.originalRange.lineCount - diffToRemove.modifiedRange.lineCount
)
)
: d
);
}
this._diffs.set(diffs, transaction, TextModelDiffChangeReason.other);
}
/**
* Edit must be conflict free.
*/
public applyEditRelativeToOriginal(edit: LineEdit, transaction: ITransaction | undefined): void {
this.ensureUpToDate();
let firstAfter = false;
let delta = 0;
const newDiffs = new Array<LineDiff>();
for (const diff of this.diffs.get()) {
if (diff.originalRange.touches(edit.range)) {
throw new BugIndicatingError('Edit must be conflict free.');
} else if (diff.originalRange.isAfter(edit.range)) {
if (!firstAfter) {
firstAfter = true;
newDiffs.push(new LineDiff(
this.baseTextModel,
edit.range,
this.textModel,
new LineRange(edit.range.startLineNumber + delta, edit.newLines.length)
));
}
newDiffs.push(new LineDiff(
diff.originalTextModel,
diff.originalRange,
diff.modifiedTextModel,
diff.modifiedRange.delta(edit.newLines.length - edit.range.lineCount)
));
} else {
newDiffs.push(diff);
}
if (!firstAfter) {
delta += diff.modifiedRange.lineCount - diff.originalRange.lineCount;
}
}
if (!firstAfter) {
firstAfter = true;
newDiffs.push(new LineDiff(
this.baseTextModel,
edit.range,
this.textModel,
new LineRange(edit.range.startLineNumber + delta, edit.newLines.length)
));
}
this.barrier.runExclusivelyOrThrow(() => {
new LineEdit(edit.range.delta(delta), edit.newLines).apply(this.textModel);
});
this._diffs.set(newDiffs, transaction, TextModelDiffChangeReason.other);
}
public findTouchingDiffs(baseRange: LineRange): LineDiff[] {
return this.diffs.get().filter(d => d.originalRange.touches(baseRange));
}
/*
public getResultRange(baseRange: LineRange): LineRange {
let startOffset = 0;
let lengthOffset = 0;
for (const diff of this.diffs.get()) {
if (diff.originalRange.endLineNumberExclusive <= baseRange.startLineNumber) {
startOffset += diff.resultingDeltaFromOriginalToModified;
} else if (diff.originalRange.startLineNumber <= baseRange.endLineNumberExclusive) {
lengthOffset += diff.resultingDeltaFromOriginalToModified;
} else {
break;
}
}
return new LineRange(baseRange.startLineNumber + startOffset, baseRange.lineCount + lengthOffset);
}
*/
}
export const enum TextModelDiffChangeReason {
other = 0,
textChange = 1,
}
export const enum TextModelDiffState {
initializing = 1,
upToDate = 2,
updating = 3,
error = 4,
}
export interface ITextModelDiffsState {
state: TextModelDiffState;
diffs: LineDiff[];
}
export interface IDiffComputer {
computeDiff(textModel1: ITextModel, textModel2: ITextModel): Promise<LineDiff[] | null>;
}
export class EditorWorkerServiceDiffComputer implements IDiffComputer {
constructor(@IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService) { }
async computeDiff(textModel1: ITextModel, textModel2: ITextModel): Promise<LineDiff[] | null> {
//await wait(1000);
const diffs = await this.editorWorkerService.computeDiff(textModel1.uri, textModel2.uri, false, 1000);
if (!diffs || diffs.quitEarly) {
return null;
}
return diffs.changes.map((c) => LineDiff.fromLineChange(c, textModel1, textModel2));
}
}

View file

@ -544,6 +544,8 @@ registerAction2(class RevealRunningCellAction extends NotebookAction {
super({
id: REVEAL_RUNNING_CELL,
title: localize('revealRunningCell', "Go To Running Cell"),
tooltip: localize('revealRunningCell', "Go To Running Cell"),
shortTitle: localize('revealRunningCellShort', "Go To"),
precondition: NOTEBOOK_HAS_RUNNING_CELL,
menu: [
{
@ -587,7 +589,7 @@ registerAction2(class RevealRunningCellAction extends NotebookAction {
if (executingCells[0]) {
const cell = context.notebookEditor.getCellByHandle(executingCells[0].cellHandle);
if (cell) {
context.notebookEditor.revealInCenter(cell);
context.notebookEditor.focusNotebookCell(cell, 'container');
}
}
}

View file

@ -1459,11 +1459,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
}
hasPendingChangeContentHeight = true;
DOM.scheduleAtNextAnimationFrame(() => {
this._localStore.add(DOM.scheduleAtNextAnimationFrame(() => {
hasPendingChangeContentHeight = false;
this._updateScrollHeight();
this._onDidChangeContentHeight.fire(this._list.getScrollHeight());
}, 100);
}, 100));
}));
this._localStore.add(this._list.onDidRemoveOutputs(outputs => {

View file

@ -217,7 +217,7 @@ function updateToEdit(update: ICellExecuteUpdate, cellHandle: number): ICellEdit
if (update.editType === CellExecutionUpdateType.Output) {
return {
editType: CellEditType.Output,
handle: cellHandle,
handle: update.cellHandle,
append: update.append,
outputs: update.outputs,
};

View file

@ -164,9 +164,7 @@ export class CellTitleToolbarPart extends CellPart {
if (deferredUpdate && !visible) {
this._register(disposableTimeout(() => {
if (deferredUpdate) {
deferredUpdate();
}
deferredUpdate?.();
}));
deferredUpdate = undefined;

View file

@ -405,9 +405,7 @@ export class NotebookEditorToolbar extends Disposable {
if (deferredUpdate && !visible) {
setTimeout(() => {
if (deferredUpdate) {
deferredUpdate();
}
deferredUpdate?.();
}, 0);
deferredUpdate = undefined;
}

View file

@ -15,6 +15,7 @@ export enum CellExecutionUpdateType {
export interface ICellExecuteOutputEdit {
editType: CellExecutionUpdateType.Output;
cellHandle: number;
append?: boolean;
outputs: IOutputDto[];
}

View file

@ -1166,9 +1166,7 @@ export class SettingArrayRenderer extends AbstractSettingRenderer implements ITr
common.toDispose.add(
listWidget.onDidChangeList(e => {
const newList = this.computeNewList(template, e);
if (template.onChange) {
template.onChange(newList);
}
template.onChange?.(newList);
})
);
@ -1349,9 +1347,7 @@ abstract class AbstractSettingObjectRenderer extends AbstractSettingRenderer imp
template.objectDropdownWidget!.setValue(newItems);
}
if (template.onChange) {
template.onChange(newValue);
}
template.onChange?.(newValue);
}
}
@ -1539,9 +1535,7 @@ abstract class AbstractSettingTextRenderer extends AbstractSettingRenderer imple
}));
common.toDispose.add(
inputBox.onDidChange(e => {
if (template.onChange) {
template.onChange(e);
}
template.onChange?.(e);
}));
common.toDispose.add(inputBox);
inputBox.inputElement.classList.add(AbstractSettingRenderer.CONTROL_CLASS);
@ -1652,9 +1646,7 @@ export class SettingEnumRenderer extends AbstractSettingRenderer implements ITre
common.toDispose.add(
selectBox.onDidSelect(e => {
if (template.onChange) {
template.onChange(e.index);
}
template.onChange?.(e.index);
}));
const enumDescriptionElement = common.containerElement.insertBefore($('.setting-item-enumDescription'), common.descriptionElement.nextSibling);
@ -1758,9 +1750,7 @@ export class SettingNumberRenderer extends AbstractSettingRenderer implements IT
}));
common.toDispose.add(
inputBox.onDidChange(e => {
if (template.onChange) {
template.onChange(e);
}
template.onChange?.(e);
}));
common.toDispose.add(inputBox);
inputBox.inputElement.classList.add(AbstractSettingRenderer.CONTROL_CLASS);

View file

@ -1077,9 +1077,7 @@ export class SearchModel extends Disposable {
progressEmitter.fire();
this.onSearchProgress(p);
if (onProgress) {
onProgress(p);
}
onProgress?.(p);
});
const dispose = () => tokenSource.dispose();

File diff suppressed because it is too large Load diff

View file

@ -26,40 +26,40 @@ const ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE = 'tasks.run.allowAutomatic';
export class RunAutomaticTasks extends Disposable implements IWorkbenchContribution {
constructor(
@ITaskService private readonly taskService: ITaskService,
@IStorageService private readonly storageService: IStorageService,
@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService,
@ILogService private readonly logService: ILogService) {
@ITaskService private readonly _taskService: ITaskService,
@IStorageService private readonly _storageService: IStorageService,
@IWorkspaceTrustManagementService private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService,
@ILogService private readonly _logService: ILogService) {
super();
this.tryRunTasks();
this._tryRunTasks();
}
private async tryRunTasks() {
this.logService.trace('RunAutomaticTasks: Trying to run tasks.');
private async _tryRunTasks() {
this._logService.trace('RunAutomaticTasks: Trying to run tasks.');
// Wait until we have task system info (the extension host and workspace folders are available).
if (!this.taskService.hasTaskSystemInfo) {
this.logService.trace('RunAutomaticTasks: Awaiting task system info.');
await Event.toPromise(Event.once(this.taskService.onDidChangeTaskSystemInfo));
if (!this._taskService.hasTaskSystemInfo) {
this._logService.trace('RunAutomaticTasks: Awaiting task system info.');
await Event.toPromise(Event.once(this._taskService.onDidChangeTaskSystemInfo));
}
this.logService.trace('RunAutomaticTasks: Checking if automatic tasks should run.');
const isFolderAutomaticAllowed = this.storageService.getBoolean(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, StorageScope.WORKSPACE, undefined);
await this.workspaceTrustManagementService.workspaceTrustInitialized;
const isWorkspaceTrusted = this.workspaceTrustManagementService.isWorkspaceTrusted();
this._logService.trace('RunAutomaticTasks: Checking if automatic tasks should run.');
const isFolderAutomaticAllowed = this._storageService.getBoolean(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, StorageScope.WORKSPACE, undefined);
await this._workspaceTrustManagementService.workspaceTrustInitialized;
const isWorkspaceTrusted = this._workspaceTrustManagementService.isWorkspaceTrusted();
// Only run if allowed. Prompting for permission occurs when a user first tries to run a task.
if (isFolderAutomaticAllowed && isWorkspaceTrusted) {
this.taskService.getWorkspaceTasks(TaskRunSource.FolderOpen).then(workspaceTaskResult => {
const { tasks } = RunAutomaticTasks.findAutoTasks(this.taskService, workspaceTaskResult);
this.logService.trace(`RunAutomaticTasks: Found ${tasks.length} automatic tasks tasks`);
this._taskService.getWorkspaceTasks(TaskRunSource.FolderOpen).then(workspaceTaskResult => {
const { tasks } = RunAutomaticTasks._findAutoTasks(this._taskService, workspaceTaskResult);
this._logService.trace(`RunAutomaticTasks: Found ${tasks.length} automatic tasks tasks`);
if (tasks.length > 0) {
RunAutomaticTasks.runTasks(this.taskService, tasks);
RunAutomaticTasks._runTasks(this._taskService, tasks);
}
});
}
}
private static runTasks(taskService: ITaskService, tasks: Array<Task | Promise<Task | undefined>>) {
private static _runTasks(taskService: ITaskService, tasks: Array<Task | Promise<Task | undefined>>) {
tasks.forEach(task => {
if (task instanceof Promise) {
task.then(promiseResult => {
@ -73,7 +73,7 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
});
}
private static getTaskSource(source: TaskSource): URI | undefined {
private static _getTaskSource(source: TaskSource): URI | undefined {
const taskKind = TaskSourceKind.toConfigurationTarget(source.kind);
switch (taskKind) {
case ConfigurationTarget.WORKSPACE_FOLDER: {
@ -86,7 +86,7 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
return undefined;
}
private static findAutoTasks(taskService: ITaskService, workspaceTaskResult: Map<string, IWorkspaceFolderTaskResult>): { tasks: Array<Task | Promise<Task | undefined>>; taskNames: Array<string>; locations: Map<string, URI> } {
private static _findAutoTasks(taskService: ITaskService, workspaceTaskResult: Map<string, IWorkspaceFolderTaskResult>): { tasks: Array<Task | Promise<Task | undefined>>; taskNames: Array<string>; locations: Map<string, URI> } {
const tasks = new Array<Task | Promise<Task | undefined>>();
const taskNames = new Array<string>();
const locations = new Map<string, URI>();
@ -98,7 +98,7 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
if (task.runOptions.runOn === RunOnOptions.folderOpen) {
tasks.push(task);
taskNames.push(task._label);
const location = RunAutomaticTasks.getTaskSource(task._source);
const location = RunAutomaticTasks._getTaskSource(task._source);
if (location) {
locations.set(location.fsPath, location);
}
@ -116,7 +116,7 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
} else {
taskNames.push(configedTask.value.configures.task);
}
const location = RunAutomaticTasks.getTaskSource(configedTask.value._source);
const location = RunAutomaticTasks._getTaskSource(configedTask.value._source);
if (location) {
locations.set(location.fsPath, location);
}
@ -140,18 +140,18 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
return;
}
const { tasks, taskNames, locations } = RunAutomaticTasks.findAutoTasks(taskService, workspaceTaskResult);
const { tasks, taskNames, locations } = RunAutomaticTasks._findAutoTasks(taskService, workspaceTaskResult);
if (taskNames.length > 0) {
// We have automatic tasks, prompt to allow.
this.showPrompt(notificationService, storageService, taskService, openerService, taskNames, locations).then(allow => {
this._showPrompt(notificationService, storageService, taskService, openerService, taskNames, locations).then(allow => {
if (allow) {
RunAutomaticTasks.runTasks(taskService, tasks);
RunAutomaticTasks._runTasks(taskService, tasks);
}
});
}
}
private static showPrompt(notificationService: INotificationService, storageService: IStorageService, taskService: ITaskService,
private static _showPrompt(notificationService: INotificationService, storageService: IStorageService, taskService: ITaskService,
openerService: IOpenerService, taskNames: Array<string>, locations: Map<string, URI>): Promise<boolean> {
return new Promise<boolean>(resolve => {
notificationService.prompt(Severity.Info, nls.localize('tasks.run.allowAutomatic',

View file

@ -56,31 +56,31 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
});
export class TaskStatusBarContributions extends Disposable implements IWorkbenchContribution {
private runningTasksStatusItem: IStatusbarEntryAccessor | undefined;
private activeTasksCount: number = 0;
private _runningTasksStatusItem: IStatusbarEntryAccessor | undefined;
private _activeTasksCount: number = 0;
constructor(
@ITaskService private readonly taskService: ITaskService,
@IStatusbarService private readonly statusbarService: IStatusbarService,
@IProgressService private readonly progressService: IProgressService
@ITaskService private readonly _taskService: ITaskService,
@IStatusbarService private readonly _statusbarService: IStatusbarService,
@IProgressService private readonly _progressService: IProgressService
) {
super();
this.registerListeners();
this._registerListeners();
}
private registerListeners(): void {
private _registerListeners(): void {
let promise: Promise<void> | undefined = undefined;
let resolver: (value?: void | Thenable<void>) => void;
this.taskService.onDidStateChange(event => {
this._taskService.onDidStateChange(event => {
if (event.kind === TaskEventKind.Changed) {
this.updateRunningTasksStatus();
this._updateRunningTasksStatus();
}
if (!this.ignoreEventForUpdateRunningTasksCount(event)) {
if (!this._ignoreEventForUpdateRunningTasksCount(event)) {
switch (event.kind) {
case TaskEventKind.Active:
this.activeTasksCount++;
if (this.activeTasksCount === 1) {
this._activeTasksCount++;
if (this._activeTasksCount === 1) {
if (!promise) {
promise = new Promise<void>((resolve) => {
resolver = resolve;
@ -91,9 +91,9 @@ export class TaskStatusBarContributions extends Disposable implements IWorkbench
case TaskEventKind.Inactive:
// Since the exiting of the sub process is communicated async we can't order inactive and terminate events.
// So try to treat them accordingly.
if (this.activeTasksCount > 0) {
this.activeTasksCount--;
if (this.activeTasksCount === 0) {
if (this._activeTasksCount > 0) {
this._activeTasksCount--;
if (this._activeTasksCount === 0) {
if (promise && resolver!) {
resolver!();
}
@ -101,8 +101,8 @@ export class TaskStatusBarContributions extends Disposable implements IWorkbench
}
break;
case TaskEventKind.Terminated:
if (this.activeTasksCount !== 0) {
this.activeTasksCount = 0;
if (this._activeTasksCount !== 0) {
this._activeTasksCount = 0;
if (promise && resolver!) {
resolver!();
}
@ -111,8 +111,8 @@ export class TaskStatusBarContributions extends Disposable implements IWorkbench
}
}
if (promise && (event.kind === TaskEventKind.Active) && (this.activeTasksCount === 1)) {
this.progressService.withProgress({ location: ProgressLocation.Window, command: 'workbench.action.tasks.showTasks' }, progress => {
if (promise && (event.kind === TaskEventKind.Active) && (this._activeTasksCount === 1)) {
this._progressService.withProgress({ location: ProgressLocation.Window, command: 'workbench.action.tasks.showTasks' }, progress => {
progress.report({ message: nls.localize('building', 'Building...') });
return promise!;
}).then(() => {
@ -122,12 +122,12 @@ export class TaskStatusBarContributions extends Disposable implements IWorkbench
});
}
private async updateRunningTasksStatus(): Promise<void> {
const tasks = await this.taskService.getActiveTasks();
private async _updateRunningTasksStatus(): Promise<void> {
const tasks = await this._taskService.getActiveTasks();
if (tasks.length === 0) {
if (this.runningTasksStatusItem) {
this.runningTasksStatusItem.dispose();
this.runningTasksStatusItem = undefined;
if (this._runningTasksStatusItem) {
this._runningTasksStatusItem.dispose();
this._runningTasksStatusItem = undefined;
}
} else {
const itemProps: IStatusbarEntry = {
@ -138,16 +138,16 @@ export class TaskStatusBarContributions extends Disposable implements IWorkbench
command: 'workbench.action.tasks.showTasks',
};
if (!this.runningTasksStatusItem) {
this.runningTasksStatusItem = this.statusbarService.addEntry(itemProps, 'status.runningTasks', StatusbarAlignment.LEFT, 49 /* Medium Priority, next to Markers */);
if (!this._runningTasksStatusItem) {
this._runningTasksStatusItem = this._statusbarService.addEntry(itemProps, 'status.runningTasks', StatusbarAlignment.LEFT, 49 /* Medium Priority, next to Markers */);
} else {
this.runningTasksStatusItem.update(itemProps);
this._runningTasksStatusItem.update(itemProps);
}
}
}
private ignoreEventForUpdateRunningTasksCount(event: ITaskEvent): boolean {
if (!this.taskService.inTerminal()) {
private _ignoreEventForUpdateRunningTasksCount(event: ITaskEvent): boolean {
if (!this._taskService.inTerminal()) {
return false;
}

View file

@ -42,24 +42,24 @@ export const configureTaskIcon = registerIcon('tasks-list-configure', Codicon.ge
const removeTaskIcon = registerIcon('tasks-remove', Codicon.close, nls.localize('removeTaskIcon', 'Icon for remove in the tasks selection list.'));
export class TaskQuickPick extends Disposable {
private sorter: TaskSorter;
private topLevelEntries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[] | undefined;
private _sorter: TaskSorter;
private _topLevelEntries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[] | undefined;
constructor(
private taskService: ITaskService,
private configurationService: IConfigurationService,
private quickInputService: IQuickInputService,
private notificationService: INotificationService,
private dialogService: IDialogService) {
private _taskService: ITaskService,
private _configurationService: IConfigurationService,
private _quickInputService: IQuickInputService,
private _notificationService: INotificationService,
private _dialogService: IDialogService) {
super();
this.sorter = this.taskService.createSorter();
this._sorter = this._taskService.createSorter();
}
private showDetail(): boolean {
private _showDetail(): boolean {
// Ensure invalid values get converted into boolean values
return !!this.configurationService.getValue(QUICKOPEN_DETAIL_CONFIG);
return !!this._configurationService.getValue(QUICKOPEN_DETAIL_CONFIG);
}
private guessTaskLabel(task: Task | ConfiguringTask): string {
private _guessTaskLabel(task: Task | ConfiguringTask): string {
if (task._label) {
return task._label;
}
@ -74,21 +74,21 @@ export class TaskQuickPick extends Disposable {
return '';
}
private createTaskEntry(task: Task | ConfiguringTask, extraButtons: IQuickInputButton[] = []): ITaskTwoLevelQuickPickEntry {
const entry: ITaskTwoLevelQuickPickEntry = { label: this.guessTaskLabel(task), description: this.taskService.getTaskDescription(task), task, detail: this.showDetail() ? task.configurationProperties.detail : undefined };
private _createTaskEntry(task: Task | ConfiguringTask, extraButtons: IQuickInputButton[] = []): ITaskTwoLevelQuickPickEntry {
const entry: ITaskTwoLevelQuickPickEntry = { label: this._guessTaskLabel(task), description: this._taskService.getTaskDescription(task), task, detail: this._showDetail() ? task.configurationProperties.detail : undefined };
entry.buttons = [{ iconClass: ThemeIcon.asClassName(configureTaskIcon), tooltip: nls.localize('configureTask', "Configure Task") }, ...extraButtons];
return entry;
}
private createEntriesForGroup(entries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[], tasks: (Task | ConfiguringTask)[],
private _createEntriesForGroup(entries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[], tasks: (Task | ConfiguringTask)[],
groupLabel: string, extraButtons: IQuickInputButton[] = []) {
entries.push({ type: 'separator', label: groupLabel });
tasks.forEach(task => {
entries.push(this.createTaskEntry(task, extraButtons));
entries.push(this._createTaskEntry(task, extraButtons));
});
}
private createTypeEntries(entries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[], types: string[]) {
private _createTypeEntries(entries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[], types: string[]) {
entries.push({ type: 'separator', label: nls.localize('contributedTasks', "contributed") });
types.forEach(type => {
entries.push({ label: `$(folder) ${type}`, task: type, ariaLabel: nls.localize('taskType', "All {0} tasks", type) });
@ -96,7 +96,7 @@ export class TaskQuickPick extends Disposable {
entries.push({ label: SHOW_ALL, task: SHOW_ALL, alwaysShow: true });
}
private handleFolderTaskResult(result: Map<string, IWorkspaceFolderTaskResult>): (Task | ConfiguringTask)[] {
private _handleFolderTaskResult(result: Map<string, IWorkspaceFolderTaskResult>): (Task | ConfiguringTask)[] {
const tasks: (Task | ConfiguringTask)[] = [];
Array.from(result).forEach(([key, folderTasks]) => {
if (folderTasks.set) {
@ -111,7 +111,7 @@ export class TaskQuickPick extends Disposable {
return tasks;
}
private dedupeConfiguredAndRecent(recentTasks: (Task | ConfiguringTask)[], configuredTasks: (Task | ConfiguringTask)[]): { configuredTasks: (Task | ConfiguringTask)[]; recentTasks: (Task | ConfiguringTask)[] } {
private _dedupeConfiguredAndRecent(recentTasks: (Task | ConfiguringTask)[], configuredTasks: (Task | ConfiguringTask)[]): { configuredTasks: (Task | ConfiguringTask)[]; recentTasks: (Task | ConfiguringTask)[] } {
let dedupedConfiguredTasks: (Task | ConfiguringTask)[] = [];
const foundRecentTasks: boolean[] = Array(recentTasks.length).fill(false);
for (let j = 0; j < configuredTasks.length; j++) {
@ -132,7 +132,7 @@ export class TaskQuickPick extends Disposable {
foundRecentTasks[findIndex] = true;
}
}
dedupedConfiguredTasks = dedupedConfiguredTasks.sort((a, b) => this.sorter.compare(a, b));
dedupedConfiguredTasks = dedupedConfiguredTasks.sort((a, b) => this._sorter.compare(a, b));
const prunedRecentTasks: (Task | ConfiguringTask)[] = [];
for (let i = 0; i < recentTasks.length; i++) {
if (foundRecentTasks[i] || ConfiguringTask.is(recentTasks[i])) {
@ -143,15 +143,15 @@ export class TaskQuickPick extends Disposable {
}
public async getTopLevelEntries(defaultEntry?: ITaskQuickPickEntry): Promise<{ entries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[]; isSingleConfigured?: Task | ConfiguringTask }> {
if (this.topLevelEntries !== undefined) {
return { entries: this.topLevelEntries };
if (this._topLevelEntries !== undefined) {
return { entries: this._topLevelEntries };
}
let recentTasks: (Task | ConfiguringTask)[] = (await this.taskService.readRecentTasks()).reverse();
const configuredTasks: (Task | ConfiguringTask)[] = this.handleFolderTaskResult(await this.taskService.getWorkspaceTasks());
const extensionTaskTypes = this.taskService.taskTypes();
this.topLevelEntries = [];
let recentTasks: (Task | ConfiguringTask)[] = (await this._taskService.readRecentTasks()).reverse();
const configuredTasks: (Task | ConfiguringTask)[] = this._handleFolderTaskResult(await this._taskService.getWorkspaceTasks());
const extensionTaskTypes = this._taskService.taskTypes();
this._topLevelEntries = [];
// Dedupe will update recent tasks if they've changed in tasks.json.
const dedupeAndPrune = this.dedupeConfiguredAndRecent(recentTasks, configuredTasks);
const dedupeAndPrune = this._dedupeConfiguredAndRecent(recentTasks, configuredTasks);
const dedupedConfiguredTasks: (Task | ConfiguringTask)[] = dedupeAndPrune.configuredTasks;
recentTasks = dedupeAndPrune.recentTasks;
if (recentTasks.length > 0) {
@ -159,34 +159,34 @@ export class TaskQuickPick extends Disposable {
iconClass: ThemeIcon.asClassName(removeTaskIcon),
tooltip: nls.localize('removeRecent', 'Remove Recently Used Task')
};
this.createEntriesForGroup(this.topLevelEntries, recentTasks, nls.localize('recentlyUsed', 'recently used'), [removeRecentButton]);
this._createEntriesForGroup(this._topLevelEntries, recentTasks, nls.localize('recentlyUsed', 'recently used'), [removeRecentButton]);
}
if (configuredTasks.length > 0) {
if (dedupedConfiguredTasks.length > 0) {
this.createEntriesForGroup(this.topLevelEntries, dedupedConfiguredTasks, nls.localize('configured', 'configured'));
this._createEntriesForGroup(this._topLevelEntries, dedupedConfiguredTasks, nls.localize('configured', 'configured'));
}
}
if (defaultEntry && (configuredTasks.length === 0)) {
this.topLevelEntries.push({ type: 'separator', label: nls.localize('configured', 'configured') });
this.topLevelEntries.push(defaultEntry);
this._topLevelEntries.push({ type: 'separator', label: nls.localize('configured', 'configured') });
this._topLevelEntries.push(defaultEntry);
}
if (extensionTaskTypes.length > 0) {
this.createTypeEntries(this.topLevelEntries, extensionTaskTypes);
this._createTypeEntries(this._topLevelEntries, extensionTaskTypes);
}
return { entries: this.topLevelEntries, isSingleConfigured: configuredTasks.length === 1 ? configuredTasks[0] : undefined };
return { entries: this._topLevelEntries, isSingleConfigured: configuredTasks.length === 1 ? configuredTasks[0] : undefined };
}
public async handleSettingOption(selectedType: string) {
const noButton = nls.localize('TaskQuickPick.changeSettingNo', "No");
const yesButton = nls.localize('TaskQuickPick.changeSettingYes', "Yes");
const changeSettingResult = await this.dialogService.show(Severity.Warning,
const changeSettingResult = await this._dialogService.show(Severity.Warning,
nls.localize('TaskQuickPick.changeSettingDetails',
"Task detection for {0} tasks causes files in any workspace you open to be run as code. Enabling {0} task detection is a user setting and will apply to any workspace you open. Do you want to enable {0} task detection for all workspaces?", selectedType),
[noButton, yesButton]);
if (changeSettingResult.choice === 1) {
await this.configurationService.updateValue(`${selectedType}.autoDetect`, 'on');
await this._configurationService.updateValue(`${selectedType}.autoDetect`, 'on');
await new Promise<void>(resolve => setTimeout(() => resolve(), 100));
return this.show(nls.localize('TaskService.pickRunTask', 'Select the task to run'), undefined, selectedType);
}
@ -194,7 +194,7 @@ export class TaskQuickPick extends Disposable {
}
public async show(placeHolder: string, defaultEntry?: ITaskQuickPickEntry, startAtType?: string): Promise<Task | undefined | null> {
const picker: IQuickPick<ITaskTwoLevelQuickPickEntry> = this.quickInputService.createQuickPick();
const picker: IQuickPick<ITaskTwoLevelQuickPickEntry> = this._quickInputService.createQuickPick();
picker.placeholder = placeHolder;
picker.matchOnDescription = true;
picker.ignoreFocusOut = false;
@ -205,25 +205,25 @@ export class TaskQuickPick extends Disposable {
if (context.button.iconClass === ThemeIcon.asClassName(removeTaskIcon)) {
const key = (task && !Types.isString(task)) ? task.getRecentlyUsedKey() : undefined;
if (key) {
this.taskService.removeRecentlyUsedTask(key);
this._taskService.removeRecentlyUsedTask(key);
}
const indexToRemove = picker.items.indexOf(context.item);
if (indexToRemove >= 0) {
picker.items = [...picker.items.slice(0, indexToRemove), ...picker.items.slice(indexToRemove + 1)];
}
} else {
this.quickInputService.cancel();
this._quickInputService.cancel();
if (ContributedTask.is(task)) {
this.taskService.customize(task, undefined, true);
this._taskService.customize(task, undefined, true);
} else if (CustomTask.is(task) || ConfiguringTask.is(task)) {
let canOpenConfig: boolean = false;
try {
canOpenConfig = await this.taskService.openConfig(task);
canOpenConfig = await this._taskService.openConfig(task);
} catch (e) {
// do nothing.
}
if (!canOpenConfig) {
this.taskService.customize(task, undefined, true);
this._taskService.customize(task, undefined, true);
}
}
}
@ -233,30 +233,30 @@ export class TaskQuickPick extends Disposable {
if (!firstLevelTask) {
// First show recent tasks configured tasks. Other tasks will be available at a second level
const topLevelEntriesResult = await this.getTopLevelEntries(defaultEntry);
if (topLevelEntriesResult.isSingleConfigured && this.configurationService.getValue<boolean>(QUICKOPEN_SKIP_CONFIG)) {
if (topLevelEntriesResult.isSingleConfigured && this._configurationService.getValue<boolean>(QUICKOPEN_SKIP_CONFIG)) {
picker.dispose();
return this.toTask(topLevelEntriesResult.isSingleConfigured);
return this._toTask(topLevelEntriesResult.isSingleConfigured);
}
const taskQuickPickEntries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[] = topLevelEntriesResult.entries;
firstLevelTask = await this.doPickerFirstLevel(picker, taskQuickPickEntries);
firstLevelTask = await this._doPickerFirstLevel(picker, taskQuickPickEntries);
}
do {
if (Types.isString(firstLevelTask)) {
// Proceed to second level of quick pick
const selectedEntry = await this.doPickerSecondLevel(picker, firstLevelTask);
const selectedEntry = await this._doPickerSecondLevel(picker, firstLevelTask);
if (selectedEntry && !selectedEntry.settingType && selectedEntry.task === null) {
// The user has chosen to go back to the first level
firstLevelTask = await this.doPickerFirstLevel(picker, (await this.getTopLevelEntries(defaultEntry)).entries);
firstLevelTask = await this._doPickerFirstLevel(picker, (await this.getTopLevelEntries(defaultEntry)).entries);
} else if (selectedEntry && Types.isString(selectedEntry.settingType)) {
picker.dispose();
return this.handleSettingOption(selectedEntry.settingType);
} else {
picker.dispose();
return (selectedEntry?.task && !Types.isString(selectedEntry?.task)) ? this.toTask(selectedEntry?.task) : undefined;
return (selectedEntry?.task && !Types.isString(selectedEntry?.task)) ? this._toTask(selectedEntry?.task) : undefined;
}
} else if (firstLevelTask) {
picker.dispose();
return this.toTask(firstLevelTask);
return this._toTask(firstLevelTask);
} else {
picker.dispose();
return firstLevelTask;
@ -265,7 +265,7 @@ export class TaskQuickPick extends Disposable {
return;
}
private async doPickerFirstLevel(picker: IQuickPick<ITaskTwoLevelQuickPickEntry>, taskQuickPickEntries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[]): Promise<Task | ConfiguringTask | string | null | undefined> {
private async _doPickerFirstLevel(picker: IQuickPick<ITaskTwoLevelQuickPickEntry>, taskQuickPickEntries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[]): Promise<Task | ConfiguringTask | string | null | undefined> {
picker.items = taskQuickPickEntries;
const firstLevelPickerResult = await new Promise<ITaskTwoLevelQuickPickEntry | undefined | null>(resolve => {
Event.once(picker.onDidAccept)(async () => {
@ -275,15 +275,15 @@ export class TaskQuickPick extends Disposable {
return firstLevelPickerResult?.task;
}
private async doPickerSecondLevel(picker: IQuickPick<ITaskTwoLevelQuickPickEntry>, type: string) {
private async _doPickerSecondLevel(picker: IQuickPick<ITaskTwoLevelQuickPickEntry>, type: string) {
picker.busy = true;
if (type === SHOW_ALL) {
const items = (await this.taskService.tasks()).sort((a, b) => this.sorter.compare(a, b)).map(task => this.createTaskEntry(task));
items.push(...TaskQuickPick.allSettingEntries(this.configurationService));
const items = (await this._taskService.tasks()).sort((a, b) => this._sorter.compare(a, b)).map(task => this._createTaskEntry(task));
items.push(...TaskQuickPick.allSettingEntries(this._configurationService));
picker.items = items;
} else {
picker.value = '';
picker.items = await this.getEntriesForProvider(type);
picker.items = await this._getEntriesForProvider(type);
}
picker.busy = false;
const secondLevelPickerResult = await new Promise<ITaskTwoLevelQuickPickEntry | undefined | null>(resolve => {
@ -325,11 +325,11 @@ export class TaskQuickPick extends Disposable {
return undefined;
}
private async getEntriesForProvider(type: string): Promise<QuickPickInput<ITaskTwoLevelQuickPickEntry>[]> {
const tasks = (await this.taskService.tasks({ type })).sort((a, b) => this.sorter.compare(a, b));
private async _getEntriesForProvider(type: string): Promise<QuickPickInput<ITaskTwoLevelQuickPickEntry>[]> {
const tasks = (await this._taskService.tasks({ type })).sort((a, b) => this._sorter.compare(a, b));
let taskQuickPickEntries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[];
if (tasks.length > 0) {
taskQuickPickEntries = tasks.map(task => this.createTaskEntry(task));
taskQuickPickEntries = tasks.map(task => this._createTaskEntry(task));
taskQuickPickEntries.push({
type: 'separator'
}, {
@ -345,22 +345,22 @@ export class TaskQuickPick extends Disposable {
}];
}
const settingEntry = TaskQuickPick.getSettingEntry(this.configurationService, type);
const settingEntry = TaskQuickPick.getSettingEntry(this._configurationService, type);
if (settingEntry) {
taskQuickPickEntries.push(settingEntry);
}
return taskQuickPickEntries;
}
private async toTask(task: Task | ConfiguringTask): Promise<Task | undefined> {
private async _toTask(task: Task | ConfiguringTask): Promise<Task | undefined> {
if (!ConfiguringTask.is(task)) {
return task;
}
const resolvedTask = await this.taskService.tryResolveTask(task);
const resolvedTask = await this._taskService.tryResolveTask(task);
if (!resolvedTask) {
this.notificationService.error(nls.localize('noProviderForTask', "There is no task provider registered for tasks of type \"{0}\".", task.type));
this._notificationService.error(nls.localize('noProviderForTask', "There is no task provider registered for tasks of type \"{0}\".", task.type));
}
return resolvedTask;
}

View file

@ -14,12 +14,12 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
export class TaskService extends AbstractTaskService {
private static readonly ProcessTaskSystemSupportMessage = nls.localize('taskService.processTaskSystem', 'Process task system is not support in the web.');
protected getTaskSystem(): ITaskSystem {
protected _getTaskSystem(): ITaskSystem {
if (this._taskSystem) {
return this._taskSystem;
}
if (this.executionEngine === ExecutionEngine.Terminal) {
this._taskSystem = this.createTerminalTaskSystem();
this._taskSystem = this._createTerminalTaskSystem();
} else {
throw new Error(TaskService.ProcessTaskSystemSupportMessage);
}
@ -32,11 +32,11 @@ export class TaskService extends AbstractTaskService {
return this._taskSystem!;
}
protected computeLegacyConfiguration(workspaceFolder: IWorkspaceFolder): Promise<IWorkspaceFolderConfigurationResult> {
protected _computeLegacyConfiguration(workspaceFolder: IWorkspaceFolder): Promise<IWorkspaceFolderConfigurationResult> {
throw new Error(TaskService.ProcessTaskSystemSupportMessage);
}
protected versionAndEngineCompatible(filter?: ITaskFilter): boolean {
protected _versionAndEngineCompatible(filter?: ITaskFilter): boolean {
return this.executionEngine === ExecutionEngine.Terminal;
}
}

View file

@ -24,11 +24,11 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider<IPickerQ
constructor(
@IExtensionService extensionService: IExtensionService,
@ITaskService private taskService: ITaskService,
@IConfigurationService private configurationService: IConfigurationService,
@IQuickInputService private quickInputService: IQuickInputService,
@INotificationService private notificationService: INotificationService,
@IDialogService private dialogService: IDialogService
@ITaskService private _taskService: ITaskService,
@IConfigurationService private _configurationService: IConfigurationService,
@IQuickInputService private _quickInputService: IQuickInputService,
@INotificationService private _notificationService: INotificationService,
@IDialogService private _dialogService: IDialogService
) {
super(TasksQuickAccessProvider.PREFIX, {
noResultsPick: {
@ -42,7 +42,7 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider<IPickerQ
return [];
}
const taskQuickPick = new TaskQuickPick(this.taskService, this.configurationService, this.quickInputService, this.notificationService, this.dialogService);
const taskQuickPick = new TaskQuickPick(this._taskService, this._configurationService, this._quickInputService, this._notificationService, this._dialogService);
const topLevelPicks = await taskQuickPick.getTopLevelEntries();
const taskPicks: Array<IPickerQuickAccessItem | IQuickPickSeparator> = [];
@ -63,14 +63,14 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider<IPickerQ
if ((index === 1) && (quickAccessEntry.buttons?.length === 2)) {
const key = (task && !isString(task)) ? task.getRecentlyUsedKey() : undefined;
if (key) {
this.taskService.removeRecentlyUsedTask(key);
this._taskService.removeRecentlyUsedTask(key);
}
return TriggerAction.REFRESH_PICKER;
} else {
if (ContributedTask.is(task)) {
this.taskService.customize(task, undefined, true);
this._taskService.customize(task, undefined, true);
} else if (CustomTask.is(task)) {
this.taskService.openConfig(task);
this._taskService.openConfig(task);
}
return TriggerAction.CLOSE_PICKER;
}
@ -80,10 +80,10 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider<IPickerQ
// switch to quick pick and show second level
const showResult = await taskQuickPick.show(localize('TaskService.pickRunTask', 'Select the task to run'), undefined, task);
if (showResult) {
this.taskService.run(showResult, { attachProblemMatcher: true });
this._taskService.run(showResult, { attachProblemMatcher: true });
}
} else {
this.taskService.run(await this.toTask(task), { attachProblemMatcher: true });
this._taskService.run(await this._toTask(task), { attachProblemMatcher: true });
}
};
@ -92,11 +92,11 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider<IPickerQ
return taskPicks;
}
private async toTask(task: Task | ConfiguringTask): Promise<Task | undefined> {
private async _toTask(task: Task | ConfiguringTask): Promise<Task | undefined> {
if (!ConfiguringTask.is(task)) {
return task;
}
return this.taskService.tryResolveTask(task);
return this._taskService.tryResolveTask(task);
}
}

File diff suppressed because it is too large Load diff

View file

@ -119,11 +119,11 @@ export class TaskService extends AbstractTaskService {
this._register(lifecycleService.onBeforeShutdown(event => event.veto(this.beforeShutdown(), 'veto.tasks')));
}
protected getTaskSystem(): ITaskSystem {
protected _getTaskSystem(): ITaskSystem {
if (this._taskSystem) {
return this._taskSystem;
}
this._taskSystem = this.createTerminalTaskSystem();
this._taskSystem = this._createTerminalTaskSystem();
this._taskSystemListener = this._taskSystem!.onDidStateChange((event) => {
if (this._taskSystem) {
this._taskRunningState.set(this._taskSystem.isActiveSync());
@ -133,8 +133,8 @@ export class TaskService extends AbstractTaskService {
return this._taskSystem;
}
protected computeLegacyConfiguration(workspaceFolder: IWorkspaceFolder): Promise<IWorkspaceFolderConfigurationResult> {
const { config, hasParseErrors } = this.getConfiguration(workspaceFolder);
protected _computeLegacyConfiguration(workspaceFolder: IWorkspaceFolder): Promise<IWorkspaceFolderConfigurationResult> {
const { config, hasParseErrors } = this._getConfiguration(workspaceFolder);
if (hasParseErrors) {
return Promise.resolve({ workspaceFolder: workspaceFolder, hasErrors: true, config: undefined });
}
@ -145,7 +145,7 @@ export class TaskService extends AbstractTaskService {
}
}
protected versionAndEngineCompatible(filter?: ITaskFilter): boolean {
protected _versionAndEngineCompatible(filter?: ITaskFilter): boolean {
const range = filter && filter.version ? filter.version : undefined;
const engine = this.executionEngine;
@ -169,7 +169,7 @@ export class TaskService extends AbstractTaskService {
if (this._taskSystem.canAutoTerminate()) {
terminatePromise = Promise.resolve({ confirmed: true });
} else {
terminatePromise = this.dialogService.confirm({
terminatePromise = this._dialogService.confirm({
message: nls.localize('TaskSystem.runningTask', 'There is a task running. Do you want to terminate it?'),
primaryButton: nls.localize({ key: 'TaskSystem.terminateTask', comment: ['&& denotes a mnemonic'] }, "&&Terminate Task"),
type: 'question'
@ -191,10 +191,10 @@ export class TaskService extends AbstractTaskService {
}
if (success) {
this._taskSystem = undefined;
this.disposeTaskSystemListeners();
this._disposeTaskSystemListeners();
return false; // no veto
} else if (code && code === TerminateResponseCode.ProcessNotFound) {
return this.dialogService.confirm({
return this._dialogService.confirm({
message: nls.localize('TaskSystem.noProcess', 'The launched task doesn\'t exist anymore. If the task spawned background processes exiting VS Code might result in orphaned processes. To avoid this start the last background process with a wait flag.'),
primaryButton: nls.localize({ key: 'TaskSystem.exitAnyways', comment: ['&& denotes a mnemonic'] }, "&&Exit Anyways"),
type: 'info'

View file

@ -79,30 +79,30 @@ export const enum TerminalBuiltinLinkType {
/**
* The link is validated to be a file on the file system and will open an editor.
*/
LocalFile,
LocalFile = 'LocalFile',
/**
* The link is validated to be a folder on the file system and is outside the workspace. It will
* reveal the folder within the explorer.
*/
LocalFolderOutsideWorkspace,
LocalFolderOutsideWorkspace = 'LocalFolderOutsideWorkspace',
/**
* The link is validated to be a folder on the file system and is within the workspace and will
* reveal the folder within the explorer.
*/
LocalFolderInWorkspace,
LocalFolderInWorkspace = 'LocalFolderInWorkspace',
/**
* A low confidence link which will search for the file in the workspace. If there is a single
* match, it will open the file; otherwise, it will present the matches in a quick pick.
*/
Search,
Search = 'Search',
/**
* A link whose text is a valid URI.
*/
Url
Url = 'Url'
}
export interface ITerminalExternalLinkType {

View file

@ -86,7 +86,7 @@ export class TerminalLinkManager extends DisposableStore {
this._openers.set(TerminalBuiltinLinkType.LocalFile, localFileOpener);
this._openers.set(TerminalBuiltinLinkType.LocalFolderInWorkspace, localFolderInWorkspaceOpener);
this._openers.set(TerminalBuiltinLinkType.LocalFolderOutsideWorkspace, this._instantiationService.createInstance(TerminalLocalFolderOutsideWorkspaceLinkOpener));
this._openers.set(TerminalBuiltinLinkType.Search, this._instantiationService.createInstance(TerminalSearchLinkOpener, capabilities, localFileOpener, localFolderInWorkspaceOpener, this._processManager.os || OS));
this._openers.set(TerminalBuiltinLinkType.Search, this._instantiationService.createInstance(TerminalSearchLinkOpener, capabilities, this._processManager.getInitialCwd(), localFileOpener, localFolderInWorkspaceOpener, this._processManager.os || OS));
this._openers.set(TerminalBuiltinLinkType.Url, this._instantiationService.createInstance(TerminalUrlLinkOpener, !!this._processManager.remoteAuthority));
this._registerStandardLinkProviders();

View file

@ -125,6 +125,7 @@ export class TerminalSearchLinkOpener implements ITerminalLinkOpener {
constructor(
private readonly _capabilities: ITerminalCapabilityStore,
private readonly _initialCwd: Promise<string>,
private readonly _localFileOpener: TerminalLocalFileLinkOpener,
private readonly _localFolderInWorkspaceOpener: TerminalLocalFolderInWorkspaceLinkOpener,
private readonly _os: OperatingSystem,
@ -182,10 +183,20 @@ export class TerminalSearchLinkOpener implements ITerminalLinkOpener {
}
private async _getExactMatch(sanitizedLink: string): Promise<IResourceMatch | undefined> {
// Make the link relative to the cwd if it isn't absolute
const pathModule = osPathModule(this._os);
const isAbsolute = pathModule.isAbsolute(sanitizedLink);
let absolutePath: string | undefined = isAbsolute ? sanitizedLink : undefined;
const initialCwd = await this._initialCwd;
if (!isAbsolute && initialCwd.length > 0) {
absolutePath = pathModule.join(initialCwd, sanitizedLink);
}
// Try open as an absolute link
let resourceMatch: IResourceMatch | undefined;
if (osPathModule(this._os).isAbsolute(sanitizedLink)) {
if (absolutePath) {
const slashNormalizedPath = this._os === OperatingSystem.Windows ? absolutePath.replace(/\\/g, '/') : absolutePath;
const scheme = this._workbenchEnvironmentService.remoteAuthority ? Schemas.vscodeRemote : Schemas.file;
const slashNormalizedPath = this._os === OperatingSystem.Windows ? sanitizedLink.replace(/\\/g, '/') : sanitizedLink;
const uri = URI.from({ scheme, path: slashNormalizedPath });
try {
const fileStat = await this._fileService.stat(uri);
@ -194,6 +205,8 @@ export class TerminalSearchLinkOpener implements ITerminalLinkOpener {
// File or dir doesn't exist, continue on
}
}
// Search the workspace if an exact match based on the absolute path was not found
if (!resourceMatch) {
const results = await this._searchService.fileSearch(
this._fileQueryBuilder.file(this._workspaceContextService.getWorkspace().folders, {

View file

@ -669,6 +669,12 @@ export interface ITerminalInstance {
*/
clearSelection(): void;
/**
* When the panel is hidden or a terminal in the editor area becomes inactive, reset the focus context key
* to avoid issues like #147180.
*/
resetFocusContextKey(): void;
/**
* Select all text in the terminal.
*/

View file

@ -101,6 +101,14 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor
}
}
}));
this._register(this._editorService.onDidActiveEditorChange(() => {
const instance = this._editorService.activeEditor instanceof TerminalEditorInput ? this._editorService.activeEditor : undefined;
if (!instance) {
for (const instance of this.instances) {
instance.resetFocusContextKey();
}
}
}));
}
private _getActiveTerminalEditors(): EditorInput[] {

View file

@ -385,7 +385,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
});
this._fixedRows = _shellLaunchConfig.attachPersistentProcess?.fixedDimensions?.rows;
this._fixedCols = _shellLaunchConfig.attachPersistentProcess?.fixedDimensions?.cols;
this._icon = _shellLaunchConfig.attachPersistentProcess?.icon || _shellLaunchConfig.icon;
// the resource is already set when it's been moved from another window
this._resource = resource || getTerminalUri(this._workspaceContextService.getWorkspace().id, this.instanceId, this.title);
@ -431,10 +430,12 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
// Resolve just the icon ahead of time so that it shows up immediately in the tabs. This is
// disabled in remote because this needs to be sync and the OS may differ on the remote
// which would result in the wrong profile being selected and the wrong icon being
// permanently attached to the terminal.
// permanently attached to the terminal. This also doesn't work when the default profile
// setting is set to null, that's handled after the process is created.
if (!this.shellLaunchConfig.executable && !workbenchEnvironmentService.remoteAuthority) {
this._terminalProfileResolverService.resolveIcon(this._shellLaunchConfig, OS);
}
this._icon = _shellLaunchConfig.attachPersistentProcess?.icon || _shellLaunchConfig.icon;
// When a custom pty is used set the name immediately so it gets passed over to the exthost
// and is available when Pseudoterminal.open fires.
@ -1150,6 +1151,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
}
resetFocusContextKey(): void {
this._terminalFocusContextKey.reset();
}
private _initDragAndDrop(container: HTMLElement) {
this._dndObserver?.dispose();
const dndController = this._instantiationService.createInstance(TerminalInstanceDragAndDropController, container);
@ -1550,8 +1555,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this.xterm?.raw.resize(this._cols || Constants.DefaultCols, this._rows || Constants.DefaultRows);
}
const hadIcon = !!this.shellLaunchConfig.icon;
const originalIcon = this.shellLaunchConfig.icon;
await this._processManager.createProcess(this._shellLaunchConfig, this._cols || Constants.DefaultCols, this._rows || Constants.DefaultRows, this._accessibilityService.isScreenReaderOptimized()).then(error => {
if (error) {
this._onProcessExit(error, error.code === ShellIntegrationExitCode);
@ -1560,7 +1564,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
if (this.xterm?.shellIntegration) {
this.capabilities.add(this.xterm?.shellIntegration.capabilities);
}
if (!hadIcon && this.shellLaunchConfig.icon || this.shellLaunchConfig.color) {
if (originalIcon !== this.shellLaunchConfig.icon || this.shellLaunchConfig.color) {
this._icon = this._shellLaunchConfig.attachPersistentProcess?.icon || this._shellLaunchConfig.icon;
this._onIconChanged.fire(this);
}
}

Some files were not shown because too many files have changed in this diff Show more