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