Repository: vscode
Issue: Consider merging multiple diffs into one
Labels: feature-request,under-discussion,merge-editor

Testing #150389

If multiple diffs are one after the other they are currently rendered as separate (because there is a line with a whitespace). 
Could we more aggressively combine diffs. Because the current approach might be overly complicated for users

<details>
<summary>Example repro case</summary>

1. VS Code repo, git checkout `15e4ae374b6` (some commit from 8 days ago)
2. Do the following changes to `extensionHostStarter.ts` and commit
3. git pull - you should get latest and conflicts in extensionHostStarter


![Screenshot 2022-05-30 at 19 50 56](https://user-images.githubusercontent.com/1926584/171195622-48b50a0d-9dde-4a28-9ac1-003f02e91532.png)

```
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import { canceled, SerializedError, transformErrorForSerialization } from 'vs/base/common/errors';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter';
import { Emitter, Event } from 'vs/base/common/event';
import { ILogService } from 'vs/platform/log/common/log';
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { StopWatch } from 'vs/base/common/stopwatch';
import { ChildProcess, fork } from 'child_process';
import { StringDecoder } from 'string_decoder';
import { Promises, timeout } from 'vs/base/common/async';
import { FileAccess } from 'vs/base/common/network';
import { mixin } from 'vs/base/common/objects';
import * as platform from 'vs/base/common/platform';
import { cwd } from 'vs/base/common/process';

export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter {
	_serviceBrand: undefined;

	private static _lastId: number = 0;

	protected readonly _extHosts: Map<string, ExtensionHostProcess>;
	private _shutdown = false;

	constructor(
		@ILogService private readonly _logService: ILogService,
		@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService
	) {
		this._extHosts = new Map<string, ExtensionHostProcess>();

		// On shutdown: gracefully await extension host shutdowns
		this.lifecycleMainService.onWillShutdown((e) => {
			this._shutdown = true;
			e.join(this._waitForAllExit(6000));
		});
	}

	dispose(): void {
		// Intentionally not killing the extension host processes
	}

	private _getExtHost(id: string): ExtensionHostProcess {
		const extHostProcess = this._extHosts.get(id);
		if (!extHostProcess) {
			throw new Error(`Unknown extension host!`);
		}
		return extHostProcess;
	}

	onDynamicStdout(id: string): Event<string> {
		return this._getExtHost(id).onStdout;
	}

	onDynamicStderr(id: string): Event<string> {
		return this._getExtHost(id).onStderr;
	}

	onDynamicMessage(id: string): Event<any> {
		return this._getExtHost(id).onMessage;
	}

	onDynamicError(id: string): Event<{ error: SerializedError }> {
		return this._getExtHost(id).onError;
	}

	onDynamicExit(id: string): Event<{ code: number; signal: string }> {
		return this._getExtHost(id).onExit;
	}

	async createExtensionHost(): Promise<{ id: string }> {
		if (this._shutdown) {
			throw canceled();
		}
		const id = String(++ExtensionHostStarter._lastId);
		const extHost = new ExtensionHostProcess(id, this._logService);
		this._extHosts.set(id, extHost);
		extHost.onExit(({ pid, code, signal }) => {
			this._logService.info(`Extension host with pid ${pid} exited with code: ${code}, signal: ${signal}.`);
			setTimeout(() => {
				extHost.dispose();
				this._extHosts.delete(id);
			});
		});
		return { id };
	}

	async start(id: string, opts: IExtensionHostProcessOptions): Promise<{ pid: number }> {
		if (this._shutdown) {
			throw canceled();
		}
		return this._getExtHost(id).start(opts);
	}

	async enableInspectPort(id: string): Promise<boolean> {
		if (this._shutdown) {
			throw canceled();
		}
		const extHostProcess = this._extHosts.get(id);
		if (!extHostProcess) {
			return false;
		}
		return extHostProcess.enableInspectPort();
	}

	async kill(id: string): Promise<void> {
		if (this._shutdown) {
			throw canceled();
		}
		const extHostProcess = this._extHosts.get(id);
		if (!extHostProcess) {
			// already gone!
			return;
		}
		extHostProcess.kill();
	}

	async _killAllNow(): Promise<void> {
		for (const [, extHost] of this._extHosts) {
			extHost.kill();
		}
	}

	async _waitForAllExit(maxWaitTimeMs: number): Promise<void> {
		const exitPromises: Promise<void>[] = [];
		for (const [, extHost] of this._extHosts) {
			exitPromises.push(extHost.waitForExit(maxWaitTimeMs));
		}
		return Promises.settled(exitPromises).then(() => { });
	}
}

class ExtensionHostProcess extends Disposable {

	readonly _onStdout = this._register(new Emitter<string>());
	readonly onStdout = this._onStdout.event;

	readonly _onStderr = this._register(new Emitter<string>());
	readonly onStderr = this._onStderr.event;

	readonly _onMessage = this._register(new Emitter<any>());
	readonly onMessage = this._onMessage.event;

	readonly _onError = this._register(new Emitter<{ error: SerializedError }>());
	readonly onError = this._onError.event;

	readonly _onExit = this._register(new Emitter<{ pid: number; code: number; signal: string }>());
	readonly onExit = this._onExit.event;

	private _process: ChildProcess | null = null;
	private _hasExited: boolean = false;

	constructor(
		public readonly id: string,
		@ILogService private readonly _logService: ILogService,
	) {
		super();
	}

	start(opts: IExtensionHostProcessOptions): { pid: number } {
		if (this._process) {
			return {} as any;
		}
		// if (platform.isCI) {
		// 	this._logService.info(`Calling fork to start extension host...`);
		// }
		// const sw = StopWatch.create(false);
		// this._process = fork(
		// 	FileAccess.asFileUri('bootstrap-fork', require).fsPath,
		// 	['--type=extensionHost', '--skipWorkspaceStorageLock'],
		// 	mixin({ cwd: cwd() }, opts),
		// );
		// const forkTime = sw.elapsed();
		// const pid = this._process.pid!;

		// this._logService.info(`Starting extension host with pid ${pid} (fork() took ${forkTime} ms).`);

		// const stdoutDecoder = new StringDecoder('utf-8');
		// this._process.stdout?.on('data', (chunk) => {
		// 	const strChunk = typeof chunk === 'string' ? chunk : stdoutDecoder.write(chunk);
		// 	this._onStdout.fire(strChunk);
		// });

		// const stderrDecoder = new StringDecoder('utf-8');
		// this._process.stderr?.on('data', (chunk) => {
		// 	const strChunk = typeof chunk === 'string' ? chunk : stderrDecoder.write(chunk);
		// 	this._onStderr.fire(strChunk);
		// });

		// this._process.on('message', msg => {
		// 	this._onMessage.fire(msg);
		// });

		// this._process.on('error', (err) => {
		// 	this._onError.fire({ error: transformErrorForSerialization(err) });
		// });

		// this._process.on('exit', (code: number, signal: string) => {
		// 	this._hasExited = true;
		// 	this._onExit.fire({ pid, code, signal });
		// });

		// return { pid };
	}

	enableInspectPort(): boolean {
		if (!this._process) {
			return false;
		}

		this._logService.info(`Enabling inspect port on extension host with pid ${this._process.pid}.`);

		interface ProcessExt {
			_debugProcess?(n: number): any;
		}

		if (typeof (<ProcessExt>process)._debugProcess === 'function') {
			// use (undocumented) _debugProcess feature of node
			(<ProcessExt>process)._debugProcess!(this._process.pid!);
			return true;
		} else if (!platform.isWindows) {
			// use KILL USR1 on non-windows platforms (fallback)
			this._process.kill('SIGUSR1');
			return true;
		} else {
			// not supported...
			return false;
		}
	}

	kill(): void {
		if (!this._process) {
			return;
		}
		this._logService.info(`Killing extension host with pid ${this._process.pid}.`);
		this._process.kill();
	}

	async waitForExit(maxWaitTimeMs: number): Promise<void> {
		if (!this._process) {
			return;
		}
		const pid = this._process.pid;
		this._logService.info(`Waiting for extension host with pid ${pid} to exit.`);
		await Promise.race([Event.toPromise(this.onExit), timeout(maxWaitTimeMs)]);

		if (!this._hasExited) {
			// looks like we timed out
			this._logService.info(`Extension host with pid ${pid} did not exit within ${maxWaitTimeMs}ms.`);
			this._process.kill();
		}
	}
}

```
</details>