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
2
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.60 to 0.0.61

out/src/fileTreeService.d.ts

7

out/src/controller.d.ts

@@ -1,2 +0,1 @@

import * as vscode from "vscode";
import { Client, ClientOptions, ServerOptions } from "libzap/lib/remote/client";

@@ -16,10 +15,10 @@ import { Handler } from "libzap/lib/remote/handler";

private attach();
private reset(mergeStrategy);
dispose(): void;
queryRefInfo(refID: RefIdentifier): Thenable<RefInfoResult>;
queryRepoInfo(repo: string): Thenable<RepoInfoResult>;
start(): Thenable<void>;
start(): Promise<void>;
stop(): Thenable<void>;
private resolvedRefUpdateEmitter;
readonly onDidChangeResolvedRef: vscode.Event<string>;
addWorkspace(): Promise<void>;
showRefSelectMenu(): Thenable<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 vscode = require("vscode");

@@ -13,4 +21,3 @@ const client_1 = require("libzap/lib/remote/client");

this.toDispose = [];
this.resolvedRefUpdateEmitter = new vscode.EventEmitter();
this.client = new client_1.Client(this.serverOptions, this.clientOptions);
this.client = new client_1.Client(this.environment.userID, this.serverOptions, this.clientOptions);
this.handler = new handler_1.Handler(this.client);

@@ -39,3 +46,3 @@ this.toDispose.push(this.handler);

this.client.onDidChangeState((event) => {
if (event.newState === client_1.State.Running) {
if (event.newState === client_1.State.Running && this.environment.zapRef) {
return this.attach();

@@ -48,2 +55,10 @@ }

});
// Keep merge strategy configuration setting up-to-date.
let overwrite = vscode.workspace.getConfiguration("zap").get("overwrite");
vscode.workspace.onDidChangeConfiguration(() => {
if (overwrite !== vscode.workspace.getConfiguration("zap").get("overwrite")) {
overwrite = vscode.workspace.getConfiguration("zap").get("overwrite");
this.attach();
}
}, null, this.toDispose);
}

@@ -61,7 +76,14 @@ attach() {

ref: this.environment.zapRef,
}, this.workspace, mergeStrategy)
.then(() => null, (err) => {
}, this.workspace, mergeStrategy).then(() => null, (err) => {
console.error(`Error watching repo: ${err} ${err.stack}`);
});
}
reset(mergeStrategy) {
return this.handler.resetWorkspaceRefState({
repo: this.environment.repo,
ref: this.environment.zapRef,
}, this.workspace, mergeStrategy).then(() => null, (err) => {
console.error(`Error watching repo: ${err} ${err.stack}`);
});
}
dispose() {

@@ -80,8 +102,11 @@ if (this.client.needsStop()) {

start() {
if (!this.client.needsStart()) {
return Promise.resolve();
}
const { initialStart, disposable } = this.client.start();
this.toDispose.push(disposable);
return initialStart;
return __awaiter(this, void 0, void 0, function* () {
yield this.client.clearStartFailedState();
if (!this.client.needsStart()) {
return;
}
const { initialStart, disposable } = this.client.start();
this.toDispose.push(disposable);
return initialStart;
});
}

@@ -94,22 +119,39 @@ stop() {

}
get onDidChangeResolvedRef() { return this.resolvedRefUpdateEmitter.event; }
addWorkspace() {
return __awaiter(this, void 0, void 0, function* () {
try {
yield this.client.sendRequest(protocol_1.WorkspaceAddRequest.type, { dir: vscode.workspace.rootPath });
vscode.window.setStatusBarMessage("Workspace added", 2000);
yield this.attach();
Promise.resolve();
}
catch (err) {
vscode.window.showErrorMessage(err);
Promise.reject(err);
}
});
}
showRefSelectMenu() {
return vscode.window.showQuickPick(this.client.sendRequest(protocol_1.RefListRequest.type, void 0).then((refs) => {
const items = [];
let canClobberRemote = false;
let matchingRemoteRegex = `refs\/remotes\/(.*?)\/(${this.environment.zapBranch})`;
for (const ref of refs) {
if (ref.repo === this.environment.repo) {
if (ref.ref.match(matchingRemoteRegex)) {
canClobberRemote = true;
continue;
}
if (ref.repo === this.environment.repo && !(ref.ref === "HEAD" && ref.target === this.environment.zapBranch)) {
let onSelect = undefined;
let label = ref.ref;
if (this.environment.zapRef === ref.ref) {
let detail = undefined;
if (this.environment.zapBranch === ref.ref) {
label = "$(check) " + label;
detail = "Current branch";
onSelect = () => vscode.window.showInformationMessage(`Zap branch ${ref.ref} is already active.`);
}
if (ref.ref.startsWith("refs/remotes/")) {
label = "$(circle-slash) " + label;
onSelect = () => vscode.window.showErrorMessage("Switching to a remote-tracking Zap branch is not allowed.");
}
items.push({
label,
description: ref.target ? `$(arrow-right) ${ref.target}` : `${ref.rev} revisions $(git - branch) ${ref.gitBranch}@${ref.gitBase.slice(0, 6)} `,
detail: ref.watchers ? `Watchers: ${ref.watchers.join(" ")} ` : undefined,
description: ref.watchers ? `Watchers: ${ref.watchers.join(" ")} ` : "No current watchers",
detail: detail || "Switch to this branch and start editing",
onSelect: onSelect ? onSelect : () => this.environment.zapRef = ref.ref,

@@ -119,2 +161,28 @@ });

}
if (this.environment.zapRef === "HEAD") {
items.push({
label: "$(x) Clobber Local Changes",
description: "Resets the zap state to match the remote state",
onSelect: () => {
this.reset(handler_1.MergeStrategy.RemoteClobbersLocal);
},
});
if (canClobberRemote) {
items.push({
label: "$(x) Clobber Remote Changes",
description: "Overwrites the zap remote branch with your current state",
onSelect: () => {
this.reset(handler_1.MergeStrategy.LocalClobbersRemote);
},
});
}
items.push({
label: "$(check) Authenticate Zap",
description: "Authenticates Zap with a Sourcegraph access token",
detail: "Copy and paste token from https://sourcegraph.com/-/show-auth.",
onSelect: () => {
vscode.commands.executeCommand("zap.auth");
},
});
}
items.push({

@@ -121,0 +189,0 @@ label: "$(x) Turn Zap Off",

@@ -179,2 +179,16 @@ "use strict";

}
case "closeDocument":
{
const uri = this.asAbsoluteURI(params.closeDocument);
this.logDebugData("Action: closeDocument", uri.toString());
const editor = vscode.window.activeTextEditor;
if (!editor) {
throw new Error(`closeDocument ${uri.toString()}: no active text editor`);
}
if (editor.document.uri.toString() !== uri.toString()) {
throw new Error(`closeDocument ${uri.toString()}: active text editor has different document ${editor.document.uri.toString()}`);
}
yield vscode.commands.executeCommand("workbench.action.closeActiveEditor");
return Promise.resolve({});
}
case "saveDocument":

@@ -187,2 +201,11 @@ {

}
case "revertDocument":
{
const uri = this.asAbsoluteURI(params.revertDocument);
this.logDebugData("Action: revertDocument", uri.toString());
const doc = yield vscode.workspace.openTextDocument(uri);
yield vscode.commands.executeCommand("workbench.action.files.revert", doc.uri);
yield sleep(300);
return Promise.resolve({});
}
case "typeText":

@@ -284,3 +307,3 @@ {

return "warning";
case "":
case "white":
return "ok";

@@ -287,0 +310,0 @@ default:

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

onDidChangeZapRef: vscode.Event<string>;
/**
* zapBranch is the current Zap branch name. If zapRef is "HEAD"
* or some other symbolic ref, zapBranch is the target ref name of
* zapRef. Otherwise they are equal.
*/
readonly zapBranch: string | undefined;
/**
* onDidChangeZapBranch is fired (with the new Zap branch) whenever the
* workspace's Zap branch changes.
*/
onDidChangeZapBranch: vscode.Event<string | undefined>;
asRelativePathInsideWorkspace(uri: vscode.Uri): string | null;

@@ -29,5 +40,24 @@ asAbsoluteURI(fileName: string): vscode.Uri;

readTextDocumentOnDisk(uri: vscode.Uri): string;
revertTextDocument2(doc: vscode.TextDocument): Thenable<any>;
revertTextDocument(doc: vscode.TextDocument): Thenable<void>;
/**
* revertTextDocumentToBase reverts doc to its contents as of the
* beginning of the Zap ref.
*/
revertTextDocumentToBase(doc: vscode.TextDocument): Thenable<void>;
openChannel(id: string): Thenable<MessageStream>;
/**
* userID is (1) sent to the server to (non-securely) identify
* this client and (2) associated with text selections made by
* this client (so other users can hover a selection to see who
* it's from).
*
* It is typically of the form "user@host" or "user@app", such as
* "alice@vscode". Its value is treated opaquely by Zap; it is for
* human consumption only right now.
*/
readonly userID: string;
/**
* isRunning is the current state of the zap client server
*/
isRunning: boolean;
}

@@ -0,3 +1,34 @@

import * as vscode from "vscode";
import { MessageStream } from "libzap/lib/remote/client";
import { IEnvironment } from "./environment";
export declare class VSCodeEnvironment implements IEnvironment {
private _zapRef;
private _isRunning;
readonly rootURI: vscode.Uri | undefined;
readonly repo: string;
private zapRefChangeEmitter;
zapRef: string;
readonly onDidChangeZapRef: vscode.Event<string>;
private _zapBranch;
private zapBranchChangeEmitter;
readonly onDidChangeZapBranch: vscode.Event<string>;
readonly zapBranch: string;
/**
* _updateZapBranchInternal updates the environment's Zap
* branch. It should only be called from within vscode-zap and
* only in response to an actual observed update of the HEAD ref.
*/
_updateZapBranchInternal(branch: string): void;
asRelativePathInsideWorkspace(uri: vscode.Uri): string | null;
asAbsoluteURI(fileName: string): vscode.Uri;
automaticallyApplyingFileSystemChanges: boolean;
readTextDocumentOnDisk(uri: vscode.Uri): string;
revertTextDocument(doc: vscode.TextDocument): Thenable<void>;
testOnly_fakeDocumentBaseContents?: string;
revertTextDocumentToBase(doc: vscode.TextDocument): Promise<void>;
openChannel(id: string): Thenable<MessageStream>;
readonly userID: string;
isRunning: boolean;
}
declare var _default: IEnvironment;
export default _default;
"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 vscode = require("vscode");

@@ -12,6 +20,7 @@ const fs_1 = require("fs");

this.zapRefChangeEmitter = new vscode.EventEmitter();
this.zapBranchChangeEmitter = new vscode.EventEmitter();
this.automaticallyApplyingFileSystemChanges = true;
}
get rootURI() {
return vscode.Uri.file(vscode.workspace.rootPath);
return vscode.workspace.rootPath ? vscode.Uri.file(vscode.workspace.rootPath) : undefined;
}

@@ -33,3 +42,3 @@ get repo() {

if (ref !== "HEAD") {
console.warn(`Attempting to set zap ref to ${ref} instead of HEAD which is currently unsupported in the extension`);
vscode.window.showWarningMessage(`Attempting to set Zap ref to ${ref} instead of HEAD, which is currently unsupported in the extension`);
return;

@@ -41,2 +50,15 @@ }

get onDidChangeZapRef() { return this.zapRefChangeEmitter.event; } // never fires
get onDidChangeZapBranch() { return this.zapBranchChangeEmitter.event; }
get zapBranch() {
return this._zapBranch;
}
/**
* _updateZapBranchInternal updates the environment's Zap
* branch. It should only be called from within vscode-zap and
* only in response to an actual observed update of the HEAD ref.
*/
_updateZapBranchInternal(branch) {
this._zapBranch = branch;
this.zapBranchChangeEmitter.fire(branch);
}
asRelativePathInsideWorkspace(uri) {

@@ -60,11 +82,83 @@ if (uri.scheme !== "file") {

}
revertTextDocument2(doc) {
return vscode.commands.executeCommand("workbench.action.files.revert", doc.uri);
}
revertTextDocument(doc) {
const data = fs_1.readFileSync(doc.uri.fsPath, "utf8");
const edit = new vscode.WorkspaceEdit();
edit.replace(doc.uri, new vscode.Range(new vscode.Position(0, 0), doc.positionAt(doc.getText().length)), data);
return vscode.workspace.applyEdit(edit).then(() => doc.save());
// TODO(sqs): to run workbench.action.files.revert, the
// document needs to be the foreground, for some reason. this
// causes a visual flicker where we switch to each document
// before reverting it.
//
// Also, for some reason, waiting on the executeCommand
// thenable is not sufficient to ensure the document has been
// reverted. So, set up an extra listener.
let waitForReverted;
let cleanup;
if (this.readTextDocumentOnDisk(doc.uri) === doc.getText()) {
// Can't detect when revert completes because there will be no
// onDidChangeTextDocument fired (because contents won't change).
waitForReverted = Promise.resolve();
cleanup = () => void 0;
}
else {
let disposable;
cleanup = () => {
if (disposable) {
disposable.dispose();
disposable = undefined;
}
};
waitForReverted = new Promise((resolve) => {
disposable = vscode.workspace.onDidChangeTextDocument((e) => {
if (e.document.uri.toString() === doc.uri.toString()) {
resolve();
cleanup();
}
});
});
}
return vscode.window.showTextDocument(doc).then(editor => {
const preSel = editor.selections;
return vscode.commands.executeCommand("workbench.action.files.revert", doc.uri).then(() => {
// Debug helper: log if waitForReverted takes a long
// time to be resolved.
let done = false;
setTimeout(() => {
if (!done) {
console.log(`Warning: waitForReverted is taking a long time to be resolved (document: ${doc.uri.toString()}`);
}
}, 1000);
return waitForReverted.then(() => {
done = true;
if (editor && preSel) {
editor.selections = preSel; // restore original selection - TODO(sqs): this is not generally accurate
}
cleanup();
});
}, err => {
cleanup();
console.error(`Error reverting document ${doc.uri.toString()}: ${err}`);
throw err;
});
});
}
revertTextDocumentToBase(doc) {
return __awaiter(this, void 0, void 0, function* () {
// Dummy implementation only used during tests.
if (this.testOnly_fakeDocumentBaseContents === undefined) {
throw new Error("revertTextDocumentToBase in environment.vscode.ts is only for use in tests");
}
const editor = vscode.window.activeTextEditor;
if (!editor || editor.document.uri.toString() !== doc.uri.toString()) {
throw new Error(`document ${doc.uri.toString()} is not in active editor`);
}
const editOK = yield editor.edit(editBuilder => {
editBuilder.replace(new vscode.Range(new vscode.Position(0, 0), doc.positionAt(doc.getText().length)), this.testOnly_fakeDocumentBaseContents);
});
if (!editOK) {
throw new Error("edit failed");
}
const saveOK = yield doc.save();
if (!saveOK) {
throw new Error("save failed");
}
});
}
openChannel(id) {

@@ -89,5 +183,13 @@ if (id !== "zap") {

}
get userID() { return (process.env.ZAP_E2E_NAME || process.env.USER || "anonymous") + "@vscode"; }
set isRunning(status) {
this._isRunning = status;
}
get isRunning() {
return this._isRunning;
}
}
exports.VSCodeEnvironment = VSCodeEnvironment;
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = new VSCodeEnvironment();
//# sourceMappingURL=environment.vscode.js.map

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

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

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

const status_1 = require("./status");
const protocol_1 = require("libzap/lib/remote/protocol");
function activate(env, ctx) {

@@ -27,4 +26,4 @@ if (!vscode.workspace.rootPath) {

controller.client.registerHandler(status_1.StatusHandler.id, statusHandler);
controller.onDidChangeResolvedRef(activeRef => {
statusHandler.setActiveRef(activeRef);
env.onDidChangeZapBranch(branch => {
statusHandler.setBranch(branch);
}, null, ctx.subscriptions);

@@ -38,2 +37,5 @@ ctx.subscriptions.push(vscode.commands.registerCommand("zap.ref.select", () => {

})));
ctx.subscriptions.push(vscode.commands.registerCommand("zap.workspace.add", () => __awaiter(this, void 0, void 0, function* () {
yield controller.addWorkspace();
})));
const updateEnabled = (enabled) => {

@@ -55,16 +57,2 @@ if (enabled) {

updateEnabled(isEnabled());
ctx.subscriptions.push(vscode.commands.registerCommand("zap.enableAndAddWorkspace", () => __awaiter(this, void 0, void 0, function* () {
try {
yield vscode.workspace.getConfiguration("zap").update("enable", true, true);
yield startController(controller);
}
catch (err) {
if (err.code === protocol_1.ErrorCode.ErrorCodeWorkspaceExists) {
return;
}
status_1.outputChannel.appendLine(`Enabling Zap in this workspace failed: ${err}`);
status_1.outputChannel.show();
vscode.window.showErrorMessage("Enabling Zap in this workspace failed.");
}
})));
return controller;

@@ -77,15 +65,12 @@ }

// provide a mock connection.
return vscode.workspace.getConfiguration("zap").get("enable") && !vscode.workspace.getConfiguration("zap").get("_test") && !process.env.CI;
return Boolean(vscode.workspace.getConfiguration("zap").get("enable") && !vscode.workspace.getConfiguration("zap").get("_test") && !process.env.CI);
}
exports.isEnabled = isEnabled;
function isWebEnv() {
return process.env.NODE_ENV && !vscode.workspace.getConfiguration("zap").get("_test") && !process.env.CI;
}
exports.isWebEnv = isWebEnv;
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.`);
return controller.start().then(() => {
if (!process.env.CI && !process.env.ZAP_E2E) {
vscode.workspace.getConfiguration("zap").update("enable", true, true);
}
}, (err) => {
status_1.outputChannel.appendLine(`Error starting the controller: ${err}`);
});

@@ -96,4 +81,8 @@ }

function stopController(controller) {
return controller.stop().then(() => { }, err => {
console.error(`Zap failed to stop: ${err}`);
return controller.stop().then(() => {
if (!process.env.CI && !process.env.ZAP_E2E) {
vscode.workspace.getConfiguration("zap").update("enable", false, true);
}
}, err => {
status_1.outputChannel.appendLine(`Zap failed to stop: ${err}`);
});

@@ -100,0 +89,0 @@ }

@@ -21,6 +21,17 @@ // This is the extension's entrypoint used by vscode. Things in here

ctx.subscriptions.push(vscode.commands.registerCommand("zap.install", () => zapLocalServer.install()));
ctx.subscriptions.push(vscode.commands.registerCommand("zap.auth", () => zapLocalServer.authenticateUser()));
web_1.activate(ctx);
return extension_common_1.activate(environment_vscode_1.default, ctx);
const controller = extension_common_1.activate(environment_vscode_1.default, ctx);
if (controller) {
// Ensure (IEnvironment).{zapBranch,onDidChangeZapBranch}
// reflect the HEAD ref's target.
controller.handler.onDidUpdateSymbolicRef(e => {
if (e.ref === environment_vscode_1.default.zapRef) {
environment_vscode_1.default._updateZapBranchInternal(e.newTarget);
}
}, null, ctx.subscriptions);
}
return controller;
}
exports.activate = activate;
//# sourceMappingURL=extension.vscode.js.map
export declare function promptToInstall(): Promise<boolean>;
export declare function install(): Promise<void>;
export declare function authenticateUser(): Promise<void>;
export declare function zapExecutable(): string;

@@ -13,2 +13,3 @@ "use strict";

const status_1 = require("./status");
const open = require("open"); // tslint:disable-line no-var-requires
function promptToInstall() {

@@ -51,6 +52,35 @@ return new Promise((resolve, reject) => {

exports.install = install;
function authenticateUser() {
open("https://sourcegraph.com/-/show-auth");
let authPrompt = {
placeHolder: "Enter your Sourcegraph auth token found at: https://sourcegraph.com/-/show-auth",
ignoreFocusOut: true,
};
return new Promise((resolve, reject) => {
return vscode.window.showInputBox(authPrompt).then((result) => {
if (!result) {
resolve();
return;
}
sp.spawn(zapExecutable(), ["auth", result]);
const checkConfig = sp.spawn(zapExecutable(), ["config", "--global", "--get", "auth.token"]);
checkConfig.stdout.on("data", () => {
resolve();
});
checkConfig.stderr.on("data", (err) => {
reject(err);
});
vscode.window.setStatusBarMessage("Access token set", 5000);
});
});
}
exports.authenticateUser = authenticateUser;
function zapExecutable() {
return vscode.workspace.getConfiguration("zap").get("executable");
const value = vscode.workspace.getConfiguration("zap").get("executable");
if (!value) {
throw new Error("no zap.executable config set");
}
return value;
}
exports.zapExecutable = zapExecutable;
//# sourceMappingURL=localServer.js.map
import * as vscode from "vscode";
import { EditOps } from "libzap/lib/ot/textDoc";
import { WorkspaceOp } from "libzap/lib/ot/workspace";
import { WorkspaceOp, FileEdits } from "libzap/lib/ot/workspace";
import { IEnvironment } from "./environment";

@@ -8,3 +8,7 @@ export declare function sortTextDocumentContentChangeEvents(changes: vscode.TextDocumentContentChangeEvent[]): vscode.TextDocumentContentChangeEvent[];

export declare function editOpsFromContentChanges(doc: vscode.TextDocument, changes: vscode.TextDocumentContentChangeEvent[]): EditOps;
export declare function workspaceEditFromOp(docs: vscode.TextDocument[], env: IEnvironment, op: WorkspaceOp): vscode.WorkspaceEdit | null;
export declare function workspaceEditFromOp(docs: vscode.TextDocument[], isDirty: Map<string, boolean>, env: IEnvironment, op: WorkspaceOp, currentPending: FileEdits): {
edit: vscode.WorkspaceEdit | null;
pendingFileEdits: FileEdits;
};
export declare function resolvePendingEditOpsToWorkspaceEdit(doc: vscode.TextDocument, editOps: EditOps): vscode.WorkspaceEdit;
export declare function addEditOpsToWorkspaceEdit(workspaceEdit: vscode.WorkspaceEdit, doc: vscode.TextDocument, editOps: EditOps): void;

@@ -21,9 +25,1 @@ export declare function workspaceEditsEqual(a: vscode.WorkspaceEdit, b: vscode.WorkspaceEdit): boolean;

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;

@@ -111,5 +111,5 @@ "use strict";

exports.editOpsFromContentChanges = editOpsFromContentChanges;
function workspaceEditFromOp(docs, env, op) {
function workspaceEditFromOp(docs, isDirty, env, op, currentPending) {
if (!op.edit) {
return null;
return { edit: null, pendingFileEdits: currentPending };
}

@@ -119,10 +119,24 @@ let workspaceEdit = new vscode.WorkspaceEdit();

if (op.edit.hasOwnProperty(path)) {
const edits = op.edit[path];
let edits = op.edit[path];
const strippedPath = workspace_1.stripFileOrBufferPathPrefix(path);
const doc = docs.find(doc => env.asRelativePathInsideWorkspace(doc.uri) === strippedPath);
if (!doc) {
console.log("No such file:", strippedPath);
if (currentPending && currentPending[path]) {
let p = currentPending[path].concat(edits);
const mergedEdit = textDoc_1.mergeEdits(p);
currentPending[path] = mergedEdit;
}
else {
currentPending[path] = edits;
}
continue;
}
const isOrWillBecomeDirty = doc.isDirty || (workspace_1.isFilePath(path) && op.copy && op.copy[workspace_1.fileToBufferPath(path)] === path);
// otherwise add them up!
if (currentPending[path]) {
// Concat edits array together
const combined = currentPending[path].concat(edits);
edits = textDoc_1.mergeEdits(combined);
delete currentPending[path];
}
const isOrWillBecomeDirty = Boolean(doc.isDirty || (workspace_1.isFilePath(path) && op.copy && op.copy[workspace_1.fileToBufferPath(path)] === path));
if (!workspace_1.isBufferPath(path) && (env.automaticallyApplyingFileSystemChanges || isOrWillBecomeDirty)) {

@@ -134,4 +148,3 @@ // Writes to the underlying disk have already been applied to the editor state by

}
// TODO(sqs): this used to have textDocumentIsDirtyHack to make the web version work
const bufferFileExists = doc.isDirty || (op.copy && op.copy[`#${strippedPath}`] === `/${strippedPath}`);
const bufferFileExists = doc.isDirty || isDirty.get(doc.uri.toString()) || (op.copy && op.copy[`#${strippedPath}`] === `/${strippedPath}`);
if (workspace_1.isBufferPath(path) && !bufferFileExists) {

@@ -145,5 +158,29 @@ throw new Error(`edit to nonexistent buffer file ${path} (either the file does not exist at all, or it is not dirty and therefore only has a disk/non-buffer file)`);

}
return workspaceEdit.entries().length > 0 ? workspaceEdit : null;
return { edit: workspaceEdit.entries().length > 0 ? workspaceEdit : null, pendingFileEdits: currentPending };
}
exports.workspaceEditFromOp = workspaceEditFromOp;
function resolvePendingEditOpsToWorkspaceEdit(doc, editOps) {
let workspaceEdit = new vscode.WorkspaceEdit();
let ret = 0; // OT retain count
let del = 0; // OT delete count
for (let edit of editOps) {
if (typeof edit === "string") {
// insert
workspaceEdit.insert(doc.uri, doc.positionAt(ret + del), edit);
}
else if (edit < 0) {
// delete
workspaceEdit.delete(doc.uri, new vscode.Range(doc.positionAt(ret + del), doc.positionAt(ret + del + -1 * edit)));
del += -1 * edit;
}
else if (edit > 0) {
ret += edit; // retain
}
}
if (ret + del !== doc.getText().length) {
throw new Error(`base length must equal document length (${ret + del} != ${doc.getText().length}) [doc ${doc.uri.toString()} contents ${JSON.stringify(doc.getText())}, op ${JSON.stringify(editOps)}]`);
}
return workspaceEdit;
}
exports.resolvePendingEditOpsToWorkspaceEdit = resolvePendingEditOpsToWorkspaceEdit;
function addEditOpsToWorkspaceEdit(workspaceEdit, doc, editOps) {

@@ -159,6 +196,6 @@ let ret = 0; // OT retain count

// delete
workspaceEdit.delete(doc.uri, new vscode.Range(doc.positionAt(ret), doc.positionAt(ret + -1 * edit)));
workspaceEdit.delete(doc.uri, new vscode.Range(doc.positionAt(ret + del), doc.positionAt(ret + del + -1 * edit)));
del += -1 * edit;
}
else if (edit) {
else if (edit > 0) {
ret += edit; // retain

@@ -318,95 +355,2 @@ }

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

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

dispose(): void;
setActiveRef(ref: string): void;
setBranch(branch: string | undefined): void;
private setStatus(status, data?);

@@ -32,0 +32,0 @@ private handleShowStatusNotification(params);

@@ -27,6 +27,8 @@ "use strict";

this.item.show();
this.setStatus(Status.Starting);
if (!vscode.workspace.getConfiguration("zap").get("enable")) {
this.setStatus(Status.ServerUnavailable);
}
else {
this.setStatus(Status.Starting);
}
this.remoteClient.onDidChangeState((event) => {

@@ -56,10 +58,10 @@ exports.outputChannel.appendLine(`Status: ${client_1.State[event.oldState]} ⟶ ${client_1.State[event.newState]}`);

dispose() {
// noop
this.item.dispose();
}
setActiveRef(ref) {
this.setStatus(Status.OK, { branch: ref });
setBranch(branch) {
this.setStatus(Status.OK, { branch });
}
setStatus(status, data) {
if (data) {
if (data.branch) {
if (data.branch && data.branch !== "HEAD") {
this.lastBranch = data.branch;

@@ -70,21 +72,25 @@ }

case Status.Starting:
this.item.color = "";
this.item.text = "$(zap) $(ellipses)";
this.item.tooltip = "";
const branchText = this.lastBranch || "$(ellipses)";
this.item.color = "white";
this.item.text = `$(zap) ${branchText}`;
this.item.command = "zap.restart";
this.environment.isRunning = true;
break;
case Status.ServerUnavailable:
this.item.color = "";
this.item.color = "red";
this.item.text = "$(zap) Off";
this.item.tooltip = "Start Zap and enable in workspace";
this.item.command = "zap.enableAndAddWorkspace";
this.item.tooltip = "Restart Zap";
this.item.command = "zap.restart";
this.environment.isRunning = false;
break;
case Status.WorkspaceNotAddedOnServer:
this.item.color = "";
this.item.color = "white";
this.item.text = "$(zap) Off";
this.item.tooltip = "Enable Zap in this workspace";
this.item.command = "zap.workspace.add";
this.environment.isRunning = false;
break;
case Status.OK:
this.item.color = ""; // default color
this.item.text = `$(zap) ${this.lastBranch}`;
this.item.color = "white"; // default color
this.item.text = `$(zap) ${this.lastBranch || "Select a Branch"}`;
this.item.tooltip = "";

@@ -94,9 +100,4 @@ // TODO(sqs): make this a menu with the choice to remove

this.item.command = "zap.ref.select";
this.environment.isRunning = true;
break;
case Status.Running:
this.item.color = "white";
this.item.text = `$(zap) Select a Branch`;
this.item.tooltip = "";
this.item.command = "zap.ref.select";
break;
case Status.ServerWarning:

@@ -107,2 +108,3 @@ this.item.color = "yellow";

this.item.command = "";
this.environment.isRunning = false;
break;

@@ -114,3 +116,11 @@ case Status.ServerError:

this.item.command = "";
this.environment.isRunning = false;
break;
case Status.Running:
this.item.color = "white";
this.item.text = `$(zap) Select a Branch`;
this.item.tooltip = "";
this.item.command = "zap.ref.select";
this.environment.isRunning = true;
break;
}

@@ -117,0 +127,0 @@ this.item.show();

import * as vscode from "vscode";
export declare function activate(context: vscode.ExtensionContext): void;
export declare function openInBrowser(uri: vscode.Uri, sel: vscode.Selection | null, trackingEventName: "OpenFile" | "OpenAtCursor"): Promise<boolean | void>;
export declare function openInBrowser(uri: vscode.Uri, sel: vscode.Selection | null, trackingEventName: "OpenFile" | "OpenAtCursor"): Promise<boolean>;
export declare function openWorkspaceInBrowser(): Promise<void>;
"use strict";
var __assign = (this && this.__assign) || Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {

@@ -17,2 +25,3 @@ return new (P || (P = Promise))(function (resolve, reject) {

context.subscriptions.push(vscode.commands.registerTextEditorCommand("zap.web.openInBrowser.cursor", openAtCursor));
context.subscriptions.push(vscode.commands.registerCommand("zap.web.openWorkspaceInBrowser", openWorkspaceInBrowser));
}

@@ -33,18 +42,37 @@ exports.activate = activate;

// the repo (can use workspaceDirInRepo from vscode-sourcegraph).
return workspaceBrowserURL().then((identifier) => {
let url = `${identifier.url}/-/blob/${vscode.workspace.asRelativePath(uri)}?_event=${trackingEventName}&_source=${versionString()}&tmpZapRef=${identifier.ref}#${sel ? formatSelection(sel) : ""}`;
open(url);
return true;
});
}
exports.openInBrowser = openInBrowser;
function workspaceBrowserURL() {
return getRemoteRefID().then(remoteInfo => {
let url = vscode.workspace.getConfiguration("zap").get("web.url") +
"/${REPO}@${GITBRANCH}/-/blob/${PATH}${QUERY}#${LINE}"
"/${REPO}@${GITBRANCH}"
.replace("${REPO}", remoteInfo.repo)
.replace("${GITBRANCH}", remoteInfo.gitBranch)
.replace("${PATH}", vscode.workspace.asRelativePath(uri))
.replace("${QUERY}", `?_event=${trackingEventName}&_source=${versionString()}&tmpZapRef=${remoteInfo.ref}`)
.replace("${LINE}", sel ? formatSelection(sel) : "");
open(url);
return true;
.replace("${GITBRANCH}", remoteInfo.gitBranch);
return __assign({}, remoteInfo, { url });
}).catch(err => {
if (err === "Upstream branch not configured") {
vscode.window.showErrorMessage("Missing upstream branch. Run 'zap checkout -upstream origin -overwrite <your_branch>@<your_unix_user_name>");
return;
}
status_1.outputChannel.appendLine(`Error opening Web browser to URL: ${err}`);
vscode.window.showErrorMessage("Opening your Web browser failed.");
vscode.window.showInformationMessage("Enable Zap to open your current position in Sourcegraph.", "Enable Zap").then(choice => {
if (choice === "Enable Zap") {
if (!process.env.CI && !process.env.ZAP_E2E) {
vscode.workspace.getConfiguration("zap").update("enable", true, true);
return;
}
}
});
return null;
});
}
exports.openInBrowser = openInBrowser;
function openWorkspaceInBrowser() {
return workspaceBrowserURL().then((identifier) => open(`${identifier.url}?_event=OpenWorkspace&_source=${versionString()}&tmpZapRef=${identifier.ref}`));
}
exports.openWorkspaceInBrowser = openWorkspaceInBrowser;
// getRemoteRefID queries the server to find the remote ref for the

@@ -60,2 +88,5 @@ // current workspace's HEAD. This is necessary because (e.g.) the

const repoInfo = yield controller.queryRepoInfo(localRepoName);
if (!repoInfo.refs) {
return Promise.reject("Upstream branch not configured");
}
const refUpstreamRemoteName = repoInfo.refs[refInfo.target].upstream;

@@ -62,0 +93,0 @@ return {

@@ -0,1 +1,2 @@

import * as vscode from "vscode";
import { WorkspaceOp } from "libzap/lib/ot/workspace";

@@ -6,3 +7,3 @@ import { Workspace as HandlerWorkspace } from "libzap/lib/remote/handler";

export declare class Workspace implements HandlerWorkspace {
private environment;
environment: IEnvironment;
private onOpListener?;

@@ -15,2 +16,4 @@ private onResetListener?;

private suppressRecordEdits;
private fileTreeService;
private pendingWorkspaceFileEdits;
private outputChannel;

@@ -27,14 +30,7 @@ constructor(environment: IEnvironment);

};
/**
* 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 origApplyingOp?;
private applyingOp?;
private toApply;
private appliedOp?;
private toApply?;
/**

@@ -46,2 +42,3 @@ * apply applies the op to the workspace, modifying it as

apply(op: WorkspaceOp): Promise<void>;
private revBeforeDocumentApply;
private doApply(op);

@@ -61,7 +58,8 @@ private highlightDecorationType;

private recordSelection(doc, sel);
private documentDirty;
private onDidChangeTextDocument(ev);
private documentIsDirty;
private subtractOp?;
onDidChangeTextDocument(ev: vscode.TextDocumentChangeEvent): void;
private onDidSaveTextDocument(doc);
private onWillSaveTextDocument(event);
}
export declare function sleep(msec: number): Promise<void>;

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

const workspace_1 = require("libzap/lib/ot/workspace");
const subtract_1 = require("libzap/lib/ot/subtract");
const diffOps_1 = require("libzap/lib/util/diffOps");
const op_1 = require("./op");
const fileTreeService_1 = require("./fileTreeService");
class Workspace {

@@ -22,22 +24,19 @@ constructor(environment) {

this.suppressRecordEdits = new Map();
/**
* 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
// Edits can appear before they will be detected by the file system since vscode.workspace.textDocuments returns files
// "currently known" to the system. This causes us to skip ops on files that have not been opened and therefore results in errors
// when junmping between files and making edits. - @Kingy - https://github.com/sourcegraph/sourcegraph/issues/4508
this.pendingWorkspaceFileEdits = {};
this.revBeforeDocumentApply = new Map();
this.highlightDecorationType = vscode.window.createTextEditorDecorationType({
backgroundColor: "rgba(255, 255, 20, 0.15)",
});
this.documentDirty = new Map();
this.documentIsDirty = new Map();
// Give onOpListener default noop value;
this.onOpListener = op => {
console.log(`WARNING: Op ignored because Workspace.onOpListener is not set: ${JSON.stringify(op)}`);
console.log(`WARNING: Op ignored because Workspace.onOpListener is not set: ${workspace_1.toString(op)}`);
};
this.outputChannel = vscode.window.createOutputChannel("zap controller");
this.toDispose.push(this.outputChannel);
this.fileTreeService = new fileTreeService_1.FileTreeService();
this.toDispose.push(this.fileTreeService);
}

@@ -64,10 +63,10 @@ onOp(listener) {

vscode.window.onDidChangeActiveTextEditor(e => this.onDidChangeActiveTextEditor(e), null, this.toDispose);
vscode.workspace.onDidChangeTextDocument((e) => __awaiter(this, void 0, void 0, function* () {
vscode.workspace.onDidChangeTextDocument(e => {
try {
yield this.onDidChangeTextDocument(e);
this.onDidChangeTextDocument(e);
}
catch (err) {
this.logDebug(`Error in onDidChangeTextDocument: ${err} (event: ${JSON.stringify(e)})`);
this.logDebug(`Error in onDidChangeTextDocument: ${err} (event: ${JSON.stringify(e)}, this.applyingOp=${workspace_1.toString(this.applyingOp)})`);
}
}), null, this.toDispose);
}, null, this.toDispose);
vscode.window.onDidChangeTextEditorSelection(e => this.onDidChangeTextEditorSelection(e), null, this.toDispose);

@@ -91,3 +90,3 @@ vscode.workspace.onDidCloseTextDocument(e => this.onDidCloseTextDocument(e), null, this.toDispose);

if (fileName && doc.isDirty) {
files[fileName] = doc.getText();
files[`#${fileName}`] = doc.getText();
}

@@ -104,32 +103,33 @@ }

return __awaiter(this, void 0, void 0, function* () {
this.toApply.push(op);
this.toApply = workspace_1.compose(this.toApply || {}, op);
if (this.applying) {
return this.applying;
}
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);
}
let pResolve = undefined;
let pReject = undefined;
this.applying = new Promise((resolve, reject) => {
pResolve = resolve;
pReject = reject;
});
while (this.toApply) {
this.origApplyingOp = this.toApply;
this.applyingOp = this.toApply;
this.toApply = undefined;
try {
yield this.doApply(this.applyingOp);
}
this.applying = undefined;
resolve();
}));
return this.applying;
catch (err) {
const msg = `applying op failed: ${workspace_1.toString(this.applyingOp)}: ${err}`;
console.error(msg);
this.origApplyingOp = undefined;
this.applyingOp = undefined;
this.appliedOp = undefined;
this.applying = undefined;
this.revBeforeDocumentApply.clear();
pReject(msg);
throw new Error(msg);
}
}
this.applying = undefined;
pResolve();
});

@@ -139,2 +139,4 @@ }

return __awaiter(this, void 0, void 0, function* () {
workspace_1.assertConsecutive(this.applyingOp, this.toApply, "this.applyingOp, this.toApply at start of doApply");
let docs = vscode.workspace.textDocuments.filter((doc) => !doc.uri.query.endsWith("~0"));
if (op.save) {

@@ -144,24 +146,27 @@ this.suppressRecord = true;

for (const file of op.save) {
const doc = vscode.workspace.textDocuments.find(doc => this.environment.asRelativePathInsideWorkspace(doc.uri) === workspace_1.stripFileOrBufferPathPrefix(file));
const doc = docs.find(doc => this.environment.asRelativePathInsideWorkspace(doc.uri) === workspace_1.stripFileOrBufferPathPrefix(file));
if (doc) {
if (!(yield doc.save())) {
// If the buffered file was JUST saved and
// doc.save() fails, it's most likely
// because the file on disk is newer than
// the buffered file. This means that the
// server has already flushed the
// buffer. Our contents of the buffered
// file are probably the same as what's on
// disk, so just revert.
//
// It's possible we could make an edit
// that gets clobbered by this. The real
// fix would be to make the server tell us
// to "delete #f" if it already flushed
// the buffer after seeing a "save #f",
// not just send us the same "save #f" (as
// it currently does).
yield this.environment.revertTextDocument2(doc);
if (this.environment.automaticallyApplyingFileSystemChanges) {
// Another user saved the file. By the time we
// receive this, the local server has already
// updated the file on disk. If our editor's
// file matches the contents on disk, then
// save it so it's not dirty. Otherwise, we
// have additional edits and should continue
// editing the buffer file.
const matchesDisk = this.environment.readTextDocumentOnDisk(doc.uri) === doc.getText();
if (matchesDisk) {
this.documentIsDirty.set(doc.uri.toString(), false);
yield this.environment.revertTextDocument(doc);
}
}
this.documentDirty.set(doc.uri.toString(), false);
else {
if (!doc.isDirty) {
continue;
}
if (!(yield doc.save())) {
throw new Error(`saving ${doc.uri.toString()} failed (current text is: ${JSON.stringify(doc.getText())})`);
}
this.documentIsDirty.set(doc.uri.toString(), false);
}
}

@@ -178,3 +183,3 @@ }

const docURI = this.environment.asAbsoluteURI(dest.slice(1));
this.documentDirty.set(docURI.toString(), true);
this.documentIsDirty.set(docURI.toString(), true);
yield vscode.workspace.openTextDocument(docURI);

@@ -184,3 +189,10 @@ }

}
const edit = op_1.workspaceEditFromOp(vscode.workspace.textDocuments, this.environment, op);
if (op.create || op.delete) {
this.fileTreeService.apply(op);
}
// For zap, make sure the file we apply edits to is a file based on an
// aboslute commit, instead of a ~0 file used for diff mode.
docs = vscode.workspace.textDocuments.filter((doc) => !doc.uri.query.endsWith("~0"));
const { edit, pendingFileEdits } = op_1.workspaceEditFromOp(docs, this.documentIsDirty, this.environment, op, this.pendingWorkspaceFileEdits);
this.pendingWorkspaceFileEdits = pendingFileEdits;
if (edit && edit.entries().length > 0) {

@@ -190,3 +202,3 @@ // Mark files with buffer changes as dirty.

const file = this.environment.asRelativePathInsideWorkspace(uri);
this.documentDirty.set(uri.toString(), Boolean(op.edit[`#${file}`]));
this.documentIsDirty.set(uri.toString(), this.documentIsDirty.get(uri.toString()) || Boolean(op.edit[`#${file}`]));
}

@@ -213,11 +225,26 @@ for (const [uri] of edit.entries()) {

// vscode.workspace.applyEdit's promise resolves.
yield vscode.workspace.applyEdit(edit).then((ok) => {
// Record the versions of documents prior to apply, so we
// can distinguish between applied changes (by us) and
// interactive changes (by the user) in
// onDidChangeTextDocument.
for (const doc of vscode.workspace.textDocuments) {
if (edit.has(doc.uri)) {
this.revBeforeDocumentApply.set(doc.uri.toString(), doc.version);
}
}
// DEBUG: Simulate latency. This can help surface race
// conditions.
if (process.env.SIMULATED_LATENCY) {
if (!process.env.SIMULATED_LATENCY.endsWith("ms")) {
throw new Error("the SIMULATED_LATENCY value must end in \"ms\" and specify a number of milliseconds");
}
yield sleep(parseInt(process.env.SIMULATED_LATENCY, 10));
}
yield vscode.workspace.applyEdit(edit).then((ok) => __awaiter(this, void 0, void 0, function* () {
if (!ok) {
throw new Error(`applyWorkspaceEdit failed: ${JSON.stringify(op)}`);
throw new Error(`applyWorkspaceEdit failed: ${workspace_1.toString(op)}`);
}
if (this.subtractOp) {
const subtractOp = this.subtractOp;
this.subtractOp = undefined;
return this.doApply(subtractOp);
}
workspace_1.assertConsecutive(this.applyingOp, this.toApply, "this.applyingOp, this.toApply after vscode.workspace.applyEdit");
// Fix up selections immediately before recursing, to
// reduce flicker.
editorSelsAreEmpty.forEach((isEmpty, i) => {

@@ -230,3 +257,38 @@ const editor = vscode.window.visibleTextEditors[i];

});
}, (err) => {
if (this.subtractOp) {
// Recurse until we have no more subtraction to do
// (which means no concurrent
// onDidChangeTextDocument occurred).
//
// TODO(sqs): subtract ops might need their own
// subtract ops.
const subtractOp = this.subtractOp;
this.subtractOp = undefined;
this.applyingOp = subtractOp;
this.origApplyingOp = subtractOp;
return this.doApply(subtractOp);
}
// Record the fact that we're done applying these
// docs, so we know all future changes are interactive
// changes (from the user).
for (const doc of vscode.workspace.textDocuments) {
if (edit.has(doc.uri)) {
this.revBeforeDocumentApply.delete(doc.uri.toString());
}
}
if (!this.environment.automaticallyApplyingFileSystemChanges) {
this.suppressRecord = true;
for (const doc of vscode.workspace.textDocuments) {
const fileName = this.environment.asRelativePathInsideWorkspace(doc.uri);
if (fileName !== null && edit.has(doc.uri) && !op.edit[`#${fileName}`]) {
if (!(yield doc.save())) {
throw new Error(`after applying edit to ${doc.uri.toString()}, save failed - edit was: ${JSON.stringify(edit.get(doc.uri))}`);
}
}
}
this.suppressRecord = false;
}
this.applyingOp = undefined;
this.origApplyingOp = undefined;
}), (err) => {
throw new Error(`applyWorkspaceEdit failed: ${err}`);

@@ -246,2 +308,3 @@ });

}
this.applyingOp = undefined;
});

@@ -270,11 +333,36 @@ }

try {
this.toApply = {};
this.suppressRecordEdits.clear();
this.documentDirty.clear();
this.suppressRecord = true;
for (const doc of vscode.workspace.textDocuments) {
if (this.environment.asRelativePathInsideWorkspace(doc.uri) !== null) {
yield this.environment.revertTextDocument(doc);
this.revBeforeDocumentApply.clear();
if (this.applying) {
yield this.applying;
}
// TODO(sqs): don't allow other things to be applied while we are resetting
if (this.environment.automaticallyApplyingFileSystemChanges) {
// Revert documents to their contents on disk (which
// the local server has already updated based on the
// reset ops).
for (const doc of vscode.workspace.textDocuments) {
if (!doc.isDirty && !this.documentIsDirty.get(doc.uri.toString())) {
continue;
}
const fileName = this.environment.asRelativePathInsideWorkspace(doc.uri);
if (fileName !== null) {
yield this.environment.revertTextDocument(doc);
}
}
}
else {
// Revert documents to their contents as of the
// beginning of this branch.
for (const doc of vscode.workspace.textDocuments) {
const fileName = this.environment.asRelativePathInsideWorkspace(doc.uri);
if (fileName !== null) {
yield this.environment.revertTextDocumentToBase(doc);
}
}
}
this.suppressRecord = false;
this.documentIsDirty.clear();
if (history.length > 0) {

@@ -285,5 +373,8 @@ let composed = workspace_1.composeAll(history);

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

@@ -326,22 +417,39 @@ catch (err) {

onDidOpenTextDocument(doc) {
const fileName = this.environment.asRelativePathInsideWorkspace(doc.uri);
if (fileName === null) {
return;
}
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`);
return __awaiter(this, void 0, void 0, function* () {
const fileName = this.environment.asRelativePathInsideWorkspace(doc.uri);
if (fileName === null) {
return;
}
this.onOpListener({
copy: { [`#${fileName}`]: `/${fileName}` },
edit: { [`#${fileName}`]: diffOps_1.default(oldText, newText) },
});
}
this.documentIsDirty.set(doc.uri.toString(), doc.isDirty);
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) },
});
}
else {
const changes = this.pendingWorkspaceFileEdits[`/${fileName}`];
if (this.pendingWorkspaceFileEdits[`/${fileName}`]) {
const edit = op_1.resolvePendingEditOpsToWorkspaceEdit(doc, changes);
if (edit && edit.entries().length > 0) {
for (const [uri] of edit.entries()) {
const existing = this.suppressRecordEdits.get(uri.toString()) || [];
this.suppressRecordEdits.set(uri.toString(), existing.concat(edit.get(uri)));
}
yield vscode.workspace.applyEdit(edit);
delete this.pendingWorkspaceFileEdits[`/${fileName}`];
}
}
}
});
}

@@ -356,2 +464,3 @@ onDidCloseTextDocument(doc) {

}
this.documentIsDirty.set(doc.uri.toString(), false);
if (doc.isDirty) {

@@ -372,3 +481,3 @@ this.onOpListener({ delete: [`#${fileName}`] });

[fileName]: {
[process.env.ZAP_E2E_NAME || process.env.USER || "<no-name>"]: sel ? [doc.offsetAt(sel.anchor), doc.offsetAt(sel.active)] : null,
[this.environment.userID]: sel ? [doc.offsetAt(sel.anchor), doc.offsetAt(sel.active)] : null,
},

@@ -379,109 +488,112 @@ },

onDidChangeTextDocument(ev) {
return __awaiter(this, void 0, void 0, function* () {
const doc = ev.document;
const fileName = this.environment.asRelativePathInsideWorkspace(doc.uri);
if (fileName === null) {
const doc = ev.document;
const fileName = this.environment.asRelativePathInsideWorkspace(doc.uri);
if (fileName === null) {
return;
}
if (this.suppressRecord) {
return;
}
// Ignore some changes.
if (ev.contentChanges.length === 1) {
const change = ev.contentChanges[0];
// For some reason, it sometimes triggers noop deletions
// and (re)additions of the whole file. Suppress these.
if (change.range.start.line === 0 && change.range.start.character === 0 &&
change.rangeLength === ev.document.getText().length &&
change.text === ev.document.getText()) {
return;
}
if (this.suppressRecord) {
return;
}
// Ignore some changes.
if (ev.contentChanges.length === 1) {
const change = ev.contentChanges[0];
// For some reason, it sometimes triggers noop deletions
// and (re)additions of the whole file. Suppress these.
if (change.range.start.line === 0 && change.range.start.character === 0 &&
change.rangeLength === ev.document.getText().length &&
change.text === ev.document.getText()) {
return;
}
// Skip content changes that were caused by our own apply.
let ourOwnAppliedChanges = this.suppressRecordEdits.get(doc.uri.toString()) || [];
const contentChangesToProcess = [];
for (const contentChange of ev.contentChanges) {
let isOurOwnAppliedChange = false;
ourOwnAppliedChanges = ourOwnAppliedChanges.filter(c => {
if (c.newText === contentChange.text && op_1.rangesEqual(c.range, contentChange.range)) {
isOurOwnAppliedChange = true;
return false; // remove from changes-to-suppress list
}
return true;
});
if (!isOurOwnAppliedChange) {
contentChangesToProcess.push(contentChange);
}
// Skip content changes that were caused by our own apply.
let ourOwnAppliedChanges = this.suppressRecordEdits.get(doc.uri.toString()) || [];
const contentChangesToProcess = [];
for (const contentChange of ev.contentChanges) {
let isOurOwnAppliedChange = false;
ourOwnAppliedChanges = ourOwnAppliedChanges.filter(c => {
if (c.newText === contentChange.text && op_1.rangesEqual(c.range, contentChange.range)) {
isOurOwnAppliedChange = true;
return false; // remove from changes-to-suppress list
}
return true;
});
if (!isOurOwnAppliedChange) {
contentChangesToProcess.push(contentChange);
}
}
this.suppressRecordEdits.set(doc.uri.toString(), ourOwnAppliedChanges);
if (contentChangesToProcess.length === 0) {
return; // nothing to do
}
// Store information about this change so we can suppress selection events that are caused by this edit.
this.ignoreSelectionChange.set(doc.uri, doc.version);
const wasDirty = this.documentIsDirty.get(doc.uri.toString());
const isDirty = doc.isDirty || this.environment.readTextDocumentOnDisk(doc.uri) !== doc.getText();
this.documentIsDirty.set(doc.uri.toString(), isDirty);
if (!wasDirty && !isDirty) {
return; // nothing to do; this change is handled by the local server's file system watcher
}
let op = {
edit: { [`#${fileName}`]: op_1.editOpsFromContentChanges(doc, contentChangesToProcess) },
};
if (!wasDirty) {
op.copy = { [`#${fileName}`]: `/${fileName}` };
}
workspace_1.assertConsecutive(this.applyingOp, this.toApply, "this.applyingOp, this.toApply");
// Handle when we're in the middle of applying an op and we
// get a change.
if (this.applyingOp) {
// Has this.applyingOp already been applied (but
// vscode.workspace.applyEdit's promise has not yet
// resolved)?
let applyingOpHasBeenApplied = false;
const rev = this.revBeforeDocumentApply.get(doc.uri.toString());
if (rev === undefined) {
}
this.suppressRecordEdits.set(doc.uri.toString(), ourOwnAppliedChanges);
if (contentChangesToProcess.length === 0) {
return; // nothing to do
else if (doc.version - rev === 0) {
throw new Error("unexpected rev delta 0");
}
// Store information about this change so we can suppress selection events that are caused by this edit.
this.ignoreSelectionChange.set(doc.uri, doc.version);
const wasDirty = this.documentDirty.get(doc.uri.toString());
const isDirty = doc.isDirty;
this.documentDirty.set(doc.uri.toString(), isDirty);
let op = {};
if (wasDirty && !isDirty && !doc.isDirty) {
op.delete = [`#${fileName}`];
else if (doc.version - rev === 1) {
// console.log(`I think that this.applyingOp (${opToString(this.applyingOp)}) has NOT been applied to the document (text: ${JSON.stringify(doc.getText())}) because the doc rev ${this.revBeforeDocumentApply.get(doc.uri.toString())} was before we started the last vscode.workspace.applyEdit and is now ${doc.version} (with the 1 version number difference coming from the interactive change).`);
applyingOpHasBeenApplied = false;
// Bump up the version number that we're diffing
// against because this last version increment was not
// due to apply.
this.revBeforeDocumentApply.set(doc.uri.toString(), doc.version);
}
else {
op.edit = { [`#${fileName}`]: op_1.editOpsFromContentChanges(doc, contentChangesToProcess) };
if (isDirty && !wasDirty) {
op.copy = { [`#${fileName}`]: `/${fileName}` };
}
else if (doc.version - rev === 2) {
// console.log(`I think that this.applyingOp (${opToString(this.applyingOp)}) has been applied to the document (text: ${JSON.stringify(doc.getText())}) because the doc rev ${this.revBeforeDocumentApply.get(doc.uri.toString())} was before we started the last vscode.workspace.applyEdit and is now ${doc.version}.`);
this.appliedOp = this.applyingOp;
this.applyingOp = undefined;
applyingOpHasBeenApplied = true;
this.revBeforeDocumentApply.set(doc.uri.toString(), doc.version); // bump
}
// 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 "fix" the incorrect op that was applied so
// that we end up in the correct end state. We need X 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 (by algebra):
//
// 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);
else if (doc.version - rev > 2) {
throw new Error("unexpected rev delta 2");
}
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 && process.env.DEV_NOISY) {
console.log(msg);
this.outputChannel.appendLine(msg);
}
}
if (this.applyingOp) {
// The op from apply has not yet been applied to the state
// of the document.
workspace_1.assertConcurrent(this.applyingOp, op, "applyingOp, op");
workspace_1.assertConsecutive(this.applyingOp, this.toApply, "this.applyingOp, this.toApply in onDidChangeTextDocument");
const rawOp = op;
const { a1, b1 } = workspace_1.transform(this.applyingOp, op);
// Remove from this.applyingOp what we know was just
// applied concurrently.
this.applyingOp = a1;
// Transform the op we derived from the
// vscode.TextDocumentChangeEvent to account for being
// applied concurrently with this.applyingOp...
op = b1;
// ...and the other ops that have been recorded but not
// yet applied.
op = workspace_1.transform(this.toApply || {}, op).b1;
workspace_1.assertConsecutive(rawOp, this.applyingOp, "rawOp, this.applyingOp in onDidChangeTextDocument");
const subtractOp = subtract_1.subtract(this.origApplyingOp, this.applyingOp, rawOp);
this.subtractOp = workspace_1.noop(subtractOp) ? undefined : subtractOp;
}
if (!workspace_1.noop(op)) {
this.onOpListener(op);
});
}
}

@@ -497,3 +609,3 @@ onDidSaveTextDocument(doc) {

this.onOpListener({ save: [`#${fileName}`] });
this.documentDirty.set(doc.uri.toString(), false);
this.documentIsDirty.set(doc.uri.toString(), false);
}

@@ -522,2 +634,10 @@ // onWillSaveTextDocument notifies the workspace watcher that a

exports.Workspace = Workspace;
function sleep(msec) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve) => {
setTimeout(() => resolve(void 0), msec);
});
});
}
exports.sleep = sleep;
//# sourceMappingURL=workspace.js.map

@@ -44,4 +44,7 @@ "use strict";

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));
const editor = yield vscode.window.showTextDocument(doc);
return ensureAfterPromise(revertFile(doc).then(() => {
editor.selection = new vscode.Selection(new vscode.Position(0, 0), new vscode.Position(0, 0)); // reset selection
return f(doc);
}), () => revertFile(doc));
});

@@ -71,3 +74,3 @@ }

if (vscode.workspace.getConfiguration("editor").get("insertSpaces")) {
return " ".repeat(vscode.workspace.getConfiguration("editor").get("tabSize"));
return " ".repeat(vscode.workspace.getConfiguration("editor").get("tabSize", 4));
}

@@ -74,0 +77,0 @@ return "\t";

// HACK(sqs): my vscode kept adding spaces back in
// tslint:disable indent
"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 vscode = require("vscode");
const textDoc_1 = require("libzap/lib/ot/textDoc");
const op = require("../src/op");

@@ -431,2 +438,12 @@ const types_1 = require("./types");

},
{
text: "abcd\n",
op: [-2, "X", -1, 2],
want: [
vscode.TextEdit.delete(new vscode.Range(0, 0, 0, 2)),
vscode.TextEdit.insert(new vscode.Position(0, 2), "X"),
vscode.TextEdit.delete(new vscode.Range(0, 2, 0, 3)),
],
wantText: "Xd\n",
},
].forEach((t) => {

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

};
const f = () => op.workspaceEditFromOp([doc], fakeEnv, { edit: { [`/${fileName}`]: t.op } });
const f = () => op.workspaceEditFromOp([doc], new Map(), fakeEnv, { edit: { [`/${fileName}`]: t.op } }, {}).edit;
if (t.wantThrow) {

@@ -455,3 +472,4 @@ assert.throws(f, t.wantThrow);

if (t.wantText) {
return vscode.workspace.openTextDocument(doc.uri).then(doc2 => {
return vscode.workspace.openTextDocument(doc.uri).then((doc2) => __awaiter(this, void 0, void 0, function* () {
yield vscode.window.showTextDocument(doc);
assert.equal(doc2.getText(), "", "new file initially should be empty");

@@ -471,3 +489,3 @@ const resetDoc = new vscode.WorkspaceEdit();

});
});
}));
}

@@ -552,73 +570,2 @@ return Promise.resolve();

});
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

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

assert.equal(doc.getText(), "abc\n");
assert.ok(!doc.isDirty);
assert.ok(!doc.isDirty, "document is dirty");
if (ops !== undefined) {

@@ -48,2 +48,86 @@ assert.deepEqual(ops, []);

});
suite("files changed outside of editor:", () => {
// HACK: For some reason, for file changes made by this same process
// (e.g., during a test), the file's dirty state is not detected nor
// is the file reverted. To force a revert, we need to switch to a
// different editor and then back to this document's editor. Opening
// a new untitled doc is the most glitchy, so we only do it if we
// need to.
//
// This bug occurs on some Linux vscode 1.10 insider builds and
// possibly other platforms.
const hackTriggerFileIsDirtyRefresh = () => __awaiter(this, void 0, void 0, function* () {
const untitledDoc = yield vscode.workspace.openTextDocument(vscode.Uri.parse("untitled:/dummy"));
yield vscode.window.showTextDocument(untitledDoc, vscode.ViewColumn.One, true);
yield vscode.commands.executeCommand("workbench.action.closeActiveEditor");
yield common_1.sleep(200); // give time for the editor to detect the change
});
test("update to unedited file", () => {
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);
common_1.writeWorkspace({ f: "abcx\n" });
yield hackTriggerFileIsDirtyRefresh();
yield workspace.environment.revertTextDocument(doc);
assert.equal(doc.getText(), "abcx\n");
assert.ok(!doc.isDirty);
common_1.assertOpsEqual(ops, []);
}));
});
});
test("update to edited & unsaved file outside of editor", () => {
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();
edit.insert(doc.uri, new vscode.Position(0, 3), "x");
const editOK = yield vscode.workspace.applyEdit(edit);
assert.ok(editOK);
assert.equal(doc.getText(), "abcx\n");
common_1.assertOpsEqual(ops, [{ copy: { "#f": "/f" }, edit: { "#f": [3, "x", 1] } }]);
ops.shift();
common_1.writeWorkspace({ f: "abcy\n" });
yield hackTriggerFileIsDirtyRefresh();
assert.equal(doc.getText(), "abcx\n");
assert.ok(doc.isDirty);
common_1.assertOpsEqual(ops, []);
// Clean up (dismiss "file contents have
// changed on disk" dialog, which would cause
// subsequent tests to fail).
yield workspace.environment.revertTextDocument(doc);
}));
});
});
test("update to edited & saved file outside of editor", () => {
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();
edit.insert(doc.uri, new vscode.Position(0, 3), "x");
const editOK = yield vscode.workspace.applyEdit(edit);
assert.ok(editOK);
assert.equal(doc.getText(), "abcx\n");
common_1.assertOpsEqual(ops, [{ copy: { "#f": "/f" }, edit: { "#f": [3, "x", 1] } }]);
ops.shift();
const saveOK = yield doc.save();
assert.ok(saveOK);
common_1.assertOpsEqual(ops, [{ save: ["#f"] }]);
ops.shift();
common_1.writeWorkspace({ f: "abcx\n" });
yield hackTriggerFileIsDirtyRefresh();
yield workspace.environment.revertTextDocument(doc);
assert.equal(doc.getText(), "abcx\n");
assert.ok(!doc.isDirty);
common_1.assertOpsEqual(ops, []);
// HACK: Eliminate occasional exception thrown by main
// vscode workbench: "These files have changed in the
// meantime: ..."."
yield common_1.sleep(500);
}));
});
});
});
test("remote reset", () => {

@@ -105,4 +189,7 @@ return common_1.withOpenTextDocument("f", (doc) => {

assert.ok(doc.isDirty);
// Simulate local server saving.
common_1.writeWorkspace({ f: "abcq\n" });
yield workspace.apply({ save: ["#f"] });
assert.equal(doc.getText(), "abcq\n");
yield common_1.sleep(400); // sleep to detect new dirty state
assert.ok(!doc.isDirty);

@@ -229,4 +316,8 @@ // A local edit should trigger another copy to create the buffer.

environment_vscode_1.default.automaticallyApplyingFileSystemChanges = false;
environment_vscode_1.default.testOnly_fakeDocumentBaseContents = "abc\n";
});
suiteTeardown(() => { environment_vscode_1.default.automaticallyApplyingFileSystemChanges = orig; });
suiteTeardown(() => {
environment_vscode_1.default.automaticallyApplyingFileSystemChanges = orig;
environment_vscode_1.default.testOnly_fakeDocumentBaseContents = undefined;
});
test("one remote non-buffered edit", () => {

@@ -261,2 +352,48 @@ return common_1.withOpenTextDocument("f", (doc) => {

}));
test("remote reset on file with edits", () => __awaiter(this, void 0, void 0, function* () {
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({ edit: { "/f": [3, "q", 1] } });
assert.equal(doc.getText(), "abcq\n");
assert.ok(!doc.isDirty);
const editor = yield vscode.window.showTextDocument(doc);
const preResetSel = editor.selection;
yield workspace.reset([
{ edit: { "/f": [3, "x", 1] } },
]);
assert.equal(doc.getText(), "abcx\n");
assert.ok(!doc.isDirty);
assert.deepEqual(editor.selection, preResetSel);
assert.deepEqual(ops, []);
}));
});
}));
test("remote reset on buffer file with saved edits", () => __awaiter(this, void 0, void 0, function* () {
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], "#f": [3, "p", 1] },
});
assert.equal(doc.getText(), "abcp\n"); // vscode doc reflects buffer file, not disk file
assert.ok(doc.isDirty);
yield workspace.apply({ save: ["#f"] });
assert.equal(doc.getText(), "abcp\n");
assert.ok(!doc.isDirty);
const editor = yield vscode.window.showTextDocument(doc);
const preResetSel = editor.selection;
yield workspace.reset([
{ edit: { "/f": [3, "x", 1] } },
]);
assert.equal(doc.getText(), "abcx\n");
assert.ok(!doc.isDirty);
assert.deepEqual(editor.selection, preResetSel);
assert.deepEqual(ops, []);
}));
});
}));
});

@@ -267,3 +404,8 @@ test("remote buffered edit when no buffered file exists", () => {

checkInitialState(doc);
yield common_1.assertRejects(() => workspace.apply({ edit: { "#f": [3, "q", 1] } }));
try {
yield workspace.apply({ edit: { "#f": [3, "q", 1] } });
throw new Error("expected exception");
}
catch (err) {
}
// The doc should be unchanged.

@@ -420,3 +562,10 @@ assert.equal(doc.getText(), "abc\n");

});
test("simultaneous edits", () => {
// TODO(sqs): this is useful for poorly behaved document formatters
// (e.g., the old vscode-go would clobber the document contents with the
// output of gofmt). Most new document formatters use diffs, which is
// better, but for those that don't, we should auto-diff. It may require
// us to buffer all file contents, unfortunately.
//
// Skipped until we implement this (it will fail now).
test.skip("auto-diff when TextEdit replaces entire document", () => {
return common_1.withOpenTextDocument("f", (doc) => {

@@ -426,69 +575,8 @@ return common_1.withWorkspace((workspace) => __awaiter(this, void 0, void 0, function* () {

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);
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;
}
}
appending = undefined;
resolve();
}));
return appending;
});
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] } }]);
const edit = new vscode.WorkspaceEdit();
edit.replace(doc.uri, new vscode.Range(new vscode.Position(0, 0), doc.positionAt(doc.getText().length)), doc.getText() + "x");
const editOK = yield vscode.workspace.applyEdit(edit);
assert.ok(editOK);
assert.equal(doc.getText(), "abc\nx");
common_1.assertOpsEqual(ops, [{ copy: { "#f": "/f" }, edit: { "#f": [4, "x"] } }]);
}));

@@ -495,0 +583,0 @@ });

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

"description": "WIP",
"version": "0.0.60",
"version": "0.0.61",
"publisher": "sqs",

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

"dependencies": {
"libzap": "^0.0.60",
"libzap": "^0.0.61",
"open": "^0.0.5",

@@ -45,0 +45,0 @@ "vscode-jsonrpc": "3.0.1-alpha.7"

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

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