Fork channel

Create a new channel as a copy of main.

Rename channel

Rename main to:

Delete channel

Delete main? This cannot be undone.

extension.ts
import * as vscode from 'vscode';
import { Uri } from "vscode";
import * as path from 'path';
import * as cp from 'child_process';
import * as os from 'os';

export interface IPijul {
	path: string;
	version: string;
}

export interface Diff {
	operation: string;
	line: number | undefined;
}

enum Section {
	Init,
	Changes,
}

class Repository implements vscode.TextDocumentContentProvider {

	panel: vscode.WebviewPanel | undefined;

	provideTextDocumentContent(uri: vscode.Uri): Promise<string> {
		return new Promise<string>((c, e) => {
			if (vscode.workspace.rootPath == undefined) {
				return e(new Error("No workspace open"));
			}
			const buffers: Buffer[] = [];
			const child = cp.spawn("pijul", [
				'reset',
				'--repository', vscode.workspace.rootPath,
				'--dry-run',
				uri.path.slice(1)
			]);
			child.stdout.on('data', (b: Buffer) => buffers.push(b));
			child.on('exit', code => code ? e(new Error(`Could not output ${uri}`)) : c(Buffer.concat(buffers).toString('utf8')));
		})
	}


	initPanel(): vscode.WebviewPanel {
		if (this.panel == undefined) {
			this.panel = vscode.window.createWebviewPanel(
				'pijul.record',
				'Record a change',
				vscode.ViewColumn.One,
				{ enableScripts: true }
			);
			this.panel.onDidDispose(
				() => {
					this.panel = undefined;
				},
				undefined,
			);
		} else {
			this.panel.reveal(vscode.ViewColumn.One);
		}
		return this.panel
	}

	async recordPanel() {
		console.log('recordPanel');
		const change = await diff();
		const lines = change.split(os.EOL);
		let cont = "";
		let section = Section.Init;
		let currentChange = "";
		let currentChangeName = "";
		change.split(os.EOL).forEach((line) => {
			if (line == "# Changes") {
				section = Section.Changes;
				cont = cont + "<h2>Changes</h2>"
			} else if (section == Section.Changes) {
				if (line.startsWith(' ')) {
					currentChange = currentChange + '\n' + line
				} else if (line.startsWith('+')) {
					currentChange = currentChange + `\n<div style="background:#afa">${line}</div>`
				} else if (line.startsWith('-')) {
					currentChange = currentChange + `\n<div style="background:#faa">${line}</div>`
				} else {
					if (currentChange != "") {
						cont = cont + `<h3><input type = "checkbox" checked/>${currentChangeName}</h3><div style="margin-left:2em"><pre>${currentChange}</pre></div>`;
					}
					currentChangeName = line;
					currentChange = "";
				}
			}
		});
		console.log(cont);
		let panel = this.initPanel();
		panel.webview.html = `<!DOCTYPE html>
		<html lang="en">
		<head>
			<meta charset="UTF-8">
			<meta name="viewport" content="width=device-width, initial-scale=1.0">
			<title>Record a change</title>
		</head>
		<body>
		${cont}
		<button>Ok</button>
		</body>
		</html>`;
	}
}

function diff(): Promise<string> {
	return new Promise<string>((c, e) => {
		const buffers: Buffer[] = [];
		if (vscode.workspace.rootPath == undefined) {
			return c();
		}
		const child = cp.spawn("pijul", [
			'diff',
			'--repository', vscode.workspace.rootPath,
		]);
		child.stdout.on('data', (b: Buffer) => buffers.push(b));
		child.on('exit', code => {
			if (code) {
				return e(new Error(`Could not record`))
			}
			return c(Buffer.concat(buffers).toString('utf8'))
		})
	})
}

function findPijul(path: string): Promise<IPijul> {
	return new Promise<IPijul>((c, e) => {
		const buffers: Buffer[] = [];
		const child = cp.spawn(path, ['--version']);
		child.stdout.on('data', (b: Buffer) => buffers.push(b));
		child.on('exit', code => code ? e(new Error('No Pijul found on path')) : c({
			path,
			version: Buffer.concat(buffers).toString('utf8').trim()
		}));
	});
}

async function pijulReset(path: string | undefined): Promise<void> {
	return new Promise<void>((c, e) => {
		if (vscode.workspace.rootPath == undefined) {
			return c();
		}
		let args: string[] = ['reset', '--repository', vscode.workspace.rootPath];
		if (path != undefined) {
			args.push(path)
		}
		const child = cp.spawn("pijul", args);
		child.on('exit', (code: number) => {
			if (code) {
				e(new Error(`Cannot reset repository ${vscode.workspace.rootPath}`));
			} else {
				c();
			}
		});
	});
}

async function pijulRecord(message: string | undefined): Promise<void> {
	return new Promise<void>((c, e) => {
		if (vscode.workspace.rootPath == undefined) {
			return c();
		}
		if (message == undefined) {
			return e(new Error(`No change message`));
		}
		let args: string[] = ['record', '--repository', vscode.workspace.rootPath, '-a', '-m', message];
		const child = cp.spawn("pijul", args);
		child.on('exit', (code: number) => {
			if (code) {
				e(new Error(`Cannot record in repository ${vscode.workspace.rootPath}`));
			} else {
				c();
			}
		});
	});
}


function pijulStatus(): Promise<Map<string, [Diff]>> {
	return new Promise<Map<string, [Diff]>>((c, e) => {
		const buffers: Buffer[] = [];
		const errbuf: Buffer[] = [];
		if (vscode.workspace.rootPath == undefined) {
			return e(new Error('no root'));
		}
		const child = cp.spawn("pijul", ['diff', '--json', '--repository', vscode.workspace.rootPath]);
		child.stdout.on('data', (b: Buffer) => buffers.push(b));
		child.stderr.on('data', (b: Buffer) => errbuf.push(b));
		child.on('exit', code => {
			const out = Buffer.concat(buffers).toString('utf8').trim();
			const err = Buffer.concat(errbuf).toString('utf8');
			if (out == '') {
				return c(new Map());
			}
			const json = JSON.parse(out);
			let changes: Map<string, [Diff]> = new Map();
			for (let k of Object.keys(json)) {
				changes.set(k, json[k]);
			}
			if (code) {
				return e(new Error('Not found'))
			} else {
				return c(changes);
			}
		})
	});
}

function createResourceUri(relativePath: string): vscode.Uri {
	const root = "/";
	if (vscode.workspace.rootPath != undefined) {
		vscode.workspace.rootPath
	};
	const absolutePath = path.join(root, relativePath);
	return vscode.Uri.file(absolutePath);
}

async function updateStatus(event: Uri | undefined, diff: vscode.SourceControlResourceGroup) {
	if (event != undefined && event.path.includes(".pijul")) {
		return;
	}
	try {
		const changes = await pijulStatus();
		var states: vscode.SourceControlResourceState[] = [];
		changes.forEach((value: [Diff], key: string) => {
			states.push({
				resourceUri: createResourceUri(key),
				command: {
					command: "pijul.diff",
					title: "Check",
					arguments: [key, value],
				}
			});
		});
		diff.resourceStates = states;
	} catch (error) {
		return
	};
}

async function hide_dotpijul() {
	const config = vscode.workspace.getConfiguration('files');
	let excl: any | undefined = config.inspect('exclude');
	if (excl != undefined) {
		if (excl.globalValue != undefined) {
			excl.globalValue["**/.pijul"] = true;
			await config.update('exclude', excl.globalValue, true);
		} else {
			await config.update('exclude', { "**/.pijul": true }, true);
		}
	}
}

export async function activate(context: vscode.ExtensionContext) {
	// const ipijul = await findPijul("pijul");
	await hide_dotpijul();
	const pijulSCM = vscode.scm.createSourceControl('pijul', 'Pijul');
	pijulSCM.acceptInputCommand = {
		command: "pijul.record",
		title: "Record change"
	};
	const diff = pijulSCM.createResourceGroup('diff', 'Diff');
	await updateStatus(undefined, diff);

	const watcher = vscode.workspace.createFileSystemWatcher(`**/*`);
	watcher.onDidChange(async event => { await updateStatus(event, diff); });
	watcher.onDidCreate(async event => { await updateStatus(event, diff); });
	watcher.onDidDelete(async event => { await updateStatus(event, diff); });

	context.subscriptions.push(vscode.commands.registerCommand("pijul.diff",
		async (uri: string, diff: [Diff]) => {
			if (vscode.workspace.rootPath == undefined) {
				return;
			};
			const absolutePath = path.join(vscode.workspace.rootPath, uri);
			const new_version = vscode.Uri.file(absolutePath);
			const old_version = vscode.Uri.file(uri).with({ scheme: "pijul" });
			console.log(diff);
			const op = diff[0].operation;
			if (op == 'edit' || op == 'replacement' || op == 'solve order conflict' ||
				op == 'unsolve order conflict' || op == 'resurrect zombies') {
				await vscode.commands.executeCommand('vscode.diff', old_version, new_version);
			}
		}
	));
	context.subscriptions.push(vscode.commands.registerCommand("pijul.reset",
		async () => {
			await pijulReset(undefined);
			await updateStatus(undefined, diff);
		}));
	let repo = new Repository();
	context.subscriptions.push(vscode.commands.registerCommand("pijul.record",
		async () => {
			await repo.recordPanel();
			// await pijulRecord(pijulSCM.inputBox.value);
			pijulSCM.inputBox.value = "";
			await updateStatus(undefined, diff);
		}));

	vscode.workspace.registerTextDocumentContentProvider('pijul', repo);
}

export function deactivate() { }