mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 21:09:43 +00:00
Merge branch 'main' into joh/voluminous-lobster
This commit is contained in:
commit
ed9ed45794
|
@ -318,7 +318,7 @@ steps:
|
||||||
targetPath: .build/logs
|
targetPath: .build/logs
|
||||||
displayName: "Publish Log Files"
|
displayName: "Publish Log Files"
|
||||||
continueOnError: true
|
continueOnError: true
|
||||||
condition: failed()
|
condition: succeededOrFailed()
|
||||||
|
|
||||||
- task: PublishTestResults@2
|
- task: PublishTestResults@2
|
||||||
displayName: Publish Tests Results
|
displayName: Publish Tests Results
|
||||||
|
|
|
@ -387,7 +387,7 @@ steps:
|
||||||
targetPath: .build/logs
|
targetPath: .build/logs
|
||||||
displayName: "Publish Log Files"
|
displayName: "Publish Log Files"
|
||||||
continueOnError: true
|
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
|
- task: PublishTestResults@2
|
||||||
displayName: Publish Tests Results
|
displayName: Publish Tests Results
|
||||||
|
|
|
@ -339,7 +339,7 @@ steps:
|
||||||
targetPath: .build\logs
|
targetPath: .build\logs
|
||||||
displayName: "Publish Log Files"
|
displayName: "Publish Log Files"
|
||||||
continueOnError: true
|
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
|
- task: PublishTestResults@2
|
||||||
displayName: Publish Tests Results
|
displayName: Publish Tests Results
|
||||||
|
|
|
@ -3129,9 +3129,7 @@ export class CommandCenter {
|
||||||
if (result) {
|
if (result) {
|
||||||
const resultFn = choices.get(result);
|
const resultFn = choices.get(result);
|
||||||
|
|
||||||
if (resultFn) {
|
resultFn?.();
|
||||||
resultFn();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -556,9 +556,7 @@ export class Git {
|
||||||
private async _exec(args: string[], options: SpawnOptions = {}): Promise<IExecutionResult<string>> {
|
private async _exec(args: string[], options: SpawnOptions = {}): Promise<IExecutionResult<string>> {
|
||||||
const child = this.spawn(args, options);
|
const child = this.spawn(args, options);
|
||||||
|
|
||||||
if (options.onSpawn) {
|
options.onSpawn?.(child);
|
||||||
options.onSpawn(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.input) {
|
if (options.input) {
|
||||||
child.stdin!.end(options.input, 'utf8');
|
child.stdin!.end(options.input, 'utf8');
|
||||||
|
|
|
@ -251,9 +251,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
||||||
// The settings have changed. Is send on server activation as well.
|
// The settings have changed. Is send on server activation as well.
|
||||||
connection.onDidChangeConfiguration((change) => {
|
connection.onDidChangeConfiguration((change) => {
|
||||||
const settings = <Settings>change.settings;
|
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;
|
jsonConfigurationSettings = settings.json?.schemas;
|
||||||
validateEnabled = !!settings.json?.validate?.enable;
|
validateEnabled = !!settings.json?.validate?.enable;
|
||||||
updateConfiguration();
|
updateConfiguration();
|
||||||
|
|
|
@ -185,12 +185,12 @@ function stripAngleBrackets(link: string) {
|
||||||
/**
|
/**
|
||||||
* Matches `[text](link)`
|
* 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>)`
|
* Matches `[text](<link>)`
|
||||||
*/
|
*/
|
||||||
const linkPatternAngle = /(\[((!\[[^\]]*?\]\(\s*)([^\s\(\)]+?)\s*\)\]|(?:\\\]|[^\]])*\])\(\s*<)(([^<>]|\([^\s\(\)]*?\))+)>\s*("[^"]*"|'[^']*'|\([^\(\)]*\))?\s*\)/g;
|
const linkPatternAngle = /(\[((!\[[^\]]*?\]\(\s*)([^\s\(\)]+?)\s*\)\]|(?:\\\]|[^\]]|\][^(])*\])\(\s*<)(([^<>]|\([^\s\(\)]*?\))+)>\s*("[^"]*"|'[^']*'|\([^\(\)]*\))?\s*\)/g;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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 () => {
|
test('Should handle two links without space', async () => {
|
||||||
const links = await getLinksForFile('a ([test](test)[test2](test2)) c');
|
const links = await getLinksForFile('a ([test](test)[test2](test2)) c');
|
||||||
assertLinksEqual(links, [
|
assertLinksEqual(links, [
|
||||||
|
|
|
@ -287,8 +287,9 @@ async function provideNpmScriptsForFolder(context: ExtensionContext, packageJson
|
||||||
result.push({ task, location: new Location(packageJsonUri, nameRange) });
|
result.push({ task, location: new Location(packageJsonUri, nameRange) });
|
||||||
}
|
}
|
||||||
|
|
||||||
// always add npm install (without a problem matcher)
|
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', []) });
|
result.push({ task: await createTask(packageManager, INSTALL_SCRIPT, [INSTALL_SCRIPT], folder, packageJsonUri, 'install dependencies from package', []) });
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -927,6 +927,80 @@ const apiTestContentProvider: vscode.NotebookContentProvider = {
|
||||||
|
|
||||||
assert.strictEqual(executionWasCancelled, true);
|
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', () => {
|
(vscode.env.uiKind === vscode.UIKind.Web ? suite.skip : suite)('statusbar', () => {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "code-oss-dev",
|
"name": "code-oss-dev",
|
||||||
"version": "1.68.0",
|
"version": "1.69.0",
|
||||||
"distro": "2966cd72fc1a3a5fb89bf2d85a1a66e56206961a",
|
"distro": "2966cd72fc1a3a5fb89bf2d85a1a66e56206961a",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Microsoft Corporation"
|
"name": "Microsoft Corporation"
|
||||||
|
|
|
@ -1733,9 +1733,7 @@ export class DragAndDropObserver extends Disposable {
|
||||||
this._register(addDisposableListener(this.element, EventType.DRAG_OVER, (e: DragEvent) => {
|
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)
|
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) => {
|
this._register(addDisposableListener(this.element, EventType.DRAG_LEAVE, (e: DragEvent) => {
|
||||||
|
|
|
@ -220,9 +220,7 @@ export class ContextView extends Disposable {
|
||||||
this.doLayout();
|
this.doLayout();
|
||||||
|
|
||||||
// Focus
|
// Focus
|
||||||
if (this.delegate.focus) {
|
this.delegate.focus?.();
|
||||||
this.delegate.focus();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewElement(): HTMLElement {
|
getViewElement(): HTMLElement {
|
||||||
|
|
|
@ -857,9 +857,7 @@ class LeafNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||||
set boundarySashes(boundarySashes: IRelativeBoundarySashes) {
|
set boundarySashes(boundarySashes: IRelativeBoundarySashes) {
|
||||||
this._boundarySashes = boundarySashes;
|
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 {
|
layout(size: number, offset: number, ctx: ILayoutContext | undefined): void {
|
||||||
|
@ -897,9 +895,7 @@ class LeafNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||||
}
|
}
|
||||||
|
|
||||||
setVisible(visible: boolean): void {
|
setVisible(visible: boolean): void {
|
||||||
if (this.view.setVisible) {
|
this.view.setVisible?.(visible);
|
||||||
this.view.setVisible(visible);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
|
|
|
@ -1032,9 +1032,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||||
this.currentDragData = new ElementsDragAndDropData(elements);
|
this.currentDragData = new ElementsDragAndDropData(elements);
|
||||||
StaticDND.CurrentDragAndDropData = new ExternalElementsDragAndDropData(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 {
|
private onDragOver(event: IListDragEvent<T>): boolean {
|
||||||
|
@ -1169,9 +1167,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||||
this.currentDragData = undefined;
|
this.currentDragData = undefined;
|
||||||
StaticDND.CurrentDragAndDropData = undefined;
|
StaticDND.CurrentDragAndDropData = undefined;
|
||||||
|
|
||||||
if (this.dnd.onDragEnd) {
|
this.dnd.onDragEnd?.(event);
|
||||||
this.dnd.onDragEnd(event);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private clearDragOverFeedback(): void {
|
private clearDragOverFeedback(): void {
|
||||||
|
@ -1379,16 +1375,12 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||||
if (renderer) {
|
if (renderer) {
|
||||||
renderer.renderElement(item.element, index, row.templateData, undefined);
|
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;
|
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;
|
item.lastDynamicHeightWidth = this.renderWidth;
|
||||||
this.rowsContainer.removeChild(row.domNode);
|
this.rowsContainer.removeChild(row.domNode);
|
||||||
|
@ -1429,9 +1421,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||||
if (item.row) {
|
if (item.row) {
|
||||||
const renderer = this.renderers.get(item.row.templateId);
|
const renderer = this.renderers.get(item.row.templateId);
|
||||||
if (renderer) {
|
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);
|
renderer.disposeTemplate(item.row.templateData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1109,9 +1109,7 @@ class PipelineRenderer<T> implements IListRenderer<T, any> {
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
for (const renderer of this.renderers) {
|
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;
|
i += 1;
|
||||||
}
|
}
|
||||||
|
@ -1182,9 +1180,7 @@ class ListViewDragAndDrop<T> implements IListViewDragAndDrop<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
|
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 {
|
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 {
|
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 {
|
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) {
|
if (this.accessibilityProvider) {
|
||||||
baseRenderers.push(new AccessibiltyRenderer<T>(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]));
|
renderers = renderers.map(r => new PipelineRenderer(r.templateId, [...baseRenderers, r]));
|
||||||
|
|
|
@ -231,9 +231,7 @@ abstract class ViewItem<TLayoutContext> {
|
||||||
|
|
||||||
this.container.classList.toggle('visible', visible);
|
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; }
|
get minimumSize(): number { return this.visible ? this.view.minimumSize : 0; }
|
||||||
|
|
|
@ -70,9 +70,7 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
|
||||||
}
|
}
|
||||||
|
|
||||||
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
|
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 {
|
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 {
|
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 {
|
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);
|
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 {
|
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 {
|
disposeElement(node: ITreeNode<T, TFilterData>, index: number, templateData: ITreeListTemplateData<TTemplateData>, height: number | undefined): void {
|
||||||
templateData.indentGuidesDisposable.dispose();
|
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') {
|
if (typeof height === 'number') {
|
||||||
this.renderedNodes.delete(node);
|
this.renderedNodes.delete(node);
|
||||||
|
|
|
@ -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 {
|
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 {
|
disposeTemplate(templateData: IDataTreeListTemplateData<TTemplateData>): void {
|
||||||
|
@ -196,9 +194,7 @@ class AsyncDataTreeNodeListDragAndDrop<TInput, T> implements IListDragAndDrop<IA
|
||||||
}
|
}
|
||||||
|
|
||||||
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
disposeTemplate(templateData: IDataTreeListTemplateData<TTemplateData>): void {
|
||||||
|
|
|
@ -140,9 +140,7 @@ export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | nu
|
||||||
insertedElements.add(id);
|
insertedElements.add(id);
|
||||||
this.nodesByIdentity.set(id, node);
|
this.nodesByIdentity.set(id, node);
|
||||||
|
|
||||||
if (outerOnDidCreateNode) {
|
outerOnDidCreateNode?.(node);
|
||||||
outerOnDidCreateNode(node);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onDidDeleteNode = (node: ITreeNode<T, TFilterData>) => {
|
onDidDeleteNode = (node: ITreeNode<T, TFilterData>) => {
|
||||||
|
|
|
@ -554,9 +554,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||||
node.renderNodeCount = renderNodeCount;
|
node.renderNodeCount = renderNodeCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onDidCreateNode) {
|
onDidCreateNode?.(node);
|
||||||
onDidCreateNode(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
disposeElement(node: ITreeNode<T, TFilterData>, index: number, templateData: CompressibleTemplateData<T, TFilterData, TTemplateData>, height: number | undefined): void {
|
||||||
if (templateData.compressedTreeNode) {
|
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 {
|
} else {
|
||||||
if (this.renderer.disposeElement) {
|
this.renderer.disposeElement?.(node, index, templateData.data, height);
|
||||||
this.renderer.disposeElement(node, index, templateData.data, height);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -354,9 +354,7 @@ export class Delayer<T> implements IDisposable {
|
||||||
this.cancelTimeout();
|
this.cancelTimeout();
|
||||||
|
|
||||||
if (this.completionPromise) {
|
if (this.completionPromise) {
|
||||||
if (this.doReject) {
|
this.doReject?.(new CancellationError());
|
||||||
this.doReject(new CancellationError());
|
|
||||||
}
|
|
||||||
this.completionPromise = null;
|
this.completionPromise = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -885,9 +883,7 @@ export class RunOnceScheduler {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected doRun(): void {
|
protected doRun(): void {
|
||||||
if (this.runner) {
|
this.runner?.();
|
||||||
this.runner();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -960,9 +956,7 @@ export class ProcessTimeRunOnceScheduler {
|
||||||
// time elapsed
|
// time elapsed
|
||||||
clearInterval(this.intervalToken);
|
clearInterval(this.intervalToken);
|
||||||
this.intervalToken = -1;
|
this.intervalToken = -1;
|
||||||
if (this.runner) {
|
this.runner?.();
|
||||||
this.runner();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -985,9 +979,7 @@ export class RunOnceWorker<T> extends RunOnceScheduler {
|
||||||
const units = this.units;
|
const units = this.units;
|
||||||
this.units = [];
|
this.units = [];
|
||||||
|
|
||||||
if (this.runner) {
|
this.runner?.(units);
|
||||||
this.runner(units);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override dispose(): void {
|
override dispose(): void {
|
||||||
|
|
|
@ -95,6 +95,7 @@ export interface SerializedError {
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly message: string;
|
readonly message: string;
|
||||||
readonly stack: string;
|
readonly stack: string;
|
||||||
|
readonly noTelemetry: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transformErrorForSerialization(error: Error): SerializedError;
|
export function transformErrorForSerialization(error: Error): SerializedError;
|
||||||
|
@ -107,7 +108,8 @@ export function transformErrorForSerialization(error: any): any {
|
||||||
$isError: true,
|
$isError: true,
|
||||||
name,
|
name,
|
||||||
message,
|
message,
|
||||||
stack
|
stack,
|
||||||
|
noTelemetry: error instanceof ErrorNoTelemetry
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -690,9 +690,7 @@ export class Emitter<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = listener.subscription.set(() => {
|
const result = listener.subscription.set(() => {
|
||||||
if (removeMonitor) {
|
removeMonitor?.();
|
||||||
removeMonitor();
|
|
||||||
}
|
|
||||||
if (!this._disposed) {
|
if (!this._disposed) {
|
||||||
removeListener();
|
removeListener();
|
||||||
if (this._options && this._options.onLastListenerRemove) {
|
if (this._options && this._options.onLastListenerRemove) {
|
||||||
|
|
|
@ -283,9 +283,7 @@ export class SimpleWorkerClient<W extends object, H extends object> extends Disp
|
||||||
(err: any) => {
|
(err: any) => {
|
||||||
// in Firefox, web workers fail lazily :(
|
// in Firefox, web workers fail lazily :(
|
||||||
// we will reject the proxy
|
// we will reject the proxy
|
||||||
if (lazyProxyReject) {
|
lazyProxyReject?.(err);
|
||||||
lazyProxyReject(err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,7 @@ export function popup(items: IContextMenuItem[], options?: IPopupOptions, onHide
|
||||||
const onClickChannel = `vscode:onContextMenu${contextMenuId}`;
|
const onClickChannel = `vscode:onContextMenu${contextMenuId}`;
|
||||||
const onClickChannelHandler = (event: unknown, itemId: number, context: IContextMenuEvent) => {
|
const onClickChannelHandler = (event: unknown, itemId: number, context: IContextMenuEvent) => {
|
||||||
const item = processedItems[itemId];
|
const item = processedItems[itemId];
|
||||||
if (item.click) {
|
item.click?.(context);
|
||||||
item.click(context);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ipcRenderer.once(onClickChannel, onClickChannelHandler);
|
ipcRenderer.once(onClickChannel, onClickChannelHandler);
|
||||||
|
@ -28,9 +26,7 @@ export function popup(items: IContextMenuItem[], options?: IPopupOptions, onHide
|
||||||
|
|
||||||
ipcRenderer.removeListener(onClickChannel, onClickChannelHandler);
|
ipcRenderer.removeListener(onClickChannel, onClickChannelHandler);
|
||||||
|
|
||||||
if (onHide) {
|
onHide?.();
|
||||||
onHide();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.send(CONTEXT_MENU_CHANNEL, contextMenuId, items.map(item => createItem(item, processedItems)), onClickChannel, options);
|
ipcRenderer.send(CONTEXT_MENU_CHANNEL, contextMenuId, items.map(item => createItem(item, processedItems)), onClickChannel, options);
|
||||||
|
|
|
@ -707,9 +707,7 @@ export class ChannelClient implements IChannelClient, IDisposable {
|
||||||
|
|
||||||
const handler = this.handlers.get(response.id);
|
const handler = this.handlers.get(response.id);
|
||||||
|
|
||||||
if (handler) {
|
handler?.(response);
|
||||||
handler(response);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@memoize
|
@memoize
|
||||||
|
|
|
@ -26,9 +26,7 @@ export class Server<TContext extends string> extends IPCServer<TContext> {
|
||||||
super({
|
super({
|
||||||
send: r => {
|
send: r => {
|
||||||
try {
|
try {
|
||||||
if (process.send) {
|
process.send?.((<Buffer>r.buffer).toString('base64'));
|
||||||
process.send((<Buffer>r.buffer).toString('base64'));
|
|
||||||
}
|
|
||||||
} catch (e) { /* not much to do */ }
|
} catch (e) { /* not much to do */ }
|
||||||
},
|
},
|
||||||
onMessage: Event.fromNodeEventEmitter(process, 'message', msg => VSBuffer.wrap(Buffer.from(msg, 'base64')))
|
onMessage: Event.fromNodeEventEmitter(process, 'message', msg => VSBuffer.wrap(Buffer.from(msg, 'base64')))
|
||||||
|
|
|
@ -1406,9 +1406,7 @@ export class QuickInputController extends Disposable {
|
||||||
return new Promise<R>((doResolve, reject) => {
|
return new Promise<R>((doResolve, reject) => {
|
||||||
let resolve = (result: R) => {
|
let resolve = (result: R) => {
|
||||||
resolve = doResolve;
|
resolve = doResolve;
|
||||||
if (options.onKeyMods) {
|
options.onKeyMods?.(input.keyMods);
|
||||||
options.onKeyMods(input.keyMods);
|
|
||||||
}
|
|
||||||
doResolve(result);
|
doResolve(result);
|
||||||
};
|
};
|
||||||
if (token.isCancellationRequested) {
|
if (token.isCancellationRequested) {
|
||||||
|
|
|
@ -440,14 +440,10 @@ class SQLiteStorageDatabaseLogger {
|
||||||
}
|
}
|
||||||
|
|
||||||
trace(msg: string): void {
|
trace(msg: string): void {
|
||||||
if (this.logTrace) {
|
this.logTrace?.(msg);
|
||||||
this.logTrace(msg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
error(error: string | Error): void {
|
error(error: string | Error): void {
|
||||||
if (this.logError) {
|
this.logError?.(error);
|
||||||
this.logError(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,13 +47,9 @@ export function endTrackingDisposables(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function beginLoggingFS(withStacks: boolean = false): void {
|
export function beginLoggingFS(withStacks: boolean = false): void {
|
||||||
if ((<any>self).beginLoggingFS) {
|
(<any>self).beginLoggingFS?.(withStacks);
|
||||||
(<any>self).beginLoggingFS(withStacks);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function endLoggingFS(): void {
|
export function endLoggingFS(): void {
|
||||||
if ((<any>self).endLoggingFS) {
|
(<any>self).endLoggingFS?.();
|
||||||
(<any>self).endLoggingFS();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,69 +33,47 @@ export class ViewUserInputEvents {
|
||||||
}
|
}
|
||||||
|
|
||||||
public emitKeyDown(e: IKeyboardEvent): void {
|
public emitKeyDown(e: IKeyboardEvent): void {
|
||||||
if (this.onKeyDown) {
|
this.onKeyDown?.(e);
|
||||||
this.onKeyDown(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public emitKeyUp(e: IKeyboardEvent): void {
|
public emitKeyUp(e: IKeyboardEvent): void {
|
||||||
if (this.onKeyUp) {
|
this.onKeyUp?.(e);
|
||||||
this.onKeyUp(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public emitContextMenu(e: IEditorMouseEvent): void {
|
public emitContextMenu(e: IEditorMouseEvent): void {
|
||||||
if (this.onContextMenu) {
|
this.onContextMenu?.(this._convertViewToModelMouseEvent(e));
|
||||||
this.onContextMenu(this._convertViewToModelMouseEvent(e));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public emitMouseMove(e: IEditorMouseEvent): void {
|
public emitMouseMove(e: IEditorMouseEvent): void {
|
||||||
if (this.onMouseMove) {
|
this.onMouseMove?.(this._convertViewToModelMouseEvent(e));
|
||||||
this.onMouseMove(this._convertViewToModelMouseEvent(e));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public emitMouseLeave(e: IPartialEditorMouseEvent): void {
|
public emitMouseLeave(e: IPartialEditorMouseEvent): void {
|
||||||
if (this.onMouseLeave) {
|
this.onMouseLeave?.(this._convertViewToModelMouseEvent(e));
|
||||||
this.onMouseLeave(this._convertViewToModelMouseEvent(e));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public emitMouseDown(e: IEditorMouseEvent): void {
|
public emitMouseDown(e: IEditorMouseEvent): void {
|
||||||
if (this.onMouseDown) {
|
this.onMouseDown?.(this._convertViewToModelMouseEvent(e));
|
||||||
this.onMouseDown(this._convertViewToModelMouseEvent(e));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public emitMouseUp(e: IEditorMouseEvent): void {
|
public emitMouseUp(e: IEditorMouseEvent): void {
|
||||||
if (this.onMouseUp) {
|
this.onMouseUp?.(this._convertViewToModelMouseEvent(e));
|
||||||
this.onMouseUp(this._convertViewToModelMouseEvent(e));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public emitMouseDrag(e: IEditorMouseEvent): void {
|
public emitMouseDrag(e: IEditorMouseEvent): void {
|
||||||
if (this.onMouseDrag) {
|
this.onMouseDrag?.(this._convertViewToModelMouseEvent(e));
|
||||||
this.onMouseDrag(this._convertViewToModelMouseEvent(e));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public emitMouseDrop(e: IPartialEditorMouseEvent): void {
|
public emitMouseDrop(e: IPartialEditorMouseEvent): void {
|
||||||
if (this.onMouseDrop) {
|
this.onMouseDrop?.(this._convertViewToModelMouseEvent(e));
|
||||||
this.onMouseDrop(this._convertViewToModelMouseEvent(e));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public emitMouseDropCanceled(): void {
|
public emitMouseDropCanceled(): void {
|
||||||
if (this.onMouseDropCanceled) {
|
this.onMouseDropCanceled?.();
|
||||||
this.onMouseDropCanceled();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public emitMouseWheel(e: IMouseWheelEvent): void {
|
public emitMouseWheel(e: IMouseWheelEvent): void {
|
||||||
if (this.onMouseWheel) {
|
this.onMouseWheel?.(e);
|
||||||
this.onMouseWheel(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _convertViewToModelMouseEvent(e: IEditorMouseEvent): IEditorMouseEvent;
|
private _convertViewToModelMouseEvent(e: IEditorMouseEvent): IEditorMouseEvent;
|
||||||
|
|
|
@ -2136,9 +2136,7 @@ class CodeEditorWidgetFocusTracker extends Disposable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public refreshState(): void {
|
public refreshState(): void {
|
||||||
if (this._domFocusTracker.refreshState) {
|
this._domFocusTracker.refreshState?.();
|
||||||
this._domFocusTracker.refreshState();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat
|
||||||
|
|
||||||
export const codeActionCommandId = 'editor.action.codeAction';
|
export const codeActionCommandId = 'editor.action.codeAction';
|
||||||
export const refactorCommandId = 'editor.action.refactor';
|
export const refactorCommandId = 'editor.action.refactor';
|
||||||
|
export const refactorPreviewCommandId = 'editor.action.refactor.preview';
|
||||||
export const sourceActionCommandId = 'editor.action.sourceAction';
|
export const sourceActionCommandId = 'editor.action.sourceAction';
|
||||||
export const organizeImportsCommandId = 'editor.action.organizeImports';
|
export const organizeImportsCommandId = 'editor.action.organizeImports';
|
||||||
export const fixAllCommandId = 'editor.action.fixAll';
|
export const fixAllCommandId = 'editor.action.fixAll';
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||||
import { CodeActionTriggerType } from 'vs/editor/common/languages';
|
import { CodeActionTriggerType } from 'vs/editor/common/languages';
|
||||||
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
|
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 { CodeActionUi } from 'vs/editor/contrib/codeAction/browser/codeActionUi';
|
||||||
import { MessageController } from 'vs/editor/contrib/message/browser/messageController';
|
import { MessageController } from 'vs/editor/contrib/message/browser/messageController';
|
||||||
import * as nls from 'vs/nls';
|
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 { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||||
import { IMarkerService } from 'vs/platform/markers/common/markers';
|
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 { IEditorProgressService } from 'vs/platform/progress/common/progress';
|
||||||
|
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
import { CodeActionModel, CodeActionsState, SUPPORTED_CODE_ACTIONS } from './codeActionModel';
|
import { CodeActionModel, CodeActionsState, SUPPORTED_CODE_ACTIONS } from './codeActionModel';
|
||||||
import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionFilter, CodeActionKind, CodeActionTrigger } from './types';
|
import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionFilter, CodeActionKind, CodeActionTrigger } from './types';
|
||||||
|
@ -39,6 +39,26 @@ function contextKeyForSupportedActions(kind: CodeActionKind) {
|
||||||
new RegExp('(\\s|^)' + escapeRegExpCharacters(kind.value) + '\\b'));
|
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 = {
|
const argsSchema: IJSONSchema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
defaultSnippets: [{ body: { kind: '' } }],
|
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._model = this._register(new CodeActionModel(this._editor, languageFeaturesService.codeActionProvider, markerService, contextKeyService, progressService));
|
||||||
this._register(this._model.onDidChangeState(newState => this.update(newState)));
|
this._register(this._model.onDidChangeState(newState => this.update(newState)));
|
||||||
|
|
||||||
|
|
||||||
this._ui = new Lazy(() =>
|
this._ui = new Lazy(() =>
|
||||||
this._register(new CodeActionUi(editor, QuickFixAction.Id, AutoFixAction.Id, {
|
this._register(new CodeActionUi(editor, QuickFixAction.Id, AutoFixAction.Id, {
|
||||||
applyCodeAction: async (action, retrigger) => {
|
applyCodeAction: async (action, retrigger, preview) => {
|
||||||
try {
|
try {
|
||||||
await this._applyCodeAction(action);
|
await this._applyCodeAction(action, preview);
|
||||||
} finally {
|
} finally {
|
||||||
if (retrigger) {
|
if (retrigger) {
|
||||||
this._trigger({ type: CodeActionTriggerType.Auto, filter: {} });
|
this._trigger({ type: CodeActionTriggerType.Auto, filter: {} });
|
||||||
|
@ -118,7 +139,8 @@ export class QuickFixController extends Disposable implements IEditorContributio
|
||||||
public manualTriggerAtCurrentPosition(
|
public manualTriggerAtCurrentPosition(
|
||||||
notAvailableMessage: string,
|
notAvailableMessage: string,
|
||||||
filter?: CodeActionFilter,
|
filter?: CodeActionFilter,
|
||||||
autoApply?: CodeActionAutoApply
|
autoApply?: CodeActionAutoApply,
|
||||||
|
preview?: boolean
|
||||||
): void {
|
): void {
|
||||||
if (!this._editor.hasModel()) {
|
if (!this._editor.hasModel()) {
|
||||||
return;
|
return;
|
||||||
|
@ -126,22 +148,22 @@ export class QuickFixController extends Disposable implements IEditorContributio
|
||||||
|
|
||||||
MessageController.get(this._editor)?.closeMessage();
|
MessageController.get(this._editor)?.closeMessage();
|
||||||
const triggerPosition = this._editor.getPosition();
|
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) {
|
private _trigger(trigger: CodeActionTrigger) {
|
||||||
return this._model.trigger(trigger);
|
return this._model.trigger(trigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _applyCodeAction(action: CodeActionItem): Promise<void> {
|
private _applyCodeAction(action: CodeActionItem, preview: boolean): Promise<void> {
|
||||||
return this._instantiationService.invokeFunction(applyCodeAction, action, this._editor);
|
return this._instantiationService.invokeFunction(applyCodeAction, action, { preview, editor: this._editor });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function applyCodeAction(
|
export async function applyCodeAction(
|
||||||
accessor: ServicesAccessor,
|
accessor: ServicesAccessor,
|
||||||
item: CodeActionItem,
|
item: CodeActionItem,
|
||||||
editor?: ICodeEditor,
|
options?: { preview?: boolean; editor?: ICodeEditor }
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const bulkEditService = accessor.get(IBulkEditService);
|
const bulkEditService = accessor.get(IBulkEditService);
|
||||||
const commandService = accessor.get(ICommandService);
|
const commandService = accessor.get(ICommandService);
|
||||||
|
@ -171,11 +193,12 @@ export async function applyCodeAction(
|
||||||
|
|
||||||
if (item.action.edit) {
|
if (item.action.edit) {
|
||||||
await bulkEditService.apply(ResourceEdit.convert(item.action.edit), {
|
await bulkEditService.apply(ResourceEdit.convert(item.action.edit), {
|
||||||
editor,
|
editor: options?.editor,
|
||||||
label: item.action.title,
|
label: item.action.title,
|
||||||
quotableLabel: item.action.title,
|
quotableLabel: item.action.title,
|
||||||
code: 'undoredo.codeAction',
|
code: 'undoredo.codeAction',
|
||||||
respectAutoSaveConfig: true
|
respectAutoSaveConfig: true,
|
||||||
|
showPreview: options?.preview,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,12 +229,13 @@ function triggerCodeActionsForEditorSelection(
|
||||||
editor: ICodeEditor,
|
editor: ICodeEditor,
|
||||||
notAvailableMessage: string,
|
notAvailableMessage: string,
|
||||||
filter: CodeActionFilter | undefined,
|
filter: CodeActionFilter | undefined,
|
||||||
autoApply: CodeActionAutoApply | undefined
|
autoApply: CodeActionAutoApply | undefined,
|
||||||
|
preview: boolean = false
|
||||||
): void {
|
): void {
|
||||||
if (editor.hasModel()) {
|
if (editor.hasModel()) {
|
||||||
const controller = QuickFixController.get(editor);
|
const controller = QuickFixController.get(editor);
|
||||||
if (controller) {
|
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 {
|
public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs: any): void {
|
||||||
const args = CodeActionCommandArgs.fromUser(userArgs, {
|
return RefactorTrigger(editor, userArgs, false);
|
||||||
kind: CodeActionKind.Refactor,
|
}
|
||||||
apply: CodeActionAutoApply.Never
|
}
|
||||||
|
|
||||||
|
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
|
public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs: any): void {
|
||||||
? nls.localize('editor.action.refactor.noneMessage.preferred.kind', "No preferred refactorings for '{0}' available", userArgs.kind)
|
return RefactorTrigger(editor, userArgs, true);
|
||||||
: 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,13 @@
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { registerEditorAction, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
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);
|
registerEditorContribution(QuickFixController.ID, QuickFixController);
|
||||||
registerEditorAction(QuickFixAction);
|
registerEditorAction(QuickFixAction);
|
||||||
registerEditorAction(RefactorAction);
|
registerEditorAction(RefactorAction);
|
||||||
|
registerEditorAction(RefactorPreview);
|
||||||
registerEditorAction(SourceAction);
|
registerEditorAction(SourceAction);
|
||||||
registerEditorAction(OrganizeImportsAction);
|
registerEditorAction(OrganizeImportsAction);
|
||||||
registerEditorAction(AutoFixAction);
|
registerEditorAction(AutoFixAction);
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||||
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
|
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
|
||||||
|
|
||||||
interface CodeActionWidgetDelegate {
|
interface CodeActionWidgetDelegate {
|
||||||
onSelectCodeAction: (action: CodeActionItem) => Promise<any>;
|
onSelectCodeAction: (action: CodeActionItem, trigger: CodeActionTrigger) => Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ResolveCodeActionKeybinding {
|
interface ResolveCodeActionKeybinding {
|
||||||
|
@ -115,7 +115,7 @@ export class CodeActionMenu extends Disposable {
|
||||||
actionsToShow: readonly CodeActionItem[],
|
actionsToShow: readonly CodeActionItem[],
|
||||||
documentation: readonly Command[]
|
documentation: readonly Command[]
|
||||||
): IAction[] {
|
): 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
|
const result: IAction[] = actionsToShow
|
||||||
.map(toCodeActionAction);
|
.map(toCodeActionAction);
|
||||||
|
|
|
@ -31,7 +31,7 @@ export class CodeActionUi extends Disposable {
|
||||||
quickFixActionId: string,
|
quickFixActionId: string,
|
||||||
preferredFixActionId: string,
|
preferredFixActionId: string,
|
||||||
private readonly delegate: {
|
private readonly delegate: {
|
||||||
applyCodeAction: (action: CodeActionItem, regtriggerAfterApply: boolean) => Promise<void>;
|
applyCodeAction: (action: CodeActionItem, regtriggerAfterApply: boolean, preview: boolean) => Promise<void>;
|
||||||
},
|
},
|
||||||
@IInstantiationService instantiationService: IInstantiationService,
|
@IInstantiationService instantiationService: IInstantiationService,
|
||||||
) {
|
) {
|
||||||
|
@ -39,8 +39,8 @@ export class CodeActionUi extends Disposable {
|
||||||
|
|
||||||
this._codeActionWidget = new Lazy(() => {
|
this._codeActionWidget = new Lazy(() => {
|
||||||
return this._register(instantiationService.createInstance(CodeActionMenu, this._editor, {
|
return this._register(instantiationService.createInstance(CodeActionMenu, this._editor, {
|
||||||
onSelectCodeAction: async (action) => {
|
onSelectCodeAction: async (action, trigger) => {
|
||||||
this.delegate.applyCodeAction(action, /* retrigger */ true);
|
this.delegate.applyCodeAction(action, /* retrigger */ true, Boolean(trigger.preview));
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
@ -85,7 +85,7 @@ export class CodeActionUi extends Disposable {
|
||||||
if (validActionToApply) {
|
if (validActionToApply) {
|
||||||
try {
|
try {
|
||||||
this._lightBulbWidget.getValue().hide();
|
this._lightBulbWidget.getValue().hide();
|
||||||
await this.delegate.applyCodeAction(validActionToApply, false);
|
await this.delegate.applyCodeAction(validActionToApply, false, false);
|
||||||
} finally {
|
} finally {
|
||||||
actions.dispose();
|
actions.dispose();
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,6 +122,7 @@ export interface CodeActionTrigger {
|
||||||
readonly notAvailableMessage: string;
|
readonly notAvailableMessage: string;
|
||||||
readonly position: Position;
|
readonly position: Position;
|
||||||
};
|
};
|
||||||
|
readonly preview?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CodeActionCommandArgs {
|
export class CodeActionCommandArgs {
|
||||||
|
|
|
@ -53,7 +53,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi
|
||||||
|
|
||||||
private readonly _editor: ICodeEditor;
|
private readonly _editor: ICodeEditor;
|
||||||
|
|
||||||
private _currentClipboardItem: undefined | {
|
private _currentClipboardItem?: {
|
||||||
readonly handle: string;
|
readonly handle: string;
|
||||||
readonly dataTransferPromise: CancelablePromise<VSDataTransfer>;
|
readonly dataTransferPromise: CancelablePromise<VSDataTransfer>;
|
||||||
};
|
};
|
||||||
|
@ -70,131 +70,134 @@ export class CopyPasteController extends Disposable implements IEditorContributi
|
||||||
this._editor = editor;
|
this._editor = editor;
|
||||||
|
|
||||||
const container = editor.getContainerDomNode();
|
const container = editor.getContainerDomNode();
|
||||||
|
this._register(addDisposableListener(container, 'copy', e => this.handleCopy(e)));
|
||||||
this._register(addDisposableListener(container, 'copy', (e: ClipboardEvent) => {
|
this._register(addDisposableListener(container, 'cut', e => this.handleCopy(e)));
|
||||||
if (!e.clipboardData) {
|
this._register(addDisposableListener(container, 'paste', e => this.handlePaste(e), true));
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public arePasteActionsEnabled(model: ITextModel): boolean {
|
private arePasteActionsEnabled(model: ITextModel): boolean {
|
||||||
return this._configurationService.getValue('editor.experimental.pasteActions.enabled', {
|
return this._configurationService.getValue('editor.experimental.pasteActions.enabled', {
|
||||||
resource: model.uri
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1348,9 +1348,7 @@ export class SimpleButton extends Widget {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this._opts.onKeyDown) {
|
this._opts.onKeyDown?.(e);
|
||||||
this._opts.onKeyDown(e);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,9 +74,7 @@ export class RangesCollector {
|
||||||
}
|
}
|
||||||
return new FoldingRegions(startIndexes, endIndexes);
|
return new FoldingRegions(startIndexes, endIndexes);
|
||||||
} else {
|
} else {
|
||||||
if (this._notifyTooManyRegions) {
|
this._notifyTooManyRegions?.(this._foldingRangesLimit);
|
||||||
this._notifyTooManyRegions(this._foldingRangesLimit);
|
|
||||||
}
|
|
||||||
let entries = 0;
|
let entries = 0;
|
||||||
let maxIndent = this._indentOccurrences.length;
|
let maxIndent = this._indentOccurrences.length;
|
||||||
for (let i = 0; i < this._indentOccurrences.length; i++) {
|
for (let i = 0; i < this._indentOccurrences.length; i++) {
|
||||||
|
|
|
@ -121,9 +121,7 @@ export class RangesCollector {
|
||||||
}
|
}
|
||||||
return new FoldingRegions(startIndexes, endIndexes, this._types);
|
return new FoldingRegions(startIndexes, endIndexes, this._types);
|
||||||
} else {
|
} else {
|
||||||
if (this._notifyTooManyRegions) {
|
this._notifyTooManyRegions?.(this._foldingRangesLimit);
|
||||||
this._notifyTooManyRegions(this._foldingRangesLimit);
|
|
||||||
}
|
|
||||||
let entries = 0;
|
let entries = 0;
|
||||||
let maxLevel = this._nestingLevelCounts.length;
|
let maxLevel = this._nestingLevelCounts.length;
|
||||||
for (let i = 0; i < this._nestingLevelCounts.length; i++) {
|
for (let i = 0; i < this._nestingLevelCounts.length; i++) {
|
||||||
|
|
|
@ -243,9 +243,7 @@ export class InlineCompletionsSession extends BaseGhostTextWidgetModel {
|
||||||
lastCompletionItem = currentCompletion.sourceInlineCompletion;
|
lastCompletionItem = currentCompletion.sourceInlineCompletion;
|
||||||
|
|
||||||
const provider = currentCompletion.sourceProvider;
|
const provider = currentCompletion.sourceProvider;
|
||||||
if (provider.handleItemDidShow) {
|
provider.handleItemDidShow?.(currentCompletion.sourceInlineCompletions, lastCompletionItem);
|
||||||
provider.handleItemDidShow(currentCompletion.sourceInlineCompletions, lastCompletionItem);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -146,15 +146,11 @@ export class RenameInputField implements IContentWidget {
|
||||||
private _currentCancelInput?: (focusEditor: boolean) => void;
|
private _currentCancelInput?: (focusEditor: boolean) => void;
|
||||||
|
|
||||||
acceptInput(wantsPreview: boolean): void {
|
acceptInput(wantsPreview: boolean): void {
|
||||||
if (this._currentAcceptInput) {
|
this._currentAcceptInput?.(wantsPreview);
|
||||||
this._currentAcceptInput(wantsPreview);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelInput(focusEditor: boolean): void {
|
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> {
|
getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean, token: CancellationToken): Promise<RenameInputFieldResult | boolean> {
|
||||||
|
|
|
@ -39,9 +39,7 @@ export class BannerController extends Disposable {
|
||||||
...item,
|
...item,
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
this.hide();
|
this.hide();
|
||||||
if (item.onClose) {
|
item.onClose?.();
|
||||||
item.onClose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this._editor.setBanner(this.banner.element, BANNER_ELEMENT_HEIGHT);
|
this._editor.setBanner(this.banner.element, BANNER_ELEMENT_HEIGHT);
|
||||||
|
|
|
@ -129,9 +129,7 @@ export class ContextMenuHandler {
|
||||||
},
|
},
|
||||||
|
|
||||||
onHide: (didCancel?: boolean) => {
|
onHide: (didCancel?: boolean) => {
|
||||||
if (delegate.onHide) {
|
delegate.onHide?.(!!didCancel);
|
||||||
delegate.onHide(!!didCancel);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.block) {
|
if (this.block) {
|
||||||
this.block.remove();
|
this.block.remove();
|
||||||
|
|
|
@ -47,9 +47,7 @@ export class ContextMenuService extends Disposable implements IContextMenuServic
|
||||||
this.contextMenuHandler.showContextMenu({
|
this.contextMenuHandler.showContextMenu({
|
||||||
...delegate,
|
...delegate,
|
||||||
onHide: (didCancel) => {
|
onHide: (didCancel) => {
|
||||||
if (delegate.onHide) {
|
delegate.onHide?.(didCancel);
|
||||||
delegate.onHide(didCancel);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._onDidHideContextMenu.fire();
|
this._onDidHideContextMenu.fire();
|
||||||
}
|
}
|
||||||
|
|
|
@ -816,9 +816,7 @@ export class Menubar {
|
||||||
const originalClick = options.click;
|
const originalClick = options.click;
|
||||||
options.click = (item, window, event) => {
|
options.click = (item, window, event) => {
|
||||||
this.reportMenuActionTelemetry(commandId);
|
this.reportMenuActionTelemetry(commandId);
|
||||||
if (originalClick) {
|
originalClick?.(item, window, event);
|
||||||
originalClick(item, window, event);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
|
|
|
@ -71,9 +71,7 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
|
||||||
const key = uri.toString();
|
const key = uri.toString();
|
||||||
if (!this._canonicalURIRequests.has(key)) {
|
if (!this._canonicalURIRequests.has(key)) {
|
||||||
const request = new PendingPromise<URI, URI>(uri);
|
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);
|
this._canonicalURIRequests.set(key, request);
|
||||||
}
|
}
|
||||||
return this._canonicalURIRequests.get(key)!.promise;
|
return this._canonicalURIRequests.get(key)!.promise;
|
||||||
|
|
|
@ -183,9 +183,10 @@ async function transformToTerminalProfiles(
|
||||||
validatedProfile.color = profile.color;
|
validatedProfile.color = profile.color;
|
||||||
resultProfiles.push(validatedProfile);
|
resultProfiles.push(validatedProfile);
|
||||||
} else {
|
} else {
|
||||||
logService?.trace('profile not validated', profileName, originalPaths);
|
logService?.debug('Terminal profile not validated', profileName, originalPaths);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
logService?.debug('Validated terminal profiles', resultProfiles);
|
||||||
return resultProfiles;
|
return resultProfiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
if (args instanceof SerializableObjectWithBuffers) {
|
||||||
args = args.value;
|
args = args.value;
|
||||||
}
|
}
|
||||||
for (let i = 0; i < args.length; i++) {
|
for (let i = 0; i < args.length; i++) {
|
||||||
args[i] = revive(args[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);
|
return this._commandService.executeCommand<T>(id, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
* 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 { extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||||
import { MainContext, MainThreadErrorsShape } from 'vs/workbench/api/common/extHost.protocol';
|
import { MainContext, MainThreadErrorsShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ export class MainThreadErrors implements MainThreadErrorsShape {
|
||||||
$onUnexpectedError(err: any | SerializedError): void {
|
$onUnexpectedError(err: any | SerializedError): void {
|
||||||
if (err && err.$isError) {
|
if (err && err.$isError) {
|
||||||
const { name, message, stack } = err;
|
const { name, message, stack } = err;
|
||||||
err = new Error();
|
err = err.noTelemetry ? new ErrorNoTelemetry() : new Error();
|
||||||
err.message = message;
|
err.message = message;
|
||||||
err.name = name;
|
err.name = name;
|
||||||
err.stack = stack;
|
err.stack = stack;
|
||||||
|
|
|
@ -96,6 +96,7 @@ export namespace NotebookDto {
|
||||||
if (data.editType === CellExecutionUpdateType.Output) {
|
if (data.editType === CellExecutionUpdateType.Output) {
|
||||||
return {
|
return {
|
||||||
editType: data.editType,
|
editType: data.editType,
|
||||||
|
cellHandle: data.cellHandle,
|
||||||
append: data.append,
|
append: data.append,
|
||||||
outputs: data.outputs.map(fromNotebookOutputDto)
|
outputs: data.outputs.map(fromNotebookOutputDto)
|
||||||
};
|
};
|
||||||
|
|
|
@ -94,9 +94,7 @@ class SearchOperation {
|
||||||
this.matches.set(match.resource.toString(), match);
|
this.matches.set(match.resource.toString(), match);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.progress) {
|
this.progress?.(match);
|
||||||
this.progress(match);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,8 @@ export interface MainThreadClipboardShape extends IDisposable {
|
||||||
export interface MainThreadCommandsShape extends IDisposable {
|
export interface MainThreadCommandsShape extends IDisposable {
|
||||||
$registerCommand(id: string): void;
|
$registerCommand(id: string): void;
|
||||||
$unregisterCommand(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[]>;
|
$getCommands(): Promise<string[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -999,6 +1000,7 @@ export interface INotebookProxyKernelDto {
|
||||||
|
|
||||||
export interface ICellExecuteOutputEditDto {
|
export interface ICellExecuteOutputEditDto {
|
||||||
editType: CellExecutionUpdateType.Output;
|
editType: CellExecutionUpdateType.Output;
|
||||||
|
cellHandle: number;
|
||||||
append?: boolean;
|
append?: boolean;
|
||||||
outputs: NotebookOutputDto[];
|
outputs: NotebookOutputDto[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,10 +163,15 @@ export class ExtHostCommands implements ExtHostCommandsShape {
|
||||||
|
|
||||||
executeCommand<T>(id: string, ...args: any[]): Promise<T> {
|
executeCommand<T>(id: string, ...args: any[]): Promise<T> {
|
||||||
this._logService.trace('ExtHostCommands#executeCommand', id);
|
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)) {
|
if (this._commands.has(id)) {
|
||||||
// we stay inside the extension host and support
|
// we stay inside the extension host and support
|
||||||
|
@ -201,17 +206,10 @@ export class ExtHostCommands implements ExtHostCommandsShape {
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
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);
|
return revive<any>(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Rerun the command when it wasn't known, had arguments, and when retry
|
throw e;
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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[] {
|
private validateAndConvertOutputs(items: vscode.NotebookCellOutput[]): NotebookOutputDto[] {
|
||||||
return items.map(output => {
|
return items.map(output => {
|
||||||
const newOutput = NotebookCellOutput.ensureUniqueMimeTypes(output.items, true);
|
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> {
|
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));
|
const outputDtos = this.validateAndConvertOutputs(asArray(outputs));
|
||||||
return this.updateSoon(
|
return this.updateSoon(
|
||||||
{
|
{
|
||||||
editType: CellExecutionUpdateType.Output,
|
editType: CellExecutionUpdateType.Output,
|
||||||
|
cellHandle: handle,
|
||||||
append,
|
append,
|
||||||
outputs: outputDtos
|
outputs: outputDtos
|
||||||
});
|
});
|
||||||
|
|
|
@ -136,9 +136,7 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx
|
||||||
}
|
}
|
||||||
|
|
||||||
$onItemSelected(handle: number): void {
|
$onItemSelected(handle: number): void {
|
||||||
if (this._onDidSelectItem) {
|
this._onDidSelectItem?.(handle);
|
||||||
this._onDidSelectItem(handle);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- input
|
// ---- input
|
||||||
|
|
|
@ -2473,9 +2473,9 @@ export class DataTransfer {
|
||||||
this.#items.set(mimeType, [value]);
|
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) {
|
for (const [mime, items] of this.#items) {
|
||||||
items.forEach(item => callbackfn(item, mime));
|
items.forEach(item => callbackfn(item, mime), thisArg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -546,9 +546,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
|
||||||
}
|
}
|
||||||
|
|
||||||
$handleTextSearchResult(result: IRawFileMatch2, requestId: number): void {
|
$handleTextSearchResult(result: IRawFileMatch2, requestId: number): void {
|
||||||
if (this._activeSearchCallbacks[requestId]) {
|
this._activeSearchCallbacks[requestId]?.(result);
|
||||||
this._activeSearchCallbacks[requestId](result);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
saveAll(includeUntitled?: boolean): Promise<boolean> {
|
saveAll(includeUntitled?: boolean): Promise<boolean> {
|
||||||
|
|
|
@ -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
|
// 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' };
|
const req: IExtHostReadyMessage = { type: 'VSCODE_EXTHOST_IPC_READY' };
|
||||||
if (process.send) {
|
process.send?.(req);
|
||||||
process.send(req);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -60,7 +60,7 @@ suite('ExtHostCommands', function () {
|
||||||
assert.strictEqual(unregisterCounter, 1);
|
assert.strictEqual(unregisterCounter, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('execute with retry', async function () {
|
test('execute triggers activate', async function () {
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
|
@ -68,16 +68,13 @@ suite('ExtHostCommands', function () {
|
||||||
override $registerCommand(id: string): void {
|
override $registerCommand(id: string): void {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
override async $executeCommand<T>(id: string, args: any[], retry: boolean): Promise<T | undefined> {
|
|
||||||
count++;
|
override async $activateByCommandEvent(id: string): Promise<void> {
|
||||||
assert.strictEqual(retry, count === 1);
|
count += 1;
|
||||||
if (count === 1) {
|
}
|
||||||
assert.strictEqual(retry, true);
|
|
||||||
throw new Error('$executeCommand:retry');
|
override async $executeCommand<T>(id: string, args: any[]): Promise<T | undefined> {
|
||||||
} else {
|
return undefined;
|
||||||
assert.strictEqual(retry, false);
|
|
||||||
return <any>17;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -86,8 +83,7 @@ suite('ExtHostCommands', function () {
|
||||||
new NullLogService()
|
new NullLogService()
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = await commands.executeCommand('fooo', [this, true]);
|
await commands.executeCommand('fooo', [this, true]);
|
||||||
assert.strictEqual(result, 17);
|
assert.strictEqual(count, 1);
|
||||||
assert.strictEqual(count, 2);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { MainThreadCommands } from 'vs/workbench/api/browser/mainThreadCommands';
|
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 { SingleProxyRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol';
|
||||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||||
import { mock } from 'vs/base/test/common/mock';
|
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('foo'), undefined);
|
||||||
assert.strictEqual(CommandsRegistry.getCommand('bar'), 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']);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -57,9 +57,7 @@ export class TestRPCProtocol implements IExtHostContext, IExtHostRpcService {
|
||||||
private set _callCount(value: number) {
|
private set _callCount(value: number) {
|
||||||
this._callCountValue = value;
|
this._callCountValue = value;
|
||||||
if (this._callCountValue === 0) {
|
if (this._callCountValue === 0) {
|
||||||
if (this._completeIdle) {
|
this._completeIdle?.();
|
||||||
this._completeIdle();
|
|
||||||
}
|
|
||||||
this._idle = undefined;
|
this._idle = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -609,9 +609,7 @@ export class CompositeDragAndDropObserver extends Disposable {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (callbacks.onDragLeave) {
|
callbacks.onDragLeave?.({ eventData: e, dragAndDropData: data! });
|
||||||
callbacks.onDragLeave({ eventData: e, dragAndDropData: data! });
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onDrop: e => {
|
onDrop: e => {
|
||||||
if (callbacks.onDrop) {
|
if (callbacks.onDrop) {
|
||||||
|
|
|
@ -6,7 +6,9 @@
|
||||||
import { Event } from 'vs/base/common/event';
|
import { Event } from 'vs/base/common/event';
|
||||||
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
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.
|
* Reads the current value.
|
||||||
*
|
*
|
||||||
|
@ -39,7 +41,7 @@ export interface IReader {
|
||||||
*
|
*
|
||||||
* Is called by `Observable.read`.
|
* Is called by `Observable.read`.
|
||||||
*/
|
*/
|
||||||
handleBeforeReadObservable<T>(observable: IObservable<T>): void;
|
handleBeforeReadObservable<T>(observable: IObservable<T, any>): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IObserver {
|
export interface IObserver {
|
||||||
|
@ -61,7 +63,7 @@ export interface IObserver {
|
||||||
* Implementations must not call into other observables!
|
* Implementations must not call into other observables!
|
||||||
* The change should be processed when {@link IObserver.endUpdate} is called.
|
* 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.
|
* Indicates that an update operation has completed.
|
||||||
|
@ -69,8 +71,8 @@ export interface IObserver {
|
||||||
endUpdate<T>(observable: IObservable<T>): void;
|
endUpdate<T>(observable: IObservable<T>): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISettable<T> {
|
export interface ISettable<T, TChange = void> {
|
||||||
set(value: T, transaction: ITransaction | undefined): void;
|
set(value: T, transaction: ITransaction | undefined, change: TChange): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITransaction {
|
export interface ITransaction {
|
||||||
|
@ -80,12 +82,14 @@ export interface ITransaction {
|
||||||
*/
|
*/
|
||||||
updateObserver(
|
updateObserver(
|
||||||
observer: IObserver,
|
observer: IObserver,
|
||||||
observable: IObservable<any>
|
observable: IObservable<any, any>
|
||||||
): void;
|
): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Base ===
|
// === 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 get(): T;
|
||||||
public abstract subscribe(observer: IObserver): void;
|
public abstract subscribe(observer: IObserver): void;
|
||||||
public abstract unsubscribe(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>();
|
protected readonly observers = new Set<IObserver>();
|
||||||
|
|
||||||
public subscribe(observer: IObserver): void {
|
public subscribe(observer: IObserver): void {
|
||||||
|
@ -151,9 +155,9 @@ class TransactionImpl implements ITransaction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ObservableValue<T>
|
export class ObservableValue<T, TChange = void>
|
||||||
extends BaseObservable<T>
|
extends BaseObservable<T, TChange>
|
||||||
implements ISettable<T>
|
implements ISettable<T, TChange>
|
||||||
{
|
{
|
||||||
private value: T;
|
private value: T;
|
||||||
|
|
||||||
|
@ -166,14 +170,14 @@ export class ObservableValue<T>
|
||||||
return this.value;
|
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) {
|
if (this.value === value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tx) {
|
if (!tx) {
|
||||||
transaction((tx) => {
|
transaction((tx) => {
|
||||||
this.set(value, tx);
|
this.set(value, tx, change);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -182,7 +186,7 @@ export class ObservableValue<T>
|
||||||
|
|
||||||
for (const observer of this.observers) {
|
for (const observer of this.observers) {
|
||||||
tx.updateObserver(observer, this);
|
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);
|
return new ConstObservable(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConstObservable<T> extends ConvenientObservable<T> {
|
class ConstObservable<T> extends ConvenientObservable<T, void> {
|
||||||
constructor(private readonly value: T) {
|
constructor(private readonly value: T) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
@ -208,11 +212,28 @@ class ConstObservable<T> extends ConvenientObservable<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// == autorun ==
|
// == autorun ==
|
||||||
export function autorun(
|
export function autorun(fn: (reader: IReader) => void, name: string): IDisposable {
|
||||||
fn: (reader: IReader) => void,
|
return new AutorunObserver(fn, name, undefined);
|
||||||
name: string
|
}
|
||||||
|
|
||||||
|
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 {
|
): IDisposable {
|
||||||
return new AutorunObserver(fn, name);
|
return new AutorunObserver(fn, name, options.handleChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function autorunWithStore(
|
export function autorunWithStore(
|
||||||
|
@ -252,7 +273,8 @@ export class AutorunObserver implements IObserver, IReader, IDisposable {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly runFn: (reader: IReader) => void,
|
private readonly runFn: (reader: IReader) => void,
|
||||||
public readonly name: string
|
public readonly name: string,
|
||||||
|
private readonly _handleChange: ((context: IChangeContext) => boolean) | undefined
|
||||||
) {
|
) {
|
||||||
this.runIfNeeded();
|
this.runIfNeeded();
|
||||||
}
|
}
|
||||||
|
@ -264,8 +286,13 @@ export class AutorunObserver implements IObserver, IReader, IDisposable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public handleChange() {
|
public handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void {
|
||||||
this.needsToRun = true;
|
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) {
|
if (this.updateCount === 0) {
|
||||||
this.runIfNeeded();
|
this.runIfNeeded();
|
||||||
|
@ -337,7 +364,7 @@ export function autorunDelta<T>(
|
||||||
export function derivedObservable<T>(name: string, computeFn: (reader: IReader) => T): IObservable<T> {
|
export function derivedObservable<T>(name: string, computeFn: (reader: IReader) => T): IObservable<T> {
|
||||||
return new LazyDerived(computeFn, name);
|
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>;
|
private readonly observer: LazyDerivedObserver<T>;
|
||||||
|
|
||||||
constructor(computeFn: (reader: IReader) => T, name: string) {
|
constructor(computeFn: (reader: IReader) => T, name: string) {
|
||||||
|
@ -366,7 +393,7 @@ export class LazyDerived<T> extends ConvenientObservable<T> {
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
class LazyDerivedObserver<T>
|
class LazyDerivedObserver<T>
|
||||||
extends BaseObservable<T>
|
extends BaseObservable<T, void>
|
||||||
implements IReader, IObserver {
|
implements IReader, IObserver {
|
||||||
private hadValue = false;
|
private hadValue = false;
|
||||||
private hasValue = false;
|
private hasValue = false;
|
||||||
|
@ -486,9 +513,8 @@ class LazyDerivedObserver<T>
|
||||||
|
|
||||||
this.hasValue = true;
|
this.hasValue = true;
|
||||||
if (this.hadValue && oldValue !== this.value) {
|
if (this.hadValue && oldValue !== this.value) {
|
||||||
//
|
|
||||||
for (const r of this.observers) {
|
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;
|
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>(
|
export function observableFromEvent<T, TArgs = unknown>(
|
||||||
event: Event<TArgs>,
|
event: Event<TArgs>,
|
||||||
getValue: (args: TArgs | undefined) => T
|
getValue: (args: TArgs | undefined) => T
|
||||||
|
@ -540,7 +580,7 @@ class FromEventObservable<TArgs, T> extends BaseObservable<T> {
|
||||||
transaction(tx => {
|
transaction(tx => {
|
||||||
for (const o of this.observers) {
|
for (const o of this.observers) {
|
||||||
tx.updateObserver(o, this);
|
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);
|
observable.read(reader);
|
||||||
}, 'keep-alive');
|
}, '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;
|
||||||
|
}
|
||||||
|
|
|
@ -272,9 +272,7 @@ export class BulkEditPane extends ViewPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _done(accept: boolean): void {
|
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._currentInput = undefined;
|
||||||
this._setState(State.Message);
|
this._setState(State.Message);
|
||||||
this._sessionDisposables.clear();
|
this._sessionDisposables.clear();
|
||||||
|
|
|
@ -250,9 +250,7 @@ export class CommentReply<T extends IRange | ICellRange> extends Disposable {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._commentFormActions = new CommentFormActions(container, async (action: IAction) => {
|
this._commentFormActions = new CommentFormActions(container, async (action: IAction) => {
|
||||||
if (this._actionRunDelegate) {
|
this._actionRunDelegate?.();
|
||||||
this._actionRunDelegate();
|
|
||||||
}
|
|
||||||
|
|
||||||
action.run({
|
action.run({
|
||||||
thread: this._commentThread,
|
thread: this._commentThread,
|
||||||
|
|
|
@ -155,14 +155,10 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter {
|
||||||
|
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case 'event':
|
case 'event':
|
||||||
if (this.eventCallback) {
|
this.eventCallback?.(<DebugProtocol.Event>message);
|
||||||
this.eventCallback(<DebugProtocol.Event>message);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'request':
|
case 'request':
|
||||||
if (this.requestCallback) {
|
this.requestCallback?.(<DebugProtocol.Request>message);
|
||||||
this.requestCallback(<DebugProtocol.Request>message);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'response': {
|
case 'response': {
|
||||||
const response = <DebugProtocol.Response>message;
|
const response = <DebugProtocol.Response>message;
|
||||||
|
|
|
@ -1519,9 +1519,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
||||||
this.progressService.withProgress({ location: ProgressLocation.Extensions }, () => new Promise(resolve => this._activityCallBack = resolve));
|
this.progressService.withProgress({ location: ProgressLocation.Extensions }, () => new Promise(resolve => this._activityCallBack = resolve));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this._activityCallBack) {
|
this._activityCallBack?.();
|
||||||
this._activityCallBack();
|
|
||||||
}
|
|
||||||
this._activityCallBack = null;
|
this._activityCallBack = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -215,6 +215,10 @@
|
||||||
min-width: 0;
|
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 {
|
.extension-list-item .monaco-action-bar .action-label.icon {
|
||||||
padding: 1px 2px;
|
padding: 1px 2px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,12 +45,14 @@ interface IMarkerCodeColumnTemplateData {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IMarkerFileColumnTemplateData {
|
interface IMarkerFileColumnTemplateData {
|
||||||
|
readonly columnElement: HTMLElement;
|
||||||
readonly fileLabel: HighlightedLabel;
|
readonly fileLabel: HighlightedLabel;
|
||||||
readonly positionLabel: HighlightedLabel;
|
readonly positionLabel: HighlightedLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
interface IMarkerHighlightedLabelColumnTemplateData {
|
interface IMarkerHighlightedLabelColumnTemplateData {
|
||||||
|
readonly columnElement: HTMLElement;
|
||||||
readonly highlightedLabel: HighlightedLabel;
|
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.icon.className = `marker-icon codicon ${SeverityIcon.className(MarkerSeverity.toSeverity(element.marker.severity))}`;
|
||||||
|
|
||||||
templateData.actionBar.clear();
|
templateData.actionBar.clear();
|
||||||
|
@ -137,9 +140,11 @@ class MarkerCodeColumnRenderer implements ITableRenderer<MarkerTableItem, IMarke
|
||||||
DOM.show(templateData.codeLabel.element);
|
DOM.show(templateData.codeLabel.element);
|
||||||
|
|
||||||
if (typeof element.marker.code === 'string') {
|
if (typeof element.marker.code === 'string') {
|
||||||
|
templateData.codeColumn.title = `${element.marker.source} (${element.marker.code})`;
|
||||||
templateData.sourceLabel.set(element.marker.source, element.sourceMatches);
|
templateData.sourceLabel.set(element.marker.source, element.sourceMatches);
|
||||||
templateData.codeLabel.set(element.marker.code, element.codeMatches);
|
templateData.codeLabel.set(element.marker.code, element.codeMatches);
|
||||||
} else {
|
} else {
|
||||||
|
templateData.codeColumn.title = `${element.marker.source} (${element.marker.code.value})`;
|
||||||
templateData.sourceLabel.set(element.marker.source, element.sourceMatches);
|
templateData.sourceLabel.set(element.marker.source, element.sourceMatches);
|
||||||
|
|
||||||
const codeLinkLabel = new HighlightedLabel($('.code-link-label'));
|
const codeLinkLabel = new HighlightedLabel($('.code-link-label'));
|
||||||
|
@ -152,6 +157,7 @@ class MarkerCodeColumnRenderer implements ITableRenderer<MarkerTableItem, IMarke
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
templateData.codeColumn.title = '';
|
||||||
templateData.sourceLabel.set('-');
|
templateData.sourceLabel.set('-');
|
||||||
DOM.hide(templateData.codeLabel.element);
|
DOM.hide(templateData.codeLabel.element);
|
||||||
}
|
}
|
||||||
|
@ -167,13 +173,14 @@ class MarkerMessageColumnRenderer implements ITableRenderer<MarkerTableItem, IMa
|
||||||
readonly templateId: string = MarkerMessageColumnRenderer.TEMPLATE_ID;
|
readonly templateId: string = MarkerMessageColumnRenderer.TEMPLATE_ID;
|
||||||
|
|
||||||
renderTemplate(container: HTMLElement): IMarkerHighlightedLabelColumnTemplateData {
|
renderTemplate(container: HTMLElement): IMarkerHighlightedLabelColumnTemplateData {
|
||||||
const fileColumn = DOM.append(container, $('.message'));
|
const columnElement = DOM.append(container, $('.message'));
|
||||||
const highlightedLabel = new HighlightedLabel(fileColumn);
|
const highlightedLabel = new HighlightedLabel(columnElement);
|
||||||
|
|
||||||
return { highlightedLabel };
|
return { columnElement, highlightedLabel };
|
||||||
}
|
}
|
||||||
|
|
||||||
renderElement(element: MarkerTableItem, index: number, templateData: IMarkerHighlightedLabelColumnTemplateData, height: number | undefined): void {
|
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);
|
templateData.highlightedLabel.set(element.marker.message, element.messageMatches);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,18 +198,21 @@ class MarkerFileColumnRenderer implements ITableRenderer<MarkerTableItem, IMarke
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
renderTemplate(container: HTMLElement): IMarkerFileColumnTemplateData {
|
renderTemplate(container: HTMLElement): IMarkerFileColumnTemplateData {
|
||||||
const fileColumn = DOM.append(container, $('.file'));
|
const columnElement = DOM.append(container, $('.file'));
|
||||||
const fileLabel = new HighlightedLabel(fileColumn);
|
const fileLabel = new HighlightedLabel(columnElement);
|
||||||
fileLabel.element.classList.add('file-label');
|
fileLabel.element.classList.add('file-label');
|
||||||
const positionLabel = new HighlightedLabel(fileColumn);
|
const positionLabel = new HighlightedLabel(columnElement);
|
||||||
positionLabel.element.classList.add('file-position');
|
positionLabel.element.classList.add('file-position');
|
||||||
|
|
||||||
return { fileLabel, positionLabel };
|
return { columnElement, fileLabel, positionLabel };
|
||||||
}
|
}
|
||||||
|
|
||||||
renderElement(element: MarkerTableItem, index: number, templateData: IMarkerFileColumnTemplateData, height: number | undefined): void {
|
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.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 { }
|
disposeTemplate(templateData: IMarkerFileColumnTemplateData): void { }
|
||||||
|
@ -215,12 +225,13 @@ class MarkerOwnerColumnRenderer implements ITableRenderer<MarkerTableItem, IMark
|
||||||
readonly templateId: string = MarkerOwnerColumnRenderer.TEMPLATE_ID;
|
readonly templateId: string = MarkerOwnerColumnRenderer.TEMPLATE_ID;
|
||||||
|
|
||||||
renderTemplate(container: HTMLElement): IMarkerHighlightedLabelColumnTemplateData {
|
renderTemplate(container: HTMLElement): IMarkerHighlightedLabelColumnTemplateData {
|
||||||
const fileColumn = DOM.append(container, $('.owner'));
|
const columnElement = DOM.append(container, $('.owner'));
|
||||||
const highlightedLabel = new HighlightedLabel(fileColumn);
|
const highlightedLabel = new HighlightedLabel(columnElement);
|
||||||
return { highlightedLabel };
|
return { columnElement, highlightedLabel };
|
||||||
}
|
}
|
||||||
|
|
||||||
renderElement(element: MarkerTableItem, index: number, templateData: IMarkerHighlightedLabelColumnTemplateData, height: number | undefined): void {
|
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);
|
templateData.highlightedLabel.set(element.marker.owner, element.ownerMatches);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/tex
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||||
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
|
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 { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||||
import { IEditorOptions } from 'vs/platform/editor/common/editor';
|
import { IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
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 { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
|
||||||
import { IEditorControl, IEditorOpenContext } from 'vs/workbench/common/editor';
|
import { IEditorControl, IEditorOpenContext } from 'vs/workbench/common/editor';
|
||||||
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
|
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 { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput';
|
||||||
import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorModel';
|
import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorModel';
|
||||||
import { LineRange, ModifiedBaseRange } from 'vs/workbench/contrib/mergeEditor/browser/model';
|
import { LineRange, ModifiedBaseRange } from 'vs/workbench/contrib/mergeEditor/browser/model';
|
||||||
|
@ -61,8 +62,8 @@ export class MergeEditor extends EditorPane {
|
||||||
|
|
||||||
private _grid!: Grid<IView>;
|
private _grid!: Grid<IView>;
|
||||||
|
|
||||||
private readonly input1View = this.instantiation.createInstance(InputCodeEditorView, 1, { readonly: true });
|
private readonly input1View = this.instantiation.createInstance(InputCodeEditorView, 1, { readonly: !this.inputsWritable });
|
||||||
private readonly input2View = this.instantiation.createInstance(InputCodeEditorView, 2, { readonly: true });
|
private readonly input2View = this.instantiation.createInstance(InputCodeEditorView, 2, { readonly: !this.inputsWritable });
|
||||||
private readonly inputResultView = this.instantiation.createInstance(ResultCodeEditorView, { readonly: false });
|
private readonly inputResultView = this.instantiation.createInstance(ResultCodeEditorView, { readonly: false });
|
||||||
|
|
||||||
private readonly _ctxIsMergeEditor: IContextKey<boolean>;
|
private readonly _ctxIsMergeEditor: IContextKey<boolean>;
|
||||||
|
@ -71,6 +72,10 @@ export class MergeEditor extends EditorPane {
|
||||||
private _model: MergeEditorModel | undefined;
|
private _model: MergeEditorModel | undefined;
|
||||||
public get model(): MergeEditorModel | undefined { return this._model; }
|
public get model(): MergeEditorModel | undefined { return this._model; }
|
||||||
|
|
||||||
|
private get inputsWritable(): boolean {
|
||||||
|
return !!this._configurationService.getValue<boolean>('mergeEditor.writableInputs');
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@IInstantiationService private readonly instantiation: IInstantiationService,
|
@IInstantiationService private readonly instantiation: IInstantiationService,
|
||||||
@ILabelService private readonly _labelService: ILabelService,
|
@ILabelService private readonly _labelService: ILabelService,
|
||||||
|
@ -80,6 +85,7 @@ export class MergeEditor extends EditorPane {
|
||||||
@IStorageService storageService: IStorageService,
|
@IStorageService storageService: IStorageService,
|
||||||
@IThemeService themeService: IThemeService,
|
@IThemeService themeService: IThemeService,
|
||||||
@ITextResourceConfigurationService private readonly textResourceConfigurationService: ITextResourceConfigurationService,
|
@ITextResourceConfigurationService private readonly textResourceConfigurationService: ITextResourceConfigurationService,
|
||||||
|
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||||
) {
|
) {
|
||||||
super(MergeEditor.ID, telemetryService, themeService, storageService);
|
super(MergeEditor.ID, telemetryService, themeService, storageService);
|
||||||
|
|
||||||
|
@ -94,7 +100,7 @@ export class MergeEditor extends EditorPane {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const resultDiffs = model.resultDiffs.read(reader);
|
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;
|
return modifiedBaseRanges;
|
||||||
});
|
});
|
||||||
const input2ResultMapping = derivedObservable('input2ResultMapping', reader => {
|
const input2ResultMapping = derivedObservable('input2ResultMapping', reader => {
|
||||||
|
@ -103,7 +109,7 @@ export class MergeEditor extends EditorPane {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const resultDiffs = model.resultDiffs.read(reader);
|
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;
|
return modifiedBaseRanges;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -228,42 +234,44 @@ export class MergeEditor extends EditorPane {
|
||||||
|
|
||||||
// TODO: Update editor options!
|
// TODO: Update editor options!
|
||||||
|
|
||||||
const input1ViewZoneIds: string[] = [];
|
this._sessionDisposables.add(autorunWithStore((reader, store) => {
|
||||||
const input2ViewZoneIds: string[] = [];
|
const input1ViewZoneIds: string[] = [];
|
||||||
for (const m of model.modifiedBaseRanges) {
|
const input2ViewZoneIds: string[] = [];
|
||||||
const max = Math.max(m.input1Range.lineCount, m.input2Range.lineCount, 1);
|
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 => {
|
this.input1View.editor.changeViewZones(a => {
|
||||||
for (const zone of input1ViewZoneIds) {
|
input1ViewZoneIds.push(a.addZone({
|
||||||
a.removeZone(zone);
|
afterLineNumber: m.input1Range.endLineNumberExclusive - 1,
|
||||||
}
|
heightInLines: max - m.input1Range.lineCount,
|
||||||
|
domNode: $('div.diagonal-fill'),
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
this.input2View.editor.changeViewZones(a => {
|
this.input2View.editor.changeViewZones(a => {
|
||||||
for (const zone of input2ViewZoneIds) {
|
input2ViewZoneIds.push(a.addZone({
|
||||||
a.removeZone(zone);
|
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 {
|
protected override setEditorVisible(visible: boolean): void {
|
||||||
|
@ -448,7 +456,7 @@ class InputCodeEditorView extends CodeEditorView {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const result = new Array<IModelDeltaDecoration>();
|
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);
|
const range = m.getInputRange(this.inputNumber);
|
||||||
if (!range.isEmpty) {
|
if (!range.isEmpty) {
|
||||||
result.push({
|
result.push({
|
||||||
|
@ -478,13 +486,14 @@ class InputCodeEditorView extends CodeEditorView {
|
||||||
getIntersectingGutterItems: (range, reader) => {
|
getIntersectingGutterItems: (range, reader) => {
|
||||||
const model = this.model.read(reader);
|
const model = this.model.read(reader);
|
||||||
if (!model) { return []; }
|
if (!model) { return []; }
|
||||||
return model.modifiedBaseRanges
|
return model.modifiedBaseRanges.read(reader)
|
||||||
.filter((r) => r.getInputDiffs(this.inputNumber).length > 0)
|
.filter((r) => r.getInputDiffs(this.inputNumber).length > 0)
|
||||||
.map<ModifiedBaseRangeGutterItemInfo>((baseRange, idx) => ({
|
.map<ModifiedBaseRangeGutterItemInfo>((baseRange, idx) => ({
|
||||||
id: idx.toString(),
|
id: idx.toString(),
|
||||||
additionalHeightInPx: 0,
|
additionalHeightInPx: 0,
|
||||||
offsetInPx: 0,
|
offsetInPx: 0,
|
||||||
range: baseRange.getInputRange(this.inputNumber),
|
range: baseRange.getInputRange(this.inputNumber),
|
||||||
|
enabled: model.isUpToDate,
|
||||||
toggleState: derivedObservable('toggle', (reader) =>
|
toggleState: derivedObservable('toggle', (reader) =>
|
||||||
model
|
model
|
||||||
.getState(baseRange)
|
.getState(baseRange)
|
||||||
|
@ -510,14 +519,19 @@ class InputCodeEditorView extends CodeEditorView {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ModifiedBaseRangeGutterItemInfo extends IGutterItemInfo {
|
interface ModifiedBaseRangeGutterItemInfo extends IGutterItemInfo {
|
||||||
|
enabled: IObservable<boolean>;
|
||||||
toggleState: IObservable<boolean | undefined>;
|
toggleState: IObservable<boolean | undefined>;
|
||||||
setState(value: boolean, tx: ITransaction | undefined): void;
|
setState(value: boolean, tx: ITransaction): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MergeConflictGutterItemView extends Disposable implements IGutterItemView<ModifiedBaseRangeGutterItemInfo> {
|
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();
|
super();
|
||||||
|
|
||||||
|
this.item.set(item, undefined);
|
||||||
|
|
||||||
target.classList.add('merge-accept-gutter-marker');
|
target.classList.add('merge-accept-gutter-marker');
|
||||||
|
|
||||||
// TODO: localized title
|
// TODO: localized title
|
||||||
|
@ -526,7 +540,8 @@ class MergeConflictGutterItemView extends Disposable implements IGutterItemView<
|
||||||
|
|
||||||
this._register(
|
this._register(
|
||||||
autorun((reader) => {
|
autorun((reader) => {
|
||||||
const value = this.item.toggleState.read(reader);
|
const item = this.item.read(reader)!;
|
||||||
|
const value = item.toggleState.read(reader);
|
||||||
checkBox.setIcon(
|
checkBox.setIcon(
|
||||||
value === true
|
value === true
|
||||||
? Codicon.check
|
? Codicon.check
|
||||||
|
@ -535,11 +550,19 @@ class MergeConflictGutterItemView extends Disposable implements IGutterItemView<
|
||||||
: Codicon.circleFilled
|
: Codicon.circleFilled
|
||||||
);
|
);
|
||||||
checkBox.checked = value === true;
|
checkBox.checked = value === true;
|
||||||
|
|
||||||
|
if (!item.enabled.read(reader)) {
|
||||||
|
checkBox.disable();
|
||||||
|
} else {
|
||||||
|
checkBox.enable();
|
||||||
|
}
|
||||||
}, 'Update Toggle State')
|
}, 'Update Toggle State')
|
||||||
);
|
);
|
||||||
|
|
||||||
this._register(checkBox.onChange(() => {
|
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);
|
target.appendChild(n('div.background', [noBreakWhitespace]).root);
|
||||||
|
@ -555,7 +578,7 @@ class MergeConflictGutterItemView extends Disposable implements IGutterItemView<
|
||||||
}
|
}
|
||||||
|
|
||||||
update(baseRange: ModifiedBaseRangeGutterItemInfo): void {
|
update(baseRange: ModifiedBaseRangeGutterItemInfo): void {
|
||||||
this.item = baseRange;
|
this.item.set(baseRange, undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { ILabelService } from 'vs/platform/label/common/label';
|
||||||
import { IUntypedEditorInput, EditorInputCapabilities } from 'vs/workbench/common/editor';
|
import { IUntypedEditorInput, EditorInputCapabilities } from 'vs/workbench/common/editor';
|
||||||
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
|
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
|
||||||
import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput';
|
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 { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||||
import { ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
import { ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||||
|
|
||||||
|
@ -39,10 +39,9 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput {
|
||||||
|
|
||||||
private _model?: MergeEditorModel;
|
private _model?: MergeEditorModel;
|
||||||
private _outTextModel?: ITextFileEditorModel;
|
private _outTextModel?: ITextFileEditorModel;
|
||||||
private readonly mergeEditorModelFactory = this._instaService.createInstance(MergeEditorModelFactory);
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _anchestor: URI,
|
private readonly _base: URI,
|
||||||
private readonly _input1: MergeEditorInputData,
|
private readonly _input1: MergeEditorInputData,
|
||||||
private readonly _input2: MergeEditorInputData,
|
private readonly _input2: MergeEditorInputData,
|
||||||
private readonly _result: URI,
|
private readonly _result: URI,
|
||||||
|
@ -101,13 +100,14 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput {
|
||||||
|
|
||||||
if (!this._model) {
|
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 input1 = await this._textModelService.createModelReference(this._input1.uri);
|
||||||
const input2 = await this._textModelService.createModelReference(this._input2.uri);
|
const input2 = await this._textModelService.createModelReference(this._input2.uri);
|
||||||
const result = await this._textModelService.createModelReference(this._result);
|
const result = await this._textModelService.createModelReference(this._result);
|
||||||
|
|
||||||
this._model = await this.mergeEditorModelFactory.create(
|
this._model = this._instaService.createInstance(
|
||||||
anchestor.object.textEditorModel,
|
MergeEditorModel,
|
||||||
|
base.object.textEditorModel,
|
||||||
input1.object.textEditorModel,
|
input1.object.textEditorModel,
|
||||||
this._input1.detail,
|
this._input1.detail,
|
||||||
this._input1.description,
|
this._input1.description,
|
||||||
|
@ -117,13 +117,13 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput {
|
||||||
result.object.textEditorModel
|
result.object.textEditorModel
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await this._model.onInitialized;
|
||||||
|
|
||||||
this._store.add(this._model);
|
this._store.add(this._model);
|
||||||
this._store.add(anchestor);
|
this._store.add(base);
|
||||||
this._store.add(input1);
|
this._store.add(input1);
|
||||||
this._store.add(input2);
|
this._store.add(input2);
|
||||||
this._store.add(result);
|
this._store.add(result);
|
||||||
|
|
||||||
// result.object.
|
|
||||||
}
|
}
|
||||||
return this._model;
|
return this._model;
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput {
|
||||||
if (!(otherInput instanceof MergeEditorInput)) {
|
if (!(otherInput instanceof MergeEditorInput)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return isEqual(this._anchestor, otherInput._anchestor)
|
return isEqual(this._base, otherInput._base)
|
||||||
&& isEqual(this._input1.uri, otherInput._input1.uri)
|
&& isEqual(this._input1.uri, otherInput._input1.uri)
|
||||||
&& isEqual(this._input2.uri, otherInput._input2.uri)
|
&& isEqual(this._input2.uri, otherInput._input2.uri)
|
||||||
&& isEqual(this._result, otherInput._result);
|
&& isEqual(this._result, otherInput._result);
|
||||||
|
@ -140,7 +140,7 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput {
|
||||||
|
|
||||||
toJSON(): MergeEditorInputJSON {
|
toJSON(): MergeEditorInputJSON {
|
||||||
return {
|
return {
|
||||||
anchestor: this._anchestor,
|
anchestor: this._base,
|
||||||
inputOne: this._input1,
|
inputOne: this._input1,
|
||||||
inputTwo: this._input2,
|
inputTwo: this._input2,
|
||||||
result: this._result,
|
result: this._result,
|
||||||
|
|
|
@ -3,96 +3,72 @@
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
* 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 } from 'vs/base/common/arrays';
|
||||||
import { compareBy, CompareResult, equals, numberComparator } from 'vs/base/common/arrays';
|
|
||||||
import { BugIndicatingError } from 'vs/base/common/errors';
|
import { BugIndicatingError } from 'vs/base/common/errors';
|
||||||
import { ITextModel } from 'vs/editor/common/model';
|
import { ITextModel } from 'vs/editor/common/model';
|
||||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
|
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
|
||||||
import { EditorModel } from 'vs/workbench/common/editor/editorModel';
|
import { EditorModel } from 'vs/workbench/common/editor/editorModel';
|
||||||
import { IObservable, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable';
|
import { autorunHandleChanges, derivedObservable, derivedObservableWithCache, IObservable, ITransaction, keepAlive, ObservableValue, transaction, waitForState } from 'vs/workbench/contrib/audioCues/browser/observable';
|
||||||
import { ModifiedBaseRange, LineEdit, LineDiff, ModifiedBaseRangeState, LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model';
|
import { LineDiff, LineEdit, LineRange, ModifiedBaseRange, ModifiedBaseRangeState } from 'vs/workbench/contrib/mergeEditor/browser/model';
|
||||||
import { leftJoin, ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/utils';
|
import { EditorWorkerServiceDiffComputer, TextModelDiffChangeReason, TextModelDiffs, TextModelDiffState } from 'vs/workbench/contrib/mergeEditor/browser/textModelDiffs';
|
||||||
|
import { leftJoin } from 'vs/workbench/contrib/mergeEditor/browser/utils';
|
||||||
|
|
||||||
export class MergeEditorModelFactory {
|
export const enum MergeEditorModelState {
|
||||||
constructor(
|
initializing = 1,
|
||||||
@IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService
|
upToDate = 2,
|
||||||
) {
|
updating = 3,
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const InternalSymbol: unique symbol = null!;
|
|
||||||
|
|
||||||
export class MergeEditorModel extends EditorModel {
|
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(
|
constructor(
|
||||||
_symbol: typeof InternalSymbol,
|
|
||||||
readonly base: ITextModel,
|
readonly base: ITextModel,
|
||||||
readonly input1: ITextModel,
|
readonly input1: ITextModel,
|
||||||
readonly input1Detail: string | undefined,
|
readonly input1Detail: string | undefined,
|
||||||
|
@ -101,27 +77,43 @@ export class MergeEditorModel extends EditorModel {
|
||||||
readonly input2Detail: string | undefined,
|
readonly input2Detail: string | undefined,
|
||||||
readonly input2Description: string | undefined,
|
readonly input2Description: string | undefined,
|
||||||
readonly result: ITextModel,
|
readonly result: ITextModel,
|
||||||
public readonly input1LinesDiffs: readonly LineDiff[],
|
@IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService
|
||||||
public readonly input2LinesDiffs: readonly LineDiff[],
|
|
||||||
resultDiffs: LineDiff[],
|
|
||||||
private readonly editorWorkerService: IEditorWorkerService
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.resultEdits = new ResultEdits(resultDiffs, this.base, this.result, this.editorWorkerService);
|
this._register(keepAlive(this.modifiedBaseRangeStateStores));
|
||||||
this.resultEdits.onDidChange(() => {
|
|
||||||
this.recomputeState();
|
|
||||||
});
|
|
||||||
this.recomputeState();
|
|
||||||
|
|
||||||
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 => {
|
transaction(tx => {
|
||||||
const baseRangeWithStoreAndTouchingDiffs = leftJoin(
|
const baseRangeWithStoreAndTouchingDiffs = leftJoin(
|
||||||
this.modifiedBaseRangeStateStores,
|
stores,
|
||||||
this.resultEdits.diffs.get(),
|
resultDiffs,
|
||||||
(baseRange, diff) =>
|
(baseRange, diff) =>
|
||||||
baseRange[0].baseRange.touches(diff.originalRange)
|
baseRange[0].baseRange.touches(diff.originalRange)
|
||||||
? CompareResult.neitherLessOrGreaterThan
|
? CompareResult.neitherLessOrGreaterThan
|
||||||
|
@ -132,14 +124,14 @@ export class MergeEditorModel extends EditorModel {
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const row of baseRangeWithStoreAndTouchingDiffs) {
|
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 {
|
public resetUnknown(): void {
|
||||||
transaction(tx => {
|
transaction(tx => {
|
||||||
for (const range of this.modifiedBaseRanges) {
|
for (const range of this.modifiedBaseRanges.get()) {
|
||||||
if (this.getState(range).get().conflicting) {
|
if (this.getState(range).get().conflicting) {
|
||||||
this.setState(range, ModifiedBaseRangeState.default, tx);
|
this.setState(range, ModifiedBaseRangeState.default, tx);
|
||||||
}
|
}
|
||||||
|
@ -149,7 +141,7 @@ export class MergeEditorModel extends EditorModel {
|
||||||
|
|
||||||
public mergeNonConflictingDiffs(): void {
|
public mergeNonConflictingDiffs(): void {
|
||||||
transaction((tx) => {
|
transaction((tx) => {
|
||||||
for (const m of this.modifiedBaseRanges) {
|
for (const m of this.modifiedBaseRanges.get()) {
|
||||||
if (m.isConflicting) {
|
if (m.isConflicting) {
|
||||||
continue;
|
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> {
|
public getState(baseRange: ModifiedBaseRange): IObservable<ModifiedBaseRangeState> {
|
||||||
const existingState = this.modifiedBaseRangeStateStores.get(baseRange);
|
const existingState = this.modifiedBaseRangeStateStores.get().get(baseRange);
|
||||||
if (!existingState) {
|
if (!existingState) {
|
||||||
throw new BugIndicatingError('object must be from this instance');
|
throw new BugIndicatingError('object must be from this instance');
|
||||||
}
|
}
|
||||||
|
@ -221,240 +167,122 @@ export class MergeEditorModel extends EditorModel {
|
||||||
public setState(
|
public setState(
|
||||||
baseRange: ModifiedBaseRange,
|
baseRange: ModifiedBaseRange,
|
||||||
state: ModifiedBaseRangeState,
|
state: ModifiedBaseRangeState,
|
||||||
transaction: ITransaction | undefined
|
transaction: ITransaction
|
||||||
): void {
|
): 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) {
|
if (!existingState) {
|
||||||
throw new BugIndicatingError('object must be from this instance');
|
throw new BugIndicatingError('object must be from this instance');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const conflictingDiffs = this.resultTextModelDiffs.findTouchingDiffs(
|
||||||
const conflictingDiffs = this.resultEdits.findTouchingDiffs(
|
|
||||||
baseRange.baseRange
|
baseRange.baseRange
|
||||||
);
|
);
|
||||||
if (conflictingDiffs) {
|
if (conflictingDiffs) {
|
||||||
this.resultEdits.removeDiffs(conflictingDiffs, transaction);
|
this.resultTextModelDiffs.removeDiffs(conflictingDiffs, transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEdit(baseRange: ModifiedBaseRange, state: ModifiedBaseRangeState): { edit: LineEdit | undefined; effectiveState: ModifiedBaseRangeState } {
|
const { edit, effectiveState } = getEditForBase(baseRange, state);
|
||||||
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);
|
|
||||||
|
|
||||||
existingState.set(effectiveState, transaction);
|
existingState.set(effectiveState, transaction);
|
||||||
|
|
||||||
if (edit) {
|
if (edit) {
|
||||||
this.resultEdits.applyEditRelativeToOriginal(edit, transaction);
|
this.resultTextModelDiffs.applyEditRelativeToOriginal(edit, transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getResultRange(baseRange: LineRange): LineRange {
|
|
||||||
return this.resultEdits.getResultRange(baseRange);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ResultEdits {
|
function getEditForBase(baseRange: ModifiedBaseRange, state: ModifiedBaseRangeState): { edit: LineEdit | undefined; effectiveState: ModifiedBaseRangeState } {
|
||||||
private readonly barrier = new ReentrancyBarrier();
|
interface LineDiffWithInputNumber {
|
||||||
private readonly onDidChangeEmitter = new Emitter();
|
diff: LineDiff;
|
||||||
public readonly onDidChange = this.onDidChangeEmitter.event;
|
inputNumber: 1 | 2;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
const diffs = new Array<LineDiffWithInputNumber>();
|
||||||
diffs: LineDiff[],
|
if (state.input1) {
|
||||||
private readonly baseTextModel: ITextModel,
|
if (baseRange.input1CombinedDiff) {
|
||||||
private readonly resultTextModel: ITextModel,
|
diffs.push({ diff: baseRange.input1CombinedDiff, inputNumber: 1 });
|
||||||
private readonly _editorWorkerService: IEditorWorkerService
|
}
|
||||||
|
}
|
||||||
|
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));
|
return {
|
||||||
this._diffs.set(diffs, undefined);
|
edit: new LineEdit(
|
||||||
|
firstDiff.diff.originalRange,
|
||||||
resultTextModel.onDidChangeContent(e => {
|
firstDiff.diff
|
||||||
this.barrier.runExclusively(() => {
|
.getLineEdit()
|
||||||
this._editorWorkerService.computeDiff(
|
.newLines.concat(secondDiff.diff.getLineEdit().newLines)
|
||||||
baseTextModel.uri,
|
),
|
||||||
resultTextModel.uri,
|
effectiveState: state,
|
||||||
false,
|
};
|
||||||
1000
|
|
||||||
).then(e => {
|
|
||||||
const diffs =
|
|
||||||
e?.changes.map((c) =>
|
|
||||||
LineDiff.fromLineChange(c, baseTextModel, resultTextModel)
|
|
||||||
) || [];
|
|
||||||
this._diffs.set(diffs, undefined);
|
|
||||||
|
|
||||||
this.onDidChangeEmitter.fire(undefined);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly _diffs = new ObservableValue<LineDiff[]>([], 'diffs');
|
// Technically non-conflicting diffs
|
||||||
|
if (diffs.length === 2 && diffs[0].diff.originalRange.endLineNumberExclusive === diffs[1].diff.originalRange.startLineNumber) {
|
||||||
public readonly diffs: IObservable<readonly LineDiff[]> = this._diffs;
|
return {
|
||||||
|
edit: new LineEdit(
|
||||||
public removeDiffs(diffToRemoves: LineDiff[], transaction: ITransaction | undefined): void {
|
LineRange.join(diffs.map(d => d.diff.originalRange))!,
|
||||||
diffToRemoves.sort(compareBy((d) => d.originalRange.startLineNumber, numberComparator));
|
diffs.flatMap(d => d.diff.getLineEdit().newLines)
|
||||||
diffToRemoves.reverse();
|
),
|
||||||
|
effectiveState: state,
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
return {
|
||||||
* Edit must be conflict free.
|
edit: secondDiff.diff.getLineEdit(),
|
||||||
*/
|
effectiveState: ModifiedBaseRangeState.default.withInputValue(
|
||||||
public applyEditRelativeToOriginal(edit: LineEdit, transaction: ITransaction | undefined): void {
|
secondDiff.inputNumber,
|
||||||
let firstAfter = false;
|
true
|
||||||
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.');
|
function computeState(baseRange: ModifiedBaseRange, conflictingDiffs: LineDiff[]): ModifiedBaseRangeState {
|
||||||
} else if (diff.originalRange.isAfter(edit.range)) {
|
if (conflictingDiffs.length === 0) {
|
||||||
if (!firstAfter) {
|
return ModifiedBaseRangeState.default;
|
||||||
firstAfter = true;
|
}
|
||||||
|
const conflictingEdits = conflictingDiffs.map((d) => d.getLineEdit());
|
||||||
newDiffs.push(new LineDiff(
|
|
||||||
this.baseTextModel,
|
function editsAgreeWithDiffs(diffs: readonly LineDiff[]): boolean {
|
||||||
edit.range,
|
return equals(
|
||||||
this.resultTextModel,
|
conflictingEdits,
|
||||||
new LineRange(edit.range.startLineNumber + delta, edit.newLines.length)
|
diffs.map((d) => d.getLineEdit()),
|
||||||
));
|
(a, b) => a.equals(b)
|
||||||
}
|
);
|
||||||
|
}
|
||||||
newDiffs.push(new LineDiff(
|
|
||||||
diff.originalTextModel,
|
if (editsAgreeWithDiffs(baseRange.input1Diffs)) {
|
||||||
diff.originalRange,
|
return ModifiedBaseRangeState.default.withInput1(true);
|
||||||
diff.modifiedTextModel,
|
}
|
||||||
diff.modifiedRange.delta(edit.newLines.length - edit.range.lineCount)
|
if (editsAgreeWithDiffs(baseRange.input2Diffs)) {
|
||||||
));
|
return ModifiedBaseRangeState.default.withInput2(true);
|
||||||
} else {
|
}
|
||||||
newDiffs.push(diff);
|
|
||||||
}
|
return ModifiedBaseRangeState.conflicting;
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
229
src/vs/workbench/contrib/mergeEditor/browser/textModelDiffs.ts
Normal file
229
src/vs/workbench/contrib/mergeEditor/browser/textModelDiffs.ts
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -544,6 +544,8 @@ registerAction2(class RevealRunningCellAction extends NotebookAction {
|
||||||
super({
|
super({
|
||||||
id: REVEAL_RUNNING_CELL,
|
id: REVEAL_RUNNING_CELL,
|
||||||
title: localize('revealRunningCell', "Go To 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,
|
precondition: NOTEBOOK_HAS_RUNNING_CELL,
|
||||||
menu: [
|
menu: [
|
||||||
{
|
{
|
||||||
|
@ -587,7 +589,7 @@ registerAction2(class RevealRunningCellAction extends NotebookAction {
|
||||||
if (executingCells[0]) {
|
if (executingCells[0]) {
|
||||||
const cell = context.notebookEditor.getCellByHandle(executingCells[0].cellHandle);
|
const cell = context.notebookEditor.getCellByHandle(executingCells[0].cellHandle);
|
||||||
if (cell) {
|
if (cell) {
|
||||||
context.notebookEditor.revealInCenter(cell);
|
context.notebookEditor.focusNotebookCell(cell, 'container');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1459,11 +1459,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
|
||||||
}
|
}
|
||||||
hasPendingChangeContentHeight = true;
|
hasPendingChangeContentHeight = true;
|
||||||
|
|
||||||
DOM.scheduleAtNextAnimationFrame(() => {
|
this._localStore.add(DOM.scheduleAtNextAnimationFrame(() => {
|
||||||
hasPendingChangeContentHeight = false;
|
hasPendingChangeContentHeight = false;
|
||||||
this._updateScrollHeight();
|
this._updateScrollHeight();
|
||||||
this._onDidChangeContentHeight.fire(this._list.getScrollHeight());
|
this._onDidChangeContentHeight.fire(this._list.getScrollHeight());
|
||||||
}, 100);
|
}, 100));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._localStore.add(this._list.onDidRemoveOutputs(outputs => {
|
this._localStore.add(this._list.onDidRemoveOutputs(outputs => {
|
||||||
|
|
|
@ -217,7 +217,7 @@ function updateToEdit(update: ICellExecuteUpdate, cellHandle: number): ICellEdit
|
||||||
if (update.editType === CellExecutionUpdateType.Output) {
|
if (update.editType === CellExecutionUpdateType.Output) {
|
||||||
return {
|
return {
|
||||||
editType: CellEditType.Output,
|
editType: CellEditType.Output,
|
||||||
handle: cellHandle,
|
handle: update.cellHandle,
|
||||||
append: update.append,
|
append: update.append,
|
||||||
outputs: update.outputs,
|
outputs: update.outputs,
|
||||||
};
|
};
|
||||||
|
|
|
@ -164,9 +164,7 @@ export class CellTitleToolbarPart extends CellPart {
|
||||||
|
|
||||||
if (deferredUpdate && !visible) {
|
if (deferredUpdate && !visible) {
|
||||||
this._register(disposableTimeout(() => {
|
this._register(disposableTimeout(() => {
|
||||||
if (deferredUpdate) {
|
deferredUpdate?.();
|
||||||
deferredUpdate();
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
deferredUpdate = undefined;
|
deferredUpdate = undefined;
|
||||||
|
|
|
@ -405,9 +405,7 @@ export class NotebookEditorToolbar extends Disposable {
|
||||||
|
|
||||||
if (deferredUpdate && !visible) {
|
if (deferredUpdate && !visible) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (deferredUpdate) {
|
deferredUpdate?.();
|
||||||
deferredUpdate();
|
|
||||||
}
|
|
||||||
}, 0);
|
}, 0);
|
||||||
deferredUpdate = undefined;
|
deferredUpdate = undefined;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ export enum CellExecutionUpdateType {
|
||||||
|
|
||||||
export interface ICellExecuteOutputEdit {
|
export interface ICellExecuteOutputEdit {
|
||||||
editType: CellExecutionUpdateType.Output;
|
editType: CellExecutionUpdateType.Output;
|
||||||
|
cellHandle: number;
|
||||||
append?: boolean;
|
append?: boolean;
|
||||||
outputs: IOutputDto[];
|
outputs: IOutputDto[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1166,9 +1166,7 @@ export class SettingArrayRenderer extends AbstractSettingRenderer implements ITr
|
||||||
common.toDispose.add(
|
common.toDispose.add(
|
||||||
listWidget.onDidChangeList(e => {
|
listWidget.onDidChangeList(e => {
|
||||||
const newList = this.computeNewList(template, 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);
|
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(
|
common.toDispose.add(
|
||||||
inputBox.onDidChange(e => {
|
inputBox.onDidChange(e => {
|
||||||
if (template.onChange) {
|
template.onChange?.(e);
|
||||||
template.onChange(e);
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
common.toDispose.add(inputBox);
|
common.toDispose.add(inputBox);
|
||||||
inputBox.inputElement.classList.add(AbstractSettingRenderer.CONTROL_CLASS);
|
inputBox.inputElement.classList.add(AbstractSettingRenderer.CONTROL_CLASS);
|
||||||
|
@ -1652,9 +1646,7 @@ export class SettingEnumRenderer extends AbstractSettingRenderer implements ITre
|
||||||
|
|
||||||
common.toDispose.add(
|
common.toDispose.add(
|
||||||
selectBox.onDidSelect(e => {
|
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);
|
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(
|
common.toDispose.add(
|
||||||
inputBox.onDidChange(e => {
|
inputBox.onDidChange(e => {
|
||||||
if (template.onChange) {
|
template.onChange?.(e);
|
||||||
template.onChange(e);
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
common.toDispose.add(inputBox);
|
common.toDispose.add(inputBox);
|
||||||
inputBox.inputElement.classList.add(AbstractSettingRenderer.CONTROL_CLASS);
|
inputBox.inputElement.classList.add(AbstractSettingRenderer.CONTROL_CLASS);
|
||||||
|
|
|
@ -1077,9 +1077,7 @@ export class SearchModel extends Disposable {
|
||||||
progressEmitter.fire();
|
progressEmitter.fire();
|
||||||
this.onSearchProgress(p);
|
this.onSearchProgress(p);
|
||||||
|
|
||||||
if (onProgress) {
|
onProgress?.(p);
|
||||||
onProgress(p);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const dispose = () => tokenSource.dispose();
|
const dispose = () => tokenSource.dispose();
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -26,40 +26,40 @@ const ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE = 'tasks.run.allowAutomatic';
|
||||||
|
|
||||||
export class RunAutomaticTasks extends Disposable implements IWorkbenchContribution {
|
export class RunAutomaticTasks extends Disposable implements IWorkbenchContribution {
|
||||||
constructor(
|
constructor(
|
||||||
@ITaskService private readonly taskService: ITaskService,
|
@ITaskService private readonly _taskService: ITaskService,
|
||||||
@IStorageService private readonly storageService: IStorageService,
|
@IStorageService private readonly _storageService: IStorageService,
|
||||||
@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService,
|
@IWorkspaceTrustManagementService private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService,
|
||||||
@ILogService private readonly logService: ILogService) {
|
@ILogService private readonly _logService: ILogService) {
|
||||||
super();
|
super();
|
||||||
this.tryRunTasks();
|
this._tryRunTasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async tryRunTasks() {
|
private async _tryRunTasks() {
|
||||||
this.logService.trace('RunAutomaticTasks: Trying to run tasks.');
|
this._logService.trace('RunAutomaticTasks: Trying to run tasks.');
|
||||||
// Wait until we have task system info (the extension host and workspace folders are available).
|
// Wait until we have task system info (the extension host and workspace folders are available).
|
||||||
if (!this.taskService.hasTaskSystemInfo) {
|
if (!this._taskService.hasTaskSystemInfo) {
|
||||||
this.logService.trace('RunAutomaticTasks: Awaiting task system info.');
|
this._logService.trace('RunAutomaticTasks: Awaiting task system info.');
|
||||||
await Event.toPromise(Event.once(this.taskService.onDidChangeTaskSystemInfo));
|
await Event.toPromise(Event.once(this._taskService.onDidChangeTaskSystemInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logService.trace('RunAutomaticTasks: Checking if automatic tasks should run.');
|
this._logService.trace('RunAutomaticTasks: Checking if automatic tasks should run.');
|
||||||
const isFolderAutomaticAllowed = this.storageService.getBoolean(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, StorageScope.WORKSPACE, undefined);
|
const isFolderAutomaticAllowed = this._storageService.getBoolean(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, StorageScope.WORKSPACE, undefined);
|
||||||
await this.workspaceTrustManagementService.workspaceTrustInitialized;
|
await this._workspaceTrustManagementService.workspaceTrustInitialized;
|
||||||
const isWorkspaceTrusted = this.workspaceTrustManagementService.isWorkspaceTrusted();
|
const isWorkspaceTrusted = this._workspaceTrustManagementService.isWorkspaceTrusted();
|
||||||
// Only run if allowed. Prompting for permission occurs when a user first tries to run a task.
|
// Only run if allowed. Prompting for permission occurs when a user first tries to run a task.
|
||||||
if (isFolderAutomaticAllowed && isWorkspaceTrusted) {
|
if (isFolderAutomaticAllowed && isWorkspaceTrusted) {
|
||||||
this.taskService.getWorkspaceTasks(TaskRunSource.FolderOpen).then(workspaceTaskResult => {
|
this._taskService.getWorkspaceTasks(TaskRunSource.FolderOpen).then(workspaceTaskResult => {
|
||||||
const { tasks } = RunAutomaticTasks.findAutoTasks(this.taskService, workspaceTaskResult);
|
const { tasks } = RunAutomaticTasks._findAutoTasks(this._taskService, workspaceTaskResult);
|
||||||
this.logService.trace(`RunAutomaticTasks: Found ${tasks.length} automatic tasks tasks`);
|
this._logService.trace(`RunAutomaticTasks: Found ${tasks.length} automatic tasks tasks`);
|
||||||
|
|
||||||
if (tasks.length > 0) {
|
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 => {
|
tasks.forEach(task => {
|
||||||
if (task instanceof Promise) {
|
if (task instanceof Promise) {
|
||||||
task.then(promiseResult => {
|
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);
|
const taskKind = TaskSourceKind.toConfigurationTarget(source.kind);
|
||||||
switch (taskKind) {
|
switch (taskKind) {
|
||||||
case ConfigurationTarget.WORKSPACE_FOLDER: {
|
case ConfigurationTarget.WORKSPACE_FOLDER: {
|
||||||
|
@ -86,7 +86,7 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
|
||||||
return undefined;
|
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 tasks = new Array<Task | Promise<Task | undefined>>();
|
||||||
const taskNames = new Array<string>();
|
const taskNames = new Array<string>();
|
||||||
const locations = new Map<string, URI>();
|
const locations = new Map<string, URI>();
|
||||||
|
@ -98,7 +98,7 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
|
||||||
if (task.runOptions.runOn === RunOnOptions.folderOpen) {
|
if (task.runOptions.runOn === RunOnOptions.folderOpen) {
|
||||||
tasks.push(task);
|
tasks.push(task);
|
||||||
taskNames.push(task._label);
|
taskNames.push(task._label);
|
||||||
const location = RunAutomaticTasks.getTaskSource(task._source);
|
const location = RunAutomaticTasks._getTaskSource(task._source);
|
||||||
if (location) {
|
if (location) {
|
||||||
locations.set(location.fsPath, location);
|
locations.set(location.fsPath, location);
|
||||||
}
|
}
|
||||||
|
@ -116,7 +116,7 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
|
||||||
} else {
|
} else {
|
||||||
taskNames.push(configedTask.value.configures.task);
|
taskNames.push(configedTask.value.configures.task);
|
||||||
}
|
}
|
||||||
const location = RunAutomaticTasks.getTaskSource(configedTask.value._source);
|
const location = RunAutomaticTasks._getTaskSource(configedTask.value._source);
|
||||||
if (location) {
|
if (location) {
|
||||||
locations.set(location.fsPath, location);
|
locations.set(location.fsPath, location);
|
||||||
}
|
}
|
||||||
|
@ -140,18 +140,18 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { tasks, taskNames, locations } = RunAutomaticTasks.findAutoTasks(taskService, workspaceTaskResult);
|
const { tasks, taskNames, locations } = RunAutomaticTasks._findAutoTasks(taskService, workspaceTaskResult);
|
||||||
if (taskNames.length > 0) {
|
if (taskNames.length > 0) {
|
||||||
// We have automatic tasks, prompt to allow.
|
// 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) {
|
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> {
|
openerService: IOpenerService, taskNames: Array<string>, locations: Map<string, URI>): Promise<boolean> {
|
||||||
return new Promise<boolean>(resolve => {
|
return new Promise<boolean>(resolve => {
|
||||||
notificationService.prompt(Severity.Info, nls.localize('tasks.run.allowAutomatic',
|
notificationService.prompt(Severity.Info, nls.localize('tasks.run.allowAutomatic',
|
||||||
|
|
|
@ -56,31 +56,31 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||||
});
|
});
|
||||||
|
|
||||||
export class TaskStatusBarContributions extends Disposable implements IWorkbenchContribution {
|
export class TaskStatusBarContributions extends Disposable implements IWorkbenchContribution {
|
||||||
private runningTasksStatusItem: IStatusbarEntryAccessor | undefined;
|
private _runningTasksStatusItem: IStatusbarEntryAccessor | undefined;
|
||||||
private activeTasksCount: number = 0;
|
private _activeTasksCount: number = 0;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ITaskService private readonly taskService: ITaskService,
|
@ITaskService private readonly _taskService: ITaskService,
|
||||||
@IStatusbarService private readonly statusbarService: IStatusbarService,
|
@IStatusbarService private readonly _statusbarService: IStatusbarService,
|
||||||
@IProgressService private readonly progressService: IProgressService
|
@IProgressService private readonly _progressService: IProgressService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.registerListeners();
|
this._registerListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerListeners(): void {
|
private _registerListeners(): void {
|
||||||
let promise: Promise<void> | undefined = undefined;
|
let promise: Promise<void> | undefined = undefined;
|
||||||
let resolver: (value?: void | Thenable<void>) => void;
|
let resolver: (value?: void | Thenable<void>) => void;
|
||||||
this.taskService.onDidStateChange(event => {
|
this._taskService.onDidStateChange(event => {
|
||||||
if (event.kind === TaskEventKind.Changed) {
|
if (event.kind === TaskEventKind.Changed) {
|
||||||
this.updateRunningTasksStatus();
|
this._updateRunningTasksStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.ignoreEventForUpdateRunningTasksCount(event)) {
|
if (!this._ignoreEventForUpdateRunningTasksCount(event)) {
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
case TaskEventKind.Active:
|
case TaskEventKind.Active:
|
||||||
this.activeTasksCount++;
|
this._activeTasksCount++;
|
||||||
if (this.activeTasksCount === 1) {
|
if (this._activeTasksCount === 1) {
|
||||||
if (!promise) {
|
if (!promise) {
|
||||||
promise = new Promise<void>((resolve) => {
|
promise = new Promise<void>((resolve) => {
|
||||||
resolver = resolve;
|
resolver = resolve;
|
||||||
|
@ -91,9 +91,9 @@ export class TaskStatusBarContributions extends Disposable implements IWorkbench
|
||||||
case TaskEventKind.Inactive:
|
case TaskEventKind.Inactive:
|
||||||
// Since the exiting of the sub process is communicated async we can't order inactive and terminate events.
|
// Since the exiting of the sub process is communicated async we can't order inactive and terminate events.
|
||||||
// So try to treat them accordingly.
|
// So try to treat them accordingly.
|
||||||
if (this.activeTasksCount > 0) {
|
if (this._activeTasksCount > 0) {
|
||||||
this.activeTasksCount--;
|
this._activeTasksCount--;
|
||||||
if (this.activeTasksCount === 0) {
|
if (this._activeTasksCount === 0) {
|
||||||
if (promise && resolver!) {
|
if (promise && resolver!) {
|
||||||
resolver!();
|
resolver!();
|
||||||
}
|
}
|
||||||
|
@ -101,8 +101,8 @@ export class TaskStatusBarContributions extends Disposable implements IWorkbench
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TaskEventKind.Terminated:
|
case TaskEventKind.Terminated:
|
||||||
if (this.activeTasksCount !== 0) {
|
if (this._activeTasksCount !== 0) {
|
||||||
this.activeTasksCount = 0;
|
this._activeTasksCount = 0;
|
||||||
if (promise && resolver!) {
|
if (promise && resolver!) {
|
||||||
resolver!();
|
resolver!();
|
||||||
}
|
}
|
||||||
|
@ -111,8 +111,8 @@ export class TaskStatusBarContributions extends Disposable implements IWorkbench
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (promise && (event.kind === TaskEventKind.Active) && (this.activeTasksCount === 1)) {
|
if (promise && (event.kind === TaskEventKind.Active) && (this._activeTasksCount === 1)) {
|
||||||
this.progressService.withProgress({ location: ProgressLocation.Window, command: 'workbench.action.tasks.showTasks' }, progress => {
|
this._progressService.withProgress({ location: ProgressLocation.Window, command: 'workbench.action.tasks.showTasks' }, progress => {
|
||||||
progress.report({ message: nls.localize('building', 'Building...') });
|
progress.report({ message: nls.localize('building', 'Building...') });
|
||||||
return promise!;
|
return promise!;
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
@ -122,12 +122,12 @@ export class TaskStatusBarContributions extends Disposable implements IWorkbench
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateRunningTasksStatus(): Promise<void> {
|
private async _updateRunningTasksStatus(): Promise<void> {
|
||||||
const tasks = await this.taskService.getActiveTasks();
|
const tasks = await this._taskService.getActiveTasks();
|
||||||
if (tasks.length === 0) {
|
if (tasks.length === 0) {
|
||||||
if (this.runningTasksStatusItem) {
|
if (this._runningTasksStatusItem) {
|
||||||
this.runningTasksStatusItem.dispose();
|
this._runningTasksStatusItem.dispose();
|
||||||
this.runningTasksStatusItem = undefined;
|
this._runningTasksStatusItem = undefined;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const itemProps: IStatusbarEntry = {
|
const itemProps: IStatusbarEntry = {
|
||||||
|
@ -138,16 +138,16 @@ export class TaskStatusBarContributions extends Disposable implements IWorkbench
|
||||||
command: 'workbench.action.tasks.showTasks',
|
command: 'workbench.action.tasks.showTasks',
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!this.runningTasksStatusItem) {
|
if (!this._runningTasksStatusItem) {
|
||||||
this.runningTasksStatusItem = this.statusbarService.addEntry(itemProps, 'status.runningTasks', StatusbarAlignment.LEFT, 49 /* Medium Priority, next to Markers */);
|
this._runningTasksStatusItem = this._statusbarService.addEntry(itemProps, 'status.runningTasks', StatusbarAlignment.LEFT, 49 /* Medium Priority, next to Markers */);
|
||||||
} else {
|
} else {
|
||||||
this.runningTasksStatusItem.update(itemProps);
|
this._runningTasksStatusItem.update(itemProps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ignoreEventForUpdateRunningTasksCount(event: ITaskEvent): boolean {
|
private _ignoreEventForUpdateRunningTasksCount(event: ITaskEvent): boolean {
|
||||||
if (!this.taskService.inTerminal()) {
|
if (!this._taskService.inTerminal()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.'));
|
const removeTaskIcon = registerIcon('tasks-remove', Codicon.close, nls.localize('removeTaskIcon', 'Icon for remove in the tasks selection list.'));
|
||||||
|
|
||||||
export class TaskQuickPick extends Disposable {
|
export class TaskQuickPick extends Disposable {
|
||||||
private sorter: TaskSorter;
|
private _sorter: TaskSorter;
|
||||||
private topLevelEntries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[] | undefined;
|
private _topLevelEntries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[] | undefined;
|
||||||
constructor(
|
constructor(
|
||||||
private taskService: ITaskService,
|
private _taskService: ITaskService,
|
||||||
private configurationService: IConfigurationService,
|
private _configurationService: IConfigurationService,
|
||||||
private quickInputService: IQuickInputService,
|
private _quickInputService: IQuickInputService,
|
||||||
private notificationService: INotificationService,
|
private _notificationService: INotificationService,
|
||||||
private dialogService: IDialogService) {
|
private _dialogService: IDialogService) {
|
||||||
super();
|
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
|
// 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) {
|
if (task._label) {
|
||||||
return task._label;
|
return task._label;
|
||||||
}
|
}
|
||||||
|
@ -74,21 +74,21 @@ export class TaskQuickPick extends Disposable {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
private createTaskEntry(task: Task | ConfiguringTask, extraButtons: IQuickInputButton[] = []): ITaskTwoLevelQuickPickEntry {
|
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 };
|
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];
|
entry.buttons = [{ iconClass: ThemeIcon.asClassName(configureTaskIcon), tooltip: nls.localize('configureTask', "Configure Task") }, ...extraButtons];
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createEntriesForGroup(entries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[], tasks: (Task | ConfiguringTask)[],
|
private _createEntriesForGroup(entries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[], tasks: (Task | ConfiguringTask)[],
|
||||||
groupLabel: string, extraButtons: IQuickInputButton[] = []) {
|
groupLabel: string, extraButtons: IQuickInputButton[] = []) {
|
||||||
entries.push({ type: 'separator', label: groupLabel });
|
entries.push({ type: 'separator', label: groupLabel });
|
||||||
tasks.forEach(task => {
|
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") });
|
entries.push({ type: 'separator', label: nls.localize('contributedTasks', "contributed") });
|
||||||
types.forEach(type => {
|
types.forEach(type => {
|
||||||
entries.push({ label: `$(folder) ${type}`, task: type, ariaLabel: nls.localize('taskType', "All {0} tasks", 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 });
|
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)[] = [];
|
const tasks: (Task | ConfiguringTask)[] = [];
|
||||||
Array.from(result).forEach(([key, folderTasks]) => {
|
Array.from(result).forEach(([key, folderTasks]) => {
|
||||||
if (folderTasks.set) {
|
if (folderTasks.set) {
|
||||||
|
@ -111,7 +111,7 @@ export class TaskQuickPick extends Disposable {
|
||||||
return tasks;
|
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)[] = [];
|
let dedupedConfiguredTasks: (Task | ConfiguringTask)[] = [];
|
||||||
const foundRecentTasks: boolean[] = Array(recentTasks.length).fill(false);
|
const foundRecentTasks: boolean[] = Array(recentTasks.length).fill(false);
|
||||||
for (let j = 0; j < configuredTasks.length; j++) {
|
for (let j = 0; j < configuredTasks.length; j++) {
|
||||||
|
@ -132,7 +132,7 @@ export class TaskQuickPick extends Disposable {
|
||||||
foundRecentTasks[findIndex] = true;
|
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)[] = [];
|
const prunedRecentTasks: (Task | ConfiguringTask)[] = [];
|
||||||
for (let i = 0; i < recentTasks.length; i++) {
|
for (let i = 0; i < recentTasks.length; i++) {
|
||||||
if (foundRecentTasks[i] || ConfiguringTask.is(recentTasks[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 }> {
|
public async getTopLevelEntries(defaultEntry?: ITaskQuickPickEntry): Promise<{ entries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[]; isSingleConfigured?: Task | ConfiguringTask }> {
|
||||||
if (this.topLevelEntries !== undefined) {
|
if (this._topLevelEntries !== undefined) {
|
||||||
return { entries: this.topLevelEntries };
|
return { entries: this._topLevelEntries };
|
||||||
}
|
}
|
||||||
let recentTasks: (Task | ConfiguringTask)[] = (await this.taskService.readRecentTasks()).reverse();
|
let recentTasks: (Task | ConfiguringTask)[] = (await this._taskService.readRecentTasks()).reverse();
|
||||||
const configuredTasks: (Task | ConfiguringTask)[] = this.handleFolderTaskResult(await this.taskService.getWorkspaceTasks());
|
const configuredTasks: (Task | ConfiguringTask)[] = this._handleFolderTaskResult(await this._taskService.getWorkspaceTasks());
|
||||||
const extensionTaskTypes = this.taskService.taskTypes();
|
const extensionTaskTypes = this._taskService.taskTypes();
|
||||||
this.topLevelEntries = [];
|
this._topLevelEntries = [];
|
||||||
// Dedupe will update recent tasks if they've changed in tasks.json.
|
// 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;
|
const dedupedConfiguredTasks: (Task | ConfiguringTask)[] = dedupeAndPrune.configuredTasks;
|
||||||
recentTasks = dedupeAndPrune.recentTasks;
|
recentTasks = dedupeAndPrune.recentTasks;
|
||||||
if (recentTasks.length > 0) {
|
if (recentTasks.length > 0) {
|
||||||
|
@ -159,34 +159,34 @@ export class TaskQuickPick extends Disposable {
|
||||||
iconClass: ThemeIcon.asClassName(removeTaskIcon),
|
iconClass: ThemeIcon.asClassName(removeTaskIcon),
|
||||||
tooltip: nls.localize('removeRecent', 'Remove Recently Used Task')
|
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 (configuredTasks.length > 0) {
|
||||||
if (dedupedConfiguredTasks.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)) {
|
if (defaultEntry && (configuredTasks.length === 0)) {
|
||||||
this.topLevelEntries.push({ type: 'separator', label: nls.localize('configured', 'configured') });
|
this._topLevelEntries.push({ type: 'separator', label: nls.localize('configured', 'configured') });
|
||||||
this.topLevelEntries.push(defaultEntry);
|
this._topLevelEntries.push(defaultEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extensionTaskTypes.length > 0) {
|
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) {
|
public async handleSettingOption(selectedType: string) {
|
||||||
const noButton = nls.localize('TaskQuickPick.changeSettingNo', "No");
|
const noButton = nls.localize('TaskQuickPick.changeSettingNo', "No");
|
||||||
const yesButton = nls.localize('TaskQuickPick.changeSettingYes', "Yes");
|
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',
|
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),
|
"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]);
|
[noButton, yesButton]);
|
||||||
if (changeSettingResult.choice === 1) {
|
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));
|
await new Promise<void>(resolve => setTimeout(() => resolve(), 100));
|
||||||
return this.show(nls.localize('TaskService.pickRunTask', 'Select the task to run'), undefined, selectedType);
|
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> {
|
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.placeholder = placeHolder;
|
||||||
picker.matchOnDescription = true;
|
picker.matchOnDescription = true;
|
||||||
picker.ignoreFocusOut = false;
|
picker.ignoreFocusOut = false;
|
||||||
|
@ -205,25 +205,25 @@ export class TaskQuickPick extends Disposable {
|
||||||
if (context.button.iconClass === ThemeIcon.asClassName(removeTaskIcon)) {
|
if (context.button.iconClass === ThemeIcon.asClassName(removeTaskIcon)) {
|
||||||
const key = (task && !Types.isString(task)) ? task.getRecentlyUsedKey() : undefined;
|
const key = (task && !Types.isString(task)) ? task.getRecentlyUsedKey() : undefined;
|
||||||
if (key) {
|
if (key) {
|
||||||
this.taskService.removeRecentlyUsedTask(key);
|
this._taskService.removeRecentlyUsedTask(key);
|
||||||
}
|
}
|
||||||
const indexToRemove = picker.items.indexOf(context.item);
|
const indexToRemove = picker.items.indexOf(context.item);
|
||||||
if (indexToRemove >= 0) {
|
if (indexToRemove >= 0) {
|
||||||
picker.items = [...picker.items.slice(0, indexToRemove), ...picker.items.slice(indexToRemove + 1)];
|
picker.items = [...picker.items.slice(0, indexToRemove), ...picker.items.slice(indexToRemove + 1)];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.quickInputService.cancel();
|
this._quickInputService.cancel();
|
||||||
if (ContributedTask.is(task)) {
|
if (ContributedTask.is(task)) {
|
||||||
this.taskService.customize(task, undefined, true);
|
this._taskService.customize(task, undefined, true);
|
||||||
} else if (CustomTask.is(task) || ConfiguringTask.is(task)) {
|
} else if (CustomTask.is(task) || ConfiguringTask.is(task)) {
|
||||||
let canOpenConfig: boolean = false;
|
let canOpenConfig: boolean = false;
|
||||||
try {
|
try {
|
||||||
canOpenConfig = await this.taskService.openConfig(task);
|
canOpenConfig = await this._taskService.openConfig(task);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// do nothing.
|
// do nothing.
|
||||||
}
|
}
|
||||||
if (!canOpenConfig) {
|
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) {
|
if (!firstLevelTask) {
|
||||||
// First show recent tasks configured tasks. Other tasks will be available at a second level
|
// First show recent tasks configured tasks. Other tasks will be available at a second level
|
||||||
const topLevelEntriesResult = await this.getTopLevelEntries(defaultEntry);
|
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();
|
picker.dispose();
|
||||||
return this.toTask(topLevelEntriesResult.isSingleConfigured);
|
return this._toTask(topLevelEntriesResult.isSingleConfigured);
|
||||||
}
|
}
|
||||||
const taskQuickPickEntries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[] = topLevelEntriesResult.entries;
|
const taskQuickPickEntries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[] = topLevelEntriesResult.entries;
|
||||||
firstLevelTask = await this.doPickerFirstLevel(picker, taskQuickPickEntries);
|
firstLevelTask = await this._doPickerFirstLevel(picker, taskQuickPickEntries);
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
if (Types.isString(firstLevelTask)) {
|
if (Types.isString(firstLevelTask)) {
|
||||||
// Proceed to second level of quick pick
|
// 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) {
|
if (selectedEntry && !selectedEntry.settingType && selectedEntry.task === null) {
|
||||||
// The user has chosen to go back to the first level
|
// 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)) {
|
} else if (selectedEntry && Types.isString(selectedEntry.settingType)) {
|
||||||
picker.dispose();
|
picker.dispose();
|
||||||
return this.handleSettingOption(selectedEntry.settingType);
|
return this.handleSettingOption(selectedEntry.settingType);
|
||||||
} else {
|
} else {
|
||||||
picker.dispose();
|
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) {
|
} else if (firstLevelTask) {
|
||||||
picker.dispose();
|
picker.dispose();
|
||||||
return this.toTask(firstLevelTask);
|
return this._toTask(firstLevelTask);
|
||||||
} else {
|
} else {
|
||||||
picker.dispose();
|
picker.dispose();
|
||||||
return firstLevelTask;
|
return firstLevelTask;
|
||||||
|
@ -265,7 +265,7 @@ export class TaskQuickPick extends Disposable {
|
||||||
return;
|
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;
|
picker.items = taskQuickPickEntries;
|
||||||
const firstLevelPickerResult = await new Promise<ITaskTwoLevelQuickPickEntry | undefined | null>(resolve => {
|
const firstLevelPickerResult = await new Promise<ITaskTwoLevelQuickPickEntry | undefined | null>(resolve => {
|
||||||
Event.once(picker.onDidAccept)(async () => {
|
Event.once(picker.onDidAccept)(async () => {
|
||||||
|
@ -275,15 +275,15 @@ export class TaskQuickPick extends Disposable {
|
||||||
return firstLevelPickerResult?.task;
|
return firstLevelPickerResult?.task;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async doPickerSecondLevel(picker: IQuickPick<ITaskTwoLevelQuickPickEntry>, type: string) {
|
private async _doPickerSecondLevel(picker: IQuickPick<ITaskTwoLevelQuickPickEntry>, type: string) {
|
||||||
picker.busy = true;
|
picker.busy = true;
|
||||||
if (type === SHOW_ALL) {
|
if (type === SHOW_ALL) {
|
||||||
const items = (await this.taskService.tasks()).sort((a, b) => this.sorter.compare(a, b)).map(task => this.createTaskEntry(task));
|
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));
|
items.push(...TaskQuickPick.allSettingEntries(this._configurationService));
|
||||||
picker.items = items;
|
picker.items = items;
|
||||||
} else {
|
} else {
|
||||||
picker.value = '';
|
picker.value = '';
|
||||||
picker.items = await this.getEntriesForProvider(type);
|
picker.items = await this._getEntriesForProvider(type);
|
||||||
}
|
}
|
||||||
picker.busy = false;
|
picker.busy = false;
|
||||||
const secondLevelPickerResult = await new Promise<ITaskTwoLevelQuickPickEntry | undefined | null>(resolve => {
|
const secondLevelPickerResult = await new Promise<ITaskTwoLevelQuickPickEntry | undefined | null>(resolve => {
|
||||||
|
@ -325,11 +325,11 @@ export class TaskQuickPick extends Disposable {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getEntriesForProvider(type: string): Promise<QuickPickInput<ITaskTwoLevelQuickPickEntry>[]> {
|
private async _getEntriesForProvider(type: string): Promise<QuickPickInput<ITaskTwoLevelQuickPickEntry>[]> {
|
||||||
const tasks = (await this.taskService.tasks({ type })).sort((a, b) => this.sorter.compare(a, b));
|
const tasks = (await this._taskService.tasks({ type })).sort((a, b) => this._sorter.compare(a, b));
|
||||||
let taskQuickPickEntries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[];
|
let taskQuickPickEntries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[];
|
||||||
if (tasks.length > 0) {
|
if (tasks.length > 0) {
|
||||||
taskQuickPickEntries = tasks.map(task => this.createTaskEntry(task));
|
taskQuickPickEntries = tasks.map(task => this._createTaskEntry(task));
|
||||||
taskQuickPickEntries.push({
|
taskQuickPickEntries.push({
|
||||||
type: 'separator'
|
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) {
|
if (settingEntry) {
|
||||||
taskQuickPickEntries.push(settingEntry);
|
taskQuickPickEntries.push(settingEntry);
|
||||||
}
|
}
|
||||||
return taskQuickPickEntries;
|
return taskQuickPickEntries;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async toTask(task: Task | ConfiguringTask): Promise<Task | undefined> {
|
private async _toTask(task: Task | ConfiguringTask): Promise<Task | undefined> {
|
||||||
if (!ConfiguringTask.is(task)) {
|
if (!ConfiguringTask.is(task)) {
|
||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolvedTask = await this.taskService.tryResolveTask(task);
|
const resolvedTask = await this._taskService.tryResolveTask(task);
|
||||||
|
|
||||||
if (!resolvedTask) {
|
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;
|
return resolvedTask;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,12 +14,12 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||||
export class TaskService extends AbstractTaskService {
|
export class TaskService extends AbstractTaskService {
|
||||||
private static readonly ProcessTaskSystemSupportMessage = nls.localize('taskService.processTaskSystem', 'Process task system is not support in the web.');
|
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) {
|
if (this._taskSystem) {
|
||||||
return this._taskSystem;
|
return this._taskSystem;
|
||||||
}
|
}
|
||||||
if (this.executionEngine === ExecutionEngine.Terminal) {
|
if (this.executionEngine === ExecutionEngine.Terminal) {
|
||||||
this._taskSystem = this.createTerminalTaskSystem();
|
this._taskSystem = this._createTerminalTaskSystem();
|
||||||
} else {
|
} else {
|
||||||
throw new Error(TaskService.ProcessTaskSystemSupportMessage);
|
throw new Error(TaskService.ProcessTaskSystemSupportMessage);
|
||||||
}
|
}
|
||||||
|
@ -32,11 +32,11 @@ export class TaskService extends AbstractTaskService {
|
||||||
return this._taskSystem!;
|
return this._taskSystem!;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected computeLegacyConfiguration(workspaceFolder: IWorkspaceFolder): Promise<IWorkspaceFolderConfigurationResult> {
|
protected _computeLegacyConfiguration(workspaceFolder: IWorkspaceFolder): Promise<IWorkspaceFolderConfigurationResult> {
|
||||||
throw new Error(TaskService.ProcessTaskSystemSupportMessage);
|
throw new Error(TaskService.ProcessTaskSystemSupportMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected versionAndEngineCompatible(filter?: ITaskFilter): boolean {
|
protected _versionAndEngineCompatible(filter?: ITaskFilter): boolean {
|
||||||
return this.executionEngine === ExecutionEngine.Terminal;
|
return this.executionEngine === ExecutionEngine.Terminal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,11 +24,11 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider<IPickerQ
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@IExtensionService extensionService: IExtensionService,
|
@IExtensionService extensionService: IExtensionService,
|
||||||
@ITaskService private taskService: ITaskService,
|
@ITaskService private _taskService: ITaskService,
|
||||||
@IConfigurationService private configurationService: IConfigurationService,
|
@IConfigurationService private _configurationService: IConfigurationService,
|
||||||
@IQuickInputService private quickInputService: IQuickInputService,
|
@IQuickInputService private _quickInputService: IQuickInputService,
|
||||||
@INotificationService private notificationService: INotificationService,
|
@INotificationService private _notificationService: INotificationService,
|
||||||
@IDialogService private dialogService: IDialogService
|
@IDialogService private _dialogService: IDialogService
|
||||||
) {
|
) {
|
||||||
super(TasksQuickAccessProvider.PREFIX, {
|
super(TasksQuickAccessProvider.PREFIX, {
|
||||||
noResultsPick: {
|
noResultsPick: {
|
||||||
|
@ -42,7 +42,7 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider<IPickerQ
|
||||||
return [];
|
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 topLevelPicks = await taskQuickPick.getTopLevelEntries();
|
||||||
const taskPicks: Array<IPickerQuickAccessItem | IQuickPickSeparator> = [];
|
const taskPicks: Array<IPickerQuickAccessItem | IQuickPickSeparator> = [];
|
||||||
|
|
||||||
|
@ -63,14 +63,14 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider<IPickerQ
|
||||||
if ((index === 1) && (quickAccessEntry.buttons?.length === 2)) {
|
if ((index === 1) && (quickAccessEntry.buttons?.length === 2)) {
|
||||||
const key = (task && !isString(task)) ? task.getRecentlyUsedKey() : undefined;
|
const key = (task && !isString(task)) ? task.getRecentlyUsedKey() : undefined;
|
||||||
if (key) {
|
if (key) {
|
||||||
this.taskService.removeRecentlyUsedTask(key);
|
this._taskService.removeRecentlyUsedTask(key);
|
||||||
}
|
}
|
||||||
return TriggerAction.REFRESH_PICKER;
|
return TriggerAction.REFRESH_PICKER;
|
||||||
} else {
|
} else {
|
||||||
if (ContributedTask.is(task)) {
|
if (ContributedTask.is(task)) {
|
||||||
this.taskService.customize(task, undefined, true);
|
this._taskService.customize(task, undefined, true);
|
||||||
} else if (CustomTask.is(task)) {
|
} else if (CustomTask.is(task)) {
|
||||||
this.taskService.openConfig(task);
|
this._taskService.openConfig(task);
|
||||||
}
|
}
|
||||||
return TriggerAction.CLOSE_PICKER;
|
return TriggerAction.CLOSE_PICKER;
|
||||||
}
|
}
|
||||||
|
@ -80,10 +80,10 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider<IPickerQ
|
||||||
// switch to quick pick and show second level
|
// switch to quick pick and show second level
|
||||||
const showResult = await taskQuickPick.show(localize('TaskService.pickRunTask', 'Select the task to run'), undefined, task);
|
const showResult = await taskQuickPick.show(localize('TaskService.pickRunTask', 'Select the task to run'), undefined, task);
|
||||||
if (showResult) {
|
if (showResult) {
|
||||||
this.taskService.run(showResult, { attachProblemMatcher: true });
|
this._taskService.run(showResult, { attachProblemMatcher: true });
|
||||||
}
|
}
|
||||||
} else {
|
} 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;
|
return taskPicks;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async toTask(task: Task | ConfiguringTask): Promise<Task | undefined> {
|
private async _toTask(task: Task | ConfiguringTask): Promise<Task | undefined> {
|
||||||
if (!ConfiguringTask.is(task)) {
|
if (!ConfiguringTask.is(task)) {
|
||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.taskService.tryResolveTask(task);
|
return this._taskService.tryResolveTask(task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -119,11 +119,11 @@ export class TaskService extends AbstractTaskService {
|
||||||
this._register(lifecycleService.onBeforeShutdown(event => event.veto(this.beforeShutdown(), 'veto.tasks')));
|
this._register(lifecycleService.onBeforeShutdown(event => event.veto(this.beforeShutdown(), 'veto.tasks')));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getTaskSystem(): ITaskSystem {
|
protected _getTaskSystem(): ITaskSystem {
|
||||||
if (this._taskSystem) {
|
if (this._taskSystem) {
|
||||||
return this._taskSystem;
|
return this._taskSystem;
|
||||||
}
|
}
|
||||||
this._taskSystem = this.createTerminalTaskSystem();
|
this._taskSystem = this._createTerminalTaskSystem();
|
||||||
this._taskSystemListener = this._taskSystem!.onDidStateChange((event) => {
|
this._taskSystemListener = this._taskSystem!.onDidStateChange((event) => {
|
||||||
if (this._taskSystem) {
|
if (this._taskSystem) {
|
||||||
this._taskRunningState.set(this._taskSystem.isActiveSync());
|
this._taskRunningState.set(this._taskSystem.isActiveSync());
|
||||||
|
@ -133,8 +133,8 @@ export class TaskService extends AbstractTaskService {
|
||||||
return this._taskSystem;
|
return this._taskSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected computeLegacyConfiguration(workspaceFolder: IWorkspaceFolder): Promise<IWorkspaceFolderConfigurationResult> {
|
protected _computeLegacyConfiguration(workspaceFolder: IWorkspaceFolder): Promise<IWorkspaceFolderConfigurationResult> {
|
||||||
const { config, hasParseErrors } = this.getConfiguration(workspaceFolder);
|
const { config, hasParseErrors } = this._getConfiguration(workspaceFolder);
|
||||||
if (hasParseErrors) {
|
if (hasParseErrors) {
|
||||||
return Promise.resolve({ workspaceFolder: workspaceFolder, hasErrors: true, config: undefined });
|
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 range = filter && filter.version ? filter.version : undefined;
|
||||||
const engine = this.executionEngine;
|
const engine = this.executionEngine;
|
||||||
|
|
||||||
|
@ -169,7 +169,7 @@ export class TaskService extends AbstractTaskService {
|
||||||
if (this._taskSystem.canAutoTerminate()) {
|
if (this._taskSystem.canAutoTerminate()) {
|
||||||
terminatePromise = Promise.resolve({ confirmed: true });
|
terminatePromise = Promise.resolve({ confirmed: true });
|
||||||
} else {
|
} 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?'),
|
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"),
|
primaryButton: nls.localize({ key: 'TaskSystem.terminateTask', comment: ['&& denotes a mnemonic'] }, "&&Terminate Task"),
|
||||||
type: 'question'
|
type: 'question'
|
||||||
|
@ -191,10 +191,10 @@ export class TaskService extends AbstractTaskService {
|
||||||
}
|
}
|
||||||
if (success) {
|
if (success) {
|
||||||
this._taskSystem = undefined;
|
this._taskSystem = undefined;
|
||||||
this.disposeTaskSystemListeners();
|
this._disposeTaskSystemListeners();
|
||||||
return false; // no veto
|
return false; // no veto
|
||||||
} else if (code && code === TerminateResponseCode.ProcessNotFound) {
|
} 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.'),
|
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"),
|
primaryButton: nls.localize({ key: 'TaskSystem.exitAnyways', comment: ['&& denotes a mnemonic'] }, "&&Exit Anyways"),
|
||||||
type: 'info'
|
type: 'info'
|
||||||
|
|
|
@ -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.
|
* 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
|
* 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.
|
* 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
|
* 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.
|
* 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
|
* 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.
|
* 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.
|
* A link whose text is a valid URI.
|
||||||
*/
|
*/
|
||||||
Url
|
Url = 'Url'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITerminalExternalLinkType {
|
export interface ITerminalExternalLinkType {
|
||||||
|
|
|
@ -86,7 +86,7 @@ export class TerminalLinkManager extends DisposableStore {
|
||||||
this._openers.set(TerminalBuiltinLinkType.LocalFile, localFileOpener);
|
this._openers.set(TerminalBuiltinLinkType.LocalFile, localFileOpener);
|
||||||
this._openers.set(TerminalBuiltinLinkType.LocalFolderInWorkspace, localFolderInWorkspaceOpener);
|
this._openers.set(TerminalBuiltinLinkType.LocalFolderInWorkspace, localFolderInWorkspaceOpener);
|
||||||
this._openers.set(TerminalBuiltinLinkType.LocalFolderOutsideWorkspace, this._instantiationService.createInstance(TerminalLocalFolderOutsideWorkspaceLinkOpener));
|
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._openers.set(TerminalBuiltinLinkType.Url, this._instantiationService.createInstance(TerminalUrlLinkOpener, !!this._processManager.remoteAuthority));
|
||||||
|
|
||||||
this._registerStandardLinkProviders();
|
this._registerStandardLinkProviders();
|
||||||
|
|
|
@ -125,6 +125,7 @@ export class TerminalSearchLinkOpener implements ITerminalLinkOpener {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _capabilities: ITerminalCapabilityStore,
|
private readonly _capabilities: ITerminalCapabilityStore,
|
||||||
|
private readonly _initialCwd: Promise<string>,
|
||||||
private readonly _localFileOpener: TerminalLocalFileLinkOpener,
|
private readonly _localFileOpener: TerminalLocalFileLinkOpener,
|
||||||
private readonly _localFolderInWorkspaceOpener: TerminalLocalFolderInWorkspaceLinkOpener,
|
private readonly _localFolderInWorkspaceOpener: TerminalLocalFolderInWorkspaceLinkOpener,
|
||||||
private readonly _os: OperatingSystem,
|
private readonly _os: OperatingSystem,
|
||||||
|
@ -182,10 +183,20 @@ export class TerminalSearchLinkOpener implements ITerminalLinkOpener {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _getExactMatch(sanitizedLink: string): Promise<IResourceMatch | undefined> {
|
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;
|
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 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 });
|
const uri = URI.from({ scheme, path: slashNormalizedPath });
|
||||||
try {
|
try {
|
||||||
const fileStat = await this._fileService.stat(uri);
|
const fileStat = await this._fileService.stat(uri);
|
||||||
|
@ -194,6 +205,8 @@ export class TerminalSearchLinkOpener implements ITerminalLinkOpener {
|
||||||
// File or dir doesn't exist, continue on
|
// 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) {
|
if (!resourceMatch) {
|
||||||
const results = await this._searchService.fileSearch(
|
const results = await this._searchService.fileSearch(
|
||||||
this._fileQueryBuilder.file(this._workspaceContextService.getWorkspace().folders, {
|
this._fileQueryBuilder.file(this._workspaceContextService.getWorkspace().folders, {
|
||||||
|
|
|
@ -669,6 +669,12 @@ export interface ITerminalInstance {
|
||||||
*/
|
*/
|
||||||
clearSelection(): void;
|
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.
|
* Select all text in the terminal.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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[] {
|
private _getActiveTerminalEditors(): EditorInput[] {
|
||||||
|
|
|
@ -385,7 +385,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||||
});
|
});
|
||||||
this._fixedRows = _shellLaunchConfig.attachPersistentProcess?.fixedDimensions?.rows;
|
this._fixedRows = _shellLaunchConfig.attachPersistentProcess?.fixedDimensions?.rows;
|
||||||
this._fixedCols = _shellLaunchConfig.attachPersistentProcess?.fixedDimensions?.cols;
|
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
|
// 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);
|
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
|
// 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
|
// 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
|
// 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) {
|
if (!this.shellLaunchConfig.executable && !workbenchEnvironmentService.remoteAuthority) {
|
||||||
this._terminalProfileResolverService.resolveIcon(this._shellLaunchConfig, OS);
|
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
|
// 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.
|
// 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) {
|
private _initDragAndDrop(container: HTMLElement) {
|
||||||
this._dndObserver?.dispose();
|
this._dndObserver?.dispose();
|
||||||
const dndController = this._instantiationService.createInstance(TerminalInstanceDragAndDropController, container);
|
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);
|
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 => {
|
await this._processManager.createProcess(this._shellLaunchConfig, this._cols || Constants.DefaultCols, this._rows || Constants.DefaultRows, this._accessibilityService.isScreenReaderOptimized()).then(error => {
|
||||||
if (error) {
|
if (error) {
|
||||||
this._onProcessExit(error, error.code === ShellIntegrationExitCode);
|
this._onProcessExit(error, error.code === ShellIntegrationExitCode);
|
||||||
|
@ -1560,7 +1564,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||||
if (this.xterm?.shellIntegration) {
|
if (this.xterm?.shellIntegration) {
|
||||||
this.capabilities.add(this.xterm?.shellIntegration.capabilities);
|
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);
|
this._onIconChanged.fire(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue