New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

vscode-zap

Package Overview
Dependencies
Maintainers
1
Versions
107
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

vscode-zap - npm Package Compare versions

Comparing version 0.0.33 to 0.0.34

out/test/controller.test.d.ts

40

out/src/controller.js

@@ -17,3 +17,18 @@ "use strict";

this.toDispose.push(this.handler);
// Create workspace and ensure hooks are enabled only when the
// server is running.
this.workspace = new workspace_1.Workspace(this.environment);
let initedHooks = false;
this.client.onDidChangeState((event) => {
switch (event.newState) {
case client_1.State.Running:
if (!initedHooks) {
this.workspace.initHooks();
initedHooks = true;
}
break;
case client_1.State.Stopped:
break;
}
});
// Register client features.

@@ -26,10 +41,10 @@ this.client.registerHandler(handler_1.Handler.id, this.handler);

if (event.newState === client_1.State.Running) {
let p;
if (!this.resolvedRef) {
p = this.resolveRef();
// this.initializeConnection will be called in the
// onDidChangeResolvedRef listener.
this.resolveRef();
}
else {
p = Promise.resolve(void 0);
this.initializeConnection();
}
return p.then(() => this.initializeConnection());
}

@@ -39,3 +54,5 @@ });

this.onDidChangeResolvedRef(() => {
return this.initializeConnection();
if (this.resolvedRef) {
return this.initializeConnection();
}
});

@@ -49,3 +66,9 @@ }

// contain "*".
if (!this.resolvedRef) {
return Promise.reject(new Error(`unable to initialize connection without a resolved ref (environment.zapRef is ${this.environment.zapRef})`));
}
return this.handler.repoWatch({ repo: this.environment.repo, refspec: this.resolvedRef }).then(() => {
if (!this.resolvedRef) {
return; // the user concurrently switched refs; abort here
}
return this.handler.attachWorkspace({

@@ -56,3 +79,3 @@ repo: this.environment.repo,

}).then(() => null, (err) => {
console.error(`Error watching repo: ${err}`);
console.error(`Error watching repo: ${err} ${err.stack}`);
});

@@ -89,6 +112,3 @@ }

return this.queryRefInfo({ repo: this.environment.repo, ref: this.environment.zapRef }).then(info => {
if (!info.target) {
throw new Error(`new ref ${this.environment.zapRef} is not a symbolic ref: ${JSON.stringify(info)}`);
}
this.resolvedRef = info.target;
this.resolvedRef = info.target ? info.target : this.environment.zapRef; // use target if symbolic ref
this.resolvedRefUpdateEmitter.fire(this.resolvedRef);

@@ -95,0 +115,0 @@ }).then(() => null, (err) => {

@@ -250,2 +250,9 @@ "use strict";

}
case "configure":
{
Object.keys(params.configure).forEach((key) => __awaiter(this, void 0, void 0, function* () {
yield vscode.workspace.getConfiguration("zap").update(key, params.configure[key], true);
}));
return Promise.resolve({});
}
default:

@@ -252,0 +259,0 @@ throw new Error(`unrecognized action: ${action} `);

@@ -20,2 +20,11 @@ import { MessageStream } from "libzap/lib/remote/client";

automaticallyApplyingFileSystemChanges: boolean;
/**
* readTextDocumentOnDisk returns the contents of a doc on
* disk. These contents can differ from doc.getText when the
* document is dirty.
*
* If there is no disk or file system (e.g., in the browser), then
* the environment should emulate one.
*/
readTextDocumentOnDisk(uri: vscode.Uri): string;
revertTextDocument2(doc: vscode.TextDocument): Thenable<any>;

@@ -22,0 +31,0 @@ revertTextDocument(doc: vscode.TextDocument): Thenable<void>;

@@ -56,2 +56,5 @@ "use strict";

}
readTextDocumentOnDisk(uri) {
return fs_1.readFileSync(uri.fsPath, "utf8");
}
revertTextDocument2(doc) {

@@ -64,5 +67,3 @@ return vscode.commands.executeCommand("workbench.action.files.revert", doc.uri);

edit.replace(doc.uri, new vscode.Range(new vscode.Position(0, 0), doc.positionAt(doc.getText().length)), data);
return vscode.workspace.applyEdit(edit).then(() => {
return doc.save();
});
return vscode.workspace.applyEdit(edit).then(() => doc.save());
}

@@ -69,0 +70,0 @@ openChannel(id) {

@@ -6,2 +6,3 @@ import * as vscode from "vscode";

export declare function isEnabled(): boolean;
export declare function startAndMonitor(controller: Controller): Thenable<void>;
export declare function startController(controller: Controller): Thenable<void>;
export declare function stopController(controller: Controller): Thenable<void>;

@@ -29,22 +29,25 @@ // This is the extension's shared entrypoint. All platforms call this

}, null, ctx.subscriptions);
ctx.subscriptions.push(vscode.commands.registerCommand("zap.restart", () => __awaiter(this, void 0, void 0, function* () {
yield controller.stop();
startAndMonitor(controller);
})));
ctx.subscriptions.push(vscode.commands.registerCommand("zap.ref.select", () => {
return controller.showRefSelectMenu();
}));
let enabled = isEnabled();
ctx.subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => __awaiter(this, void 0, void 0, function* () {
const enable = isEnabled();
if (enable) {
yield startAndMonitor(controller);
ctx.subscriptions.push(vscode.commands.registerCommand("zap.restart", () => __awaiter(this, void 0, void 0, function* () {
yield stopController(controller);
yield startController(controller);
})));
const updateEnabled = (enabled) => {
if (enabled) {
startController(controller);
}
else {
yield controller.stop();
stopController(controller);
}
})));
if (enabled) {
startAndMonitor(controller);
}
};
let lastEnabled = isEnabled();
ctx.subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => {
if (isEnabled() !== lastEnabled) {
updateEnabled(isEnabled());
lastEnabled = isEnabled();
}
}));
updateEnabled(isEnabled());
return controller;

@@ -60,13 +63,19 @@ }

exports.isEnabled = isEnabled;
function startAndMonitor(controller) {
return controller.client.stop().then(() => {
return controller.start().then(() => { }, err => {
console.log(`Zap failed to start: ${err}`);
status_1.outputChannel.appendLine(`Zap failed to start: ${err}`);
status_1.outputChannel.show();
vscode.window.showErrorMessage(`Zap failed to start.`);
});
function startController(controller) {
return controller.start().then(() => { }, err => {
console.log(`Zap failed to start: ${err}`);
status_1.outputChannel.appendLine(`Zap failed to start: ${err}`);
status_1.outputChannel.show();
vscode.window.showErrorMessage(`Zap failed to start.`);
});
}
exports.startAndMonitor = startAndMonitor;
exports.startController = startController;
;
function stopController(controller) {
return controller.stop().then(() => { }, err => {
console.error(`Zap failed to stop: ${err}`);
});
}
exports.stopController = stopController;
;
//# sourceMappingURL=extension.common.js.map

@@ -37,3 +37,3 @@ // This is the extension's entrypoint used by vscode. Things in here

vscode.workspace.getConfiguration("zap").update("enable", true, true);
yield extension_common_1.startAndMonitor(controller);
yield extension_common_1.startController(controller);
try {

@@ -40,0 +40,0 @@ yield controller.client.sendRequest(protocol_1.WorkspaceAddRequest.type, { dir: vscode.workspace.rootPath });

@@ -20,1 +20,9 @@ import * as vscode from "vscode";

export declare function opWithBufferEditsOmittingChangesOnDisk(op: WorkspaceOp): WorkspaceOp;
/**
* subtract: see the "SUBTRACTING OT OPERATIONS" comment in
* workspace.ts for more information.
*
* We can assume that a1 is obtained from transform(a, x), where x is
* an arbitrary op.
*/
export declare function subtract(a: WorkspaceOp, a1: WorkspaceOp): WorkspaceOp;
"use strict";
const vscode = require("vscode");
const textDoc_1 = require("libzap/lib/ot/textDoc");
const workspace_1 = require("libzap/lib/ot/workspace");

@@ -150,3 +151,3 @@ function sortTextDocumentContentChangeEvents(changes) {

// insert
workspaceEdit.insert(doc.uri, doc.positionAt(ret), edit);
workspaceEdit.insert(doc.uri, doc.positionAt(ret + del), edit);
}

@@ -313,2 +314,95 @@ else if (edit < 0) {

exports.opWithBufferEditsOmittingChangesOnDisk = opWithBufferEditsOmittingChangesOnDisk;
/**
* subtract: see the "SUBTRACTING OT OPERATIONS" comment in
* workspace.ts for more information.
*
* We can assume that a1 is obtained from transform(a, x), where x is
* an arbitrary op.
*/
function subtract(a, a1) {
if (!a.edit) {
throw new Error("no a.edit");
}
if (!a1.edit) {
return {};
}
// It's OK for a and b to differ after their last insert.
let fileEdits;
const subtractFileEdits = (fileName, aEdits, a1Edits) => {
// if (aEdits.length > a1Edits.length) {
// throw new Error(`unexpected: aEdits.length (${aEdits.length}) > a1Edits.length (${a1Edits.length})`);
// }
const edits = [];
let ac = 0;
let a1c = 0;
let ai = 0;
let a1i = 0;
let divergedSinceLastInsert = false;
let meaningful = false;
while (ai < aEdits.length || a1i < a1Edits.length) {
let ae = aEdits[ai];
let a1e = a1Edits[a1i];
if (typeof a1 === "string" && typeof a1e === "number") {
ae = 0; // treat as a collapsed retain 0
a1i++;
}
else if (typeof a1 === "number" && typeof a1e === "string") {
ai++;
a1e = 0; // treat as a collapsed retain 0
}
else {
if (ai <= aEdits.length) {
ai++;
}
if (a1i <= a1Edits.length) {
a1i++;
}
}
if (edits.length > 0 && ae === undefined && typeof a1e === "number") {
edits.push(a1e);
continue;
}
if (typeof ae === "number") {
ac += Math.abs(ae);
}
if (typeof a1e === "number") {
a1c += Math.abs(a1e);
}
if (typeof ae === "string") {
if (ae === a1e && !divergedSinceLastInsert) {
if (ac !== a1c) {
throw new Error(`invariant: ac (${ac}) != a1c (${a1c})`);
}
edits.push(ac + ae.length);
}
else {
edits.push(ac);
edits.push(-1 * ae.length);
edits.push(a1c - ac);
edits.push(ae);
ac = 0;
a1c = 0;
divergedSinceLastInsert = false;
meaningful = true;
}
}
else if (ae !== a1e) {
divergedSinceLastInsert = true;
}
}
if (edits.length > 0 && meaningful) {
if (!fileEdits) {
fileEdits = {};
}
fileEdits[fileName] = textDoc_1.mergeEdits(edits);
}
};
for (const f in a.edit) {
if (a.edit.hasOwnProperty(f) && a1.edit.hasOwnProperty(f)) {
subtractFileEdits(f, a.edit[f], a1.edit[f]);
}
}
return fileEdits ? { edit: fileEdits } : {};
}
exports.subtract = subtract;
//# sourceMappingURL=op.js.map

@@ -21,10 +21,53 @@ import { WorkspaceOp } from "libzap/lib/ot/workspace";

private logDebug(msg);
private initHooks();
readonly pendingChangesAreDone: Promise<void>;
private pendingChanges;
private pendingChangesDone?;
initHooks(): void;
/**
* Passing a function in EditOps is a special facility enabled for
* tests only. It is required to synthesize ops because the op
* must take into account the current document length when the op
* is being applied, and the document length can change between
* the test code's call to workspace.apply and when the op is
* actually applied.
*/
testAllowFunctionsAsApplyOps: boolean;
private applying?;
private applyingOp?;
private toApply;
/**
* apply applies the op to the workspace, modifying it as
* described by the op. It ensures that ops are applied in the
* order in which synchronous calls to apply are made.
*/
apply(op: WorkspaceOp): Promise<void>;
private doApply(op);
private highlightDecorationType;
private setUserSelection(fileName, userID, sel);
/**
* Reset is called to reset the state of the workspace to reflect
* history. If isAttaching is true, then this method is being
* called to attach a workspace to a new ref (e.g., the user
* switches HEAD refs, reconnects to the server, etc.).
*
* NOTES
*
* There are 2 scenarios in which we must perform recovery or resolution:
*
* 1. You disconnect for a brief period then come back online. Your ops
* should be transformed against the server's history. Both arrive at
* a consistent state. This is equivalent to having a very laggy
* connection.
*
* 2. You disconnect, and while you're disconnected, someone else resets
* the ref so it restarts rev numbering from 0, has a different base
* commit, etc. Here, you can choose either "remote clobbers local"
* (zap.overwrite=false in config) or "local clobbers remote"
* (zap.overwrite=true) resolution strategies (the former is suited
* for the web client and the latter for vscode).
*
* Currently only #2 is implemented, and it is executed even when
* you disconnect for a brief period of time.
*
*/
reset(history: WorkspaceOp[]): Promise<void>;
diffBufferFilesAgainst(history: WorkspaceOp[]): WorkspaceOp;
localOverwritesRemote(): boolean;
private onDidChangeActiveTextEditor(editor);

@@ -35,6 +78,15 @@ private onDidChangeTextEditorSelection(ev);

private recordSelection(doc, sel);
/**
* concurrentOpWhileApplyingOp is the operation (possibly composed
* of multiple sequential operations) that represents all of the
* changes received by the onDidChangeTextDocument listener while
* we were in the doApply method's vscode.workspace.applyEdit
* call. It is used by the doApply method to compensate for
* concurrent changes that yield incorrect document contents.
*/
private documentDirty;
private onDidChangeTextDocument(ev);
private subtractOp?;
private onDidSaveTextDocument(doc);
private onWillSaveTextDocument(event);
}

@@ -12,2 +12,4 @@ "use strict";

const workspace_1 = require("libzap/lib/ot/workspace");
const textDoc_1 = require("libzap/lib/ot/textDoc");
const diffOps_1 = require("libzap/lib/util/diffOps");
const op_1 = require("./op");

@@ -21,6 +23,24 @@ class Workspace {

this.suppressRecordEdits = new Map();
this.pendingChanges = new Set();
/**
* Passing a function in EditOps is a special facility enabled for
* tests only. It is required to synthesize ops because the op
* must take into account the current document length when the op
* is being applied, and the document length can change between
* the test code's call to workspace.apply and when the op is
* actually applied.
*/
this.testAllowFunctionsAsApplyOps = false;
this.toApply = []; // ops (in order) waiting to be applied
this.highlightDecorationType = vscode.window.createTextEditorDecorationType({
backgroundColor: "rgba(255, 255, 20, 0.15)",
});
/**
* concurrentOpWhileApplyingOp is the operation (possibly composed
* of multiple sequential operations) that represents all of the
* changes received by the onDidChangeTextDocument listener while
* we were in the doApply method's vscode.workspace.applyEdit
* call. It is used by the doApply method to compensate for
* concurrent changes that yield incorrect document contents.
*/
// private concurrentOpWhileApplyingOp?: WorkspaceOp;
this.documentDirty = new Map();

@@ -33,3 +53,2 @@ // Give onOpListener default noop value;

this.toDispose.push(this.outputChannel);
this.initHooks();
}

@@ -55,4 +74,4 @@ onOp(listener) {

initHooks() {
this.toDispose.push(vscode.window.onDidChangeActiveTextEditor(e => this.onDidChangeActiveTextEditor(e)));
this.toDispose.push(vscode.workspace.onDidChangeTextDocument((e) => __awaiter(this, void 0, void 0, function* () {
vscode.window.onDidChangeActiveTextEditor(e => this.onDidChangeActiveTextEditor(e), null, this.toDispose);
vscode.workspace.onDidChangeTextDocument((e) => __awaiter(this, void 0, void 0, function* () {
try {

@@ -64,8 +83,8 @@ yield this.onDidChangeTextDocument(e);

}
})));
this.toDispose.push(vscode.window.onDidChangeTextEditorSelection(e => this.onDidChangeTextEditorSelection(e)));
this.toDispose.push(vscode.workspace.onDidCloseTextDocument(e => this.onDidCloseTextDocument(e)));
this.toDispose.push(vscode.workspace.onDidOpenTextDocument(e => this.onDidOpenTextDocument(e)));
this.toDispose.push(vscode.workspace.onWillSaveTextDocument(e => this.onWillSaveTextDocument(e)));
this.toDispose.push(vscode.workspace.onDidSaveTextDocument(e => this.onDidSaveTextDocument(e)));
}), null, this.toDispose);
vscode.window.onDidChangeTextEditorSelection(e => this.onDidChangeTextEditorSelection(e), null, this.toDispose);
vscode.workspace.onDidCloseTextDocument(e => this.onDidCloseTextDocument(e), null, this.toDispose);
vscode.workspace.onDidOpenTextDocument(e => this.onDidOpenTextDocument(e), null, this.toDispose);
vscode.workspace.onWillSaveTextDocument(e => this.onWillSaveTextDocument(e), null, this.toDispose);
vscode.workspace.onDidSaveTextDocument(e => this.onDidSaveTextDocument(e), null, this.toDispose);
// Handle documents/editors that are open initially.

@@ -79,14 +98,43 @@ for (let doc of vscode.workspace.textDocuments) {

}
get pendingChangesAreDone() {
return this.pendingChangesDone ? this.pendingChangesDone : Promise.resolve(void 0);
}
/**
* apply applies the op to the workspace, modifying it as
* described by the op. It ensures that ops are applied in the
* order in which synchronous calls to apply are made.
*/
apply(op) {
return __awaiter(this, void 0, void 0, function* () {
if (this.pendingChangesDone) {
throw new Error(`workspace already has pending changes but apply was called on ${JSON.stringify(op)} (it should not have been called until the pending changes were applied`);
this.toApply.push(op);
if (this.applying) {
return this.applying;
}
let resolvePendingChangesDone = undefined;
this.pendingChangesDone = new Promise((resolveTmp) => {
resolvePendingChangesDone = resolveTmp;
});
this.applying = new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
while (this.applyingOp = this.toApply.shift()) {
// Handle functions in EditOps for
// testAllowFunctionsAsApplyOps (see docstring for
// more info).
if (typeof this.applyingOp === "function") {
this.applyingOp = this.applyingOp();
}
try {
yield this.doApply(this.applyingOp);
}
catch (err) {
// TODO(sqs): handle errors here; and for ease of
// debugging, consider including original stack
// trace of caller to apply.
const msg = `applying op failed: ${JSON.stringify(this.applyingOp)}: ${err}`;
console.error(msg);
this.applyingOp = undefined;
reject(err);
throw new Error(msg);
}
}
this.applying = undefined;
resolve();
}));
return this.applying;
});
}
doApply(op) {
return __awaiter(this, void 0, void 0, function* () {
if (op.save) {

@@ -138,12 +186,3 @@ this.suppressRecord = true;

this.documentDirty.set(uri.toString(), Boolean(op.edit[`#${file}`]));
if (this.pendingChanges.has(uri.toString())) {
throw new Error(`unexpected: document ${uri.toString()} has pending (not-yet-applied) changes, but apply was called again to apply more changes to it (${JSON.stringify(edit.get(uri))})`);
}
this.pendingChanges.add(uri.toString());
}
const removeFromPendingChanges = () => {
for (const [uri] of edit.entries()) {
this.pendingChanges.delete(uri.toString());
}
};
for (const [uri] of edit.entries()) {

@@ -157,7 +196,29 @@ const existing = this.suppressRecordEdits.get(uri.toString()) || [];

const editorSelsAreEmpty = vscode.window.visibleTextEditors.map(e => e.selection.isEmpty);
// During the async call to vscode.workspace.applyEdit,
// other edits (e.g., interactive edits from the user) can
// come in, and our edit could be applied to a different
// version of the document. This would yield incorrect
// text.
//
// To fix this, our onDidChangeTextDocument listener
// checks if there are any pending edits to a document,
// and if there are, it stores extra information that we
// can use to compensate for the change it made. We can
// detect whether we need to compensate after
// vscode.workspace.applyEdit's promise resolves.
const preEditDocVersions = new Map();
for (const doc of vscode.workspace.textDocuments) {
if (edit.has(doc.uri)) {
preEditDocVersions.set(doc.uri.toString(), doc.version);
}
}
yield vscode.workspace.applyEdit(edit).then((ok) => {
removeFromPendingChanges();
if (!ok) {
throw new Error(`applyWorkspaceEdit failed: ${JSON.stringify(op)}`);
}
if (this.subtractOp) {
const subtractOp = this.subtractOp;
this.subtractOp = undefined;
return this.doApply(subtractOp);
}
editorSelsAreEmpty.forEach((isEmpty, i) => {

@@ -171,3 +232,2 @@ const editor = vscode.window.visibleTextEditors[i];

}, (err) => {
removeFromPendingChanges();
throw new Error(`applyWorkspaceEdit failed: ${err}`);

@@ -187,4 +247,2 @@ });

}
resolvePendingChangesDone();
this.pendingChangesDone = undefined;
});

@@ -205,7 +263,43 @@ }

}
/**
* Reset is called to reset the state of the workspace to reflect
* history. If isAttaching is true, then this method is being
* called to attach a workspace to a new ref (e.g., the user
* switches HEAD refs, reconnects to the server, etc.).
*
* NOTES
*
* There are 2 scenarios in which we must perform recovery or resolution:
*
* 1. You disconnect for a brief period then come back online. Your ops
* should be transformed against the server's history. Both arrive at
* a consistent state. This is equivalent to having a very laggy
* connection.
*
* 2. You disconnect, and while you're disconnected, someone else resets
* the ref so it restarts rev numbering from 0, has a different base
* commit, etc. Here, you can choose either "remote clobbers local"
* (zap.overwrite=false in config) or "local clobbers remote"
* (zap.overwrite=true) resolution strategies (the former is suited
* for the web client and the latter for vscode).
*
* Currently only #2 is implemented, and it is executed even when
* you disconnect for a brief period of time.
*
*/
reset(history) {
return __awaiter(this, void 0, void 0, function* () {
const allFiles = () => {
const allFiles2 = {};
for (const doc of vscode.workspace.textDocuments) {
if (doc.uri.scheme === "file") {
allFiles2[this.environment.asRelativePathInsideWorkspace(doc.uri)] = doc.getText();
}
}
return allFiles2;
};
if (process.env.DEV) {
console.log(`Workspace.reset: history=${JSON.stringify(history)} files=${JSON.stringify(allFiles())}`);
}
try {
this.pendingChanges.clear();
this.pendingChangesDone = undefined;
this.suppressRecordEdits.clear();

@@ -220,5 +314,2 @@ this.documentDirty.clear();

this.suppressRecord = false;
while (this.pendingChangesDone) {
yield this.pendingChangesDone;
}
if (history.length > 0) {

@@ -229,2 +320,3 @@ let composed = workspace_1.composeAll(history);

}
console.log(`Workspace.reset: going to apply composed=${JSON.stringify(history)} files=${JSON.stringify(allFiles())}`);
yield this.apply(composed);

@@ -234,4 +326,2 @@ }

catch (err) {
// Allow the workspace to be reused.
this.pendingChangesDone = undefined;
console.error(`Zap reset failed: ${err}`);

@@ -243,2 +333,61 @@ // Pass along error to server to tell it we failed.

}
// diffBufferFilesAgainst returns an op that, when composed with
// the provided history, represents the current state of the
// workspace.
//
// For example, if the workspace has a dirty file f1 with "abc"
// and history == [{copy:{"#f":"/f"},edit:{"#f":["ab"]}}], then
// the returned op will be {edit:{"#f":[2,"c"]}}. This op is not
// applied, but it is sent back to the server to sync the server
// to our local workspace state.
diffBufferFilesAgainst(history) {
let composed = workspace_1.composeAll(history);
if (this.environment.automaticallyApplyingFileSystemChanges) {
composed = op_1.opWithBufferEditsOmittingChangesOnDisk(composed);
}
const existingBufferFilesInHistory = new Set();
// The only way for a buffer file to exist is for there to be
// an outstanding create or copy op with the buffer file.
//
// TODO(sqs): technically there could be a rename
if (composed.create) {
for (const f of composed.create) {
if (workspace_1.isBufferPath(f)) {
existingBufferFilesInHistory.add(f);
}
}
}
if (composed.copy) {
Object.keys(composed.copy).forEach(dest => {
if (workspace_1.isBufferPath(dest)) {
existingBufferFilesInHistory.add(dest);
}
});
}
// Compute the diff between the history version of each buffer
// file vs. the workspace version of each unsaved file.
const diffOp = {};
for (const f of Array.from(existingBufferFilesInHistory)) {
const strippedPath = workspace_1.stripFileOrBufferPathPrefix(f);
let historyText = this.environment.readTextDocumentOnDisk(this.environment.asAbsoluteURI(strippedPath));
historyText = composed.edit[f] ?
textDoc_1.applyEdits(composed.edit[f], historyText) :
historyText; // edits can be undefined if they cancel each other out (all retain)
// historyText is now the text of the file as the server sees it.
const doc = vscode.workspace.textDocuments.find(doc => this.environment.asRelativePathInsideWorkspace(doc.uri) === strippedPath);
if (!doc) {
throw new Error(`no such file: ${strippedPath}`);
}
const currentText = doc.getText();
if (!diffOp.edit) {
diffOp.edit = {};
}
diffOp.edit[f] = diffOps_1.default(historyText, currentText);
}
return diffOp;
}
localOverwritesRemote() {
// HACK
return vscode.workspace.getConfiguration("zap").get("overwrite");
}
onDidChangeActiveTextEditor(editor) {

@@ -273,3 +422,2 @@ if (editor) {

onDidOpenTextDocument(doc) {
// If file is dirty, create the buffered file.
const fileName = this.environment.asRelativePathInsideWorkspace(doc.uri);

@@ -279,4 +427,17 @@ if (fileName === null) {

}
if (this.environment.textDocumentIsDirtyHack(doc)) {
console.log("WARNING: doc is dirty but we currently have no way of diffing in JS");
if (doc.isDirty) {
// If file is dirty, create the buffered file and
// determine the OT diff from the file on disk (we weren't
// listening to the change events, and they might never
// have even been sent (e.g., if this file was dirty
// because of hot exit)).
const oldText = this.environment.readTextDocumentOnDisk(doc.uri);
const newText = doc.getText();
if (oldText === newText) {
console.error(`unexpected: document ${doc.uri.toString()} is dirty, but its on-disk contents equal its in-memory contents`);
}
this.onOpListener({
copy: { [`#${fileName}`]: `/${fileName}` },
edit: { [`#${fileName}`]: diffOps_1.default(oldText, newText) },
});
}

@@ -314,16 +475,2 @@ }

return __awaiter(this, void 0, void 0, function* () {
if (this.suppressRecord) {
return;
}
const origText = ev.document.getText();
if (this.pendingChangesDone) {
yield this.pendingChangesDone;
}
const newText = ev.document.getText();
if (origText !== newText) {
console.log(`DEV NOTE: Good thing we waited for pendingChangesDone! The document text changed after some just-applied operations. ${JSON.stringify(origText)} !== ${JSON.stringify(newText)}`);
}
if (this.pendingChanges.has(ev.document.uri.toString())) {
throw new Error(`pending, unapplied changes exist for ${ev.document.uri.toString()}; this indicates the presence of a race condition or other bug`);
}
const doc = ev.document;

@@ -334,2 +481,5 @@ const fileName = this.environment.asRelativePathInsideWorkspace(doc.uri);

}
if (this.suppressRecord) {
return;
}
// Ignore some changes.

@@ -371,3 +521,3 @@ if (ev.contentChanges.length === 1) {

this.documentDirty.set(doc.uri.toString(), isDirty);
const op = {};
let op = {};
if (wasDirty && !isDirty && !doc.isDirty) {

@@ -382,4 +532,48 @@ op.delete = [`#${fileName}`];

}
// Handle when we're in the middle of applying an op and we
// get a change.
if (this.applyingOp) {
const { a1, b1 } = workspace_1.transform(this.applyingOp, op);
op = b1;
// SUBTRACTING OT OPERATIONS
//
// a1 is what doApply *should* apply, but there's no way
// to cancel it or to modify its edit after it has already
// started.
//
// We need to produce the op X that doApply needs to
// execute such that the following composition of ops
// yields the correct document state:
//
// op + this.applyingOp + X
//
// Note that this sequence is equal to:
//
// == op + a1
//
// which means:
//
// this.applyingOp + X == a1
//
// For example, suppose:
//
// original doc := "a@A"
// op := [1,b,2]
// this.applyingOp := [3,B]
// desired doc := "ab@AB"
//
// We can compute (via transformWorkspaceOps) a1 :=
// [4,B]. So, then we can manually solve for X in:
//
// this.applyingOp + X == a1
// [3,B] + X == [4,B]
//
// By intuition, we can determine that X :=
// [3,-1,1,"B"]. Another way to think about this is to
// determine what edit op would make "ab@BA" into "ab@AB"
// (it is the same X).
this.subtractOp = op_1.subtract(this.applyingOp, a1);
}
const msg = `Client=${process.env.ZAP_E2E_NAME} Change ${doc.uri.toString()}: ${JSON.stringify(op)} dirty=${isDirty} wasDirty=${wasDirty} contents=${JSON.stringify(doc.getText())}} changes=${JSON.stringify(contentChangesToProcess)}`;
if (process.env.DEV) {
if (process.env.DEV && process.env.DEV_NOISY) {
console.log(msg);

@@ -386,0 +580,0 @@ this.outputChannel.appendLine(msg);

@@ -0,1 +1,16 @@

import * as vscode from "vscode";
import { Workspace } from "../src/workspace";
import { WorkspaceOp } from "libzap/lib/ot/workspace";
export declare function checkZapNotEnabledAtStartup(): void;
export declare function capture(workspace: Workspace): {
ops: WorkspaceOp[];
};
export declare function assertOpsEqual(a: WorkspaceOp[], b: WorkspaceOp[], label?: string): void;
export declare function writeWorkspace(files: {
[fileName: string]: string;
}): void;
export declare function withOpenTextDocument(fileName: string, f: (doc: vscode.TextDocument) => Thenable<any>): Promise<void>;
export declare function withWorkspace(f: (workspace: Workspace) => Thenable<any>): Promise<any>;
export declare function sleep(msec: number): Promise<void>;
export declare function getTabString(): string;
export declare function assertRejects(f: Function, typ?: any): Promise<void>;
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
const assert = require("assert");
const fs = require("fs");
const path = require("path");
const vscode = require("vscode");
const extension_common_1 = require("../src/extension.common");
const workspace_1 = require("../src/workspace");
const environment_vscode_1 = require("../src/environment.vscode");
function checkZapNotEnabledAtStartup() {

@@ -9,2 +23,93 @@ if (extension_common_1.isEnabled()) {

exports.checkZapNotEnabledAtStartup = checkZapNotEnabledAtStartup;
function capture(workspace) {
const ops = [];
workspace.onOp((op) => ops.push(op));
return { ops };
}
exports.capture = capture;
function assertOpsEqual(a, b, label = "") {
assert.deepEqual(a, b, `${label ? `${label} ` : ""}ops ${JSON.stringify(a)} != ${JSON.stringify(b)}`);
}
exports.assertOpsEqual = assertOpsEqual;
function writeWorkspace(files) {
for (const fileName in files) {
if (files.hasOwnProperty(fileName)) {
fs.writeFileSync(path.join(vscode.workspace.rootPath, fileName), files[fileName]);
}
}
}
exports.writeWorkspace = writeWorkspace;
function withOpenTextDocument(fileName, f) {
return __awaiter(this, void 0, void 0, function* () {
const doc = yield vscode.workspace.openTextDocument(vscode.Uri.file(path.join(vscode.workspace.rootPath, fileName)));
yield vscode.window.showTextDocument(doc);
return ensureAfterPromise(revertFile(doc).then(() => f(doc)), () => revertFile(doc));
});
}
exports.withOpenTextDocument = withOpenTextDocument;
function withWorkspace(f) {
return __awaiter(this, void 0, void 0, function* () {
const workspace = new workspace_1.Workspace(environment_vscode_1.default);
workspace.initHooks();
return ensureAfter(f(workspace), () => workspace.dispose());
});
}
exports.withWorkspace = withWorkspace;
function sleep(msec) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve) => {
setTimeout(() => resolve(void 0), msec);
});
});
}
exports.sleep = sleep;
function getTabString() {
// TODO(sqs): This is not entirely accurate to use in main code, as the
// spaces/tabs behavior can be overridden by the mode. But it's OK for
// tests, since we are working only with plain text files.
if (vscode.workspace.getConfiguration("editor").get("insertSpaces")) {
return " ".repeat(vscode.workspace.getConfiguration("editor").get("tabSize"));
}
return "\t";
}
exports.getTabString = getTabString;
function revertFile(doc) {
return __awaiter(this, void 0, void 0, function* () {
const edit = new vscode.WorkspaceEdit();
edit.replace(doc.uri, new vscode.Range(new vscode.Position(0, 0), doc.positionAt(doc.getText().length)), "abc\n");
yield vscode.workspace.applyEdit(edit);
yield doc.save();
});
}
function ensureAfter(p, after) {
return p.then((value) => {
after();
return value;
}, (err) => {
after();
throw err;
});
}
function ensureAfterPromise(p, after) {
return p.then((value) => {
return after().then(() => value);
}, (err) => {
return after().then(() => { throw err; });
});
}
function assertRejects(f, typ) {
return __awaiter(this, void 0, void 0, function* () {
try {
yield f();
}
catch (err) {
if (typ && !(err instanceof typ)) {
throw new Error("async function rejected wrong error: ${e}");
}
return;
}
throw new Error("async function was not rejected");
});
}
exports.assertRejects = assertRejects;
//# sourceMappingURL=common.js.map

@@ -6,2 +6,3 @@ // HACK(sqs): my vscode kept adding spaces back in

const vscode = require("vscode");
const textDoc_1 = require("libzap/lib/ot/textDoc");
const op = require("../src/op");

@@ -422,2 +423,11 @@ const types_1 = require("./types");

},
{
text: "abA@",
op: [2, -1, 1, "A"],
want: [
vscode.TextEdit.delete(new vscode.Range(0, 2, 0, 3)),
vscode.TextEdit.insert(new vscode.Position(0, 4), "A"),
],
wantText: "ab@A",
},
].forEach((t) => {

@@ -517,3 +527,3 @@ test(`${JSON.stringify(t.text)} edit op: ${JSON.stringify(t.op)}`, () => {

const b = t.b();
assert.equal(t.want, op.workspaceEditsEqual(a, b), `${JSON.stringify(a)} != ${JSON.stringify(b)}`);
assert.equal(op.workspaceEditsEqual(a, b), t.want, `${JSON.stringify(a)} != ${JSON.stringify(b)}`);
});

@@ -538,6 +548,77 @@ });

test(JSON.stringify(t.op), () => {
assert.deepEqual(t.want, op.opWithBufferEditsOmittingChangesOnDisk(t.op));
assert.deepEqual(op.opWithBufferEditsOmittingChangesOnDisk(t.op), t.want);
});
});
});
suite("subtract", () => {
([
{
a: { edit: { "#f": [3, "B"] } },
a1: {},
want: {},
},
{
// B@ -> @B
a: { edit: { "#f": ["B"] } },
a1: { edit: { "#f": [1, "B"] } },
want: { edit: { "#f": [-1, 1, "B"] } },
},
{
a: { edit: { "#f": [3, "B"] } },
a1: { edit: { "#f": [4, "B"] } },
want: { edit: { "#f": [3, -1, 1, "B"] } },
},
{
a: { edit: { "#f": [3, "B"] } },
a1: { edit: { "#f": [4, "B", 1] } },
want: { edit: { "#f": [3, -1, 1, "B", 1] } },
},
{
// xyB@jkC -> xy@BjkC
a: { edit: { "#f": [2, "B", 2, "C"] } },
a1: { edit: { "#f": [3, "B", 2, "C"] } },
want: { edit: { "#f": [2, -1, 1, "B", 3] } },
},
{
// xyB@jkC@ -> xy@Bjk@C
a: { edit: { "#f": [2, "B", 2, "C"] } },
a1: { edit: { "#f": [3, "B", 3, "C"] } },
want: { edit: { "#f": [2, -1, 1, "B", 2, -1, 1, "C"] } },
},
{
a: { edit: { "#f": [8, "E"] }, copy: { "#f": "/f" } },
a1: { edit: { "#f": [9, "E"] } },
want: { edit: { "#f": [8, -1, 1, "E"] } },
},
{
// xyzBC@ -> xyz@BC
a: { edit: { "#f": [3, "BC"] } },
a1: { edit: { "#f": [4, "BC"] } },
want: { edit: { "#f": [3, -2, 1, "BC"] } },
},
{
a: { edit: { "#f": [1, "bc", 3] } },
a1: { edit: { "#f": [1, "bc", 4] } },
want: {},
},
{
a: { edit: { "#f": ["x", 5] } },
a1: { edit: { "#f": ["x", 10] } },
want: {},
},
]).forEach(t => {
test(JSON.stringify(`a=${JSON.stringify(t.a)} a1=${JSON.stringify(t.a1)} `), () => {
// Sanity checks.
const fileNames = t.a1.edit ? Object.keys(t.a1.edit) : [];
fileNames.forEach(f => {
if (t.want.edit && t.want.edit[f]) {
const { ret: ret1, del: del1, ins: ins1 } = textDoc_1.countEdits(t.a1.edit[f]);
const { ret: ret2, del: del2 } = textDoc_1.countEdits(t.want.edit[f]);
assert.equal(ret1 + del1 + ins1, ret2 + del2, `a1.edit[${f}] total length ${ret1 + del1 + ins1} (${JSON.stringify(t.a1.edit[f])}) != want.edit[${f}] base length ${ret2 + del2} (${JSON.stringify(t.want.edit[f])})`);
}
});
assert.deepEqual(op.subtract(t.a, t.a1), t.want);
});
});
});
//# sourceMappingURL=op.test.js.map

@@ -11,32 +11,29 @@ "use strict";

const assert = require("assert");
const fs = require("fs");
const path = require("path");
const vscode = require("vscode");
const workspace_1 = require("../src/workspace");
const workspace_2 = require("libzap/lib/ot/workspace");
const workspace_1 = require("libzap/lib/ot/workspace");
const common_1 = require("./common");
const environment_vscode_1 = require("../src/environment.vscode");
function capture(workspace) {
const ops = [];
workspace.onOp((op) => ops.push(op));
return { ops };
function checkInitialState(doc, ops) {
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
if (ops !== undefined) {
assert.deepEqual(ops, []);
}
}
suite("Workspace", () => __awaiter(this, void 0, void 0, function* () {
suiteSetup(() => common_1.checkZapNotEnabledAtStartup());
suiteSetup(() => {
vscode.workspace.getConfiguration("zap").update("share.selections", false);
writeWorkspace({ f: "abc\n" });
});
suiteSetup(() => __awaiter(this, void 0, void 0, function* () {
yield vscode.workspace.getConfiguration("zap").update("share.selections", false);
common_1.writeWorkspace({ f: "abc\n" });
}));
suiteTeardown(() => {
writeWorkspace({ f: "abc\n" });
common_1.writeWorkspace({ f: "abc\n" });
});
suiteSetup(() => __awaiter(this, void 0, void 0, function* () { return yield sleep(200); })); // fixes "TypeError: Cannot read property 'length' of undefined" when running only 1 test
suiteSetup(() => __awaiter(this, void 0, void 0, function* () { return yield common_1.sleep(200); })); // fixes "TypeError: Cannot read property 'length' of undefined" when running only 1 test
suite("zap", () => {
test("one local edit", () => {
return withOpenTextDocument("f", (doc) => {
return withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = capture(workspace);
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
assert.deepEqual(ops, []);
return common_1.withOpenTextDocument("f", (doc) => {
return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = common_1.capture(workspace);
checkInitialState(doc, ops);
const edit = new vscode.WorkspaceEdit();

@@ -47,3 +44,3 @@ edit.insert(doc.uri, new vscode.Position(0, 3), "x");

assert.equal(doc.getText(), "abcx\n");
assertOpsEqual(ops, [{ copy: { "#f": "/f" }, edit: { "#f": [3, "x", 1] } }]);
common_1.assertOpsEqual(ops, [{ copy: { "#f": "/f" }, edit: { "#f": [3, "x", 1] } }]);
}));

@@ -53,8 +50,6 @@ });

test("remote reset", () => {
return withOpenTextDocument("f", (doc) => {
return withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = capture(workspace);
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
assert.deepEqual(ops, []);
return common_1.withOpenTextDocument("f", (doc) => {
return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = common_1.capture(workspace);
checkInitialState(doc, ops);
// Make an edit that will be reverted.

@@ -66,8 +61,6 @@ const edit = new vscode.WorkspaceEdit();

assert.equal(doc.getText(), "abcx\n");
assertOpsEqual(ops, [{ copy: { "#f": "/f" }, edit: { "#f": [3, "x", 1] } }]);
common_1.assertOpsEqual(ops, [{ copy: { "#f": "/f" }, edit: { "#f": [3, "x", 1] } }]);
ops.shift();
yield workspace.reset([]);
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
assert.deepEqual(ops, []);
checkInitialState(doc, ops);
// Then dirtying the file again should emit a copy op.

@@ -79,3 +72,3 @@ const edit2 = new vscode.WorkspaceEdit();

assert.equal(doc.getText(), "abcy\n");
assertOpsEqual(ops, [{ copy: { "#f": "/f" }, edit: { "#f": [3, "y", 1] } }]);
common_1.assertOpsEqual(ops, [{ copy: { "#f": "/f" }, edit: { "#f": [3, "y", 1] } }]);
ops.shift();

@@ -86,8 +79,6 @@ }));

test("remote reset with ops", () => {
return withOpenTextDocument("f", (doc) => {
return withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = capture(workspace);
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
assert.deepEqual(ops, []);
return common_1.withOpenTextDocument("f", (doc) => {
return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = common_1.capture(workspace);
checkInitialState(doc, ops);
// Make an edit that will be reverted.

@@ -99,3 +90,3 @@ const edit = new vscode.WorkspaceEdit();

assert.equal(doc.getText(), "abcx\n");
assertOpsEqual(ops, [{ copy: { "#f": "/f" }, edit: { "#f": [3, "x", 1] } }]);
common_1.assertOpsEqual(ops, [{ copy: { "#f": "/f" }, edit: { "#f": [3, "x", 1] } }]);
ops.shift();

@@ -112,11 +103,9 @@ yield workspace.reset([{ copy: { "#f": "/f" }, edit: { "#f": [3, "y", 1] } }]);

test("remote edit then remote save", () => {
return withOpenTextDocument("f", (doc) => {
return withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = capture(workspace);
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
return common_1.withOpenTextDocument("f", (doc) => {
return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = common_1.capture(workspace);
checkInitialState(doc, ops);
yield workspace.apply({ copy: { "#f": "/f" }, edit: { "#f": [3, "q", 1] } });
assert.equal(doc.getText(), "abcq\n");
assert.ok(doc.isDirty);
yield workspace.pendingChangesAreDone;
yield workspace.apply({ save: ["#f"] });

@@ -132,3 +121,3 @@ assert.equal(doc.getText(), "abcq\n");

assert.ok(doc.isDirty);
assertOpsEqual(ops, [{ copy: { "#f": "/f" }, edit: { "#f": [3, "x", 2] } }]);
common_1.assertOpsEqual(ops, [{ copy: { "#f": "/f" }, edit: { "#f": [3, "x", 2] } }]);
}));

@@ -140,6 +129,5 @@ });

test("simultaneous writes to buffer and file", () => {
return withOpenTextDocument("f", (doc) => {
return withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
return common_1.withOpenTextDocument("f", (doc) => {
return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
checkInitialState(doc);
yield workspace.apply({

@@ -157,6 +145,5 @@ copy: { "#f": "/f" },

test("save and then edit", () => {
return withOpenTextDocument("f", (doc) => {
return withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
return common_1.withOpenTextDocument("f", (doc) => {
return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
checkInitialState(doc);
yield workspace.apply({

@@ -191,6 +178,5 @@ copy: { "#f": "/f" },

test("one remote non-buffered edit", () => {
return withOpenTextDocument("f", (doc) => {
return withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
return common_1.withOpenTextDocument("f", (doc) => {
return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
checkInitialState(doc);
yield workspace.apply({ edit: { "/f": [3, "q", 1] } });

@@ -205,8 +191,6 @@ // Ensure it's ignored by vscode; in a real situation the op would already

test("remote reset to same with after save", () => __awaiter(this, void 0, void 0, function* () {
return withOpenTextDocument("f", (doc) => {
return withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = capture(workspace);
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
assert.deepEqual(ops, []);
return common_1.withOpenTextDocument("f", (doc) => {
return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = common_1.capture(workspace);
checkInitialState(doc, ops);
// Make an edit that will be saved.

@@ -218,6 +202,6 @@ const edit1 = new vscode.WorkspaceEdit();

assert.equal(doc.getText(), "abcx\n");
assertOpsEqual(ops, [{ copy: { "#f": "/f" }, edit: { "#f": [3, "x", 1] } }]);
common_1.assertOpsEqual(ops, [{ copy: { "#f": "/f" }, edit: { "#f": [3, "x", 1] } }]);
ops.shift();
const save1OK = yield doc.save();
assertOpsEqual(ops, [{ save: ["#f"] }]);
common_1.assertOpsEqual(ops, [{ save: ["#f"] }]);
ops.shift();

@@ -234,3 +218,3 @@ assert.ok(save1OK);

assert.equal(doc.getText(), "abcxy\n");
assertOpsEqual(ops, [{ copy: { "#f": "/f" }, edit: { "#f": [4, "y", 1] } }]);
common_1.assertOpsEqual(ops, [{ copy: { "#f": "/f" }, edit: { "#f": [4, "y", 1] } }]);
ops.shift();

@@ -260,6 +244,5 @@ const editor = yield vscode.window.showTextDocument(doc);

test("one remote non-buffered edit", () => {
return withOpenTextDocument("f", (doc) => {
return withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
return common_1.withOpenTextDocument("f", (doc) => {
return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
checkInitialState(doc);
yield workspace.apply({ edit: { "/f": [3, "q", 1] } });

@@ -272,8 +255,6 @@ assert.equal(doc.getText(), "abcq\n");

test("remote reset to same with after save op", () => __awaiter(this, void 0, void 0, function* () {
return withOpenTextDocument("f", (doc) => {
return withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = capture(workspace);
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
assert.deepEqual(ops, []);
return common_1.withOpenTextDocument("f", (doc) => {
return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = common_1.capture(workspace);
checkInitialState(doc, ops);
const editor = yield vscode.window.showTextDocument(doc);

@@ -295,7 +276,6 @@ const preResetSel = editor.selection;

test("remote buffered edit when no buffered file exists", () => {
return withOpenTextDocument("f", (doc) => {
return withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
yield assertRejects(() => workspace.apply({ edit: { "#f": [3, "q", 1] } }));
return common_1.withOpenTextDocument("f", (doc) => {
return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
checkInitialState(doc);
yield common_1.assertRejects(() => workspace.apply({ edit: { "#f": [3, "q", 1] } }));
// The doc should be unchanged.

@@ -307,4 +287,4 @@ assert.equal(doc.getText(), "abc\n");

test("remote disk edit when disk file is not open", () => {
return withOpenTextDocument("f", () => {
return withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
return common_1.withOpenTextDocument("f", () => {
return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
// We just want it to not throw.

@@ -316,8 +296,6 @@ yield workspace.apply({ edit: { "/g": [1, "x"] } });

test("local then remote edit", () => {
return withOpenTextDocument("f", (doc) => {
return withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = capture(workspace);
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
assertOpsEqual(ops, []);
return common_1.withOpenTextDocument("f", (doc) => {
return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = common_1.capture(workspace);
checkInitialState(doc, ops);
const edit = new vscode.WorkspaceEdit();

@@ -329,7 +307,7 @@ edit.insert(doc.uri, new vscode.Position(0, 3), "x");

const op1 = { copy: { "#f": "/f" }, edit: { "#f": [3, "x", 1] } };
assertOpsEqual(ops, [op1]);
common_1.assertOpsEqual(ops, [op1]);
const op2 = { edit: { "#f": [2, "p", 3] } };
yield workspace.apply(op2);
assert.equal(doc.getText(), "abpcx\n");
assertOpsEqual(ops, [op1]);
common_1.assertOpsEqual(ops, [op1]);
}));

@@ -339,12 +317,10 @@ });

test("remote then local edit", () => {
return withOpenTextDocument("f", (doc) => {
return withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = capture(workspace);
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
assertOpsEqual(ops, []);
return common_1.withOpenTextDocument("f", (doc) => {
return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = common_1.capture(workspace);
checkInitialState(doc, ops);
const op1 = { copy: { "#f": "/f" }, edit: { "#f": [2, "p", 2] } };
yield workspace.apply(op1);
assert.equal(doc.getText(), "abpc\n");
assertOpsEqual(ops, []);
common_1.assertOpsEqual(ops, []);
const edit = new vscode.WorkspaceEdit();

@@ -355,3 +331,3 @@ edit.insert(doc.uri, new vscode.Position(0, 3), "x");

assert.equal(doc.getText(), "abpxc\n");
assertOpsEqual(ops, [{ edit: { "#f": [3, "x", 2] } }]);
common_1.assertOpsEqual(ops, [{ edit: { "#f": [3, "x", 2] } }]);
}));

@@ -361,8 +337,6 @@ });

test("multiple local edits", () => {
return withOpenTextDocument("f", (doc) => {
return withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = capture(workspace);
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
assertOpsEqual(ops, []);
return common_1.withOpenTextDocument("f", (doc) => {
return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = common_1.capture(workspace);
checkInitialState(doc, ops);
const edit1 = new vscode.WorkspaceEdit();

@@ -374,3 +348,3 @@ edit1.insert(doc.uri, new vscode.Position(0, 3), "x");

const op1 = { copy: { "#f": "/f" }, edit: { "#f": [3, "x", 1] } };
assertOpsEqual(ops, [op1]);
common_1.assertOpsEqual(ops, [op1]);
const edit2 = new vscode.WorkspaceEdit();

@@ -382,3 +356,3 @@ edit2.insert(doc.uri, new vscode.Position(0, 2), "y");

const op2 = { edit: { "#f": [2, "y", 3] } };
assertOpsEqual(ops, [op1, op2]);
common_1.assertOpsEqual(ops, [op1, op2]);
const edit3 = new vscode.WorkspaceEdit();

@@ -390,3 +364,3 @@ edit3.insert(doc.uri, new vscode.Position(0, 1), "z");

const op3 = { edit: { "#f": [1, "z", 5] } };
assertOpsEqual(ops, [op1, op2, op3]);
common_1.assertOpsEqual(ops, [op1, op2, op3]);
}));

@@ -396,8 +370,6 @@ });

test("many local edits", () => {
return withOpenTextDocument("f", (doc) => {
return withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = capture(workspace);
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
assertOpsEqual(ops, []);
return common_1.withOpenTextDocument("f", (doc) => {
return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = common_1.capture(workspace);
checkInitialState(doc, ops);
const editor = yield vscode.window.showTextDocument(doc);

@@ -410,6 +382,6 @@ const text = "hello, world".repeat(4);

assert.ok(ok);
yield sleep(7);
yield common_1.sleep(7);
}
assert.equal(doc.getText(), `${text}abc\n`);
assert.deepEqual(workspace_2.composeAll(ops), {
assert.deepEqual(workspace_1.composeAll(ops), {
copy: { "#f": "/f" },

@@ -422,8 +394,6 @@ edit: { "#f": [text, 4] },

test("many remote edits", () => {
return withOpenTextDocument("f", (doc) => {
return withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = capture(workspace);
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
assertOpsEqual(ops, []);
return common_1.withOpenTextDocument("f", (doc) => {
return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = common_1.capture(workspace);
checkInitialState(doc, ops);
yield workspace.apply({ copy: { "#f": "/f" }, edit: { "#f": ["x", 4] } });

@@ -441,14 +411,12 @@ ops.shift();

test("multiple remote edits", () => {
return withOpenTextDocument("f", (doc) => {
return withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = capture(workspace);
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
assertOpsEqual(ops, []);
return common_1.withOpenTextDocument("f", (doc) => {
return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = common_1.capture(workspace);
checkInitialState(doc, ops);
yield workspace.apply({ copy: { "#f": "/f" }, edit: { "#f": [3, "q", 1] } });
assert.equal(doc.getText(), "abcq\n");
assertOpsEqual(ops, []);
common_1.assertOpsEqual(ops, []);
yield workspace.apply({ edit: { "#f": [2, "p", 3] } });
assert.equal(doc.getText(), "abpcq\n");
assertOpsEqual(ops, []);
common_1.assertOpsEqual(ops, []);
}));

@@ -458,8 +426,6 @@ });

test("local edit and local save", () => {
return withOpenTextDocument("f", (doc) => {
return withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = capture(workspace);
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
assertOpsEqual(ops, []);
return common_1.withOpenTextDocument("f", (doc) => {
return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = common_1.capture(workspace);
checkInitialState(doc, ops);
const edit = new vscode.WorkspaceEdit();

@@ -471,3 +437,3 @@ edit.insert(doc.uri, new vscode.Position(0, 3), "x");

const op1 = { copy: { "#f": "/f" }, edit: { "#f": [3, "x", 1] } };
assertOpsEqual(ops, [op1]);
common_1.assertOpsEqual(ops, [op1]);
assert.ok(doc.isDirty);

@@ -477,3 +443,3 @@ const saveOK = yield doc.save();

assert.ok(saveOK);
assertOpsEqual(ops, [op1, { save: ["#f"] }]);
common_1.assertOpsEqual(ops, [op1, { save: ["#f"] }]);
}));

@@ -483,30 +449,73 @@ });

test("simultaneous edits", () => {
return withOpenTextDocument("f", (doc) => {
return withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = capture(workspace);
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
assertOpsEqual(ops, []);
// Move cursor to end.
return common_1.withOpenTextDocument("f", (doc) => {
return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = common_1.capture(workspace);
checkInitialState(doc, ops);
workspace.testAllowFunctionsAsApplyOps = true;
// Move cursor to end of line.
const editor = yield vscode.window.showTextDocument(doc);
const pos = new vscode.Position(0, doc.getText().length);
editor.selection = new vscode.Selection(pos, pos);
const appendText = () => __awaiter(this, void 0, void 0, function* () {
let attempts = 10;
for (; attempts >= 0; attempts--) {
const ok = yield editor.edit(editBuilder => {
editBuilder.insert(editor.selections[0].active, "y");
});
if (ok) {
break;
let appending;
const toAppend = []; // ensure appendText applies edits in order
const appendText = (s) => __awaiter(this, void 0, void 0, function* () {
toAppend.push(s);
if (appending) {
return appending;
}
appending = new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
let nextAppend;
while (nextAppend = toAppend.shift()) {
let attempts = 10;
for (; attempts >= 0; attempts--) {
const ok = yield editor.edit(editBuilder => {
editBuilder.insert(new vscode.Position(0, editor.document.getText().length), nextAppend);
});
if (ok) {
break;
}
}
if (attempts === 0) {
reject(new Error(`exhausted edit attempts for ${JSON.stringify(nextAppend)}`));
return;
}
}
}
assert.ok(attempts > 0, "exhausted edit attempts");
appending = undefined;
resolve();
}));
return appending;
});
const prependText = () => __awaiter(this, void 0, void 0, function* () {
yield workspace.apply({ copy: { "#f": "/f" }, edit: { "#f": ["x", 4] } });
});
yield Promise.all([appendText(), prependText()]);
assert.equal(doc.getText(), "xabcy\n");
assertOpsEqual(ops, [{ edit: { "#f": [3, "y", 2] } }]);
const prependText = (s) => {
// Yes, we are passing a function to workspace.apply.
// This is a special facility for testing. See
// Workspace.testAllowFunctionsAsApplyOps for more
// information about passing functions as WorkspaceOp
// values.
return workspace.apply(() => {
const op = {
edit: { "#f": [s, doc.getText().length] },
copy: doc.isDirty ? undefined : { "#f": "/f" },
};
ops.push(op); // so that we can composeAll below
return op;
});
};
const promises = [];
const n = 5;
let wantLeft = "";
let wantRight = "";
const left = "ABCDEFGHIJKLM";
const right = "NOPQRSTUVWXYZ";
for (let i = 0; i < n; i++) {
yield common_1.sleep(1);
const leftStr = left[i % left.length];
wantLeft = leftStr + wantLeft;
const rightStr = right[i % right.length];
wantRight += rightStr;
promises.push(appendText(rightStr));
promises.push(prependText(leftStr));
}
yield Promise.all(promises);
assert.equal(doc.getText(), wantLeft + "abc" + wantRight + "\n");
common_1.assertOpsEqual([workspace_1.composeAll(ops)], [{ copy: { "#f": "/f" }, edit: { "#f": [wantLeft, 3, wantRight, 1] } }]);
}));

@@ -519,9 +528,7 @@ });

test("delete line by individual characters (#3802)", () => {
return withOpenTextDocument("f", (doc) => {
return withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
yield sleep(100);
const { ops } = capture(workspace);
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
assert.deepEqual(ops, []);
return common_1.withOpenTextDocument("f", (doc) => {
return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
yield common_1.sleep(100);
const { ops } = common_1.capture(workspace);
checkInitialState(doc, ops);
// Need to add a 2nd line to reproduce.

@@ -546,3 +553,3 @@ const edit = new vscode.WorkspaceEdit();

assert.ok(doc.isDirty);
assertOpsEqual(ops, [{ edit: { "#f": [2, -1, 5] } }]);
common_1.assertOpsEqual(ops, [{ edit: { "#f": [2, -1, 5] } }]);
ops.shift();

@@ -552,3 +559,3 @@ yield backspaceOnce();

assert.ok(doc.isDirty);
assertOpsEqual(ops, [{ edit: { "#f": [1, -1, 5] } }]);
common_1.assertOpsEqual(ops, [{ edit: { "#f": [1, -1, 5] } }]);
ops.shift();

@@ -558,3 +565,3 @@ yield backspaceOnce();

assert.ok(doc.isDirty);
assertOpsEqual(ops, [{ edit: { "#f": [-1, 5] } }]);
common_1.assertOpsEqual(ops, [{ edit: { "#f": [-1, 5] } }]);
ops.shift();

@@ -565,8 +572,6 @@ }));

test("indent while selected all (#3727)", () => {
return withOpenTextDocument("f", (doc) => {
return withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = capture(workspace);
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
assert.deepEqual(ops, []);
return common_1.withOpenTextDocument("f", (doc) => {
return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = common_1.capture(workspace);
checkInitialState(doc, ops);
// Need to add some more text to reproduce the issue.

@@ -578,3 +583,3 @@ const edit = new vscode.WorkspaceEdit();

assert.equal(doc.getText(), "abc\n\txyz\n");
assertOpsEqual(ops, [{ copy: { "#f": "/f" }, edit: { "#f": [4, "\txyz\n"] } }]);
common_1.assertOpsEqual(ops, [{ copy: { "#f": "/f" }, edit: { "#f": [4, "\txyz\n"] } }]);
ops.shift();

@@ -585,5 +590,5 @@ // Select and indent.

// TODO(sqs): not robust to variability in editor.insertSpaces and editor.tabSize settings.
const tab = getTabString();
const tab = common_1.getTabString();
assert.equal(doc.getText(), `${tab}abc\n${tab}${tab}xyz\n`);
assertOpsEqual(ops, [
common_1.assertOpsEqual(ops, [
{ edit: { "#f": [`${tab}`, 4, `${tab}${tab}`, -1, 4] } },

@@ -598,8 +603,6 @@ ]);

test("indent while selected partial line (#3770)", () => {
return withOpenTextDocument("f", (doc) => {
return withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = capture(workspace);
assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
assert.deepEqual(ops, []);
return common_1.withOpenTextDocument("f", (doc) => {
return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {
const { ops } = common_1.capture(workspace);
checkInitialState(doc, ops);
// Need to add some more text to reproduce the issue.

@@ -623,5 +626,5 @@ const edit = new vscode.WorkspaceEdit();

// TODO(sqs): not robust to variability in editor.insertSpaces and editor.tabSize settings.
const tab = getTabString();
const tab = common_1.getTabString();
assert.equal(doc.getText(), `${tab}abc\n${tab}${tab}xyz\n`);
assertOpsEqual(ops, [
common_1.assertOpsEqual(ops, [
{ edit: { "#f": [`${tab}`, 4, `${tab}${tab}`, -1, 4] } },

@@ -634,79 +637,2 @@ ]);

}));
function sleep(msec) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve) => {
setTimeout(() => resolve(void 0), msec);
});
});
}
function getTabString() {
// TODO(sqs): This is not entirely accurate to use in main code, as the
// spaces/tabs behavior can be overridden by the mode. But it's OK for
// tests, since we are working only with plain text files.
if (vscode.workspace.getConfiguration("editor").get("insertSpaces")) {
return " ".repeat(vscode.workspace.getConfiguration("editor").get("tabSize"));
}
return "\t";
}
function assertOpsEqual(a, b, label = "") {
assert.deepEqual(a, b, `${label ? `${label} ` : ""}ops ${JSON.stringify(a)} != ${JSON.stringify(b)}`);
}
function writeWorkspace(files) {
for (const fileName in files) {
if (files.hasOwnProperty(fileName)) {
fs.writeFileSync(path.join(vscode.workspace.rootPath, fileName), files[fileName]);
}
}
}
function revertFile(doc) {
return __awaiter(this, void 0, void 0, function* () {
const edit = new vscode.WorkspaceEdit();
edit.replace(doc.uri, new vscode.Range(new vscode.Position(0, 0), doc.positionAt(doc.getText().length)), "abc\n");
yield vscode.workspace.applyEdit(edit);
yield doc.save();
});
}
function withOpenTextDocument(fileName, f) {
return __awaiter(this, void 0, void 0, function* () {
const doc = yield vscode.workspace.openTextDocument(vscode.Uri.file(path.join(vscode.workspace.rootPath, fileName)));
yield vscode.window.showTextDocument(doc);
return ensureAfterPromise(revertFile(doc).then(() => f(doc)), () => revertFile(doc));
});
}
function withWorkspace(f) {
return __awaiter(this, void 0, void 0, function* () {
const workspace = new workspace_1.Workspace(environment_vscode_1.default);
return ensureAfter(f(workspace), () => workspace.dispose());
});
}
function ensureAfter(p, after) {
return p.then((value) => {
after();
return value;
}, (err) => {
after();
throw err;
});
}
function ensureAfterPromise(p, after) {
return p.then((value) => {
return after().then(() => value);
}, (err) => {
return after().then(() => { throw err; });
});
}
function assertRejects(f, typ) {
return __awaiter(this, void 0, void 0, function* () {
try {
yield f();
}
catch (err) {
if (typ && !(err instanceof typ)) {
throw new Error("async function rejected wrong error: ${e}");
}
return;
}
throw new Error("async function was not rejected");
});
}
//# sourceMappingURL=workspace.test.js.map

@@ -5,3 +5,3 @@ {

"description": "WIP",
"version": "0.0.33",
"version": "0.0.34",
"publisher": "sqs",

@@ -32,3 +32,3 @@ "preview": true,

"vscode-isolated": "yarn run compile && USER=dummy-dont-share-vscode-instance code --user-data-dir=$PWD/.vscode-dev/user-data --extensionHomePath=$PWD/.vscode-dev/extensions --extensionDevelopmentPath=$PWD $*",
"test-isolated": "TMPDIR=$PWD/.vscode-test; rm -rf \"$TMPDIR\"; mkdir -p $TMPDIR; yarn run compile && (USER=dummy-dont-share-vscode-instance NO_AT_BRIDGE=1 ELECTRON_ENABLE_LOGGING=0 xvfb-run -- code --disable-gpu --wait --verbose --user-data-dir=$TMPDIR/user-data --extensionHomePath=$TMPDIR/extensions --extensionDevelopmentPath=$PWD --extensionTestsPath=$PWD/out/test $PWD/test/testdata 2>&1) | tee $TMPDIR/output.log && if grep -qi 'IPC#vscode:exit 1' $TMPDIR/output.log || grep -qi 'Command failed with exit code' $TMPDIR/output.log; then echo;echo FAIL;exit 1; else echo; echo PASS; fi"
"test-isolated": "TMPDIR=$PWD/.vscode-test; rm -rf \"$TMPDIR\"; mkdir -p $TMPDIR; ([ -n \"$SKIPCOMPILE\" ] || yarn run compile) && (USER=dummy-dont-share-vscode-instance NO_AT_BRIDGE=1 ELECTRON_ENABLE_LOGGING=0 xvfb-run -- code --disable-gpu --wait --verbose --user-data-dir=$TMPDIR/user-data --extensionHomePath=$TMPDIR/extensions --extensionDevelopmentPath=$PWD --extensionTestsPath=$PWD/out/test $PWD/test/testdata 2>&1) | tee $TMPDIR/output.log | grep -v /usr/share/code | grep -v original-fs.js && if grep -qi 'IPC#vscode:exit 1' $TMPDIR/output.log || grep -qi 'Command failed with exit code' $TMPDIR/output.log; then echo;echo FAIL;exit 1; else echo; echo PASS; fi"
},

@@ -43,3 +43,3 @@ "devDependencies": {

"dependencies": {
"libzap": "^0.0.33",
"libzap": "^0.0.34",
"open": "^0.0.5",

@@ -58,2 +58,7 @@ "vscode-jsonrpc": "3.0.1-alpha.7"

},
"zap.overwrite": {
"type": "boolean",
"default": true,
"description": "conflict resolution strategy; if true, local overwrites remote; otherwise remote overwrites local"
},
"zap.share.selections": {

@@ -60,0 +65,0 @@ "type": "boolean",

@@ -24,4 +24,20 @@ import * as vscode from "vscode";

// Create workspace and ensure hooks are enabled only when the
// server is running.
this.workspace = new Workspace(this.environment);
let initedHooks = false;
this.client.onDidChangeState((event: StateChangeEvent) => {
switch (event.newState) {
case State.Running:
if (!initedHooks) {
this.workspace.initHooks();
initedHooks = true;
}
break;
case State.Stopped:
break;
}
});
// Register client features.

@@ -36,9 +52,9 @@ this.client.registerHandler(Handler.id, this.handler);

if (event.newState === State.Running) {
let p: Thenable<void>;
if (!this.resolvedRef) {
p = this.resolveRef();
// this.initializeConnection will be called in the
// onDidChangeResolvedRef listener.
this.resolveRef();
} else {
p = Promise.resolve(void 0);
this.initializeConnection();
}
return p.then(() => this.initializeConnection());
}

@@ -48,3 +64,5 @@ });

this.onDidChangeResolvedRef(() => {
return this.initializeConnection();
if (this.resolvedRef) {
return this.initializeConnection();
}
});

@@ -60,9 +78,15 @@ }

return this.handler.repoWatch({ repo: this.environment.repo, refspec: this.resolvedRef! }).then(() => {
if (!this.resolvedRef) {
return Promise.reject(new Error(`unable to initialize connection without a resolved ref (environment.zapRef is ${this.environment.zapRef})`));
}
return this.handler.repoWatch({ repo: this.environment.repo, refspec: this.resolvedRef }).then(() => {
if (!this.resolvedRef) {
return; // the user concurrently switched refs; abort here
}
return this.handler.attachWorkspace({
repo: this.environment.repo,
ref: this.resolvedRef!,
ref: this.resolvedRef,
}, this.workspace);
}).then(() => null, (err) => {
console.error(`Error watching repo: ${err}`);
console.error(`Error watching repo: ${err} ${err.stack}`);
});

@@ -111,6 +135,3 @@ }

return this.queryRefInfo({ repo: this.environment.repo, ref: this.environment.zapRef }).then(info => {
if (!info.target) {
throw new Error(`new ref ${this.environment.zapRef} is not a symbolic ref: ${JSON.stringify(info)}`);
}
this.resolvedRef = info.target;
this.resolvedRef = info.target ? info.target : this.environment.zapRef; // use target if symbolic ref
this.resolvedRefUpdateEmitter.fire(this.resolvedRef);

@@ -117,0 +138,0 @@ }).then(() => null, (err) => {

@@ -96,2 +96,3 @@ import * as path from "path";

disable?: boolean;
configure: { [key: string]: boolean };
}

@@ -325,2 +326,10 @@

case "configure":
{
Object.keys(params.configure!).forEach(async (key) => {
await vscode.workspace.getConfiguration("zap").update(key, params.configure![key], true);
});
return Promise.resolve({});
}
default:

@@ -327,0 +336,0 @@ throw new Error(`unrecognized action: ${action} `);

@@ -54,2 +54,12 @@ import { MessageStream } from "libzap/lib/remote/client";

/**
* readTextDocumentOnDisk returns the contents of a doc on
* disk. These contents can differ from doc.getText when the
* document is dirty.
*
* If there is no disk or file system (e.g., in the browser), then
* the environment should emulate one.
*/
readTextDocumentOnDisk(uri: vscode.Uri): string;
revertTextDocument2(doc: vscode.TextDocument): Thenable<any>;

@@ -56,0 +66,0 @@

@@ -59,2 +59,6 @@ import * as vscode from "vscode";

readTextDocumentOnDisk(uri: vscode.Uri): string {
return readFileSync(uri.fsPath, "utf8");
}
revertTextDocument2(doc: vscode.TextDocument): Thenable<any> {

@@ -72,5 +76,3 @@ return vscode.commands.executeCommand("workbench.action.files.revert", doc.uri);

);
return vscode.workspace.applyEdit(edit).then(() => {
return doc.save();
});
return vscode.workspace.applyEdit(edit).then(() => doc.save());
}

@@ -77,0 +79,0 @@

@@ -29,7 +29,2 @@ // This is the extension's shared entrypoint. All platforms call this

ctx.subscriptions.push(vscode.commands.registerCommand("zap.restart", async () => {
await controller.stop();
startAndMonitor(controller);
}));
ctx.subscriptions.push(vscode.commands.registerCommand("zap.ref.select", () => {

@@ -39,14 +34,22 @@ return controller.showRefSelectMenu();

let enabled = isEnabled();
ctx.subscriptions.push(vscode.workspace.onDidChangeConfiguration(async () => {
const enable = isEnabled();
if (enable) {
await startAndMonitor(controller);
ctx.subscriptions.push(vscode.commands.registerCommand("zap.restart", async () => {
await stopController(controller);
await startController(controller);
}));
const updateEnabled = (enabled: boolean): void => {
if (enabled) {
startController(controller);
} else {
await controller.stop();
stopController(controller);
}
};
let lastEnabled = isEnabled();
ctx.subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => {
if (isEnabled() !== lastEnabled) {
updateEnabled(isEnabled());
lastEnabled = isEnabled();
}
}));
if (enabled) {
startAndMonitor(controller);
}
updateEnabled(isEnabled());

@@ -63,11 +66,15 @@ return controller;

export function startAndMonitor(controller: Controller): Thenable<void> {
return controller.client.stop().then(() => {
return controller.start().then(() => { /* noop */ }, err => {
console.log(`Zap failed to start: ${err}`);
outputChannel.appendLine(`Zap failed to start: ${err}`);
outputChannel.show();
vscode.window.showErrorMessage(`Zap failed to start.`);
});
export function startController(controller: Controller): Thenable<void> {
return controller.start().then(() => { /* noop */ }, err => {
console.log(`Zap failed to start: ${err}`);
outputChannel.appendLine(`Zap failed to start: ${err}`);
outputChannel.show();
vscode.window.showErrorMessage(`Zap failed to start.`);
});
}
};
export function stopController(controller: Controller): Thenable<void> {
return controller.stop().then(() => { /* noop */ }, err => {
console.error(`Zap failed to stop: ${err}`);
});
};

@@ -8,3 +8,3 @@ // This is the extension's entrypoint used by vscode. Things in here

import { enable as enableE2E } from "./e2e";
import { activate as activateCommon, startAndMonitor } from "./extension.common";
import { activate as activateCommon, startController } from "./extension.common";
import vscodeEnvironment from "./environment.vscode";

@@ -38,3 +38,3 @@ import * as zapLocalServer from "./localServer";

vscode.workspace.getConfiguration("zap").update("enable", true, true);
await startAndMonitor(controller);
await startController(controller);
try {

@@ -41,0 +41,0 @@ await controller.client.sendRequest(WorkspaceAddRequest.type, { dir: vscode.workspace.rootPath } as WorkspaceAddParams);

import * as vscode from "vscode";
import { EditOp, EditOps } from "libzap/lib/ot/textDoc";
import { WorkspaceOp, from, bufferToFilePath, fileToBufferPath, isBufferPath, isFilePath, stripFileOrBufferPathPrefix } from "libzap/lib/ot/workspace";
import { EditOp, EditOps, mergeEdits } from "libzap/lib/ot/textDoc";
import { FileEdits, WorkspaceOp, from, bufferToFilePath, fileToBufferPath, isBufferPath, isFilePath, stripFileOrBufferPathPrefix } from "libzap/lib/ot/workspace";

@@ -167,3 +167,3 @@ import { IEnvironment } from "./environment";

// insert
workspaceEdit.insert(doc.uri, doc.positionAt(ret), edit);
workspaceEdit.insert(doc.uri, doc.positionAt(ret + del), edit);
} else if (edit < 0) {

@@ -306,2 +306,92 @@ // delete

return op;
}
/**
* subtract: see the "SUBTRACTING OT OPERATIONS" comment in
* workspace.ts for more information.
*
* We can assume that a1 is obtained from transform(a, x), where x is
* an arbitrary op.
*/
export function subtract(a: WorkspaceOp, a1: WorkspaceOp): WorkspaceOp {
if (!a.edit) {
throw new Error("no a.edit");
}
if (!a1.edit) {
return {};
}
// It's OK for a and b to differ after their last insert.
let fileEdits: FileEdits | undefined;
const subtractFileEdits = (fileName: string, aEdits: EditOps, a1Edits: EditOps): void => {
// if (aEdits.length > a1Edits.length) {
// throw new Error(`unexpected: aEdits.length (${aEdits.length}) > a1Edits.length (${a1Edits.length})`);
// }
const edits: EditOps = [];
let ac = 0;
let a1c = 0;
let ai = 0;
let a1i = 0;
let divergedSinceLastInsert = false;
let meaningful = false;
while (ai < aEdits.length || a1i < a1Edits.length) {
let ae = aEdits[ai];
let a1e = a1Edits[a1i];
if (typeof a1 === "string" && typeof a1e === "number") {
ae = 0; // treat as a collapsed retain 0
a1i++;
} else if (typeof a1 === "number" && typeof a1e === "string") {
ai++;
a1e = 0; // treat as a collapsed retain 0
} else {
if (ai <= aEdits.length) { ai++; }
if (a1i <= a1Edits.length) { a1i++; }
}
if (edits.length > 0 && ae === undefined && typeof a1e === "number") {
edits.push(a1e);
continue;
}
if (typeof ae === "number") { ac += Math.abs(ae); }
if (typeof a1e === "number") { a1c += Math.abs(a1e); }
if (typeof ae === "string") {
if (ae === a1e && !divergedSinceLastInsert) {
if (ac !== a1c) {
throw new Error(`invariant: ac (${ac}) != a1c (${a1c})`);
}
edits.push(ac + ae.length);
} else {
edits.push(ac);
edits.push(-1 * ae.length);
edits.push(a1c - ac);
edits.push(ae);
ac = 0;
a1c = 0;
divergedSinceLastInsert = false;
meaningful = true;
}
} else if (ae !== a1e) {
divergedSinceLastInsert = true;
// edits.push(a1e);
}
}
if (edits.length > 0 && meaningful) {
if (!fileEdits) { fileEdits = {}; }
fileEdits[fileName] = mergeEdits(edits);
}
};
for (const f in a.edit) {
if (a.edit.hasOwnProperty(f) && a1.edit.hasOwnProperty(f)) {
subtractFileEdits(f, a.edit[f], a1.edit[f]);
}
}
return fileEdits ? { edit: fileEdits } : {};
}
import * as vscode from "vscode";
import { composeAll as composeAllWorkspaceOps, WorkspaceOp, Sel, stripFileOrBufferPathPrefix } from "libzap/lib/ot/workspace";
import { transform as transformWorkspaceOps, composeAll as composeAllWorkspaceOps, WorkspaceOp, Sel, stripFileOrBufferPathPrefix, isBufferPath } from "libzap/lib/ot/workspace";
import { applyEdits } from "libzap/lib/ot/textDoc";
import { Workspace as HandlerWorkspace } from "libzap/lib/remote/handler";
import { WorkspaceWillSaveFileParams } from "libzap/lib/remote/protocol";
import { editOpsFromContentChanges, opWithBufferEditsOmittingChangesOnDisk, workspaceEditFromOp, rangesEqual } from "./op";
import diffOps from "libzap/lib/util/diffOps";
import { editOpsFromContentChanges, opWithBufferEditsOmittingChangesOnDisk, workspaceEditFromOp, rangesEqual, subtract as subtractWorkspaceOps } from "./op";
import { IEnvironment } from "./environment";

@@ -31,3 +33,2 @@

this.toDispose.push(this.outputChannel);
this.initHooks();
}

@@ -56,5 +57,5 @@

private initHooks(): void {
this.toDispose.push(vscode.window.onDidChangeActiveTextEditor(e => this.onDidChangeActiveTextEditor(e)));
this.toDispose.push(vscode.workspace.onDidChangeTextDocument(async (e) => {
public initHooks(): void {
vscode.window.onDidChangeActiveTextEditor(e => this.onDidChangeActiveTextEditor(e), null, this.toDispose);
vscode.workspace.onDidChangeTextDocument(async (e) => {
try {

@@ -65,8 +66,8 @@ await this.onDidChangeTextDocument(e);

}
}));
this.toDispose.push(vscode.window.onDidChangeTextEditorSelection(e => this.onDidChangeTextEditorSelection(e)));
this.toDispose.push(vscode.workspace.onDidCloseTextDocument(e => this.onDidCloseTextDocument(e)));
this.toDispose.push(vscode.workspace.onDidOpenTextDocument(e => this.onDidOpenTextDocument(e)));
this.toDispose.push(vscode.workspace.onWillSaveTextDocument(e => this.onWillSaveTextDocument(e)));
this.toDispose.push(vscode.workspace.onDidSaveTextDocument(e => this.onDidSaveTextDocument(e)));
}, null, this.toDispose);
vscode.window.onDidChangeTextEditorSelection(e => this.onDidChangeTextEditorSelection(e), null, this.toDispose);
vscode.workspace.onDidCloseTextDocument(e => this.onDidCloseTextDocument(e), null, this.toDispose);
vscode.workspace.onDidOpenTextDocument(e => this.onDidOpenTextDocument(e), null, this.toDispose);
vscode.workspace.onWillSaveTextDocument(e => this.onWillSaveTextDocument(e), null, this.toDispose);
vscode.workspace.onDidSaveTextDocument(e => this.onDidSaveTextDocument(e), null, this.toDispose);

@@ -82,18 +83,59 @@ // Handle documents/editors that are open initially.

public get pendingChangesAreDone(): Promise<void> {
return this.pendingChangesDone ? this.pendingChangesDone : Promise.resolve(void 0);
}
/**
* Passing a function in EditOps is a special facility enabled for
* tests only. It is required to synthesize ops because the op
* must take into account the current document length when the op
* is being applied, and the document length can change between
* the test code's call to workspace.apply and when the op is
* actually applied.
*/
public testAllowFunctionsAsApplyOps = false;
private pendingChanges: Set<string> = new Set<string>();
private pendingChangesDone?: Promise<void>;
private applying?: Promise<void>; // resolved when the apply loop finishes
private applyingOp?: WorkspaceOp; // the op that is currently being applied
private toApply: WorkspaceOp[] = []; // ops (in order) waiting to be applied
/**
* apply applies the op to the workspace, modifying it as
* described by the op. It ensures that ops are applied in the
* order in which synchronous calls to apply are made.
*/
public async apply(op: WorkspaceOp): Promise<void> {
if (this.pendingChangesDone) {
throw new Error(`workspace already has pending changes but apply was called on ${JSON.stringify(op)} (it should not have been called until the pending changes were applied`);
this.toApply.push(op);
if (this.applying) {
return this.applying;
}
let resolvePendingChangesDone: () => void = undefined as any;
this.pendingChangesDone = new Promise<void>((resolveTmp) => {
resolvePendingChangesDone = resolveTmp;
this.applying = new Promise<void>(async (resolve, reject) => {
while (this.applyingOp = this.toApply.shift()) { // tslint:disable-line no-conditional-assignment
// Handle functions in EditOps for
// testAllowFunctionsAsApplyOps (see docstring for
// more info).
if (typeof this.applyingOp === "function") {
this.applyingOp = (this.applyingOp as () => WorkspaceOp)();
}
try {
await this.doApply(this.applyingOp);
} catch (err) {
// TODO(sqs): handle errors here; and for ease of
// debugging, consider including original stack
// trace of caller to apply.
const msg = `applying op failed: ${JSON.stringify(this.applyingOp)}: ${err}`;
console.error(msg);
this.applyingOp = undefined;
reject(err);
throw new Error(msg);
}
}
this.applying = undefined;
resolve();
});
return this.applying;
}
private async doApply(op: WorkspaceOp): Promise<void> {
if (op.save) {

@@ -146,15 +188,4 @@ this.suppressRecord = true;

this.documentDirty.set(uri.toString(), Boolean(op.edit![`#${file}`]));
if (this.pendingChanges.has(uri.toString())) {
throw new Error(`unexpected: document ${uri.toString()} has pending (not-yet-applied) changes, but apply was called again to apply more changes to it (${JSON.stringify(edit.get(uri))})`);
}
this.pendingChanges.add(uri.toString());
}
const removeFromPendingChanges = () => {
for (const [uri] of edit.entries()) {
this.pendingChanges.delete(uri.toString());
}
};
for (const [uri] of edit.entries()) {

@@ -169,4 +200,24 @@ const existing = this.suppressRecordEdits.get(uri.toString()) || [];

const editorSelsAreEmpty = vscode.window.visibleTextEditors.map(e => e.selection.isEmpty);
// During the async call to vscode.workspace.applyEdit,
// other edits (e.g., interactive edits from the user) can
// come in, and our edit could be applied to a different
// version of the document. This would yield incorrect
// text.
//
// To fix this, our onDidChangeTextDocument listener
// checks if there are any pending edits to a document,
// and if there are, it stores extra information that we
// can use to compensate for the change it made. We can
// detect whether we need to compensate after
// vscode.workspace.applyEdit's promise resolves.
const preEditDocVersions = new Map<string, number>();
for (const doc of vscode.workspace.textDocuments) {
if (edit.has(doc.uri)) {
preEditDocVersions.set(doc.uri.toString(), doc.version);
}
}
await vscode.workspace.applyEdit(edit).then((ok) => {
removeFromPendingChanges();
if (!ok) {

@@ -176,2 +227,8 @@ throw new Error(`applyWorkspaceEdit failed: ${JSON.stringify(op)}`);

if (this.subtractOp) {
const subtractOp = this.subtractOp;
this.subtractOp = undefined;
return this.doApply(subtractOp);
}
editorSelsAreEmpty.forEach((isEmpty, i) => {

@@ -185,3 +242,2 @@ const editor = vscode.window.visibleTextEditors[i];

}, (err) => {
removeFromPendingChanges();
throw new Error(`applyWorkspaceEdit failed: ${err}`);

@@ -202,5 +258,2 @@ });

}
resolvePendingChangesDone!();
this.pendingChangesDone = undefined;
}

@@ -225,9 +278,48 @@

/**
* Reset is called to reset the state of the workspace to reflect
* history. If isAttaching is true, then this method is being
* called to attach a workspace to a new ref (e.g., the user
* switches HEAD refs, reconnects to the server, etc.).
*
* NOTES
*
* There are 2 scenarios in which we must perform recovery or resolution:
*
* 1. You disconnect for a brief period then come back online. Your ops
* should be transformed against the server's history. Both arrive at
* a consistent state. This is equivalent to having a very laggy
* connection.
*
* 2. You disconnect, and while you're disconnected, someone else resets
* the ref so it restarts rev numbering from 0, has a different base
* commit, etc. Here, you can choose either "remote clobbers local"
* (zap.overwrite=false in config) or "local clobbers remote"
* (zap.overwrite=true) resolution strategies (the former is suited
* for the web client and the latter for vscode).
*
* Currently only #2 is implemented, and it is executed even when
* you disconnect for a brief period of time.
*
*/
public async reset(history: WorkspaceOp[]): Promise<void> {
const allFiles = (): { [fileName: string]: string } => {
const allFiles2: { [fileName: string]: string } = {};
for (const doc of vscode.workspace.textDocuments) {
if (doc.uri.scheme === "file") {
allFiles2[this.environment.asRelativePathInsideWorkspace(doc.uri) !] = doc.getText();
}
}
return allFiles2;
};
if (process.env.DEV) {
console.log(`Workspace.reset: history=${JSON.stringify(history)} files=${JSON.stringify(allFiles())}`);
}
try {
this.pendingChanges.clear();
this.pendingChangesDone = undefined;
this.suppressRecordEdits.clear();
this.documentDirty.clear();
this.suppressRecord = true;
for (const doc of vscode.workspace.textDocuments) {

@@ -240,5 +332,2 @@ if (this.environment.asRelativePathInsideWorkspace(doc.uri) !== null) {

while (this.pendingChangesDone) {
await this.pendingChangesDone;
}
if (history.length > 0) {

@@ -249,8 +338,6 @@ let composed = composeAllWorkspaceOps(history);

}
console.log(`Workspace.reset: going to apply composed=${JSON.stringify(history)} files=${JSON.stringify(allFiles())}`);
await this.apply(composed);
}
} catch (err) {
// Allow the workspace to be reused.
this.pendingChangesDone = undefined;
console.error(`Zap reset failed: ${err}`);

@@ -263,2 +350,65 @@

// diffBufferFilesAgainst returns an op that, when composed with
// the provided history, represents the current state of the
// workspace.
//
// For example, if the workspace has a dirty file f1 with "abc"
// and history == [{copy:{"#f":"/f"},edit:{"#f":["ab"]}}], then
// the returned op will be {edit:{"#f":[2,"c"]}}. This op is not
// applied, but it is sent back to the server to sync the server
// to our local workspace state.
public diffBufferFilesAgainst(history: WorkspaceOp[]): WorkspaceOp {
let composed = composeAllWorkspaceOps(history);
if (this.environment.automaticallyApplyingFileSystemChanges) {
composed = opWithBufferEditsOmittingChangesOnDisk(composed);
}
const existingBufferFilesInHistory = new Set<string>();
// The only way for a buffer file to exist is for there to be
// an outstanding create or copy op with the buffer file.
//
// TODO(sqs): technically there could be a rename
if (composed.create) {
for (const f of composed.create) {
if (isBufferPath(f)) {
existingBufferFilesInHistory.add(f);
}
}
}
if (composed.copy) {
Object.keys(composed.copy).forEach(dest => {
if (isBufferPath(dest)) { existingBufferFilesInHistory.add(dest); }
});
}
// Compute the diff between the history version of each buffer
// file vs. the workspace version of each unsaved file.
const diffOp: WorkspaceOp = {};
for (const f of Array.from(existingBufferFilesInHistory)) {
const strippedPath = stripFileOrBufferPathPrefix(f);
let historyText = this.environment.readTextDocumentOnDisk(this.environment.asAbsoluteURI(strippedPath));
historyText = composed.edit![f] ?
applyEdits(composed.edit![f], historyText) :
historyText; // edits can be undefined if they cancel each other out (all retain)
// historyText is now the text of the file as the server sees it.
const doc = vscode.workspace.textDocuments.find(doc => this.environment.asRelativePathInsideWorkspace(doc.uri) === strippedPath);
if (!doc) {
throw new Error(`no such file: ${strippedPath}`);
}
const currentText = doc.getText();
if (!diffOp.edit) { diffOp.edit = {}; }
diffOp.edit[f] = diffOps(historyText, currentText);
}
return diffOp;
}
public localOverwritesRemote(): boolean {
// HACK
return vscode.workspace.getConfiguration("zap").get<boolean>("overwrite");
}
private onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined): void {

@@ -297,3 +447,2 @@ if (editor) {

private onDidOpenTextDocument(doc: vscode.TextDocument) {
// If file is dirty, create the buffered file.
const fileName = this.environment.asRelativePathInsideWorkspace(doc.uri);

@@ -303,5 +452,18 @@ if (fileName === null) {

}
if (this.environment.textDocumentIsDirtyHack(doc)) {
console.log("WARNING: doc is dirty but we currently have no way of diffing in JS");
/// this.onOpListener!({ copy: { [`#${fileName}`]: `/${fileName}` } });
if (doc.isDirty) {
// If file is dirty, create the buffered file and
// determine the OT diff from the file on disk (we weren't
// listening to the change events, and they might never
// have even been sent (e.g., if this file was dirty
// because of hot exit)).
const oldText = this.environment.readTextDocumentOnDisk(doc.uri);
const newText = doc.getText();
if (oldText === newText) {
console.error(`unexpected: document ${doc.uri.toString()} is dirty, but its on-disk contents equal its in-memory contents`);
}
this.onOpListener!({
copy: { [`#${fileName}`]: `/${fileName}` },
edit: { [`#${fileName}`]: diffOps(oldText, newText) },
});
}

@@ -342,21 +504,15 @@ }

/**
* concurrentOpWhileApplyingOp is the operation (possibly composed
* of multiple sequential operations) that represents all of the
* changes received by the onDidChangeTextDocument listener while
* we were in the doApply method's vscode.workspace.applyEdit
* call. It is used by the doApply method to compensate for
* concurrent changes that yield incorrect document contents.
*/
// private concurrentOpWhileApplyingOp?: WorkspaceOp;
private documentDirty = new Map<string, boolean>();
private async onDidChangeTextDocument(ev: vscode.TextDocumentChangeEvent): Promise<void> {
if (this.suppressRecord) {
return;
}
const origText = ev.document.getText();
if (this.pendingChangesDone) {
await this.pendingChangesDone;
}
const newText = ev.document.getText();
if (origText !== newText) {
console.log(`DEV NOTE: Good thing we waited for pendingChangesDone! The document text changed after some just-applied operations. ${JSON.stringify(origText)} !== ${JSON.stringify(newText)}`);
}
if (this.pendingChanges.has(ev.document.uri.toString())) {
throw new Error(`pending, unapplied changes exist for ${ev.document.uri.toString()}; this indicates the presence of a race condition or other bug`);
}
const doc = ev.document;

@@ -368,2 +524,6 @@ const fileName = this.environment.asRelativePathInsideWorkspace(doc.uri);

if (this.suppressRecord) {
return;
}
// Ignore some changes.

@@ -411,3 +571,3 @@ if (ev.contentChanges.length === 1) {

const op: WorkspaceOp = {};
let op: WorkspaceOp = {};
if (wasDirty && !isDirty && !doc.isDirty) {

@@ -423,4 +583,51 @@ op.delete = [`#${fileName}`];

// Handle when we're in the middle of applying an op and we
// get a change.
if (this.applyingOp) {
const {a1, b1} = transformWorkspaceOps(this.applyingOp, op);
op = b1;
// SUBTRACTING OT OPERATIONS
//
// a1 is what doApply *should* apply, but there's no way
// to cancel it or to modify its edit after it has already
// started.
//
// We need to produce the op X that doApply needs to
// execute such that the following composition of ops
// yields the correct document state:
//
// op + this.applyingOp + X
//
// Note that this sequence is equal to:
//
// == op + a1
//
// which means:
//
// this.applyingOp + X == a1
//
// For example, suppose:
//
// original doc := "a@A"
// op := [1,b,2]
// this.applyingOp := [3,B]
// desired doc := "ab@AB"
//
// We can compute (via transformWorkspaceOps) a1 :=
// [4,B]. So, then we can manually solve for X in:
//
// this.applyingOp + X == a1
// [3,B] + X == [4,B]
//
// By intuition, we can determine that X :=
// [3,-1,1,"B"]. Another way to think about this is to
// determine what edit op would make "ab@BA" into "ab@AB"
// (it is the same X).
this.subtractOp = subtractWorkspaceOps(this.applyingOp, a1);
}
const msg = `Client=${process.env.ZAP_E2E_NAME} Change ${doc.uri.toString()}: ${JSON.stringify(op)} dirty=${isDirty} wasDirty=${wasDirty} contents=${JSON.stringify(doc.getText())}} changes=${JSON.stringify(contentChangesToProcess)}`;
if (process.env.DEV) {
if (process.env.DEV && process.env.DEV_NOISY) {
console.log(msg);

@@ -432,2 +639,4 @@ this.outputChannel.appendLine(msg);

private subtractOp?: WorkspaceOp;
private onDidSaveTextDocument(doc: vscode.TextDocument) {

@@ -434,0 +643,0 @@ if (this.suppressRecord) { return; }

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc