vscode-zap
Advanced tools
Comparing version 0.0.100 to 1.0.1
@@ -0,1 +1,2 @@ | ||
import * as vscode from "vscode"; | ||
import { Client, ClientOptions, ServerOptions } from "libzap/lib/remote/client"; | ||
@@ -5,2 +6,3 @@ import { Handler } from "libzap/lib/remote/handler"; | ||
import { IEnvironment } from "./environment"; | ||
import { Workspace } from "./workspace"; | ||
import { WorkspaceState } from "./extension.common"; | ||
@@ -13,10 +15,8 @@ export declare class Controller { | ||
handler: Handler; | ||
private workspace; | ||
workspace: Workspace; | ||
private scmProvider; | ||
private toDispose; | ||
private workspaceState?; | ||
outputChannel: vscode.OutputChannel; | ||
constructor(serverOptions: ServerOptions, clientOptions: ClientOptions, environment: IEnvironment, initState?: WorkspaceState, webContext?: boolean); | ||
private attach(reset?); | ||
private reset(mergeStrategy); | ||
private revertAllDocuments(rev?); | ||
dispose(): void; | ||
@@ -27,4 +27,3 @@ queryRefInfo(refID: RefIdentifier): Thenable<RefInfo>; | ||
stop(): Thenable<void>; | ||
addWorkspace(): Promise<void>; | ||
showRefSelectMenu(): Thenable<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) { | ||
@@ -18,3 +10,5 @@ return new (P || (P = Promise))(function (resolve, reject) { | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const vscode = require("vscode"); | ||
const vscode_jsonrpc_1 = require("vscode-jsonrpc"); | ||
const client_1 = require("libzap/lib/remote/client"); | ||
@@ -25,2 +19,3 @@ const handler_1 = require("libzap/lib/remote/handler"); | ||
const workspace_1 = require("./workspace"); | ||
const refQuickPick_1 = require("./refQuickPick"); | ||
class Controller { | ||
@@ -32,139 +27,21 @@ constructor(serverOptions, clientOptions, environment, initState, webContext) { | ||
this.toDispose = []; | ||
this.outputChannel = vscode.window.createOutputChannel("zap"); | ||
this.workspaceState = initState; | ||
this.client = new client_1.Client(this.environment.userID, this.serverOptions, this.clientOptions); | ||
if (process.env.TRACE) { | ||
this.client.trace = vscode_jsonrpc_1.Trace.Verbose; | ||
} | ||
this.handler = new handler_1.Handler(this.client); | ||
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); | ||
this.workspace.initHooks(); | ||
// Only enable the SCM provider on sourcegraph.com. | ||
if (webContext) { | ||
this.scmProvider = new scm_1.ZapSCMProvider(initState); | ||
this.workspace.onGenerateOp(op => this.environment.zapRef && this.scmProvider.updateFromOp(op)); | ||
this.workspace.onApplyOp(op => this.environment.zapRef && this.scmProvider.updateFromOp(op)); | ||
this.workspace.onGenerateOp(op => this.environment.workRef && this.scmProvider.updateFromOp(op)); | ||
this.workspace.onApplyOp(op => this.environment.workRef && this.scmProvider.updateFromOp(op)); | ||
this.workspace.onReset(op => this.scmProvider.reset(op)); | ||
} | ||
let initedHooks = false; | ||
this.client.onDidChangeState((event) => { | ||
switch (event.newState) { | ||
case client_1.State.Running: | ||
if (!initedHooks) { | ||
this.workspace.initHooks(); | ||
initedHooks = true; | ||
} | ||
this.attach(); | ||
break; | ||
case client_1.State.Stopped: | ||
break; | ||
} | ||
}); | ||
// Register client features. | ||
this.client.registerHandler(handler_1.Handler.id, this.handler); | ||
// Reestablish watch and reattach workspace whenever we | ||
// reconnect (and on the first time we connect)... | ||
// ...and when we switch refs. | ||
this.environment.onDidChangeZapRef(() => { | ||
if (initedHooks) { | ||
this.attach(true); | ||
} | ||
}); | ||
// 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); | ||
if (vscode.workspace.onDidUpdateWorkspace) { | ||
// This block allows Sourcegraph.com to communicate workspace rev state changes | ||
// to the zap extension. | ||
vscode.workspace.onDidUpdateWorkspace((workspace) => { | ||
this.scmProvider.reset(); | ||
this.workspaceState = { | ||
workspace: workspace.resource.toString(), | ||
revState: workspace.revState, | ||
}; | ||
if (workspace.revState) { | ||
this.environment.zapRef = workspace.revState.zapRef; | ||
} | ||
else { | ||
this.environment.zapRef = undefined; | ||
} | ||
}); | ||
} | ||
} | ||
attach(reset) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (this.environment.prevZapRef) { | ||
// Before we can attach a new zap ref, we must first detach the previous | ||
// and revert documents. | ||
if (this.environment.prevZapRef !== this.environment.zapRef) { | ||
this.handler.detachWorkspace(this.environment.repo, this.environment.prevZapRef); | ||
yield this.revertAllDocuments(); | ||
} | ||
} | ||
const mergeStrategy = reset ? handler_1.MergeStrategy.RemoteClobbersLocal : handler_1.MergeStrategy.FastForward; | ||
if (this.environment.zapRef) { | ||
try { | ||
yield this.client.onReady(); | ||
yield this.handler.attachWorkspace({ | ||
repo: this.environment.repo, | ||
ref: this.environment.zapRef, | ||
}, this.workspace, mergeStrategy).then(() => null, (err) => { | ||
console.error(`Error watching repo: ${err} ${err.stack}`); | ||
}); | ||
} | ||
catch (err) { | ||
throw new Error(`Error attaching workspace for ref: ${this.environment.zapRef}: ${err}`); | ||
} | ||
} | ||
return Promise.resolve(); | ||
}); | ||
} | ||
reset(mergeStrategy) { | ||
// this.revertAllDocuments(); | ||
if (this.environment.zapRef) { | ||
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}`); | ||
}); | ||
} | ||
return Promise.resolve(); | ||
} | ||
revertAllDocuments(rev) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { | ||
const docsToRevert = new Set(vscode.workspace.textDocuments.map(doc => { | ||
// Only revert mutable docs (not `git://github.com/gorilla/mux?sha#file/path` documents). | ||
// Only revert docs if a .revert() method exists on the vscode text document | ||
if (doc.revert && doc.uri.query === "") { | ||
return doc.uri.toString(); | ||
} | ||
return ""; | ||
}).filter(doc => doc !== "")); | ||
// The zap extension has a race condition for reverting documents and applying | ||
// new ops. If a user switches from zap ref A to B, we must revert all documents | ||
// before applying ops for B. Therefore, the zap extension must wait to be notified | ||
// from the main thread that documents have actually been reverted. | ||
let disposable; | ||
if (vscode.workspace.onDidRevertTextDocument) { | ||
disposable = vscode.workspace.onDidRevertTextDocument((docUri) => { | ||
docsToRevert.delete(docUri); | ||
if (docsToRevert.size === 0) { | ||
if (disposable) { | ||
disposable.dispose(); | ||
} | ||
resolve(); // all documents have been reverted, this function has completed | ||
} | ||
}); | ||
} | ||
yield Promise.all(vscode.workspace.textDocuments | ||
.filter(doc => docsToRevert.has(doc.uri.toString())) | ||
.map(doc => doc.revert(rev))); | ||
})); | ||
}); | ||
} | ||
dispose() { | ||
@@ -174,2 +51,3 @@ if (this.client.needsStop()) { | ||
} | ||
this.outputChannel.dispose(); | ||
this.toDispose.forEach(disposable => disposable.dispose()); | ||
@@ -200,108 +78,10 @@ } | ||
} | ||
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() { | ||
const params = { repo: this.environment.repo }; | ||
return vscode.window.showQuickPick(this.client.sendRequest(protocol_1.RefListRequest.type, params).then((refs) => { | ||
const items = []; | ||
let canClobberRemote = false; | ||
let matchingRemoteRegex = `remote\/(.*?)\/branch/(${this.environment.zapBranch})`; | ||
for (const ref of refs) { | ||
if (ref.ref.match(matchingRemoteRegex)) { | ||
canClobberRemote = true; | ||
continue; | ||
} | ||
const zapBranchRef = "branch/" + this.environment.zapBranch; | ||
if (ref.repo === this.environment.repo && !(ref.ref === "HEAD" && ref.target === zapBranchRef)) { | ||
let onSelect = undefined; | ||
let label = ref.ref.replace(/^branch\//, ""); | ||
let detail = undefined; | ||
if (zapBranchRef === ref.ref) { | ||
label = "$(check) " + label; | ||
detail = "Current branch"; | ||
onSelect = () => { | ||
vscode.window.showInformationMessage(`Zap ${ref.ref} is already active.`); | ||
if (vscode.workspace.setWorkspace) { | ||
const resource = vscode.Uri.parse(this.workspaceState.workspace); | ||
vscode.workspace.setWorkspace(resource, __assign({}, this.workspaceState.revState, { zapRev: undefined, zapRef: undefined })); | ||
} | ||
else { | ||
this.environment.zapRef = undefined; | ||
} | ||
}; | ||
} | ||
items.push({ | ||
label, | ||
description: ref.watchers ? `Watchers: ${ref.watchers.join(" ")} ` : "No current watchers", | ||
detail: detail || "Switch to this branch and start editing", | ||
onSelect: onSelect ? onSelect : () => { | ||
if (vscode.workspace.setWorkspace) { | ||
const resource = vscode.Uri.parse(this.workspaceState.workspace); | ||
vscode.workspace.setWorkspace(resource, { | ||
zapRev: ref.ref.replace(/^branch\//, ""), | ||
zapRef: ref.ref, | ||
commitID: ref.state.gitBase, | ||
branch: ref.state.gitBranch, | ||
}); | ||
} | ||
else { | ||
this.environment.zapRef = ref.ref; | ||
} | ||
}, | ||
}); | ||
} | ||
} | ||
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({ | ||
label: "$(x) Turn Zap Off", | ||
description: "Stop syncing changes in your editor with Zap", | ||
onSelect: () => { | ||
vscode.workspace.getConfiguration("zap").update("enable", false, true); | ||
this.stop(); | ||
}, | ||
}); | ||
return items; | ||
})) | ||
.then(choice => { | ||
if (choice) { | ||
return choice.onSelect(); | ||
} | ||
}); | ||
const repo = this.handler.repodb.get(this.environment.repo); | ||
if (!repo) { | ||
return vscode.window.showErrorMessage("The Zap repository is not available.").then(() => void 0); | ||
} | ||
return this.client.onReady() | ||
.then(() => this.client.sendRequest(protocol_1.RefListRequest.type, { repo: this.environment.repo })) | ||
.then(refs => refQuickPick_1.showRefQuickPick(this, repo, refs)); | ||
} | ||
@@ -308,0 +88,0 @@ } |
@@ -10,2 +10,3 @@ "use strict"; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const path = require("path"); | ||
@@ -158,3 +159,3 @@ const net = require("net"); | ||
}), | ||
ref: this.environment.zapRef, | ||
workRef: this.environment.workRef, | ||
status: { | ||
@@ -266,12 +267,2 @@ type: statusType(status_1.itemForE2ETestOnly.text, status_1.itemForE2ETestOnly.color), | ||
} | ||
case "enable": | ||
{ | ||
yield vscode.workspace.getConfiguration("zap").update("enable", true, true); | ||
return Promise.resolve({}); | ||
} | ||
case "disable": | ||
{ | ||
yield vscode.workspace.getConfiguration("zap").update("enable", false, true); | ||
return Promise.resolve({}); | ||
} | ||
case "configure": | ||
@@ -278,0 +269,0 @@ { |
@@ -0,32 +1,24 @@ | ||
import * as vscode from "vscode"; | ||
import { MessageStream } from "libzap/lib/remote/client"; | ||
import * as vscode from "vscode"; | ||
import { WorkRef } from "libzap/lib/ref"; | ||
export interface IEnvironment { | ||
rootURI: vscode.Uri | undefined; | ||
repo: string; | ||
/** | ||
* zapRef is the current Zap ref name (e.g., "branch/mybranch" or | ||
* "HEAD"). This is always "HEAD" on the desktop. | ||
* dispose frees up resources. This instance may not be used after | ||
* it is called. | ||
*/ | ||
zapRef: string | undefined; | ||
dispose(): void; | ||
readonly rootURI: vscode.Uri | undefined; | ||
readonly repo: string; | ||
/** | ||
* prevZapRef is the last Zap ref name (e.g., "branch/mybranch" or | ||
* "HEAD") that was attached. This is always "HEAD" on the desktop. | ||
* workRef is the current Zap workspace ref and its target (if | ||
* any). If Zap is not running, it is undefined. | ||
*/ | ||
prevZapRef: string | undefined; | ||
readonly workRef: WorkRef | undefined; | ||
/** | ||
* onDidChangeZapRef is fired (with the new Zap ref) whenever the | ||
* workspace's Zap ref changes. | ||
* onWorkRefReset is fired when the ref state of the work ref is | ||
* reset. The event's value is the new workRefTarget value. | ||
* | ||
* The onWorkRefReset field is undefined if Zap is not running. | ||
*/ | ||
onDidChangeZapRef: vscode.Event<string | undefined>; | ||
/** | ||
* 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>; | ||
readonly onWorkRefReset: vscode.Event<WorkRef | undefined>; | ||
asRelativePathInsideWorkspace(uri: vscode.Uri): string | null; | ||
@@ -46,6 +38,5 @@ asAbsoluteURI(fileName: string): vscode.Uri; | ||
/** | ||
* revertTextDocumentToBase reverts doc to its contents as of the | ||
* beginning of the Zap ref. | ||
* revertAllTextDocuments reverts all open text documents. | ||
*/ | ||
revertTextDocumentToBase(doc: vscode.TextDocument): Thenable<void>; | ||
revertAllTextDocuments(): Thenable<void>; | ||
deleteTextDocument(doc: vscode.TextDocument): Thenable<void>; | ||
@@ -64,7 +55,6 @@ openChannel(id: string): Thenable<MessageStream>; | ||
readonly userID: string; | ||
/** | ||
* isRunning is the current state of the zap client server | ||
*/ | ||
isRunning: boolean; | ||
} | ||
export interface IInternalEnvironment { | ||
setWorkRef(ref: WorkRef | undefined): void; | ||
} | ||
export interface IWorkspaceRevState { | ||
@@ -71,0 +61,0 @@ commitID?: string; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
//# sourceMappingURL=environment.js.map |
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 _prevZapRef; | ||
private _isRunning; | ||
import { WorkRef } from "libzap/lib/ref"; | ||
import { IEnvironment, IInternalEnvironment } from "./environment"; | ||
export declare class VSCodeEnvironment implements IEnvironment, IInternalEnvironment { | ||
dispose(): void; | ||
readonly rootURI: vscode.Uri | undefined; | ||
readonly repo: string; | ||
private zapRefChangeEmitter; | ||
zapRef: string | undefined; | ||
prevZapRef: string | undefined; | ||
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(branchRef: string): void; | ||
private workRefResetEmitter; | ||
private _workRef; | ||
readonly onWorkRefReset: vscode.Event<WorkRef | undefined>; | ||
readonly workRef: WorkRef | undefined; | ||
setWorkRef(ref: WorkRef | undefined): void; | ||
asRelativePathInsideWorkspace(uri: vscode.Uri): string | null; | ||
@@ -30,9 +20,6 @@ asAbsoluteURI(fileName: string): vscode.Uri; | ||
testOnly_fakeDocumentBaseContents?: string; | ||
revertTextDocumentToBase(doc: vscode.TextDocument): Promise<void>; | ||
revertAllTextDocuments(): Promise<void>; | ||
deleteTextDocument(): Promise<void>; | ||
openChannel(id: string): Thenable<MessageStream>; | ||
readonly userID: string; | ||
isRunning: boolean; | ||
} | ||
declare var _default: IEnvironment; | ||
export default _default; |
@@ -10,3 +10,5 @@ "use strict"; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const vscode = require("vscode"); | ||
const vscode_jsonrpc_1 = require("vscode-jsonrpc"); | ||
const fs_1 = require("fs"); | ||
@@ -20,6 +22,8 @@ const net = require("net"); | ||
constructor() { | ||
this.zapRefChangeEmitter = new vscode.EventEmitter(); | ||
this.zapBranchChangeEmitter = new vscode.EventEmitter(); | ||
this.workRefResetEmitter = new vscode_jsonrpc_1.Emitter(); | ||
this.automaticallyApplyingFileSystemChanges = true; | ||
} | ||
dispose() { | ||
this.workRefResetEmitter.dispose(); | ||
} | ||
get rootURI() { | ||
@@ -31,52 +35,8 @@ return vscode.workspace.rootPath ? vscode.Uri.file(vscode.workspace.rootPath) : undefined; | ||
} | ||
get zapRef() { | ||
// In vscode, the Zap ref is always HEAD because vscode | ||
// accesses the file system, and the file system is (by | ||
// definition) HEAD. | ||
return "HEAD"; | ||
get onWorkRefReset() { return this.workRefResetEmitter.event; } | ||
get workRef() { return this._workRef; } | ||
setWorkRef(ref) { | ||
this._workRef = ref; | ||
this.workRefResetEmitter.fire(ref); | ||
} | ||
// In vscode, the Zap ref is always HEAD because vscode | ||
// accesses the file system, and the file system is (by | ||
// definition) HEAD. | ||
// TODO: @Kingy: Improve switching workspaces in VSCode once resetting / attaching is cleaned up. | ||
set zapRef(ref) { | ||
if (ref !== "HEAD") { | ||
vscode.window.showWarningMessage(`Attempting to set Zap ref to ${ref} instead of HEAD, which is currently unsupported in the extension`); | ||
return; | ||
} | ||
this._prevZapRef = this._zapRef; | ||
this._zapRef = "HEAD"; | ||
this.zapRefChangeEmitter.fire(ref); | ||
} | ||
get prevZapRef() { | ||
// In vscode, the Zap ref is always HEAD because vscode | ||
// accesses the file system, and the file system is (by | ||
// definition) HEAD. | ||
return this._prevZapRef; | ||
} | ||
// In vscode, the Zap ref is always HEAD because vscode | ||
// accesses the file system, and the file system is (by | ||
// definition) HEAD. | ||
// TODO: @Kingy: Improve switching workspaces in VSCode once resetting / attaching is cleaned up. | ||
set prevZapRef(ref) { | ||
this._prevZapRef = ref; | ||
} | ||
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(branchRef) { | ||
if (!/^branch\//.test(branchRef)) { | ||
throw new Error(`invalid branch ref: ${branchRef}`); | ||
} | ||
const branch = branchRef.replace(/^branch\//, ""); | ||
this._zapBranch = branch; | ||
this.zapBranchChangeEmitter.fire(branch); | ||
} | ||
asRelativePathInsideWorkspace(uri) { | ||
@@ -159,5 +119,7 @@ if (uri.scheme !== "file") { | ||
} | ||
revertTextDocumentToBase(doc) { | ||
revertAllTextDocuments() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
// Dummy implementation only used during tests. | ||
// Dummy implementation only used during tests. The test | ||
// implementation only reverts a single file, because that is | ||
// all that is currently needed by the tests. | ||
if (this.testOnly_fakeDocumentBaseContents === undefined) { | ||
@@ -167,2 +129,6 @@ throw new Error("revertTextDocumentToBase in environment.vscode.ts is only for use in tests"); | ||
const editor = vscode.window.activeTextEditor; | ||
if (!editor) { | ||
throw new Error("no editor"); | ||
} | ||
const doc = editor.document; | ||
if (!editor || editor.document.uri.toString() !== doc.uri.toString()) { | ||
@@ -209,12 +175,4 @@ throw new Error(`document ${doc.uri.toString()} is not in active editor`); | ||
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 |
import * as vscode from "vscode"; | ||
import { Controller } from "./controller"; | ||
import { IEnvironment, IWorkspaceRevState } from "./environment"; | ||
import { IEnvironment, IInternalEnvironment, IWorkspaceRevState } from "./environment"; | ||
import { IRepoHandler, AbstractWorkspaceHandler } from "libzap/lib/remote/handler"; | ||
import { Ref, NewRef } from "libzap/lib/ref"; | ||
export interface Extension { | ||
controller?: Controller; | ||
/** | ||
* deactivate removes and disposes everything that the extension's | ||
* activate function created. | ||
*/ | ||
deactivate(): void; | ||
} | ||
export interface WorkspaceState { | ||
@@ -8,5 +18,5 @@ workspace: string; | ||
} | ||
export declare function activate(env: IEnvironment, ctx: vscode.ExtensionContext, initState?: WorkspaceState, webContext?: boolean): Controller | null; | ||
export declare function isEnabled(): boolean; | ||
export declare function activate(env: IEnvironment & IInternalEnvironment, ctx: vscode.ExtensionContext, workRef?: Ref | NewRef, initState?: WorkspaceState, webContext?: boolean): Controller | undefined; | ||
export declare function startController(controller: Controller): Thenable<void>; | ||
export declare function stopController(controller: Controller): Thenable<void>; | ||
export declare function isWorkspaceRepoHandler(repo: IRepoHandler): repo is AbstractWorkspaceHandler; |
@@ -5,65 +5,66 @@ // This is the extension's shared entrypoint. All platforms call this | ||
"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()); | ||
}); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const vscode = require("vscode"); | ||
const controller_1 = require("./controller"); | ||
const status_1 = require("./status"); | ||
function activate(env, ctx, initState, webContext) { | ||
function activate(env, ctx, workRef, initState, webContext) { | ||
if (!vscode.workspace.rootPath) { | ||
return null; | ||
return; | ||
} | ||
ctx.subscriptions.push(status_1.outputChannel); | ||
if (initState && initState.revState && initState.revState.zapRef) { | ||
env.zapRef = initState.revState.zapRef; | ||
} | ||
const controller = new controller_1.Controller({ connect: () => env.openChannel("zap") }, {}, env, initState, webContext); | ||
ctx.subscriptions.push(controller); | ||
const statusHandler = new status_1.StatusHandler(controller.client, env); | ||
ctx.subscriptions.push(statusHandler); | ||
controller.client.registerHandler(status_1.StatusHandler.id, statusHandler); | ||
startController(controller); | ||
const addWorkspaceRepo = (addToLocalServer) => { | ||
return controller.handler.addRepo(env.repo, controller.workspace, workRef, addToLocalServer) | ||
.then(repo => { | ||
if (isWorkspaceRepoHandler(repo)) { | ||
// Keep (IEnvironment).workRefTarget updated. | ||
env.setWorkRef(repo.workRef); | ||
repo.onWorkRefReset(newWorkRef => { | ||
env.setWorkRef(newWorkRef); | ||
}, null, ctx.subscriptions); | ||
} | ||
else { | ||
env.setWorkRef(undefined); | ||
} | ||
return repo; | ||
}) | ||
.then(repo => repo, err => vscode.window.showErrorMessage(`Zap error: ${err}.`)); | ||
}; | ||
// Add the workspace repo upon startup. If we're running in vscode | ||
// and the user's local server isn't already syncing the | ||
// workspace, this will NOT automatically workspace/add it to the | ||
// local server. (We always want the user to explicitly add repos | ||
// to Zap.) | ||
let addedRepo = addWorkspaceRepo(false); | ||
// Register commands. | ||
ctx.subscriptions.push(vscode.commands.registerCommand("zap.workspace.on", () => { | ||
return addedRepo.then(() => vscode.window.showWarningMessage("Zap is already on in this workspace."), () => { | ||
addedRepo = addWorkspaceRepo(true); | ||
return addedRepo.then(() => vscode.window.setStatusBarMessage("Turned Zap on in this workspace.", 2000)); | ||
}); | ||
})); | ||
ctx.subscriptions.push(vscode.commands.registerCommand("zap.workspace.off", () => { | ||
return addedRepo.then(repo => { | ||
return repo.stopSync().then(() => { | ||
controller.handler.removeRepo(repo.path); | ||
repo.dispose(); | ||
addedRepo = Promise.reject("You must turn Zap on in this workspace."); | ||
vscode.window.setStatusBarMessage("Turned Zap off in this workspace.", 2000); | ||
}); | ||
}); | ||
})); | ||
ctx.subscriptions.push(vscode.commands.registerCommand("zap.ref.select", () => { | ||
return controller.showRefSelectMenu(); | ||
})); | ||
ctx.subscriptions.push(vscode.commands.registerCommand("zap.restart", () => __awaiter(this, void 0, void 0, function* () { | ||
vscode.workspace.getConfiguration("zap").update("enable", true, true); | ||
yield stopController(controller); | ||
yield startController(controller); | ||
}))); | ||
ctx.subscriptions.push(vscode.commands.registerCommand("zap.workspace.add", () => __awaiter(this, void 0, void 0, function* () { | ||
yield controller.addWorkspace(); | ||
}))); | ||
const updateEnabled = (enabled) => { | ||
if (enabled) { | ||
startController(controller); | ||
} | ||
else { | ||
stopController(controller); | ||
} | ||
}; | ||
let lastEnabled = isEnabled(); | ||
ctx.subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => { | ||
if (isEnabled() !== lastEnabled) { | ||
updateEnabled(isEnabled()); | ||
lastEnabled = isEnabled(); | ||
} | ||
})); | ||
updateEnabled(isEnabled()); | ||
return controller; | ||
} | ||
exports.activate = activate; | ||
function isEnabled() { | ||
// Avoid enabling the extension (and trying to connect to the Zap | ||
// server on a socket) when running in test mode because we | ||
// provide a mock connection. | ||
return Boolean(vscode.workspace.getConfiguration("zap").get("enable") && !vscode.workspace.getConfiguration("zap").get("_test") && !process.env.CI); | ||
} | ||
exports.isEnabled = isEnabled; | ||
// TODO(sqs8): is this necessary? | ||
function startController(controller) { | ||
return controller.start().then(() => { }, (err) => { | ||
status_1.outputChannel.appendLine(`Error starting the controller: ${err}`); | ||
controller.outputChannel.appendLine(`Error starting the controller: ${err}`); | ||
}); | ||
@@ -73,5 +74,6 @@ } | ||
; | ||
// TODO(sqs8): is this necessary? | ||
function stopController(controller) { | ||
return controller.stop().then(() => { }, err => { | ||
status_1.outputChannel.appendLine(`Zap failed to stop: ${err}`); | ||
controller.outputChannel.appendLine(`Zap failed to stop: ${err}`); | ||
}); | ||
@@ -81,2 +83,6 @@ } | ||
; | ||
function isWorkspaceRepoHandler(repo) { | ||
return Boolean(repo.onWorkRefReset); | ||
} | ||
exports.isWorkspaceRepoHandler = isWorkspaceRepoHandler; | ||
//# sourceMappingURL=extension.common.js.map |
import * as vscode from "vscode"; | ||
import { Controller } from "./controller"; | ||
export declare function activate(ctx: vscode.ExtensionContext): Controller | null; | ||
import { Extension } from "./extension.common"; | ||
import { VSCodeEnvironment } from "./environment.vscode"; | ||
export declare function activate(ctx: vscode.ExtensionContext, env?: VSCodeEnvironment): Extension; |
// This is the extension's entrypoint used by vscode. Things in here | ||
// are run in vscode and not on any other platform. | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const vscode = require("vscode"); | ||
@@ -8,7 +9,14 @@ const e2e_1 = require("./e2e"); | ||
const environment_vscode_1 = require("./environment.vscode"); | ||
const zapLocalServer = require("./localServer"); | ||
const web_1 = require("./web"); | ||
function activate(ctx) { | ||
function activate(ctx, env) { | ||
if (!env) { | ||
env = new environment_vscode_1.VSCodeEnvironment(); | ||
ctx.subscriptions.push(env); // only dispose ourselves if we created it | ||
} | ||
const deactivate = () => { | ||
ctx.subscriptions.forEach(disposable => disposable.dispose()); | ||
ctx.subscriptions.length = 0; // clear the array | ||
}; | ||
if (!vscode.workspace.rootPath) { | ||
return null; | ||
return { deactivate }; | ||
} | ||
@@ -19,20 +27,11 @@ if (process.env.ZAP_E2E) { | ||
} | ||
ctx.subscriptions.push(e2e_1.enable(process.env.ZAP_E2E_NAME, process.env.ZAP_E2E_ADDR, environment_vscode_1.default)); | ||
ctx.subscriptions.push(e2e_1.enable(process.env.ZAP_E2E_NAME, process.env.ZAP_E2E_ADDR, env)); | ||
} | ||
ctx.subscriptions.push(vscode.commands.registerCommand("zap.install", () => zapLocalServer.install())); | ||
ctx.subscriptions.push(vscode.commands.registerCommand("zap.auth", () => zapLocalServer.authenticateUser())); | ||
web_1.activate(ctx); | ||
const controller = extension_common_1.activate(environment_vscode_1.default, ctx); | ||
const controller = extension_common_1.activate(env, 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); | ||
web_1.activate(ctx, controller, env); | ||
} | ||
return controller; | ||
return { controller, deactivate, subscriptions: ctx.subscriptions }; | ||
} | ||
exports.activate = activate; | ||
//# sourceMappingURL=extension.vscode.js.map |
"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; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const sp = require("child_process"); | ||
@@ -32,3 +25,3 @@ const vscode = require("vscode"); | ||
status_1.outputChannel.appendLine("Installing/upgrading zap..."); | ||
const env = __assign({}, process.env, { | ||
const env = Object.assign({}, process.env, { | ||
// Use GOPATH from vscode-go's configuration, which is sometimes set even when the GOPATH env var is not. | ||
@@ -35,0 +28,0 @@ GOPATH: process.env.GOPATH ? process.env.GOPATH : vscode.workspace.getConfiguration("go").get("gopath") }); |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const vscode = require("vscode"); | ||
@@ -148,2 +149,3 @@ const textDoc_1 = require("libzap/lib/ot/textDoc"); | ||
charDelta += newCharsBeforePos - oldCharsBeforePos; | ||
// console.log(`Char delta: ${charDelta}`); | ||
} | ||
@@ -206,2 +208,3 @@ offsetDelta += unicode_1.charLength(change.text) - rangeCharLengthFromVSCodeLength(change.range, change.rangeLength, extraWideCharsInOldDocument); | ||
retain += rangeCharLength; | ||
// console.log(`CHANGE ${JSON.stringify(change)}: rangeStart=${rangeStart} retain=${retain} netAdditions=${netAdditions} edits=${JSON.stringify(edits)}`); | ||
} | ||
@@ -213,2 +216,3 @@ const remaining = unicode_1.charLength(doc.getText()) - retain - netAdditions; | ||
edits.push(remaining); | ||
// console.log(`REMAINING ${remaining}: doc.getText().length=${doc.getText().length} retain=${retain} netAdditions=${netAdditions}`); | ||
} | ||
@@ -215,0 +219,0 @@ return edits; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const vscode_1 = require("vscode"); | ||
@@ -3,0 +4,0 @@ const workspace_1 = require("libzap/lib/ot/workspace"); |
@@ -5,9 +5,8 @@ import * as vscode from "vscode"; | ||
import { IEnvironment } from "./environment"; | ||
export declare const outputChannel: vscode.OutputChannel; | ||
export declare let itemForE2ETestOnly: vscode.StatusBarItem; | ||
export declare enum Status { | ||
Starting = 0, | ||
ServerUnavailable = 1, | ||
WorkspaceNotAddedOnServer = 2, | ||
OK = 3, | ||
Off = 0, | ||
Starting = 1, | ||
On = 2, | ||
Inactive = 3, | ||
ServerError = 4, | ||
@@ -21,7 +20,7 @@ ServerWarning = 5, | ||
export declare class StatusHandler implements FeatureHandler { | ||
private remoteClient; | ||
private environment; | ||
private client; | ||
private env; | ||
static readonly id: string; | ||
private item; | ||
constructor(remoteClient: Client, environment: IEnvironment); | ||
constructor(client: Client, env: IEnvironment); | ||
register(connection: IConnection): void; | ||
@@ -28,0 +27,0 @@ dispose(): void; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const vscode = require("vscode"); | ||
const client_1 = require("libzap/lib/remote/client"); | ||
const protocol_1 = require("libzap/lib/remote/protocol"); | ||
exports.outputChannel = vscode.window.createOutputChannel("zap"); | ||
const ref_1 = require("libzap/lib/ref"); | ||
var Status; | ||
(function (Status) { | ||
Status[Status["Starting"] = 0] = "Starting"; | ||
Status[Status["ServerUnavailable"] = 1] = "ServerUnavailable"; | ||
Status[Status["WorkspaceNotAddedOnServer"] = 2] = "WorkspaceNotAddedOnServer"; | ||
Status[Status["OK"] = 3] = "OK"; | ||
Status[Status["Off"] = 0] = "Off"; | ||
Status[Status["Starting"] = 1] = "Starting"; | ||
Status[Status["On"] = 2] = "On"; | ||
Status[Status["Inactive"] = 3] = "Inactive"; | ||
Status[Status["ServerError"] = 4] = "ServerError"; | ||
@@ -20,25 +21,19 @@ Status[Status["ServerWarning"] = 5] = "ServerWarning"; | ||
class StatusHandler { | ||
constructor(remoteClient, environment) { | ||
this.remoteClient = remoteClient; | ||
this.environment = environment; | ||
constructor(client, env) { | ||
this.client = client; | ||
this.env = env; | ||
this.item = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 10); | ||
exports.itemForE2ETestOnly = this.item; | ||
this.item.show(); | ||
if (!vscode.workspace.getConfiguration("zap").get("enable")) { | ||
this.setStatus(Status.ServerUnavailable); | ||
} | ||
else { | ||
this.setStatus(Status.Starting); | ||
} | ||
this.remoteClient.onDidChangeState((event) => { | ||
exports.outputChannel.appendLine(`Status: ${client_1.State[event.oldState]} ⟶ ${client_1.State[event.newState]}`); | ||
this.setStatus(Status.Starting); | ||
this.client.onDidChangeState((event) => { | ||
if (event.newState === client_1.State.Running) { | ||
this.setStatus(Status.OK); | ||
this.setStatus(Status.On); | ||
} | ||
if (event.newState === client_1.State.Stopped) { | ||
this.setStatus(Status.ServerUnavailable); | ||
this.setStatus(Status.Off); | ||
} | ||
}); | ||
this.environment.onDidChangeZapBranch(() => { | ||
this.setStatus(Status.OK); | ||
this.env.onWorkRefReset(() => { | ||
this.setStatus(this.env.workRef ? Status.On : Status.Inactive); | ||
}); | ||
@@ -54,46 +49,45 @@ } | ||
setStatus(status, message) { | ||
const branchText = this.environment.zapBranch || "$(ellipses)"; | ||
let refText; | ||
if (this.env.workRef) { | ||
refText = ref_1.abbrevRef(this.env.workRef.target || this.env.workRef.name); | ||
} | ||
else { | ||
refText = "$(ellipses)"; | ||
} | ||
switch (status) { | ||
case Status.Starting: | ||
this.item.color = "white"; | ||
this.item.text = `$(zap) ${branchText}`; | ||
this.item.command = "zap.restart"; | ||
this.environment.isRunning = true; | ||
this.item.text = `$(zap) ${refText}`; | ||
this.item.tooltip = "Starting..."; | ||
this.item.command = ""; | ||
break; | ||
case Status.ServerUnavailable: | ||
this.item.color = "red"; | ||
case Status.Off: | ||
this.item.color = "white"; | ||
this.item.text = "$(zap) Off"; | ||
this.item.tooltip = "Start Zap"; | ||
this.item.command = "zap.restart"; | ||
this.environment.isRunning = false; | ||
this.item.tooltip = "Start using Zap in this workspace"; | ||
this.item.command = "zap.workspace.start"; | ||
break; | ||
case Status.WorkspaceNotAddedOnServer: | ||
case Status.Inactive: | ||
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; | ||
this.item.text = "$(zap) Zap"; | ||
this.item.tooltip = "Switch to a Zap ref..."; | ||
this.item.command = "zap.ref.select"; | ||
break; | ||
case Status.OK: | ||
this.item.color = "white"; // default color | ||
this.item.text = `$(zap) ${this.environment.zapBranch || "Select a Branch"}`; | ||
this.item.tooltip = ""; | ||
// TODO(sqs): make this a menu with the choice to remove | ||
// this workspace OR switch zap branches. | ||
case Status.On: | ||
this.item.color = "white"; | ||
this.item.text = `$(zap) ${refText}`; | ||
this.item.tooltip = "Switch to Zap ref..."; | ||
this.item.command = "zap.ref.select"; | ||
this.environment.isRunning = true; | ||
break; | ||
case Status.ServerWarning: | ||
this.item.color = "yellow"; | ||
this.item.text = `$(zap) ${branchText} $(alert)`; | ||
this.item.text = `$(zap) ${refText} $(alert)`; | ||
this.item.tooltip = `Warning: ${message}`; | ||
this.item.command = ""; | ||
this.environment.isRunning = false; | ||
break; | ||
case Status.ServerError: | ||
this.item.color = "red"; | ||
this.item.text = `$(zap) ${branchText} $(alert)`; | ||
this.item.text = `$(zap) ${refText} $(alert)`; | ||
this.item.tooltip = `Error: ${message}`; | ||
this.item.command = ""; | ||
this.environment.isRunning = false; | ||
break; | ||
@@ -113,3 +107,3 @@ } | ||
case protocol_1.StatusType.StatusTypeOK: | ||
status = Status.OK; | ||
status = Status.On; | ||
break; | ||
@@ -116,0 +110,0 @@ default: |
@@ -6,2 +6,3 @@ /* -------------------------------------------------------------------------------------------- | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
class Delayer { | ||
@@ -8,0 +9,0 @@ constructor(defaultDelay) { |
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>; | ||
export declare function openWorkspaceInBrowser(): Promise<void>; | ||
import { Controller } from "./controller"; | ||
import { RefIdentifier, RefInfo, RepoInfoResult } from "libzap/lib/remote/protocol"; | ||
import { IEnvironment } from "./environment"; | ||
export declare function activate(context: vscode.ExtensionContext, controller: Controller, env: IEnvironment): void; | ||
export interface OpenArgs { | ||
localRepo: string; | ||
ref: string; | ||
sel?: vscode.Selection; | ||
path?: string; | ||
event: "OpenFile" | "OpenAtCursor" | "OpenWorkspace"; | ||
} | ||
export interface Queryer { | ||
queryRefInfo(ref: RefIdentifier): Thenable<RefInfo>; | ||
queryRepoInfo(repo: string): Thenable<RepoInfoResult>; | ||
} | ||
export declare function openInBrowser(queryer: Queryer, args: OpenArgs): Thenable<any>; |
"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) { | ||
@@ -18,28 +10,49 @@ return new (P || (P = Promise))(function (resolve, reject) { | ||
}; | ||
const fs = require("fs"); | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const vscode = require("vscode"); | ||
const status_1 = require("./status"); | ||
const open = require("open"); // tslint:disable-line no-var-requires | ||
function activate(context) { | ||
context.subscriptions.push(vscode.commands.registerCommand("zap.web.openInBrowser.file", openFile)); | ||
context.subscriptions.push(vscode.commands.registerTextEditorCommand("zap.web.openInBrowser.cursor", openAtCursor)); | ||
context.subscriptions.push(vscode.commands.registerCommand("zap.web.openWorkspaceInBrowser", openWorkspaceInBrowser)); | ||
function activate(context, controller, env) { | ||
context.subscriptions.push(vscode.commands.registerCommand("zap.web.openInBrowser.file", uri => { | ||
if (!env.workRef) { | ||
return vscode.window.showErrorMessage("Unable to open file in browser: Zap workspace is not ready."); | ||
} | ||
return openInBrowser(controller, { | ||
localRepo: env.repo, | ||
ref: env.workRef.target || env.workRef.name, | ||
path: vscode.workspace.asRelativePath(uri), | ||
event: "OpenFile", | ||
}); | ||
})); | ||
context.subscriptions.push(vscode.commands.registerTextEditorCommand("zap.web.openInBrowser.cursor", editor => { | ||
if (!env.workRef) { | ||
return vscode.window.showErrorMessage("Unable to open in browser: Zap workspace is not ready."); | ||
} | ||
return openInBrowser(controller, { | ||
localRepo: env.repo, | ||
ref: env.workRef.target || env.workRef.name, | ||
path: vscode.workspace.asRelativePath(editor.document.uri), | ||
sel: editor.selection, | ||
event: "OpenAtCursor", | ||
}); | ||
})); | ||
context.subscriptions.push(vscode.commands.registerCommand("zap.web.openWorkspaceInBrowser", () => { | ||
if (!env.workRef) { | ||
return vscode.window.showErrorMessage("Unable to open workspace in browser: Zap workspace is not ready."); | ||
} | ||
return openInBrowser(controller, { | ||
localRepo: env.repo, | ||
ref: env.workRef.target || env.workRef.name, | ||
event: "OpenWorkspace", | ||
}); | ||
})); | ||
} | ||
exports.activate = activate; | ||
function openFile(uri) { | ||
if (isDirectory(uri.fsPath)) { | ||
vscode.window.showErrorMessage("Opening a directory in the Web browser is not supported."); | ||
return; | ||
} | ||
openInBrowser(uri, null, "OpenFile"); | ||
} | ||
function openAtCursor(editor) { | ||
openInBrowser(editor.document.uri, editor.selection, "OpenAtCursor"); | ||
} | ||
function openInBrowser(uri, sel, trackingEventName) { | ||
// TODO(sqs): support Zap workspaces that are not at the root of | ||
// 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()}#${sel ? formatSelection(sel) : ""}`; | ||
open(url); | ||
function openInBrowser(queryer, args) { | ||
return queryUpstreamInfo(queryer, args).then(({ repo, ref, endpoint }) => { | ||
endpoint = endpoint.replace(/\/\.api\/zap$/, ""); | ||
let url = `${endpoint}/${repo}@${ref}`; | ||
if (args.path) { | ||
url += `/-/blob/${args.path}`; | ||
} | ||
url += `?_event=${args.event}&_source=${versionString()}#${args.sel ? formatSelection(args.sel) : ""}`; | ||
vscode.commands.executeCommand("vscode.open", vscode.Uri.parse(url)); | ||
return true; | ||
@@ -49,48 +62,15 @@ }); | ||
exports.openInBrowser = openInBrowser; | ||
function workspaceBrowserURL() { | ||
return getRemoteRefID().then(remoteInfo => { | ||
let url = vscode.workspace.getConfiguration("zap").get("web.url") + | ||
"/${REPO}@${GITBRANCH}" | ||
.replace("${REPO}", remoteInfo.repo) | ||
.replace("${GITBRANCH}", remoteInfo.ref || 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.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; | ||
}); | ||
} | ||
function openWorkspaceInBrowser() { | ||
return workspaceBrowserURL().then((identifier) => open(`${identifier.url}?_event=OpenWorkspace&_source=${versionString()}`)); | ||
} | ||
exports.openWorkspaceInBrowser = openWorkspaceInBrowser; | ||
// getRemoteRefID queries the server to find the remote ref for the | ||
// current workspace's HEAD. This is necessary because (e.g.) the | ||
// remote repo name might be "github.com/foo/bar" but the local repo | ||
// name is "/home/alice/src/github.com/foo/bar". | ||
function getRemoteRefID() { | ||
function queryUpstreamInfo(queryer, args) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const controller = vscode.extensions.getExtension("sqs.vscode-zap").exports; | ||
const localRepoName = vscode.workspace.rootPath; | ||
const refInfo = yield controller.queryRefInfo({ repo: localRepoName, ref: "HEAD" }); | ||
const repoInfo = yield controller.queryRepoInfo(localRepoName); | ||
if (!repoInfo.refs) { | ||
return Promise.reject("Upstream branch not configured"); | ||
const refInfo = yield queryer.queryRefInfo({ repo: args.localRepo, ref: args.ref }); | ||
const repoInfo = yield queryer.queryRepoInfo(args.localRepo); | ||
const remoteNames = Object.keys(repoInfo.remotes); | ||
if (remoteNames.length === 0 || remoteNames.length >= 2) { | ||
return Promise.reject(`unable to generate web URL (repository must have exactly 1 remote, but it has ${remoteNames.length})`); | ||
} | ||
const refUpstreamRemoteName = repoInfo.refs[refInfo.target].upstream; | ||
const remote = repoInfo.remotes[remoteNames[0]]; | ||
return { | ||
repo: repoInfo.remotes[refUpstreamRemoteName].repo, | ||
repo: remote.repo, | ||
ref: refInfo.target, | ||
gitBranch: refInfo.state.gitBranch, | ||
endpoint: remote.endpoint, | ||
}; | ||
@@ -105,5 +85,2 @@ }); | ||
} | ||
function isDirectory(absPath) { | ||
return fs.statSync(absPath).isDirectory(); | ||
} | ||
function versionString() { | ||
@@ -110,0 +87,0 @@ const ext = vscode.extensions.getExtension("sqs.vscode-zap"); |
import * as vscode from "vscode"; | ||
import { WorkspaceOp } from "libzap/lib/ot/workspace"; | ||
import { Workspace as HandlerWorkspace } from "libzap/lib/remote/handler"; | ||
import { Workspace as IWorkspace } from "libzap/lib/workspace"; | ||
import { WorkspaceWillSaveFileParams } from "libzap/lib/remote/protocol"; | ||
import { IEnvironment } from "./environment"; | ||
export declare class Workspace implements HandlerWorkspace { | ||
export declare class Workspace implements IWorkspace { | ||
environment: IEnvironment; | ||
@@ -55,7 +55,2 @@ private onOpExtListener?; | ||
reset(history: WorkspaceOp[]): Promise<void>; | ||
/** | ||
* updateHistory fast-forwards the workspace state to the latest version by | ||
* applying all ops in the supplied history that haven't been applied yet. | ||
*/ | ||
updateHistory(history: WorkspaceOp[]): Promise<void>; | ||
private onDidChangeActiveTextEditor(editor); | ||
@@ -62,0 +57,0 @@ private onDidChangeTextEditorSelection(ev); |
@@ -10,4 +10,4 @@ "use strict"; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const vscode = require("vscode"); | ||
const isEqual = require("lodash/isEqual"); | ||
const workspace_1 = require("libzap/lib/ot/workspace"); | ||
@@ -306,3 +306,4 @@ const subtract_1 = require("libzap/lib/ot/subtract"); | ||
if (!(yield doc.save())) { | ||
throw new Error(`after applying edit to ${doc.uri.toString()}, save failed - edit was: ${JSON.stringify(edit.get(doc.uri))}`); | ||
// TODO(sqs8): this was a throw new Error() but I made it a console.error. | ||
console.error(`after applying edit to ${doc.uri.toString()}, save failed - edit was: ${JSON.stringify(edit.get(doc.uri))}`); | ||
} | ||
@@ -380,8 +381,3 @@ } | ||
// 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); | ||
} | ||
} | ||
yield this.environment.revertAllTextDocuments(); | ||
} | ||
@@ -406,3 +402,2 @@ this.suppressRecord = false; | ||
this.history = []; | ||
this._onReset.fire({}); | ||
} | ||
@@ -417,58 +412,2 @@ } | ||
} | ||
/** | ||
* updateHistory fast-forwards the workspace state to the latest version by | ||
* applying all ops in the supplied history that haven't been applied yet. | ||
*/ | ||
updateHistory(history) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (this.applying) { | ||
yield this.applying; | ||
} | ||
try { | ||
this.suppressRecord = false; | ||
this.documentIsDirty.clear(); | ||
if (history.length > 0) { | ||
if (history.length < this.history.length) { | ||
// HACK: sometimes we get duplicate select events when updating history | ||
// after a disconnect, which causes errors. Manually remove it here. | ||
// | ||
// TODO: fix the source of the extra select ops. | ||
if (this.history.length === history.length + 1 && this.history[this.history.length - 1].sel) { | ||
console.warn("Warning: removing extra sel op while updating history"); | ||
this.history.pop(); | ||
} | ||
// Check again since this.history was possibly modified above. | ||
if (history.length < this.history.length) { | ||
console.warn(`Error updating history: supplied history version ${history.length} less than current version ${this.history.length}`); | ||
return this.reset(history); | ||
} | ||
} | ||
// Ensure the new history is a prefix of the current history so we know it can be fast-forwarded sucessfully. | ||
for (let i = 0; i < this.history.length; i++) { | ||
const [h1, h2] = [this.history[i], history[i]]; | ||
if (!isEqual(h1, h2)) { | ||
console.warn(`Error updating history: histories not equal (version ${i} ${JSON.stringify(h1)} != ${JSON.stringify(h2)}`); | ||
return this.reset(history); | ||
} | ||
} | ||
// Apply the new ops from history onto the current document. | ||
const toApply = history.slice(this.history.length); | ||
const newHistory = this.history.concat(toApply.map(copyHACK)); // copy to ensure the ops aren't mutated during compose. | ||
if (toApply.length > 0) { | ||
let composed = workspace_1.composeAll(toApply); | ||
if (this.environment.automaticallyApplyingFileSystemChanges) { | ||
composed = op_1.opWithBufferEditsOmittingChangesOnDisk(composed); | ||
} | ||
yield this.apply(composed); | ||
this.history = newHistory; | ||
} | ||
} | ||
} | ||
catch (err) { | ||
console.error(`Zap update history failed: ${err}`); | ||
// Pass along error to server to tell it we failed. | ||
throw err; | ||
} | ||
}); | ||
} | ||
onDidChangeActiveTextEditor(editor) { | ||
@@ -524,3 +463,3 @@ if (editor) { | ||
if (oldText === newText) { | ||
console.error(`unexpected: document ${doc.uri.toString()} is dirty, but its on- disk contents equal its in -memory contents`); | ||
console.error(`unexpected: document ${doc.uri.toString()} is dirty, but its on-disk contents equal its in-memory contents`); | ||
} | ||
@@ -666,2 +605,3 @@ this.onOpListener({ | ||
if (rev === undefined) { | ||
// throw new Error(`this.revBeforeDocumentApply(${doc.uri.toString()})=undefined unexpectedly even though this.applyingOp=${opToString(this.applyingOp)}`); | ||
} | ||
@@ -668,0 +608,0 @@ else if (doc.version - rev === 0) { |
import * as vscode from "vscode"; | ||
import { Workspace } from "../src/workspace"; | ||
import { WorkspaceOp } from "libzap/lib/ot/workspace"; | ||
export declare function checkZapNotEnabledAtStartup(): void; | ||
import { VSCodeEnvironment } from "../src/environment.vscode"; | ||
export declare function capture(workspace: Workspace): { | ||
@@ -14,2 +14,3 @@ ops: WorkspaceOp[]; | ||
export declare function withOpenTextDocument(fileName: string, f: (doc: vscode.TextDocument) => Thenable<any>): Promise<void>; | ||
export declare const testEnv: VSCodeEnvironment; | ||
export declare function withWorkspace(f: (workspace: Workspace) => Thenable<any>): Promise<any>; | ||
@@ -16,0 +17,0 @@ export declare function sleep(msec: number): Promise<void>; |
@@ -10,2 +10,3 @@ "use strict"; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const assert = require("assert"); | ||
@@ -15,11 +16,4 @@ const fs = require("fs"); | ||
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() { | ||
if (extension_common_1.isEnabled()) { | ||
throw new Error(`vscode config zap._test or 'CI' env var must be true before tests run, or else the extension will try to connect to the system Zap server. Check ./test/testdata/.vscode/settings.json; perhaps it got modified?`); | ||
} | ||
} | ||
exports.checkZapNotEnabledAtStartup = checkZapNotEnabledAtStartup; | ||
function capture(workspace) { | ||
@@ -65,5 +59,6 @@ const ops = []; | ||
exports.withOpenTextDocument = withOpenTextDocument; | ||
exports.testEnv = new environment_vscode_1.VSCodeEnvironment(); | ||
function withWorkspace(f) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const workspace = new workspace_1.Workspace(environment_vscode_1.default); | ||
const workspace = new workspace_1.Workspace(exports.testEnv); | ||
workspace.initHooks(); | ||
@@ -70,0 +65,0 @@ return ensureAfter(f(workspace), () => workspace.dispose()); |
@@ -12,2 +12,3 @@ // HACK(sqs): my vscode kept adding spaces back in | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const assert = require("assert"); | ||
@@ -14,0 +15,0 @@ const vscode = require("vscode"); |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const vscode_1 = require("vscode"); | ||
@@ -3,0 +4,0 @@ class FullTextDocument { |
@@ -10,2 +10,3 @@ "use strict"; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const assert = require("assert"); | ||
@@ -15,3 +16,2 @@ const vscode = require("vscode"); | ||
const common_1 = require("./common"); | ||
const environment_vscode_1 = require("../src/environment.vscode"); | ||
function checkInitialState(doc, ops) { | ||
@@ -24,4 +24,4 @@ assert.equal(doc.getText(), "abc\n"); | ||
} | ||
exports.checkInitialState = checkInitialState; | ||
suite("Workspace", () => __awaiter(this, void 0, void 0, function* () { | ||
suiteSetup(() => common_1.checkZapNotEnabledAtStartup()); | ||
suiteSetup(() => __awaiter(this, void 0, void 0, function* () { | ||
@@ -269,6 +269,6 @@ yield vscode.workspace.getConfiguration("zap").update("share.selections", false); | ||
suiteSetup(() => { | ||
orig = environment_vscode_1.default.automaticallyApplyingFileSystemChanges; | ||
environment_vscode_1.default.automaticallyApplyingFileSystemChanges = true; | ||
orig = common_1.testEnv.automaticallyApplyingFileSystemChanges; | ||
common_1.testEnv.automaticallyApplyingFileSystemChanges = true; | ||
}); | ||
suiteTeardown(() => { environment_vscode_1.default.automaticallyApplyingFileSystemChanges = orig; }); | ||
suiteTeardown(() => { common_1.testEnv.automaticallyApplyingFileSystemChanges = orig; }); | ||
test("one remote non-buffered edit", () => { | ||
@@ -332,9 +332,9 @@ return common_1.withOpenTextDocument("f", (doc) => { | ||
suiteSetup(() => { | ||
orig = environment_vscode_1.default.automaticallyApplyingFileSystemChanges; | ||
environment_vscode_1.default.automaticallyApplyingFileSystemChanges = false; | ||
environment_vscode_1.default.testOnly_fakeDocumentBaseContents = "abc\n"; | ||
orig = common_1.testEnv.automaticallyApplyingFileSystemChanges; | ||
common_1.testEnv.automaticallyApplyingFileSystemChanges = false; | ||
common_1.testEnv.testOnly_fakeDocumentBaseContents = "abc\n"; | ||
}); | ||
suiteTeardown(() => { | ||
environment_vscode_1.default.automaticallyApplyingFileSystemChanges = orig; | ||
environment_vscode_1.default.testOnly_fakeDocumentBaseContents = undefined; | ||
common_1.testEnv.automaticallyApplyingFileSystemChanges = orig; | ||
common_1.testEnv.testOnly_fakeDocumentBaseContents = undefined; | ||
}); | ||
@@ -426,2 +426,3 @@ test("one remote non-buffered edit", () => { | ||
catch (err) { | ||
// Error is expected. | ||
} | ||
@@ -428,0 +429,0 @@ // The doc should be unchanged. |
@@ -5,3 +5,3 @@ { | ||
"description": "Real-time code synchronization", | ||
"version": "0.0.100", | ||
"version": "1.0.1", | ||
"publisher": "sqs", | ||
@@ -35,13 +35,12 @@ "preview": true, | ||
"devDependencies": { | ||
"@types/lodash": "^4.14.60", | ||
"@types/mocha": "^2.2.33", | ||
"@types/node": "^6.0.40", | ||
"@types/lodash": "4.14.39", | ||
"tslint": "^4.1.0", | ||
"typescript": "^2.1.6", | ||
"typescript": "^2.2.2", | ||
"vscode": "^1.1.0" | ||
}, | ||
"dependencies": { | ||
"libzap": "^0.0.100", | ||
"lodash": "^4.17.2", | ||
"open": "^0.0.5", | ||
"libzap": "^1.0.1", | ||
"lodash": "^4.17.4", | ||
"vscode-jsonrpc": "3.0.1-alpha.7" | ||
@@ -55,12 +54,2 @@ }, | ||
"properties": { | ||
"zap.enable": { | ||
"type": "boolean", | ||
"default": true, | ||
"description": "Enable Zap" | ||
}, | ||
"zap.overwrite": { | ||
"type": "boolean", | ||
"default": true, | ||
"description": "conflict resolution strategy; if true, local overwrites remote; otherwise remote overwrites local" | ||
}, | ||
"zap.share.selections": { | ||
@@ -81,7 +70,2 @@ "type": "boolean", | ||
}, | ||
"zap.web.url": { | ||
"type": "string", | ||
"default": "https://sourcegraph.com", | ||
"description": "Base Web URL for the 'Open in Web Browser' action" | ||
}, | ||
"zap._test": { | ||
@@ -96,22 +80,12 @@ "type": "boolean", | ||
{ | ||
"command": "zap.install", | ||
"title": "Zap: Install/update client", | ||
"description": "Installs the latest version of the Zap command-line client." | ||
"command": "zap.workspace.on", | ||
"title": "Zap: Turn On in Workspace", | ||
"description": "Start using Zap in this workspace." | ||
}, | ||
{ | ||
"command": "zap.auth", | ||
"title": "Zap: Authenticate", | ||
"description": "Authenticates Zap with Sourcegraph access token" | ||
"command": "zap.workspace.off", | ||
"title": "Zap: Turn Off in Workspace", | ||
"description": "Stop using Zap in this workspace." | ||
}, | ||
{ | ||
"command": "zap.workspace.add", | ||
"title": "Zap: Enable in workspace", | ||
"description": "Enables Zap in this workspace." | ||
}, | ||
{ | ||
"command": "zap.workspace.remove", | ||
"title": "Zap: Disable in workspace", | ||
"description": "Disable Zap in this workspace." | ||
}, | ||
{ | ||
"command": "zap.web.openInBrowser.file", | ||
@@ -130,7 +104,2 @@ "title": "⚡ Open in Web Browser", | ||
"description": "Opens the current workspace in your Web browser." | ||
}, | ||
{ | ||
"command": "zap.restart", | ||
"title": "Restart Zap client", | ||
"description": "Restarts the Zap client" | ||
} | ||
@@ -137,0 +106,0 @@ ], |
import * as vscode from "vscode"; | ||
import { Client, ClientOptions, ServerOptions, State, StateChangeEvent } from "libzap/lib/remote/client"; | ||
import { Handler, MergeStrategy } from "libzap/lib/remote/handler"; | ||
import { RefIdentifier, RefListRequest, RefListParams, RefInfo, RefInfoRequest, RepoInfoResult, RepoInfoRequest, WorkspaceAddRequest } from "libzap/lib/remote/protocol"; | ||
import { Trace } from "vscode-jsonrpc"; | ||
import { Client, ClientOptions, ServerOptions } from "libzap/lib/remote/client"; | ||
import { AbstractWorkspaceHandler, Handler } from "libzap/lib/remote/handler"; | ||
import { RefIdentifier, RefListRequest, RefListParams, RefInfo, RefInfoRequest, RepoInfoResult, RepoInfoRequest } from "libzap/lib/remote/protocol"; | ||
import { IEnvironment } from "./environment"; | ||
@@ -9,2 +10,3 @@ import { ZapSCMProvider } from "./scm"; | ||
import { WorkspaceState } from "./extension.common"; | ||
import { showRefQuickPick } from "./refQuickPick"; | ||
@@ -14,3 +16,3 @@ export class Controller { | ||
public handler: Handler; | ||
private workspace: Workspace; | ||
public workspace: Workspace; | ||
private scmProvider: ZapSCMProvider; | ||
@@ -20,2 +22,4 @@ private toDispose: vscode.Disposable[] = []; | ||
public outputChannel = vscode.window.createOutputChannel("zap"); | ||
constructor( | ||
@@ -30,8 +34,8 @@ private serverOptions: ServerOptions, | ||
this.client = new Client(this.environment.userID, this.serverOptions, this.clientOptions); | ||
if (process.env.TRACE) { this.client.trace = Trace.Verbose; } | ||
this.handler = new Handler(this.client); | ||
this.toDispose.push(this.handler); | ||
// Create workspace and ensure hooks are enabled only when the | ||
// server is running. | ||
this.workspace = new Workspace(this.environment); | ||
this.workspace.initHooks(); | ||
@@ -41,137 +45,10 @@ // Only enable the SCM provider on sourcegraph.com. | ||
this.scmProvider = new ZapSCMProvider(initState); | ||
this.workspace.onGenerateOp(op => this.environment.zapRef && this.scmProvider.updateFromOp(op)); | ||
this.workspace.onApplyOp(op => this.environment.zapRef && this.scmProvider.updateFromOp(op)); | ||
this.workspace.onGenerateOp(op => this.environment.workRef && this.scmProvider.updateFromOp(op)); | ||
this.workspace.onApplyOp(op => this.environment.workRef && this.scmProvider.updateFromOp(op)); | ||
this.workspace.onReset(op => this.scmProvider.reset(op)); | ||
} | ||
let initedHooks = false; | ||
this.client.onDidChangeState((event: StateChangeEvent) => { | ||
switch (event.newState) { | ||
case State.Running: | ||
if (!initedHooks) { | ||
this.workspace.initHooks(); | ||
initedHooks = true; | ||
} | ||
this.attach(); | ||
break; | ||
case State.Stopped: | ||
break; | ||
} | ||
}); | ||
// Register client features. | ||
this.client.registerHandler(Handler.id, this.handler); | ||
// Reestablish watch and reattach workspace whenever we | ||
// reconnect (and on the first time we connect)... | ||
// ...and when we switch refs. | ||
this.environment.onDidChangeZapRef(() => { | ||
if (initedHooks) { | ||
this.attach(true); | ||
} | ||
}); | ||
// Keep merge strategy configuration setting up-to-date. | ||
let overwrite = vscode.workspace.getConfiguration("zap").get<boolean>("overwrite"); | ||
vscode.workspace.onDidChangeConfiguration(() => { | ||
if (overwrite !== vscode.workspace.getConfiguration("zap").get<boolean>("overwrite")) { | ||
overwrite = vscode.workspace.getConfiguration("zap").get<boolean>("overwrite"); | ||
this.attach(); | ||
} | ||
}, null, this.toDispose); | ||
if ((vscode.workspace as any).onDidUpdateWorkspace) { | ||
// This block allows Sourcegraph.com to communicate workspace rev state changes | ||
// to the zap extension. | ||
(vscode.workspace as any).onDidUpdateWorkspace((workspace: any) => { | ||
this.scmProvider.reset(); | ||
this.workspaceState = { | ||
workspace: workspace.resource.toString(), | ||
revState: workspace.revState, | ||
}; | ||
if (workspace.revState) { | ||
this.environment.zapRef = workspace.revState.zapRef; | ||
} else { | ||
this.environment.zapRef = undefined; | ||
} | ||
}); | ||
} | ||
} | ||
private async attach(reset?: boolean): Promise<void> { | ||
if (this.environment.prevZapRef) { | ||
// Before we can attach a new zap ref, we must first detach the previous | ||
// and revert documents. | ||
if (this.environment.prevZapRef !== this.environment.zapRef) { | ||
this.handler.detachWorkspace(this.environment.repo, this.environment.prevZapRef); | ||
await this.revertAllDocuments(); | ||
} | ||
} | ||
const mergeStrategy = reset ? MergeStrategy.RemoteClobbersLocal : MergeStrategy.FastForward; | ||
if (this.environment.zapRef) { | ||
try { | ||
await this.client.onReady(); | ||
await this.handler.attachWorkspace({ | ||
repo: this.environment.repo, | ||
ref: this.environment.zapRef, | ||
}, this.workspace, mergeStrategy).then(() => null, (err) => { | ||
console.error(`Error watching repo: ${err} ${err.stack}`); | ||
}); | ||
} catch (err) { | ||
throw new Error(`Error attaching workspace for ref: ${this.environment.zapRef}: ${err}`); | ||
} | ||
} | ||
return Promise.resolve(); | ||
} | ||
private reset(mergeStrategy: MergeStrategy): Thenable<void> { | ||
// this.revertAllDocuments(); | ||
if (this.environment.zapRef) { | ||
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}`); | ||
}); | ||
} | ||
return Promise.resolve(); | ||
} | ||
private async revertAllDocuments(rev?: string): Promise<any> { | ||
return new Promise(async (resolve) => { | ||
const docsToRevert = new Set<string>(vscode.workspace.textDocuments.map(doc => { | ||
// Only revert mutable docs (not `git://github.com/gorilla/mux?sha#file/path` documents). | ||
// Only revert docs if a .revert() method exists on the vscode text document | ||
if ((doc as any).revert && doc.uri.query === "") { | ||
return doc.uri.toString(); | ||
} | ||
return ""; | ||
}).filter(doc => doc !== "")); | ||
// The zap extension has a race condition for reverting documents and applying | ||
// new ops. If a user switches from zap ref A to B, we must revert all documents | ||
// before applying ops for B. Therefore, the zap extension must wait to be notified | ||
// from the main thread that documents have actually been reverted. | ||
let disposable: vscode.Disposable | undefined; | ||
if ((vscode.workspace as any).onDidRevertTextDocument) { | ||
disposable = (vscode.workspace as any).onDidRevertTextDocument((docUri: string) => { | ||
docsToRevert.delete(docUri); | ||
if (docsToRevert.size === 0) { | ||
if (disposable) { | ||
disposable.dispose(); | ||
} | ||
resolve(); // all documents have been reverted, this function has completed | ||
} | ||
}); | ||
} | ||
await Promise.all(vscode.workspace.textDocuments | ||
.filter(doc => docsToRevert.has(doc.uri.toString())) | ||
.map(doc => (doc as any).revert(rev))); | ||
}); | ||
} | ||
public dispose(): void { | ||
@@ -181,2 +58,3 @@ if (this.client.needsStop()) { | ||
} | ||
this.outputChannel.dispose(); | ||
this.toDispose.forEach(disposable => disposable.dispose()); | ||
@@ -206,114 +84,12 @@ } | ||
public async addWorkspace(): Promise<void> { | ||
try { | ||
await this.client.sendRequest(WorkspaceAddRequest.type, { dir: vscode.workspace.rootPath! }); | ||
vscode.window.setStatusBarMessage("Workspace added", 2000); | ||
await this.attach(); | ||
Promise.resolve(); | ||
} catch (err) { | ||
vscode.window.showErrorMessage(err); | ||
Promise.reject(err); | ||
public showRefSelectMenu(): Thenable<void> { | ||
const repo = this.handler.repodb.get(this.environment.repo) as AbstractWorkspaceHandler; | ||
if (!repo) { | ||
return vscode.window.showErrorMessage("The Zap repository is not available.").then(() => void 0); | ||
} | ||
} | ||
public showRefSelectMenu(): Thenable<void> { | ||
const params: RefListParams = { repo: this.environment.repo }; | ||
return vscode.window.showQuickPick(this.client.sendRequest(RefListRequest.type, params).then((refs: RefInfo[]) => { | ||
const items: (vscode.QuickPickItem & { onSelect: Function })[] = []; | ||
let canClobberRemote: boolean = false; | ||
let matchingRemoteRegex: string = `remote\/(.*?)\/branch/(${this.environment.zapBranch})`; | ||
for (const ref of refs) { | ||
if (ref.ref.match(matchingRemoteRegex)) { | ||
canClobberRemote = true; | ||
continue; | ||
} | ||
const zapBranchRef = "branch/" + this.environment.zapBranch; | ||
if (ref.repo === this.environment.repo && !(ref.ref === "HEAD" && ref.target === zapBranchRef)) { | ||
let onSelect: Function | undefined = undefined; | ||
let label = ref.ref.replace(/^branch\//, ""); | ||
let detail: string | undefined = undefined; | ||
if (zapBranchRef === ref.ref) { | ||
label = "$(check) " + label; | ||
detail = "Current branch"; | ||
onSelect = () => { | ||
vscode.window.showInformationMessage(`Zap ${ref.ref} is already active.`); | ||
if ((vscode.workspace as any).setWorkspace) { | ||
const resource = vscode.Uri.parse(this.workspaceState!.workspace); | ||
(vscode.workspace as any).setWorkspace(resource, { | ||
...this.workspaceState!.revState, | ||
zapRev: undefined, | ||
zapRef: undefined, | ||
}); | ||
} else { | ||
this.environment.zapRef = undefined; | ||
} | ||
}; | ||
} | ||
items.push({ | ||
label, | ||
description: ref.watchers ? `Watchers: ${ref.watchers.join(" ")} ` : "No current watchers", | ||
detail: detail || "Switch to this branch and start editing", | ||
onSelect: onSelect ? onSelect : () => { | ||
if ((vscode.workspace as any).setWorkspace) { | ||
const resource = vscode.Uri.parse(this.workspaceState!.workspace); | ||
(vscode.workspace as any).setWorkspace(resource, { | ||
zapRev: ref.ref.replace(/^branch\//, ""), | ||
zapRef: ref.ref, | ||
commitID: ref.state!.gitBase, | ||
branch: ref.state!.gitBranch, | ||
}); | ||
} else { | ||
this.environment.zapRef = ref.ref; | ||
} | ||
}, | ||
}); | ||
} | ||
} | ||
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(MergeStrategy.RemoteClobbersLocal); | ||
}, | ||
}); | ||
if (canClobberRemote) { | ||
items.push({ | ||
label: "$(x) Clobber Remote Changes", | ||
description: "Overwrites the zap remote branch with your current state", | ||
onSelect: () => { | ||
this.reset(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({ | ||
label: "$(x) Turn Zap Off", | ||
description: "Stop syncing changes in your editor with Zap", | ||
onSelect: () => { | ||
vscode.workspace.getConfiguration("zap").update("enable", false, true); | ||
this.stop(); | ||
}, | ||
}); | ||
return items; | ||
})) | ||
.then(choice => { | ||
if (choice) { | ||
return choice.onSelect(); | ||
} | ||
}); | ||
return this.client.onReady() | ||
.then(() => this.client.sendRequest(RefListRequest.type, { repo: this.environment.repo } as RefListParams)) | ||
.then(refs => showRefQuickPick(this, repo, refs)); | ||
} | ||
} |
@@ -8,2 +8,3 @@ import * as path from "path"; | ||
import { MessageStream } from "libzap/lib/remote/client"; | ||
import { WorkRef } from "libzap/lib/ref"; | ||
import { ConsoleLogger, IConnection, createConnection } from "libzap/lib/remote/connection"; | ||
@@ -83,3 +84,3 @@ import { itemForE2ETestOnly as statusBarItem } from "./status"; | ||
openDocuments: Document[]; | ||
ref: string; | ||
workRef: WorkRef | undefined; | ||
status: Status; | ||
@@ -99,4 +100,2 @@ } | ||
backspace?: string; | ||
enable?: boolean; | ||
disable?: boolean; | ||
configure: { [key: string]: boolean }; | ||
@@ -143,3 +142,3 @@ } | ||
const rawConn = this.connect(); | ||
rawConn.stream.then(async ({reader, writer}) => { | ||
rawConn.stream.then(async ({ reader, writer }) => { | ||
this.connection = createConnection(reader, writer, errorHandler, closeHandler); | ||
@@ -229,3 +228,3 @@ await this.initialize(); | ||
}), | ||
ref: this.environment.zapRef, | ||
workRef: this.environment.workRef, | ||
status: { | ||
@@ -347,14 +346,2 @@ type: statusType(statusBarItem.text, statusBarItem.color), | ||
case "enable": | ||
{ | ||
await vscode.workspace.getConfiguration("zap").update("enable", true, true); | ||
return Promise.resolve({}); | ||
} | ||
case "disable": | ||
{ | ||
await vscode.workspace.getConfiguration("zap").update("enable", false, true); | ||
return Promise.resolve({}); | ||
} | ||
case "configure": | ||
@@ -361,0 +348,0 @@ { |
@@ -0,7 +1,18 @@ | ||
import * as vscode from "vscode"; | ||
import { MessageStream } from "libzap/lib/remote/client"; | ||
import * as vscode from "vscode"; | ||
import { WorkRef } from "libzap/lib/ref"; | ||
// IEnvironment is an interface to some APIs that behave differently | ||
// in vscode vs. the Web. | ||
// | ||
// This interface should only expose functionality that is | ||
// inherent/global to the environment, not state. TODO(sqs): Remove | ||
// rootURI and repo. | ||
export interface IEnvironment { | ||
/** | ||
* dispose frees up resources. This instance may not be used after | ||
* it is called. | ||
*/ | ||
dispose(): void; | ||
// rootURI returns the workspace's root path ("file://" + | ||
@@ -11,39 +22,22 @@ // vscode.workspace.rootPath). | ||
// TODO(sqs): this is not correct on windows? | ||
rootURI: vscode.Uri | undefined; | ||
readonly rootURI: vscode.Uri | undefined; | ||
// repo is the repository that the workspace is in (e.g., | ||
// "github.com/foo/bar"). | ||
repo: string; | ||
readonly repo: string; | ||
/** | ||
* zapRef is the current Zap ref name (e.g., "branch/mybranch" or | ||
* "HEAD"). This is always "HEAD" on the desktop. | ||
* workRef is the current Zap workspace ref and its target (if | ||
* any). If Zap is not running, it is undefined. | ||
*/ | ||
zapRef: string | undefined; | ||
readonly workRef: WorkRef | undefined; | ||
/** | ||
* prevZapRef is the last Zap ref name (e.g., "branch/mybranch" or | ||
* "HEAD") that was attached. This is always "HEAD" on the desktop. | ||
* onWorkRefReset is fired when the ref state of the work ref is | ||
* reset. The event's value is the new workRefTarget value. | ||
* | ||
* The onWorkRefReset field is undefined if Zap is not running. | ||
*/ | ||
prevZapRef: string | undefined; | ||
readonly onWorkRefReset: vscode.Event<WorkRef | undefined>; | ||
/** | ||
* onDidChangeZapRef is fired (with the new Zap ref) whenever the | ||
* workspace's Zap ref changes. | ||
*/ | ||
onDidChangeZapRef: vscode.Event<string | undefined>; | ||
/** | ||
* 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 returns document's path relative | ||
@@ -82,6 +76,5 @@ // to the rootPath if document is in the rootPath. If it is | ||
/** | ||
* revertTextDocumentToBase reverts doc to its contents as of the | ||
* beginning of the Zap ref. | ||
* revertAllTextDocuments reverts all open text documents. | ||
*/ | ||
revertTextDocumentToBase(doc: vscode.TextDocument): Thenable<void>; | ||
revertAllTextDocuments(): Thenable<void>; | ||
@@ -105,7 +98,6 @@ deleteTextDocument(doc: vscode.TextDocument): Thenable<void>; | ||
readonly userID: string; | ||
} | ||
/** | ||
* isRunning is the current state of the zap client server | ||
*/ | ||
isRunning: boolean; | ||
export interface IInternalEnvironment { | ||
setWorkRef(ref: WorkRef | undefined): void; | ||
} | ||
@@ -112,0 +104,0 @@ |
import * as vscode from "vscode"; | ||
import { Emitter } from "vscode-jsonrpc"; | ||
@@ -8,4 +9,5 @@ import { readFileSync } from "fs"; | ||
import { MessageStream } from "libzap/lib/remote/client"; | ||
import { WorkRef } from "libzap/lib/ref"; | ||
import { IEnvironment } from "./environment"; | ||
import { IEnvironment, IInternalEnvironment } from "./environment"; | ||
@@ -15,6 +17,6 @@ // VSCodeEnvironment is an implementation of IEnvironment used when | ||
// vscode extension API and has access to Node.js APIs. | ||
export class VSCodeEnvironment implements IEnvironment { | ||
private _zapRef: string | undefined; | ||
private _prevZapRef: string | undefined; | ||
private _isRunning: boolean; | ||
export class VSCodeEnvironment implements IEnvironment, IInternalEnvironment { | ||
dispose(): void { | ||
this.workRefResetEmitter.dispose(); | ||
} | ||
@@ -29,62 +31,11 @@ get rootURI(): vscode.Uri | undefined { | ||
private zapRefChangeEmitter = new vscode.EventEmitter<string>(); | ||
get zapRef(): string | undefined { | ||
// In vscode, the Zap ref is always HEAD because vscode | ||
// accesses the file system, and the file system is (by | ||
// definition) HEAD. | ||
return "HEAD"; | ||
private workRefResetEmitter = new Emitter<WorkRef | undefined>(); | ||
private _workRef: WorkRef | undefined; | ||
get onWorkRefReset(): vscode.Event<WorkRef | undefined> { return this.workRefResetEmitter.event; } | ||
public get workRef(): WorkRef | undefined { return this._workRef; } | ||
public setWorkRef(ref: WorkRef | undefined): void { | ||
this._workRef = ref; | ||
this.workRefResetEmitter.fire(ref); | ||
} | ||
// In vscode, the Zap ref is always HEAD because vscode | ||
// accesses the file system, and the file system is (by | ||
// definition) HEAD. | ||
// TODO: @Kingy: Improve switching workspaces in VSCode once resetting / attaching is cleaned up. | ||
set zapRef(ref: string | undefined) { | ||
if (ref !== "HEAD") { | ||
vscode.window.showWarningMessage(`Attempting to set Zap ref to ${ref} instead of HEAD, which is currently unsupported in the extension`); | ||
return; | ||
} | ||
this._prevZapRef = this._zapRef; | ||
this._zapRef = "HEAD"; | ||
this.zapRefChangeEmitter.fire(ref); | ||
} | ||
get prevZapRef(): string | undefined { | ||
// In vscode, the Zap ref is always HEAD because vscode | ||
// accesses the file system, and the file system is (by | ||
// definition) HEAD. | ||
return this._prevZapRef; | ||
} | ||
// In vscode, the Zap ref is always HEAD because vscode | ||
// accesses the file system, and the file system is (by | ||
// definition) HEAD. | ||
// TODO: @Kingy: Improve switching workspaces in VSCode once resetting / attaching is cleaned up. | ||
set prevZapRef(ref: string | undefined) { | ||
this._prevZapRef = ref; | ||
} | ||
get onDidChangeZapRef(): vscode.Event<string> { return this.zapRefChangeEmitter.event; } // never fires | ||
private _zapBranch: string; | ||
private zapBranchChangeEmitter: vscode.EventEmitter<string> = new vscode.EventEmitter<string>(); | ||
get onDidChangeZapBranch(): vscode.Event<string> { return this.zapBranchChangeEmitter.event; } | ||
get zapBranch(): string { | ||
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. | ||
*/ | ||
public _updateZapBranchInternal(branchRef: string): void { | ||
if (!/^branch\//.test(branchRef)) { | ||
throw new Error(`invalid branch ref: ${branchRef}`); | ||
} | ||
const branch = branchRef.replace(/^branch\//, ""); | ||
this._zapBranch = branch; | ||
this.zapBranchChangeEmitter.fire(branch); | ||
} | ||
asRelativePathInsideWorkspace(uri: vscode.Uri): string | null { | ||
@@ -171,4 +122,6 @@ if (uri.scheme !== "file") { return null; } | ||
public testOnly_fakeDocumentBaseContents?: string; // tslint:disable-line variable-name | ||
async revertTextDocumentToBase(doc: vscode.TextDocument): Promise<void> { | ||
// Dummy implementation only used during tests. | ||
public async revertAllTextDocuments(): Promise<void> { | ||
// Dummy implementation only used during tests. The test | ||
// implementation only reverts a single file, because that is | ||
// all that is currently needed by the tests. | ||
if (this.testOnly_fakeDocumentBaseContents === undefined) { | ||
@@ -179,2 +132,4 @@ throw new Error("revertTextDocumentToBase in environment.vscode.ts is only for use in tests"); | ||
const editor = vscode.window.activeTextEditor; | ||
if (!editor) { throw new Error("no editor"); } | ||
const doc = editor.document; | ||
if (!editor || editor.document.uri.toString() !== doc.uri.toString()) { | ||
@@ -220,12 +175,2 @@ throw new Error(`document ${doc.uri.toString()} is not in active editor`); | ||
get userID(): string { return (process.env.ZAP_E2E_NAME || process.env.USER || "anonymous") + "@vscode"; } | ||
set isRunning(status: boolean) { | ||
this._isRunning = status; | ||
} | ||
get isRunning(): boolean { | ||
return this._isRunning; | ||
} | ||
} | ||
export default new VSCodeEnvironment() as IEnvironment; |
@@ -8,5 +8,17 @@ // This is the extension's shared entrypoint. All platforms call this | ||
import { Controller } from "./controller"; | ||
import { IEnvironment, IWorkspaceRevState } from "./environment"; | ||
import { StatusHandler, outputChannel } from "./status"; | ||
import { IEnvironment, IInternalEnvironment, IWorkspaceRevState } from "./environment"; | ||
import { StatusHandler } from "./status"; | ||
import { IRepoHandler, AbstractWorkspaceHandler } from "libzap/lib/remote/handler"; | ||
import { Ref, NewRef } from "libzap/lib/ref"; | ||
export interface Extension { | ||
controller?: Controller; | ||
/** | ||
* deactivate removes and disposes everything that the extension's | ||
* activate function created. | ||
*/ | ||
deactivate(): void; | ||
} | ||
export interface WorkspaceState { | ||
@@ -17,13 +29,7 @@ workspace: string; // e.g. "file://github.com/gorilla/mux" | ||
export function activate(env: IEnvironment, ctx: vscode.ExtensionContext, initState?: WorkspaceState, webContext?: boolean): Controller | null { | ||
export function activate(env: IEnvironment & IInternalEnvironment, ctx: vscode.ExtensionContext, workRef?: Ref | NewRef, initState?: WorkspaceState, webContext?: boolean): Controller | undefined { | ||
if (!vscode.workspace.rootPath) { | ||
return null; | ||
return; | ||
} | ||
ctx.subscriptions.push(outputChannel); | ||
if (initState && initState.revState && initState.revState.zapRef) { | ||
env.zapRef = initState.revState.zapRef; | ||
} | ||
const controller = new Controller({ connect: () => env.openChannel("zap") }, {}, env, initState, webContext); | ||
@@ -33,33 +39,54 @@ ctx.subscriptions.push(controller); | ||
const statusHandler = new StatusHandler(controller.client, env); | ||
ctx.subscriptions.push(statusHandler); | ||
controller.client.registerHandler(StatusHandler.id, statusHandler); | ||
ctx.subscriptions.push(vscode.commands.registerCommand("zap.ref.select", () => { | ||
return controller.showRefSelectMenu(); | ||
})); | ||
startController(controller); | ||
ctx.subscriptions.push(vscode.commands.registerCommand("zap.restart", async () => { | ||
vscode.workspace.getConfiguration("zap").update("enable", true, true); | ||
await stopController(controller); | ||
await startController(controller); | ||
})); | ||
const addWorkspaceRepo = (addToLocalServer: boolean): Thenable<AbstractWorkspaceHandler> => { | ||
return controller.handler.addRepo(env.repo, controller.workspace, workRef, addToLocalServer) | ||
.then(repo => { | ||
if (isWorkspaceRepoHandler(repo)) { | ||
// Keep (IEnvironment).workRefTarget updated. | ||
env.setWorkRef(repo.workRef); | ||
repo.onWorkRefReset(newWorkRef => { | ||
env.setWorkRef(newWorkRef); | ||
}, null, ctx.subscriptions); | ||
} else { | ||
env.setWorkRef(undefined); | ||
} | ||
return repo; | ||
}) | ||
.then(repo => repo, err => vscode.window.showErrorMessage(`Zap error: ${err}.`)); | ||
}; | ||
ctx.subscriptions.push(vscode.commands.registerCommand("zap.workspace.add", async () => { | ||
await controller.addWorkspace(); | ||
})); | ||
// Add the workspace repo upon startup. If we're running in vscode | ||
// and the user's local server isn't already syncing the | ||
// workspace, this will NOT automatically workspace/add it to the | ||
// local server. (We always want the user to explicitly add repos | ||
// to Zap.) | ||
let addedRepo = addWorkspaceRepo(false); | ||
const updateEnabled = (enabled: boolean): void => { | ||
if (enabled) { | ||
startController(controller); | ||
} else { | ||
stopController(controller); | ||
} | ||
}; | ||
let lastEnabled = isEnabled(); | ||
ctx.subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => { | ||
if (isEnabled() !== lastEnabled) { | ||
updateEnabled(isEnabled()); | ||
lastEnabled = isEnabled(); | ||
} | ||
// Register commands. | ||
ctx.subscriptions.push(vscode.commands.registerCommand("zap.workspace.on", () => { | ||
return addedRepo.then( | ||
() => vscode.window.showWarningMessage("Zap is already on in this workspace."), | ||
() => { | ||
addedRepo = addWorkspaceRepo(true); | ||
return addedRepo.then(() => vscode.window.setStatusBarMessage("Turned Zap on in this workspace.", 2000)); | ||
}, | ||
); | ||
})); | ||
updateEnabled(isEnabled()); | ||
ctx.subscriptions.push(vscode.commands.registerCommand("zap.workspace.off", () => { | ||
return addedRepo.then(repo => { | ||
return repo.stopSync().then(() => { | ||
controller.handler.removeRepo(repo.path); | ||
repo.dispose(); | ||
addedRepo = Promise.reject("You must turn Zap on in this workspace."); | ||
vscode.window.setStatusBarMessage("Turned Zap off in this workspace.", 2000); | ||
}); | ||
}); | ||
})); | ||
ctx.subscriptions.push(vscode.commands.registerCommand("zap.ref.select", () => { | ||
return controller.showRefSelectMenu(); | ||
})); | ||
@@ -69,19 +96,18 @@ return controller; | ||
export function isEnabled(): boolean { | ||
// Avoid enabling the extension (and trying to connect to the Zap | ||
// server on a socket) when running in test mode because we | ||
// provide a mock connection. | ||
return Boolean(vscode.workspace.getConfiguration("zap").get<boolean>("enable") && !vscode.workspace.getConfiguration("zap").get<boolean>("_test") && !process.env.CI); | ||
} | ||
// TODO(sqs8): is this necessary? | ||
export function startController(controller: Controller): Thenable<void> { | ||
return controller.start().then(() => { /* noop */ }, (err) => { | ||
outputChannel.appendLine(`Error starting the controller: ${err}`); | ||
controller.outputChannel.appendLine(`Error starting the controller: ${err}`); | ||
}); | ||
}; | ||
// TODO(sqs8): is this necessary? | ||
export function stopController(controller: Controller): Thenable<void> { | ||
return controller.stop().then(() => { /* noop */ }, err => { | ||
outputChannel.appendLine(`Zap failed to stop: ${err}`); | ||
controller.outputChannel.appendLine(`Zap failed to stop: ${err}`); | ||
}); | ||
}; | ||
export function isWorkspaceRepoHandler(repo: IRepoHandler): repo is AbstractWorkspaceHandler { | ||
return Boolean((repo as AbstractWorkspaceHandler).onWorkRefReset); | ||
} |
@@ -6,12 +6,20 @@ // This is the extension's entrypoint used by vscode. Things in here | ||
import { Controller } from "./controller"; | ||
import { enable as enableE2E } from "./e2e"; | ||
import { activate as activateCommon } from "./extension.common"; | ||
import vscodeEnvironment, { VSCodeEnvironment } from "./environment.vscode"; | ||
import * as zapLocalServer from "./localServer"; | ||
import { Extension, activate as activateCommon } from "./extension.common"; | ||
import { VSCodeEnvironment } from "./environment.vscode"; | ||
import { activate as activateWebExtension } from "./web"; | ||
export function activate(ctx: vscode.ExtensionContext): Controller | null { | ||
export function activate(ctx: vscode.ExtensionContext, env?: VSCodeEnvironment): Extension { | ||
if (!env) { | ||
env = new VSCodeEnvironment(); | ||
ctx.subscriptions.push(env); // only dispose ourselves if we created it | ||
} | ||
const deactivate = () => { | ||
ctx.subscriptions.forEach(disposable => disposable.dispose()); | ||
ctx.subscriptions.length = 0; // clear the array | ||
}; | ||
if (!vscode.workspace.rootPath) { | ||
return null; | ||
return { deactivate }; | ||
} | ||
@@ -23,23 +31,10 @@ | ||
} | ||
ctx.subscriptions.push(enableE2E(process.env.ZAP_E2E_NAME, process.env.ZAP_E2E_ADDR, vscodeEnvironment)); | ||
ctx.subscriptions.push(enableE2E(process.env.ZAP_E2E_NAME, process.env.ZAP_E2E_ADDR, env)); | ||
} | ||
ctx.subscriptions.push(vscode.commands.registerCommand("zap.install", () => zapLocalServer.install())); | ||
ctx.subscriptions.push(vscode.commands.registerCommand("zap.auth", () => zapLocalServer.authenticateUser())); | ||
activateWebExtension(ctx); | ||
const controller = activateCommon(vscodeEnvironment, ctx); | ||
const controller = activateCommon(env, ctx); | ||
if (controller) { | ||
// Ensure (IEnvironment).{zapBranch,onDidChangeZapBranch} | ||
// reflect the HEAD ref's target. | ||
controller.handler.onDidUpdateSymbolicRef(e => { | ||
if (e.ref === vscodeEnvironment.zapRef) { | ||
(vscodeEnvironment as VSCodeEnvironment)._updateZapBranchInternal(e.newTarget); | ||
} | ||
}, null, ctx.subscriptions); | ||
activateWebExtension(ctx, controller, env); | ||
} | ||
return controller; | ||
return { controller, deactivate, subscriptions: ctx.subscriptions } as any; | ||
} |
@@ -7,12 +7,11 @@ import * as vscode from "vscode"; | ||
import { IEnvironment } from "./environment"; | ||
import { abbrevRef } from "libzap/lib/ref"; | ||
export const outputChannel = vscode.window.createOutputChannel("zap"); | ||
export let itemForE2ETestOnly: vscode.StatusBarItem; // exported for e2e test only | ||
export enum Status { | ||
Off, | ||
Starting, | ||
ServerUnavailable, | ||
WorkspaceNotAddedOnServer, | ||
OK, | ||
On, | ||
Inactive, | ||
@@ -32,4 +31,4 @@ ServerError, | ||
constructor( | ||
private remoteClient: Client, | ||
private environment: IEnvironment, | ||
private client: Client, | ||
private env: IEnvironment, | ||
) { | ||
@@ -40,20 +39,15 @@ this.item = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 10); | ||
if (!vscode.workspace.getConfiguration("zap").get<boolean>("enable")) { | ||
this.setStatus(Status.ServerUnavailable); | ||
} else { | ||
this.setStatus(Status.Starting); | ||
} | ||
this.setStatus(Status.Starting); | ||
this.remoteClient.onDidChangeState((event: StateChangeEvent) => { | ||
outputChannel.appendLine(`Status: ${State[event.oldState]} ⟶ ${State[event.newState]}`); | ||
this.client.onDidChangeState((event: StateChangeEvent) => { | ||
if (event.newState === State.Running) { | ||
this.setStatus(Status.OK); | ||
this.setStatus(Status.On); | ||
} | ||
if (event.newState === State.Stopped) { | ||
this.setStatus(Status.ServerUnavailable); | ||
this.setStatus(Status.Off); | ||
} | ||
}); | ||
this.environment.onDidChangeZapBranch(() => { | ||
this.setStatus(Status.OK); | ||
this.env.onWorkRefReset(() => { | ||
this.setStatus(this.env.workRef ? Status.On : Status.Inactive); | ||
}); | ||
@@ -72,3 +66,8 @@ } | ||
private setStatus(status: Status, message?: string): void { | ||
const branchText = this.environment.zapBranch || "$(ellipses)"; | ||
let refText: string; | ||
if (this.env.workRef) { | ||
refText = abbrevRef(this.env.workRef.target || this.env.workRef.name); | ||
} else { | ||
refText = "$(ellipses)"; | ||
} | ||
@@ -78,31 +77,26 @@ switch (status) { | ||
this.item.color = "white"; | ||
this.item.text = `$(zap) ${branchText}`; | ||
this.item.command = "zap.restart"; | ||
this.environment.isRunning = true; | ||
this.item.text = `$(zap) ${refText}`; | ||
this.item.tooltip = "Starting..."; | ||
this.item.command = ""; | ||
break; | ||
case Status.ServerUnavailable: | ||
this.item.color = "red"; | ||
case Status.Off: | ||
this.item.color = "white"; | ||
this.item.text = "$(zap) Off"; | ||
this.item.tooltip = "Start Zap"; | ||
this.item.command = "zap.restart"; | ||
this.environment.isRunning = false; | ||
this.item.tooltip = "Start using Zap in this workspace"; | ||
this.item.command = "zap.workspace.start"; | ||
break; | ||
case Status.WorkspaceNotAddedOnServer: | ||
case Status.Inactive: | ||
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; | ||
this.item.text = "$(zap) Zap"; | ||
this.item.tooltip = "Switch to a Zap ref..."; | ||
this.item.command = "zap.ref.select"; | ||
break; | ||
case Status.OK: | ||
this.item.color = "white"; // default color | ||
this.item.text = `$(zap) ${this.environment.zapBranch || "Select a Branch"}`; | ||
this.item.tooltip = ""; | ||
// TODO(sqs): make this a menu with the choice to remove | ||
// this workspace OR switch zap branches. | ||
case Status.On: | ||
this.item.color = "white"; | ||
this.item.text = `$(zap) ${refText}`; | ||
this.item.tooltip = "Switch to Zap ref..."; | ||
this.item.command = "zap.ref.select"; | ||
this.environment.isRunning = true; | ||
break; | ||
@@ -112,6 +106,5 @@ | ||
this.item.color = "yellow"; | ||
this.item.text = `$(zap) ${branchText} $(alert)`; | ||
this.item.text = `$(zap) ${refText} $(alert)`; | ||
this.item.tooltip = `Warning: ${message}`; | ||
this.item.command = ""; | ||
this.environment.isRunning = false; | ||
break; | ||
@@ -121,6 +114,5 @@ | ||
this.item.color = "red"; | ||
this.item.text = `$(zap) ${branchText} $(alert)`; | ||
this.item.text = `$(zap) ${refText} $(alert)`; | ||
this.item.tooltip = `Error: ${message}`; | ||
this.item.command = ""; | ||
this.environment.isRunning = false; | ||
break; | ||
@@ -144,3 +136,3 @@ } | ||
case ProtocolStatusType.StatusTypeOK: | ||
status = Status.OK; | ||
status = Status.On; | ||
break; | ||
@@ -147,0 +139,0 @@ |
134
src/web.ts
@@ -1,34 +0,65 @@ | ||
import * as fs from "fs"; | ||
import * as vscode from "vscode"; | ||
import { Controller } from "./controller"; | ||
import { RefIdentifier } from "libzap/lib/remote/protocol"; | ||
import { outputChannel } from "./status"; | ||
import { RefIdentifier, RefInfo, RepoInfoResult } from "libzap/lib/remote/protocol"; | ||
import { IEnvironment } from "./environment"; | ||
const open: (target: string) => void = require("open"); // tslint:disable-line no-var-requires | ||
export function activate(context: vscode.ExtensionContext): void { | ||
context.subscriptions.push(vscode.commands.registerCommand("zap.web.openInBrowser.file", openFile)); | ||
context.subscriptions.push(vscode.commands.registerTextEditorCommand("zap.web.openInBrowser.cursor", openAtCursor)); | ||
context.subscriptions.push(vscode.commands.registerCommand("zap.web.openWorkspaceInBrowser", openWorkspaceInBrowser)); | ||
export function activate(context: vscode.ExtensionContext, controller: Controller, env: IEnvironment): void { | ||
context.subscriptions.push(vscode.commands.registerCommand("zap.web.openInBrowser.file", uri => { | ||
if (!env.workRef) { | ||
return vscode.window.showErrorMessage("Unable to open file in browser: Zap workspace is not ready."); | ||
} | ||
return openInBrowser(controller, { | ||
localRepo: env.repo, | ||
ref: env.workRef.target || env.workRef.name, | ||
path: vscode.workspace.asRelativePath(uri), | ||
event: "OpenFile", | ||
}); | ||
})); | ||
context.subscriptions.push(vscode.commands.registerTextEditorCommand("zap.web.openInBrowser.cursor", editor => { | ||
if (!env.workRef) { | ||
return vscode.window.showErrorMessage("Unable to open in browser: Zap workspace is not ready."); | ||
} | ||
return openInBrowser(controller, { | ||
localRepo: env.repo, | ||
ref: env.workRef.target || env.workRef.name, | ||
path: vscode.workspace.asRelativePath(editor.document.uri), | ||
sel: editor.selection, | ||
event: "OpenAtCursor", | ||
}); | ||
})); | ||
context.subscriptions.push(vscode.commands.registerCommand("zap.web.openWorkspaceInBrowser", () => { | ||
if (!env.workRef) { | ||
return vscode.window.showErrorMessage("Unable to open workspace in browser: Zap workspace is not ready."); | ||
} | ||
return openInBrowser(controller, { | ||
localRepo: env.repo, | ||
ref: env.workRef.target || env.workRef.name, | ||
event: "OpenWorkspace", | ||
}); | ||
})); | ||
} | ||
function openFile(uri: vscode.Uri): void { | ||
if (isDirectory(uri.fsPath)) { | ||
vscode.window.showErrorMessage("Opening a directory in the Web browser is not supported."); | ||
return; | ||
} | ||
openInBrowser(uri, null, "OpenFile"); | ||
export interface OpenArgs { | ||
localRepo: string; | ||
ref: string; | ||
sel?: vscode.Selection; | ||
path?: string; // file path | ||
event: "OpenFile" | "OpenAtCursor" | "OpenWorkspace"; | ||
} | ||
function openAtCursor(editor: vscode.TextEditor): void { | ||
openInBrowser(editor.document.uri, editor.selection, "OpenAtCursor"); | ||
export interface Queryer { | ||
queryRefInfo(ref: RefIdentifier): Thenable<RefInfo>; | ||
queryRepoInfo(repo: string): Thenable<RepoInfoResult>; | ||
} | ||
export function openInBrowser(uri: vscode.Uri, sel: vscode.Selection | null, trackingEventName: "OpenFile" | "OpenAtCursor") { | ||
// TODO(sqs): support Zap workspaces that are not at the root of | ||
// 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()}#${sel ? formatSelection(sel) : ""}`; | ||
open(url); | ||
export function openInBrowser(queryer: Queryer, args: OpenArgs): Thenable<any> { | ||
return queryUpstreamInfo(queryer, args).then(({ repo, ref, endpoint }) => { | ||
endpoint = endpoint.replace(/\/\.api\/zap$/, ""); | ||
let url = `${endpoint}/${repo}@${ref}`; | ||
if (args.path) { | ||
url += `/-/blob/${args.path}`; | ||
} | ||
url += `?_event=${args.event}&_source=${versionString()}#${args.sel ? formatSelection(args.sel) : ""}`; | ||
vscode.commands.executeCommand("vscode.open", vscode.Uri.parse(url)); | ||
return true; | ||
@@ -38,49 +69,14 @@ }); | ||
function workspaceBrowserURL(): Promise<RefIdentifier & { gitBranch: string; url: string }> { | ||
return getRemoteRefID().then(remoteInfo => { | ||
let url = vscode.workspace.getConfiguration("zap").get<string>("web.url") + | ||
"/${REPO}@${GITBRANCH}" | ||
.replace("${REPO}", remoteInfo.repo) | ||
.replace("${GITBRANCH}", remoteInfo.ref || remoteInfo.gitBranch); | ||
return { ...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; | ||
} | ||
outputChannel.appendLine(`Error opening Web browser to URL: ${err}`); | ||
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; | ||
}); | ||
} | ||
export function openWorkspaceInBrowser() { | ||
return workspaceBrowserURL().then((identifier) => open(`${identifier.url}?_event=OpenWorkspace&_source=${versionString()}`)); | ||
} | ||
// getRemoteRefID queries the server to find the remote ref for the | ||
// current workspace's HEAD. This is necessary because (e.g.) the | ||
// remote repo name might be "github.com/foo/bar" but the local repo | ||
// name is "/home/alice/src/github.com/foo/bar". | ||
async function getRemoteRefID(): Promise<RefIdentifier & { gitBranch: string }> { | ||
const controller: Controller = vscode.extensions.getExtension("sqs.vscode-zap")!.exports; | ||
const localRepoName = vscode.workspace.rootPath!; | ||
const refInfo = await controller.queryRefInfo({ repo: localRepoName, ref: "HEAD" }); | ||
const repoInfo = await controller.queryRepoInfo(localRepoName); | ||
if (!repoInfo.refs) { | ||
return Promise.reject("Upstream branch not configured"); | ||
async function queryUpstreamInfo(queryer: Queryer, args: OpenArgs): Promise<RefIdentifier & { endpoint: string }> { | ||
const refInfo = await queryer.queryRefInfo({ repo: args.localRepo, ref: args.ref }); | ||
const repoInfo = await queryer.queryRepoInfo(args.localRepo); | ||
const remoteNames = Object.keys(repoInfo.remotes); | ||
if (remoteNames.length === 0 || remoteNames.length >= 2) { | ||
return Promise.reject(`unable to generate web URL (repository must have exactly 1 remote, but it has ${remoteNames.length})`); | ||
} | ||
const refUpstreamRemoteName = repoInfo.refs[refInfo.target!].upstream; | ||
const remote = repoInfo.remotes![remoteNames[0]]; | ||
return { | ||
repo: repoInfo.remotes![refUpstreamRemoteName].repo, | ||
repo: remote.repo, | ||
ref: refInfo.target!, | ||
gitBranch: refInfo.state!.gitBranch, | ||
endpoint: remote.endpoint, | ||
}; | ||
@@ -96,6 +92,2 @@ } | ||
function isDirectory(absPath: string): boolean { | ||
return fs.statSync(absPath).isDirectory(); | ||
} | ||
function versionString(): string { | ||
@@ -102,0 +94,0 @@ const ext = vscode.extensions.getExtension("sqs.vscode-zap")!; |
import * as vscode from "vscode"; | ||
import * as isEqual from "lodash/isEqual"; | ||
import { noop, assertConcurrent, assertConsecutive, transform as transformWorkspaceOps, compose as composeWorkspaceOps, composeAll as composeAllWorkspaceOps, WorkspaceOp, Sel, stripFileOrBufferPathPrefix, toString as opToString, FileEdits } from "libzap/lib/ot/workspace"; | ||
import { subtract as subtractWorkspaceOps } from "libzap/lib/ot/subtract"; | ||
import { Workspace as HandlerWorkspace } from "libzap/lib/remote/handler"; | ||
import { Workspace as IWorkspace } from "libzap/lib/workspace"; | ||
import { WorkspaceWillSaveFileParams } from "libzap/lib/remote/protocol"; | ||
@@ -12,3 +11,3 @@ import diffOps from "libzap/lib/util/diffOps"; | ||
export class Workspace implements HandlerWorkspace { | ||
export class Workspace implements IWorkspace { | ||
private onOpExtListener?: (op: WorkspaceOp) => void; | ||
@@ -330,3 +329,4 @@ private onWillSaveFileListener?: (params: WorkspaceWillSaveFileParams) => Thenable<void>; | ||
if (!await doc.save()) { | ||
throw new Error(`after applying edit to ${doc.uri.toString()}, save failed - edit was: ${JSON.stringify(edit.get(doc.uri))}`); | ||
// TODO(sqs8): this was a throw new Error() but I made it a console.error. | ||
console.error(`after applying edit to ${doc.uri.toString()}, save failed - edit was: ${JSON.stringify(edit.get(doc.uri))}`); | ||
} | ||
@@ -407,8 +407,3 @@ } | ||
// beginning of this branch. | ||
for (const doc of vscode.workspace.textDocuments) { | ||
const fileName = this.environment.asRelativePathInsideWorkspace(doc.uri); | ||
if (fileName !== null) { | ||
await this.environment.revertTextDocumentToBase(doc); | ||
} | ||
} | ||
await this.environment.revertAllTextDocuments(); | ||
} | ||
@@ -435,3 +430,2 @@ this.suppressRecord = false; | ||
this.history = []; | ||
this._onReset.fire({} as WorkspaceOp); | ||
} | ||
@@ -447,57 +441,2 @@ | ||
/** | ||
* updateHistory fast-forwards the workspace state to the latest version by | ||
* applying all ops in the supplied history that haven't been applied yet. | ||
*/ | ||
public async updateHistory(history: WorkspaceOp[]): Promise<void> { | ||
if (this.applying) { await this.applying; } | ||
try { | ||
this.suppressRecord = false; | ||
this.documentIsDirty.clear(); | ||
if (history.length > 0) { | ||
if (history.length < this.history.length) { | ||
// HACK: sometimes we get duplicate select events when updating history | ||
// after a disconnect, which causes errors. Manually remove it here. | ||
// | ||
// TODO: fix the source of the extra select ops. | ||
if (this.history.length === history.length + 1 && this.history[this.history.length - 1].sel) { | ||
console.warn("Warning: removing extra sel op while updating history"); | ||
this.history.pop(); | ||
} | ||
// Check again since this.history was possibly modified above. | ||
if (history.length < this.history.length) { | ||
console.warn(`Error updating history: supplied history version ${history.length} less than current version ${this.history.length}`); | ||
return this.reset(history); | ||
} | ||
} | ||
// Ensure the new history is a prefix of the current history so we know it can be fast-forwarded sucessfully. | ||
for (let i = 0; i < this.history.length; i++) { | ||
const [h1, h2] = [this.history[i], history[i]]; | ||
if (!isEqual(h1, h2)) { | ||
console.warn(`Error updating history: histories not equal (version ${i} ${JSON.stringify(h1)} != ${JSON.stringify(h2)}`); | ||
return this.reset(history); | ||
} | ||
} | ||
// Apply the new ops from history onto the current document. | ||
const toApply = history.slice(this.history.length); | ||
const newHistory = this.history.concat(toApply.map(copyHACK)); // copy to ensure the ops aren't mutated during compose. | ||
if (toApply.length > 0) { | ||
let composed = composeAllWorkspaceOps(toApply); | ||
if (this.environment.automaticallyApplyingFileSystemChanges) { | ||
composed = opWithBufferEditsOmittingChangesOnDisk(composed); | ||
} | ||
await this.apply(composed); | ||
this.history = newHistory; | ||
} | ||
} | ||
} catch (err) { | ||
console.error(`Zap update history failed: ${err}`); | ||
// Pass along error to server to tell it we failed. | ||
throw err; | ||
} | ||
} | ||
private onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined): void { | ||
@@ -560,3 +499,3 @@ if (editor) { | ||
if (oldText === newText) { | ||
console.error(`unexpected: document ${doc.uri.toString()} is dirty, but its on- disk contents equal its in -memory contents`); | ||
console.error(`unexpected: document ${doc.uri.toString()} is dirty, but its on-disk contents equal its in-memory contents`); | ||
} | ||
@@ -563,0 +502,0 @@ this.onOpListener!({ |
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
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
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
3
82
1
20
516064
7478
+ Addedlibzap@1.0.11(transitive)
- Removedopen@^0.0.5
- Removedlibzap@0.0.100(transitive)
- Removedopen@0.0.5(transitive)
Updatedlibzap@^1.0.1
Updatedlodash@^4.17.4