From f5450be96030d0b08c093be561b6454d042a7874 Mon Sep 17 00:00:00 2001 From: Christina Lin Date: Mon, 27 May 2024 15:29:53 -0400 Subject: [PATCH 01/11] opens manifest.json when version is downloaded or opened --- package.json | 27 +++++++------ src/commands/getAddon.ts | 24 ++++++----- src/commands/openFromUrl.ts | 51 ++++++++++++------------ src/extension.ts | 53 ++++++++++++++----------- test/suite/commands/openFromUrl.test.ts | 35 +++++++++------- test/suite/extension.test.ts | 24 +++++------ 6 files changed, 115 insertions(+), 99 deletions(-) diff --git a/package.json b/package.json index 5fb45e64..9b968064 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "Other" ], "activationEvents": [ - "onUri" + "onUri", + "onStartupFinished" ], "main": "./dist/extension.js", "contributes": { @@ -36,38 +37,40 @@ "menus": { "explorer/context": [ { - "when": "explorerResourceIsFolder && listDoubleSelection", + "command": "assay.openInDiffTool", - "group": "navigation" + "group": "navigation", + "when": "assay.commentsEnabled && explorerResourceIsFolder && listDoubleSelection" }, { - "when": "explorerResourceIsFolder", + "command": "assay.exportCommentsFromContext", - "group": "navigation" + "group": "navigation", + "when": "assay.commentsEnabled && explorerResourceIsFolder" } ], "comments/commentThread/title": [ { "command": "assay.exportComments", "group": "inline@1", - "when": "!commentThreadIsEmpty" + "when": "assay.commentsEnabled && !commentThreadIsEmpty" }, { "command": "assay.editComment", "group": "inline@2", - "when": "commentController == assay-comments && !commentThreadIsEmpty" + "when": "assay.commentsEnabled && commentController == assay-comments && !commentThreadIsEmpty" }, { "command": "assay.deleteComment", "group": "inline@3", - "when": "commentController == assay-comments && !commentThreadIsEmpty" + "when": "assay.commentsEnabled && commentController == assay-comments && !commentThreadIsEmpty" } ], "comments/commentThread/context": [ { "command": "assay.addComment", "group": "inline", - "when": "commentController == assay-comments && commentThreadIsEmpty" + "when": "assay.commentsEnabled && commentController == assay-comments && commentThreadIsEmpty" } ], "comments/comment/title": [ @@ -77,12 +80,12 @@ { "command": "assay.cancelSaveComment", "group": "inline@3", - "when": "commentController == assay-comments" + "when": "assay.commentsEnabled && commentController == assay-comments" }, { "command": "assay.saveComment", "group": "inline@2", - "when": "commentController == assay-comments" + "when": "assay.commentsEnabled && commentController == assay-comments" } ] }, @@ -214,4 +217,4 @@ "jsonwebtoken": "^9.0.1", "jszip": "^3.10.1" } -} +} \ No newline at end of file diff --git a/src/commands/getAddon.ts b/src/commands/getAddon.ts index 56ae0073..c1f15c60 100644 --- a/src/commands/getAddon.ts +++ b/src/commands/getAddon.ts @@ -32,20 +32,26 @@ export async function downloadAndExtract( const versionInfo = await getVersionChoice(input, urlVersion); const addonFileId = versionInfo.fileID; - const addonVersion = versionInfo.version; - const addonGUID = json.guid; + const version = versionInfo.version; + const guid = json.guid; const workspaceFolder = await getRootFolderPath(); - const compressedFilePath = `${workspaceFolder}/${addonGUID}_${addonVersion}.xpi`; + const compressedFilePath = `${workspaceFolder}/${guid}_${version}.xpi`; - await addToCache("reviewUrls", [addonGUID], json.review_url); + await addToCache("reviewUrls", [guid], json.review_url); await downloadAddon(addonFileId, compressedFilePath); - await extractAddon( - compressedFilePath, - `${workspaceFolder}/${addonGUID}`, - `${workspaceFolder}/${addonGUID}/${addonVersion}` - ); + + try { + await extractAddon( + compressedFilePath, + `${workspaceFolder}/${guid}`, + `${workspaceFolder}/${guid}/${version}` + ); + } catch (e) { + return { workspaceFolder, guid, version }; + } + return { workspaceFolder, guid, version }; } catch (error) { console.error(error); } diff --git a/src/commands/openFromUrl.ts b/src/commands/openFromUrl.ts index 4ffe3a3b..2141282a 100644 --- a/src/commands/openFromUrl.ts +++ b/src/commands/openFromUrl.ts @@ -5,43 +5,44 @@ import { downloadAndExtract } from "./getAddon"; import { getExtensionContext } from "../config/globals"; import { getRootFolderPath } from "../utils/reviewRootDir"; -export async function openWorkspace(manifestPath: string) { - const rootUri = vscode.Uri.file(await getRootFolderPath()); - const manifestUri = vscode.Uri.file(manifestPath); - - // if the workspace is already open, just open the manifest - const existingWorkspaceFolder = vscode.workspace.workspaceFolders?.find( - (folder) => folder.uri.fsPath === rootUri.fsPath - ); +export async function openWorkspace(versionPath: string) { + const versionUri = vscode.Uri.file(versionPath); + const manifestPath = `${versionPath}/manifest.json`; + const workspace = vscode.workspace.workspaceFolders; + + // If user already has the version folder opened, open the manifest.json + if (workspace && workspace[0].uri.fsPath === versionUri.fsPath) { + await vscode.window.showTextDocument(vscode.Uri.file(manifestPath)); + } + // Otherwise, store the manifestPath (since the extension must restart) to open on launch. + else { + const context = getExtensionContext(); + await context.globalState.update("manifestPath", manifestPath); + vscode.commands.executeCommand("vscode.openFolder", versionUri); + } +} - if (existingWorkspaceFolder) { - await vscode.commands.executeCommand( - "workbench.files.action.collapseExplorerFolders" - ); - await vscode.window.showTextDocument(manifestUri); +// handles assay.get input +export async function getAddonByUrl() { + const result = await downloadAndExtract(); + if (!result) { return; } - - // otherwise, open the workspace and store the manifest URI to be opened when the workspace is ready - vscode.workspace.updateWorkspaceFolders(0, 0, { - uri: rootUri, - name: "Assay", - }); - const context = getExtensionContext(); - context.globalState.update("manifestPath", manifestPath); + const { workspaceFolder, guid, version } = result; + const versionPath = `${workspaceFolder}/${guid}/${version}`; + await openWorkspace(versionPath); } // handles urls of the form /review// export async function handleReviewUrl(guid: string, version: string) { const rootPath = await getRootFolderPath(); - const addonManifestPath = `${rootPath}/${guid}/${version}/manifest.json`; + const versionPath = `${rootPath}/${guid}/${version}`; try { - await fs.promises.stat(addonManifestPath); + await fs.promises.stat(versionPath); } catch (error) { await downloadAndExtract(guid, version); } - - await openWorkspace(addonManifestPath); + await openWorkspace(versionPath); } // handles vscode://mozilla.assay/... urls diff --git a/src/extension.ts b/src/extension.ts index 991167a4..d2fe185c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2,14 +2,13 @@ import * as vscode from "vscode"; import { Uri } from "vscode"; import { exportCommentsFromContext } from "./commands/exportComments"; -import { downloadAndExtract } from "./commands/getAddon"; import { getApiKeyFromUser, getSecretFromUser, testApiCredentials, } from "./commands/getApiCreds"; import { openInDiffTool } from "./commands/launchDiff"; -import { handleUri, openWorkspace } from "./commands/openFromUrl"; +import { getAddonByUrl, handleUri } from "./commands/openFromUrl"; import { updateAssay } from "./commands/updateAssay"; import { updateTaskbar } from "./commands/updateTaskbar"; import { @@ -26,21 +25,6 @@ import { AssayTreeDataProvider } from "./views/sidebarView"; import { WelcomeView } from "./views/welcomeView"; export async function activate(context: vscode.ExtensionContext) { - const workspace = vscode.workspace.workspaceFolders; - if (!workspace) { - return; - } - - const uri = workspace[0].uri; - const { rootFolder, fullPath } = await splitUri(uri); - - if (!fullPath.startsWith(rootFolder)) { - vscode.window.showErrorMessage( - "(Assay) Launch terminated. Workspace is not in root folder." - ); - return; - } - const storagePath: string = context.globalStorageUri.fsPath; const fileDecorator = new CustomFileDecorationProvider(); setFileDecorator(fileDecorator); @@ -48,12 +32,12 @@ export async function activate(context: vscode.ExtensionContext) { setExtensionSecretStorage(context.secrets); setExtensionContext(context); - // check if this is a newly opened workspace to open the manifest + // If a manifestPath exists, a version folder was just opened. Open the manifest. if (context.globalState.get("manifestPath") !== undefined) { const manifestPath = context.globalState.get("manifestPath")?.toString(); await context.globalState.update("manifestPath", undefined); if (manifestPath) { - await openWorkspace(manifestPath); + await vscode.window.showTextDocument(vscode.Uri.file(manifestPath)); } } @@ -86,9 +70,10 @@ export async function activate(context: vscode.ExtensionContext) { } ); - const getDisposable = vscode.commands.registerCommand("assay.get", () => { - downloadAndExtract(); - }); + const getDisposable = vscode.commands.registerCommand( + "assay.get", + getAddonByUrl + ); const apiKeyDisposable = vscode.commands.registerCommand( "assay.getApiKey", @@ -154,8 +139,30 @@ export async function activate(context: vscode.ExtensionContext) { ); // Comment API - const cmtManager = new commentManager("assay-comments", "Assay"); + await vscode.commands.executeCommand( + "setContext", + "assay.commentsEnabled", + false + ); + + const workspace = vscode.workspace.workspaceFolders; + if (workspace) { + const uri = workspace[0].uri; + const { rootFolder, fullPath } = await splitUri(uri); + // Do not launch commenting system if not in the rootFolder. + // Still allows Assay to be launched to use other commands (setup, installs). + if (!fullPath.startsWith(rootFolder)) { + return; + } + } + + await vscode.commands.executeCommand( + "setContext", + "assay.commentsEnabled", + true + ); + const cmtManager = new commentManager("assay-comments", "Assay"); const exportCommentDisposable = vscode.commands.registerCommand( "assay.exportComments", cmtManager.exportComments diff --git a/test/suite/commands/openFromUrl.test.ts b/test/suite/commands/openFromUrl.test.ts index 0b0425ee..a20276b9 100644 --- a/test/suite/commands/openFromUrl.test.ts +++ b/test/suite/commands/openFromUrl.test.ts @@ -29,9 +29,13 @@ describe("openFromUrl.ts", async () => { }); it("should fail the stat check and call downloadAndExtract() if the manifest does not exist", async () => { + const executeCommandStub = sinon.stub(vscode.commands, 'executeCommand'); + executeCommandStub.resolves(); + const uri = { path: "/review/test-guid/test-version", }; + const getRootFolderPathStub = sinon.stub( reviewRootDir, "getRootFolderPath" @@ -53,12 +57,6 @@ describe("openFromUrl.ts", async () => { ); showTextDocumentStub.resolves(); - const updateWorkspaceFoldersStub = sinon.stub( - vscode.workspace, - "updateWorkspaceFolders" - ); - updateWorkspaceFoldersStub.resolves(); - const context = { globalState: { update: sinon.stub(), @@ -75,6 +73,9 @@ describe("openFromUrl.ts", async () => { }); it("should not fail the stat check and not call downloadAndExtract()", async () => { + const executeCommandStub = sinon.stub(vscode.commands, 'executeCommand'); + executeCommandStub.resolves(); + const uri = { path: "/review/test-guid/test-version", }; @@ -98,12 +99,6 @@ describe("openFromUrl.ts", async () => { ); showTextDocumentStub.resolves(); - const updateWorkspaceFoldersStub = sinon.stub( - vscode.workspace, - "updateWorkspaceFolders" - ); - updateWorkspaceFoldersStub.resolves(); - const context = { globalState: { update: sinon.stub(), @@ -122,13 +117,24 @@ describe("openFromUrl.ts", async () => { describe("openWorkspace()", async () => { it("should open the manifest if the workspace is already open", async () => { - const manifestUri = vscode.Uri.parse("test-manifest-uri"); + + const context = { + globalState: { + update: sinon.stub(), + }, + }; + const getExtensionContextStub = sinon.stub( + globals, + "getExtensionContext" + ); + getExtensionContextStub.returns(context as any); const executeCommandStub = sinon.stub( vscode.commands, "executeCommand" ); executeCommandStub.resolves(); + const manifestUri = vscode.Uri.parse("test-manifest-uri"); const rootUri = vscode.Uri.parse("test-root-uri"); const getRootFolderPathStub = sinon.stub( reviewRootDir, @@ -150,8 +156,7 @@ describe("openFromUrl.ts", async () => { showTextDocumentStub.resolves(); await openWorkspace(manifestUri.fsPath); - expect(executeCommandStub.called).to.be.true; - expect(showTextDocumentStub.called).to.be.true; + expect(executeCommandStub.calledOnceWith("vscode.openFolder")).to.be.true; }); }); }); diff --git a/test/suite/extension.test.ts b/test/suite/extension.test.ts index 1cd05217..c73e8cb1 100644 --- a/test/suite/extension.test.ts +++ b/test/suite/extension.test.ts @@ -39,33 +39,27 @@ describe("extension.ts", () => { sinon.restore(); }); - it("should activate and register commands and have 3 subscriptions", async () => { - const context = makeContext(); - await activate(context); - const commands = await vscode.commands.getCommands(true); - expect(commands).to.include.members(["assay.get"]); - expect(commands).to.include.members(["assay.welcome"]); - expect(commands).to.include.members(["assay.review"]); - expect(context.subscriptions.length).to.be.greaterThan(10); - }); - it("should deactivate and return undefined", async () => { // placeholder due to blank deactivate function const result = deactivate(); expect(result).to.be.undefined; }); - it("should load the manifest if launched with the intention to do so", async () => { + it("should activate and register commands and load the manifest if launched with the intention to do so", async () => { const context = makeContext(); context.globalState.get = sinon.stub().returns("test"); context.globalState.update = sinon.stub(); - const openWorkspaceStub = sinon.stub(openFromUrl, "openWorkspace"); - openWorkspaceStub.resolves(); + const showTextDocumentStub = sinon.stub(vscode.window, "showTextDocument"); + showTextDocumentStub.resolves(); sinon.stub(vscode.window, "registerUriHandler"); sinon.stub(vscode.commands, "registerCommand"); await activate(context); - expect(openWorkspaceStub.calledOnce).to.be.true; - expect(openWorkspaceStub.calledWith("test")).to.be.true; + expect(showTextDocumentStub.calledOnce).to.be.true; + const commands = await vscode.commands.getCommands(true); + expect(commands).to.include.members(["assay.get"]); + expect(commands).to.include.members(["assay.welcome"]); + expect(commands).to.include.members(["assay.review"]); + expect(context.subscriptions.length).to.be.greaterThan(10); }); }); From e8d70a66b144abdb761886cd8171a155ecf00ee4 Mon Sep 17 00:00:00 2001 From: Christina Lin Date: Tue, 28 May 2024 13:02:33 -0400 Subject: [PATCH 02/11] open in new window --- src/commands/openFromUrl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/openFromUrl.ts b/src/commands/openFromUrl.ts index 2141282a..50d82013 100644 --- a/src/commands/openFromUrl.ts +++ b/src/commands/openFromUrl.ts @@ -18,7 +18,7 @@ export async function openWorkspace(versionPath: string) { else { const context = getExtensionContext(); await context.globalState.update("manifestPath", manifestPath); - vscode.commands.executeCommand("vscode.openFolder", versionUri); + vscode.commands.executeCommand("vscode.openFolder", versionUri, true); } } From dea06546b290f901fb0f0874a5f70d4d440c9840 Mon Sep 17 00:00:00 2001 From: Christina Lin Date: Tue, 28 May 2024 16:31:49 -0400 Subject: [PATCH 03/11] link capabilities --- src/commands/openFromUrl.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/commands/openFromUrl.ts b/src/commands/openFromUrl.ts index 50d82013..3911af3f 100644 --- a/src/commands/openFromUrl.ts +++ b/src/commands/openFromUrl.ts @@ -5,9 +5,9 @@ import { downloadAndExtract } from "./getAddon"; import { getExtensionContext } from "../config/globals"; import { getRootFolderPath } from "../utils/reviewRootDir"; -export async function openWorkspace(versionPath: string) { +export async function openWorkspace(versionPath: string, filepath?: string) { const versionUri = vscode.Uri.file(versionPath); - const manifestPath = `${versionPath}/manifest.json`; + const manifestPath = `${versionPath}/${filepath ?? "manifest.json"}`; const workspace = vscode.workspace.workspaceFolders; // If user already has the version folder opened, open the manifest.json @@ -33,8 +33,12 @@ export async function getAddonByUrl() { await openWorkspace(versionPath); } -// handles urls of the form /review// -export async function handleReviewUrl(guid: string, version: string) { +// handles urls of the form /review//?path= +export async function handleReviewUrl( +guid: string, +version: string, +filepath?: string +) { const rootPath = await getRootFolderPath(); const versionPath = `${rootPath}/${guid}/${version}`; try { @@ -42,16 +46,16 @@ export async function handleReviewUrl(guid: string, version: string) { } catch (error) { await downloadAndExtract(guid, version); } - await openWorkspace(versionPath); + await openWorkspace(versionPath, filepath); } // handles vscode://mozilla.assay/... urls export async function handleUri(uri: vscode.Uri) { - const { path } = uri; + const { path, query } = uri; + const filepath = new URLSearchParams(query).get("path"); const [_, action, ...rest] = path.split("/"); - if (action === "review") { const [guid, version] = rest; - await handleReviewUrl(guid, version); + await handleReviewUrl(guid, version, filepath || undefined); } -} +} \ No newline at end of file From fa113ad6a3644a7a1418db04c8ac142bc97c5ba7 Mon Sep 17 00:00:00 2001 From: Christina Lin Date: Wed, 29 May 2024 10:46:21 -0400 Subject: [PATCH 04/11] share --- media/commentIcons/link.svg | 6 +++++ media/commentIcons/link_inverse.svg | 8 +++++++ package.json | 29 ++++++++++++++++++++---- src/commands/openFromUrl.ts | 35 +++++++++++++++++++---------- src/extension.ts | 31 ++++++++++++++++++------- src/utils/commentManager.ts | 20 +++++++++++++++++ src/utils/revealFile.ts | 16 +++++++++++++ test/suite/extension.test.ts | 4 ++-- 8 files changed, 123 insertions(+), 26 deletions(-) create mode 100644 media/commentIcons/link.svg create mode 100644 media/commentIcons/link_inverse.svg create mode 100644 src/utils/revealFile.ts diff --git a/media/commentIcons/link.svg b/media/commentIcons/link.svg new file mode 100644 index 00000000..7081fe82 --- /dev/null +++ b/media/commentIcons/link.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/media/commentIcons/link_inverse.svg b/media/commentIcons/link_inverse.svg new file mode 100644 index 00000000..edbe17d3 --- /dev/null +++ b/media/commentIcons/link_inverse.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/package.json b/package.json index 9b968064..fac080ff 100644 --- a/package.json +++ b/package.json @@ -60,16 +60,26 @@ "group": "inline@2", "when": "assay.commentsEnabled && commentController == assay-comments && !commentThreadIsEmpty" }, + { + "command": "assay.copyLinkFromThread", + "group": "inline@3", + "when": "assay.commentsEnabled && !commentThreadIsEmpty" + }, { "command": "assay.deleteComment", - "group": "inline@3", + "group": "inline@5", "when": "assay.commentsEnabled && commentController == assay-comments && !commentThreadIsEmpty" } ], "comments/commentThread/context": [ - { + { "command": "assay.addComment", - "group": "inline", + "group": "inline@1", + "when": "assay.commentsEnabled && commentController == assay-comments && commentThreadIsEmpty" + }, + { + "command": "assay.copyLinkFromReply", + "group": "inline@2", "when": "assay.commentsEnabled && commentController == assay-comments && commentThreadIsEmpty" } ], @@ -114,7 +124,10 @@ "command": "assay.checkForUpdates", "title": "(Assay) Check For Updates" }, - + { + "command": "assay.copyLinkFromReply", + "title": "Copy Link" + }, { "command": "assay.addComment", "title": "Mark for Review" @@ -154,6 +167,14 @@ "dark": "media/commentIcons/export_inverse.svg", "light": "media/commentIcons/export.svg" } + }, + { + "command": "assay.copyLinkFromThread", + "title": "Copy Link", + "icon": { + "dark": "media/commentIcons/link_inverse.svg", + "light": "media/commentIcons/link.svg" + } } ] }, diff --git a/src/commands/openFromUrl.ts b/src/commands/openFromUrl.ts index 3911af3f..ccb258c6 100644 --- a/src/commands/openFromUrl.ts +++ b/src/commands/openFromUrl.ts @@ -3,21 +3,29 @@ import * as vscode from "vscode"; import { downloadAndExtract } from "./getAddon"; import { getExtensionContext } from "../config/globals"; +import revealFile from "../utils/revealFile"; import { getRootFolderPath } from "../utils/reviewRootDir"; -export async function openWorkspace(versionPath: string, filepath?: string) { +export async function openWorkspace( + versionPath: string, + filepath?: string, + lineNumber?: string +) { const versionUri = vscode.Uri.file(versionPath); - const manifestPath = `${versionPath}/${filepath ?? "manifest.json"}`; + const filePath = `${versionPath}/${filepath ?? "manifest.json"}`; const workspace = vscode.workspace.workspaceFolders; // If user already has the version folder opened, open the manifest.json if (workspace && workspace[0].uri.fsPath === versionUri.fsPath) { - await vscode.window.showTextDocument(vscode.Uri.file(manifestPath)); + revealFile(vscode.Uri.file(filePath), lineNumber); } - // Otherwise, store the manifestPath (since the extension must restart) to open on launch. + // Otherwise, store the filePath (since the extension must restart) to open on launch. else { const context = getExtensionContext(); - await context.globalState.update("manifestPath", manifestPath); + await context.globalState.update("filePath", filePath); + if (lineNumber) { + await context.globalState.update("lineNumber", lineNumber); + } vscode.commands.executeCommand("vscode.openFolder", versionUri, true); } } @@ -35,9 +43,10 @@ export async function getAddonByUrl() { // handles urls of the form /review//?path= export async function handleReviewUrl( -guid: string, -version: string, -filepath?: string + guid: string, + version: string, + filepath?: string, + lineNumber?: string ) { const rootPath = await getRootFolderPath(); const versionPath = `${rootPath}/${guid}/${version}`; @@ -46,16 +55,18 @@ filepath?: string } catch (error) { await downloadAndExtract(guid, version); } - await openWorkspace(versionPath, filepath); + await openWorkspace(versionPath, filepath, lineNumber); } // handles vscode://mozilla.assay/... urls export async function handleUri(uri: vscode.Uri) { - const { path, query } = uri; + const { path, query, fragment } = uri; const filepath = new URLSearchParams(query).get("path"); + const lineNumber = filepath ? `#${fragment}` : undefined; + const [_, action, ...rest] = path.split("/"); if (action === "review") { const [guid, version] = rest; - await handleReviewUrl(guid, version, filepath || undefined); + await handleReviewUrl(guid, version, filepath || undefined, lineNumber); } -} \ No newline at end of file +} diff --git a/src/extension.ts b/src/extension.ts index d2fe185c..31715d7d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -19,6 +19,7 @@ import { } from "./config/globals"; import { commentManager } from "./utils/commentManager"; import { loadFileDecorator } from "./utils/loadFileDecorator"; +import revealFile from "./utils/revealFile"; import { splitUri } from "./utils/splitUri"; import { CustomFileDecorationProvider } from "./views/fileDecorations"; import { AssayTreeDataProvider } from "./views/sidebarView"; @@ -32,12 +33,15 @@ export async function activate(context: vscode.ExtensionContext) { setExtensionSecretStorage(context.secrets); setExtensionContext(context); - // If a manifestPath exists, a version folder was just opened. Open the manifest. - if (context.globalState.get("manifestPath") !== undefined) { - const manifestPath = context.globalState.get("manifestPath")?.toString(); - await context.globalState.update("manifestPath", undefined); - if (manifestPath) { - await vscode.window.showTextDocument(vscode.Uri.file(manifestPath)); + // If a filePath exists, a version folder was just opened. Open the manifest. + if (context.globalState.get("filePath") !== undefined) { + const filePath = context.globalState.get("filePath")?.toString(); + const lineNumber = context.globalState.get("lineNumber")?.toString(); + console.log("hi", lineNumber); + await context.globalState.update("filePath", undefined); + await context.globalState.update("lineNumber", undefined); + if (filePath) { + revealFile(vscode.Uri.file(filePath), lineNumber); } } @@ -187,7 +191,16 @@ export async function activate(context: vscode.ExtensionContext) { "assay.editComment", cmtManager.editComment ); - + const copyLinkFromReplyDisposable = vscode.commands.registerCommand( + "assay.copyLinkFromReply", + cmtManager.copyLinkFromReply, + cmtManager + ); + const copyLinkFromThreadDisposable = vscode.commands.registerCommand( + "assay.copyLinkFromThread", + cmtManager.copyLinkFromThread, + cmtManager + ); const disposeCommentDisposable = vscode.commands.registerCommand( "assay.disposeComment", cmtManager.dispose @@ -201,7 +214,9 @@ export async function activate(context: vscode.ExtensionContext) { saveCommentDisposable, editCommentDisposable, exportCommentDisposable, - disposeCommentDisposable + disposeCommentDisposable, + copyLinkFromReplyDisposable, + copyLinkFromThreadDisposable ); } diff --git a/src/utils/commentManager.ts b/src/utils/commentManager.ts index dee58803..3bb5b990 100644 --- a/src/utils/commentManager.ts +++ b/src/utils/commentManager.ts @@ -5,6 +5,7 @@ import getCommentLocation, { rangeTruncation, stringToRange, } from "./getThreadLocation"; +import getThreadLocation from "./getThreadLocation"; import { loadFileDecorator } from "./loadFileDecorator"; import { splitUri } from "./splitUri"; import { exportVersionComments } from "../commands/exportComments"; @@ -133,6 +134,25 @@ export class commentManager { await exportVersionComments(thread.uri); } + /** + * Copies a link to the selected line(s) to the clipboard for sharing. + * @param reply Holds the thread location. + */ + async copyLinkFromReply(reply: AssayReply){ + this.copyLinkFromThread(reply.thread); + } + + /** + * Copies a link to the selected line(s) to the clipboard for sharing. + * @param thread + */ + async copyLinkFromThread(thread: AssayThread){ + const { guid, version, filepath, range } = await getThreadLocation(thread); + const link = `vscode://mozilla.assay/review/${guid}/${version}?path=${encodeURI(filepath)}${range}`; + vscode.env.clipboard.writeText(link); + vscode.window.showInformationMessage("Link copied to clipboard."); + } + /** * Dispose of the commentManager. */ diff --git a/src/utils/revealFile.ts b/src/utils/revealFile.ts new file mode 100644 index 00000000..00938359 --- /dev/null +++ b/src/utils/revealFile.ts @@ -0,0 +1,16 @@ +import * as vscode from "vscode"; + +import { stringToRange } from "./getThreadLocation"; + +export default async function revealFile(uri: vscode.Uri, lineNumber?: string) { + const editor = await vscode.window.showTextDocument(uri); + if (lineNumber) { + // highlight offending lines + const lineRange = stringToRange(lineNumber); + const selection = new vscode.Selection(lineRange.start, lineRange.end); + editor.selections = [selection]; + + // move editor to focus on line(s) + editor.revealRange(lineRange, vscode.TextEditorRevealType.InCenter); + } +} diff --git a/test/suite/extension.test.ts b/test/suite/extension.test.ts index c73e8cb1..bd6e6482 100644 --- a/test/suite/extension.test.ts +++ b/test/suite/extension.test.ts @@ -3,7 +3,6 @@ import { describe, it, afterEach, beforeEach } from "mocha"; import * as sinon from "sinon"; import * as vscode from "vscode"; -import * as openFromUrl from "../../src/commands/openFromUrl"; import { activate, deactivate } from "../../src/extension"; import * as reviewRootDir from "../../src/utils/reviewRootDir"; @@ -47,7 +46,8 @@ describe("extension.ts", () => { it("should activate and register commands and load the manifest if launched with the intention to do so", async () => { const context = makeContext(); - context.globalState.get = sinon.stub().returns("test"); + sinon.stub(context.globalState, 'get').withArgs("filePath").returns("test"); + context.globalState.update = sinon.stub(); const showTextDocumentStub = sinon.stub(vscode.window, "showTextDocument"); showTextDocumentStub.resolves(); From 6f90fadd31006caa0120a535f62fcefd8ab6b978 Mon Sep 17 00:00:00 2001 From: Christina Lin Date: Wed, 29 May 2024 10:55:26 -0400 Subject: [PATCH 05/11] fixed extension.ts from merge --- src/extension.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 5bcb97ab..e883c972 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -143,7 +143,6 @@ export async function activate(context: vscode.ExtensionContext) { ); // Comment API - const cmtManager = new CommentManager("assay-comments", "Assay"); await vscode.commands.executeCommand( "setContext", @@ -167,7 +166,7 @@ export async function activate(context: vscode.ExtensionContext) { "assay.commentsEnabled", true ); - const cmtManager = new commentManager("assay-comments", "Assay"); + const cmtManager = new CommentManager("assay-comments", "Assay"); const exportCommentDisposable = vscode.commands.registerCommand( "assay.exportComments", cmtManager.exportComments, From c3ab9f53d4bea457edae50f22d81954d63a7ad7f Mon Sep 17 00:00:00 2001 From: Christina Lin Date: Wed, 29 May 2024 11:50:18 -0400 Subject: [PATCH 06/11] eol selection --- src/utils/commentManager.ts | 10 ++++++---- src/utils/getThreadLocation.ts | 16 ++++++++++++++-- src/utils/revealFile.ts | 2 +- test/suite/utils/getThreadLocation.test.ts | 12 ++++++------ 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/utils/commentManager.ts b/src/utils/commentManager.ts index 3a9f3ade..62643004 100644 --- a/src/utils/commentManager.ts +++ b/src/utils/commentManager.ts @@ -125,7 +125,7 @@ export class CommentManager { * Copies a link to the selected line(s) to the clipboard for sharing. * @param reply Holds the thread location. */ - async copyLinkFromReply(reply: AssayReply){ + async copyLinkFromReply(reply: AssayReply) { this.copyLinkFromThread(reply.thread); } @@ -133,9 +133,11 @@ export class CommentManager { * Copies a link to the selected line(s) to the clipboard for sharing. * @param thread */ - async copyLinkFromThread(thread: AssayThread){ + async copyLinkFromThread(thread: AssayThread) { const { guid, version, filepath, range } = await getThreadLocation(thread); - const link = `vscode://mozilla.assay/review/${guid}/${version}?path=${encodeURI(filepath)}${range}`; + const link = `vscode://mozilla.assay/review/${guid}/${version}?path=${encodeURI( + filepath + )}${range}`; vscode.env.clipboard.writeText(link); vscode.window.showInformationMessage("Link copied to clipboard."); } @@ -192,7 +194,7 @@ export class CommentManager { for (const { uri, body, contextValue, lineNumber } of this.iterateComments( comments )) { - const r = stringToRange(lineNumber); + const r = await stringToRange(lineNumber, uri); const thread = this.controller.createCommentThread(uri, r, []); this.createComment(contextValue, new vscode.MarkdownString(body), thread); } diff --git a/src/utils/getThreadLocation.ts b/src/utils/getThreadLocation.ts index e6a12d16..abd506c3 100644 --- a/src/utils/getThreadLocation.ts +++ b/src/utils/getThreadLocation.ts @@ -28,14 +28,26 @@ export function rangeToString(range: vscode.Range) { : `#L${range.start.line}-${range.end.line}`; } -export function stringToRange(str: string) { +export async function stringToRange(str: string, uri?: vscode.Uri) { const list = str.match(/\d+/g); if (!list || !/#L[0-9]+(-[0-9]+)?(?!-)/.test(str)) { throw Error(`Passed string is not a line number: ${str}`); } + + let endCharacter = 0; + + // if given a file uri, set the the end range to eol + if (uri) { + const buffer = await vscode.workspace.fs.readFile(uri); + const content = buffer.toString()?.split("\n"); + endCharacter = content[parseInt(list[1])]?.length; + } + const start = new vscode.Position(parseInt(list[0]), 0); const end = - list.length > 1 ? new vscode.Position(parseInt(list[1]), 0) : start; + list.length > 1 + ? new vscode.Position(parseInt(list[1]), endCharacter ?? 0) + : start; return new vscode.Range(start, end); } diff --git a/src/utils/revealFile.ts b/src/utils/revealFile.ts index 00938359..f0afd4e1 100644 --- a/src/utils/revealFile.ts +++ b/src/utils/revealFile.ts @@ -6,7 +6,7 @@ export default async function revealFile(uri: vscode.Uri, lineNumber?: string) { const editor = await vscode.window.showTextDocument(uri); if (lineNumber) { // highlight offending lines - const lineRange = stringToRange(lineNumber); + const lineRange = await stringToRange(lineNumber, uri); const selection = new vscode.Selection(lineRange.start, lineRange.end); editor.selections = [selection]; diff --git a/test/suite/utils/getThreadLocation.test.ts b/test/suite/utils/getThreadLocation.test.ts index e4001e4d..f51665ad 100644 --- a/test/suite/utils/getThreadLocation.test.ts +++ b/test/suite/utils/getThreadLocation.test.ts @@ -73,7 +73,7 @@ describe("getThreadLocation.ts", () => { it("should reject an incorrectly-formatted string", async () => { const str = '#L1-'; try{ - const result = stringToRange(str); + const result = await stringToRange(str); expect(result).to.be.empty; }catch(e: any){ expect(e.message).to.equal("Passed string is not a line number: #L1-"); @@ -84,7 +84,7 @@ describe("getThreadLocation.ts", () => { it("should reject an incorrectly-formatted string", async () => { const str = '#L-23'; try{ - const result = stringToRange(str); + const result = await stringToRange(str); expect(result).to.be.empty; }catch(e: any){ expect(e.message).to.equal("Passed string is not a line number: #L-23"); @@ -95,7 +95,7 @@ describe("getThreadLocation.ts", () => { it("should reject an incorrectly-formatted string", async () => { const str = '#l1-2'; try{ - const result = stringToRange(str); + const result = await stringToRange(str); expect(result).to.be.empty; }catch(e: any){ expect(e.message).to.equal("Passed string is not a line number: #l1-2"); @@ -106,7 +106,7 @@ describe("getThreadLocation.ts", () => { it("should reject a string with no numbers", async () => { const str = '#L-'; try{ - const result = stringToRange(str); + const result = await stringToRange(str); expect(result).to.be.empty; }catch(e: any){ expect(e.message).to.equal("Passed string is not a line number: #L-"); @@ -115,12 +115,12 @@ describe("getThreadLocation.ts", () => { it("should correctly return a single-line range", async () => { const str = '#L1'; - const result = stringToRange(str); + const result = await stringToRange(str); expect(result).to.deep.equal(rng); }); it("should correctly return a multi-line range", async () => { const str = '#L1-5'; - const result = stringToRange(str); + const result = await stringToRange(str); expect(result).to.deep.equal(multiRng); }); }); From bf8f73a35c52f9261744375182f04f954e1592a7 Mon Sep 17 00:00:00 2001 From: Christina Lin Date: Wed, 29 May 2024 11:55:32 -0400 Subject: [PATCH 07/11] import issues --- src/utils/commentManager.ts | 9 ++++----- src/utils/revealFile.ts | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/utils/commentManager.ts b/src/utils/commentManager.ts index 62643004..bf5ee5f9 100644 --- a/src/utils/commentManager.ts +++ b/src/utils/commentManager.ts @@ -1,11 +1,10 @@ import * as vscode from "vscode"; import { addToCache, getFromCache } from "./addonCache"; -import getCommentLocation, { +import getThreadLocation, { rangeTruncation, stringToRange, } from "./getThreadLocation"; -import getThreadLocation from "./getThreadLocation"; import { loadFileDecorator } from "./loadFileDecorator"; import { splitUri } from "./splitUri"; import { exportVersionComments } from "../commands/exportComments"; @@ -158,7 +157,7 @@ export class CommentManager { body: vscode.MarkdownString, thread: AssayThread | vscode.CommentThread ) { - const { filepath, range } = await getCommentLocation(thread as AssayThread); + const { filepath, range } = await getThreadLocation(thread as AssayThread); thread.label = `${filepath}${rangeTruncation(range)}`; const newComment = new AssayComment( @@ -205,7 +204,7 @@ export class CommentManager { * @param comment */ private async saveCommentToCache(comment: AssayComment) { - const { guid, version, filepath, range } = await getCommentLocation( + const { guid, version, filepath, range } = await getThreadLocation( comment.thread ); await addToCache( @@ -220,7 +219,7 @@ export class CommentManager { * @param comment */ private async deleteCommentFromCache(comment: AssayComment) { - const { guid, version, filepath, range } = await getCommentLocation( + const { guid, version, filepath, range } = await getThreadLocation( comment.thread ); await addToCache("comments", [guid, version, filepath, range], ""); diff --git a/src/utils/revealFile.ts b/src/utils/revealFile.ts index f0afd4e1..f2403352 100644 --- a/src/utils/revealFile.ts +++ b/src/utils/revealFile.ts @@ -13,4 +13,4 @@ export default async function revealFile(uri: vscode.Uri, lineNumber?: string) { // move editor to focus on line(s) editor.revealRange(lineRange, vscode.TextEditorRevealType.InCenter); } -} +} \ No newline at end of file From 252f81bb2ec773ea99154965341b7502951ef636 Mon Sep 17 00:00:00 2001 From: Christina Lin Date: Wed, 29 May 2024 14:13:17 -0400 Subject: [PATCH 08/11] tests --- src/utils/commentManager.ts | 7 ++-- src/utils/revealFile.ts | 3 +- test/suite/commands/openFromUrl.test.ts | 27 ++++++++++++- test/suite/utils/commentManager.test.ts | 37 ++++++++++++++++++ test/suite/utils/revealFile.test.ts | 50 +++++++++++++++++++++++++ 5 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 test/suite/utils/revealFile.test.ts diff --git a/src/utils/commentManager.ts b/src/utils/commentManager.ts index bf5ee5f9..4f01df88 100644 --- a/src/utils/commentManager.ts +++ b/src/utils/commentManager.ts @@ -123,6 +123,7 @@ export class CommentManager { /** * Copies a link to the selected line(s) to the clipboard for sharing. * @param reply Holds the thread location. + * @return the generated link. */ async copyLinkFromReply(reply: AssayReply) { this.copyLinkFromThread(reply.thread); @@ -131,14 +132,14 @@ export class CommentManager { /** * Copies a link to the selected line(s) to the clipboard for sharing. * @param thread + * @return the generated link. */ async copyLinkFromThread(thread: AssayThread) { const { guid, version, filepath, range } = await getThreadLocation(thread); - const link = `vscode://mozilla.assay/review/${guid}/${version}?path=${encodeURI( - filepath - )}${range}`; + const link = `vscode://mozilla.assay/review/${guid}/${version}?path=${filepath}${range}`; vscode.env.clipboard.writeText(link); vscode.window.showInformationMessage("Link copied to clipboard."); + return link; } /** diff --git a/src/utils/revealFile.ts b/src/utils/revealFile.ts index f2403352..583a7f50 100644 --- a/src/utils/revealFile.ts +++ b/src/utils/revealFile.ts @@ -9,8 +9,7 @@ export default async function revealFile(uri: vscode.Uri, lineNumber?: string) { const lineRange = await stringToRange(lineNumber, uri); const selection = new vscode.Selection(lineRange.start, lineRange.end); editor.selections = [selection]; - // move editor to focus on line(s) editor.revealRange(lineRange, vscode.TextEditorRevealType.InCenter); } -} \ No newline at end of file +} diff --git a/test/suite/commands/openFromUrl.test.ts b/test/suite/commands/openFromUrl.test.ts index a20276b9..4cff9aa2 100644 --- a/test/suite/commands/openFromUrl.test.ts +++ b/test/suite/commands/openFromUrl.test.ts @@ -5,7 +5,8 @@ import * as sinon from "sinon"; import * as vscode from "vscode"; import * as getAddonFunctions from "../../../src/commands/getAddon"; -import { handleUri, openWorkspace } from "../../../src/commands/openFromUrl"; +import { handleUri, openWorkspace, getAddonByUrl } from "../../../src/commands/openFromUrl"; +import * as openFromUrl from "../../../src/commands/openFromUrl"; import * as globals from "../../../src/config/globals"; import * as reviewRootDir from "../../../src/utils/reviewRootDir"; @@ -159,4 +160,28 @@ describe("openFromUrl.ts", async () => { expect(executeCommandStub.calledOnceWith("vscode.openFolder")).to.be.true; }); }); + + describe("getAddonByUrl", async () => { + it("should receive a result from downloadAndExtract and correctly call openWorkspace", async () => { + const context = { + globalState: { + update: sinon.stub(), + }, + }; + const getExtensionContextStub = sinon.stub( + globals, + "getExtensionContext" + ); + getExtensionContextStub.returns(context as any); + const downloadAndExtractStub = sinon.stub( + getAddonFunctions, + "downloadAndExtract" + ); + downloadAndExtractStub.resolves({ workspaceFolder: "workspace", guid: "guid", version: "version" }); + const openWorkspaceStub = sinon.stub(openFromUrl, "openWorkspace"); + await getAddonByUrl(); + expect(openWorkspaceStub.calledWith('workspace/guid/version')); + }); + + }); }); diff --git a/test/suite/utils/commentManager.test.ts b/test/suite/utils/commentManager.test.ts index ef248dc3..eaa12836 100644 --- a/test/suite/utils/commentManager.test.ts +++ b/test/suite/utils/commentManager.test.ts @@ -9,6 +9,7 @@ import { AssayReply, AssayThread, contextValues } from "../../../src/config/comm import { setExtensionStoragePath } from "../../../src/config/globals"; import * as addonCache from "../../../src/utils/addonCache"; import { CommentManager } from "../../../src/utils/commentManager"; +import * as getThreadLocation from "../../../src/utils/getThreadLocation"; import * as reviewRootDir from "../../../src/utils/reviewRootDir"; const workspaceFolder = path.resolve(__dirname, "..", "test_workspace"); @@ -189,6 +190,42 @@ describe("CommentManager.ts", () => { }); }); + describe("copyLinkFromReply", () => { + it("should call copyLinkFromThread with the reply's thread", () => { + const cmtManager = new CommentManager("assay-tester", "Assay Tester"); + const thread = cmtManager.controller.createCommentThread(cmt.uri, range, []) as AssayThread; + const reply = new AssayReply(thread, cmt.body); + const copyLinkFromThreadStub = sinon.stub(cmtManager, 'copyLinkFromThread'); + + cmtManager.copyLinkFromReply(reply); + + expect(copyLinkFromThreadStub.calledOnceWith(thread)).to.be.true; + }); + }); + + describe("copyLinkFromThread", () => { + it("should copy the correctly formatted link to clipboard and show the info message", async () => { + const cmtManager = new CommentManager("assay-tester", "Assay Tester"); + const thread = cmtManager.controller.createCommentThread(cmt.uri, range, []) as AssayThread; + const expectedLink = `vscode://mozilla.assay/review/guid/version?path=filepath/with/slashes.py#range`; + + const showInformationMessageStub = sinon.stub(vscode.window, 'showInformationMessage'); + const getThreadLocationStub = sinon.stub(getThreadLocation, 'default').resolves({ + guid: 'guid', + version: 'version', + filepath: 'filepath/with/slashes.py', + range: '#range' + }); + + const link = await cmtManager.copyLinkFromThread(thread); + + expect(getThreadLocationStub.called).to.be.true; + expect(showInformationMessageStub.calledOnce).to.be.true; + expect(link).to.equal(expectedLink); + + }); + }); + }); diff --git a/test/suite/utils/revealFile.test.ts b/test/suite/utils/revealFile.test.ts new file mode 100644 index 00000000..53d6c6f0 --- /dev/null +++ b/test/suite/utils/revealFile.test.ts @@ -0,0 +1,50 @@ +import { expect } from "chai"; +import { describe, it, afterEach } from "mocha"; +import * as sinon from "sinon"; +import * as vscode from "vscode"; + +import * as getThreadLocation from "../../../src/utils/getThreadLocation"; +import revealFile from "../../../src/utils/revealFile"; + +describe("revealFile.ts", async () => { + afterEach(() => { + sinon.restore(); + }); + describe("revealFile()", () => { + + it("should reveal the document located at uri.", async () => { + const showTextDocumentStub = sinon.stub(vscode.window, "showTextDocument"); + const uri = vscode.Uri.parse("index.html"); + + await revealFile(uri); + + expect(showTextDocumentStub.calledOnce).to.be.true; + expect(showTextDocumentStub.firstCall.args[0]).to.deep.equal(uri); + }); + + it("should reveal the document located at uri and correctly highlight and reveal the desired range", async () => { + const range = new vscode.Range(new vscode.Position(24, 0), new vscode.Position(24, 0)); + const stringToRangeStub = sinon.stub(getThreadLocation, "stringToRange").resolves(range); + const revealRangeStub = sinon.stub(); + + const fakeEditor = { + revealRange: revealRangeStub, + selections: [], + } as unknown as vscode.TextEditor; + + const uri = vscode.Uri.file("index.html"); + const lineNumber = "#L25"; + + const showTextDocumentStub = sinon.stub(vscode.window, "showTextDocument"); + showTextDocumentStub.resolves(fakeEditor); + + await revealFile(uri, lineNumber); + + expect(stringToRangeStub.calledOnce).to.be.true; + expect(revealRangeStub.calledOnce).to.be.true; + expect(revealRangeStub.firstCall.args[0]).to.deep.equal(range); + }); + + + }); +}); From f36e51e7ca7b688549412b85c7a6b21f69e8fc94 Mon Sep 17 00:00:00 2001 From: Christina Lin Date: Wed, 29 May 2024 14:42:45 -0400 Subject: [PATCH 09/11] cleaned up stringToRange, added single line selections --- src/utils/getThreadLocation.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/utils/getThreadLocation.ts b/src/utils/getThreadLocation.ts index abd506c3..c7710d7d 100644 --- a/src/utils/getThreadLocation.ts +++ b/src/utils/getThreadLocation.ts @@ -34,21 +34,21 @@ export async function stringToRange(str: string, uri?: vscode.Uri) { throw Error(`Passed string is not a line number: ${str}`); } - let endCharacter = 0; + let endCharacter = undefined; + const startLine = parseInt(list[0]); + const endLine = list.length > 1 ? parseInt(list[1]) : startLine; // if given a file uri, set the the end range to eol if (uri) { const buffer = await vscode.workspace.fs.readFile(uri); const content = buffer.toString()?.split("\n"); - endCharacter = content[parseInt(list[1])]?.length; + endCharacter = content[endLine]?.length; } - const start = new vscode.Position(parseInt(list[0]), 0); - const end = - list.length > 1 - ? new vscode.Position(parseInt(list[1]), endCharacter ?? 0) - : start; - return new vscode.Range(start, end); + const startPosition = new vscode.Position(startLine, 0); + const endPosition = new vscode.Position(endLine, endCharacter ?? 0); + + return new vscode.Range(startPosition, endPosition); } // adjusts the range string to account for lines starting from 1 in the editor rather than 0 in the backend. From aa41b7e085c2b0d738cbb4c1b98146c3651787c7 Mon Sep 17 00:00:00 2001 From: Christina Lin Date: Wed, 29 May 2024 16:30:52 -0400 Subject: [PATCH 10/11] fixed bug where Assay crashes when trying to read a file that DNE due to deletion --- src/utils/getThreadLocation.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/utils/getThreadLocation.ts b/src/utils/getThreadLocation.ts index c7710d7d..1d9e0d0e 100644 --- a/src/utils/getThreadLocation.ts +++ b/src/utils/getThreadLocation.ts @@ -3,6 +3,14 @@ import * as vscode from "vscode"; import { splitUri } from "./splitUri"; import { AssayThread } from "../config/comment"; +async function readFile(uri: vscode.Uri) { + try { + return await vscode.workspace.fs.readFile(uri); + } catch { + return new Uint8Array(); + } +} + export default async function getThreadLocation(thread: AssayThread) { const range = rangeToString(thread.range); const { guid, version, filepath } = await getFilepathInfo(thread); @@ -40,8 +48,8 @@ export async function stringToRange(str: string, uri?: vscode.Uri) { // if given a file uri, set the the end range to eol if (uri) { - const buffer = await vscode.workspace.fs.readFile(uri); - const content = buffer.toString()?.split("\n"); + const buffer = await readFile(uri); + const content = buffer?.toString()?.split("\n"); endCharacter = content[endLine]?.length; } From e53d4caf2bbcd2cd4be76d8b87ed30c97dc36c5e Mon Sep 17 00:00:00 2001 From: Christina Lin Date: Tue, 4 Jun 2024 09:08:25 -0400 Subject: [PATCH 11/11] review changes --- src/commands/getAddon.ts | 13 ++++--------- src/extension.ts | 1 - src/utils/addonExtract.ts | 6 ++---- src/utils/commentManager.ts | 3 ++- src/utils/getThreadLocation.ts | 4 ++-- test/suite/commands/openFromUrl.test.ts | 10 +++++----- test/suite/utils/AddonExtract.test.ts | 14 +++++--------- test/suite/utils/commentManager.test.ts | 2 +- test/suite/utils/getThreadLocation.test.ts | 2 +- test/suite/utils/revealFile.test.ts | 14 +++++++------- 10 files changed, 29 insertions(+), 40 deletions(-) diff --git a/src/commands/getAddon.ts b/src/commands/getAddon.ts index c1f15c60..eb1b9ff2 100644 --- a/src/commands/getAddon.ts +++ b/src/commands/getAddon.ts @@ -42,15 +42,10 @@ export async function downloadAndExtract( await downloadAddon(addonFileId, compressedFilePath); - try { - await extractAddon( - compressedFilePath, - `${workspaceFolder}/${guid}`, - `${workspaceFolder}/${guid}/${version}` - ); - } catch (e) { - return { workspaceFolder, guid, version }; - } + await extractAddon( + compressedFilePath, + `${workspaceFolder}/${guid}/${version}` + ); return { workspaceFolder, guid, version }; } catch (error) { console.error(error); diff --git a/src/extension.ts b/src/extension.ts index e883c972..bb915e8e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -37,7 +37,6 @@ export async function activate(context: vscode.ExtensionContext) { if (context.globalState.get("filePath") !== undefined) { const filePath = context.globalState.get("filePath")?.toString(); const lineNumber = context.globalState.get("lineNumber")?.toString(); - console.log("hi", lineNumber); await context.globalState.update("filePath", undefined); await context.globalState.update("lineNumber", undefined); if (filePath) { diff --git a/src/utils/addonExtract.ts b/src/utils/addonExtract.ts index df5d9350..f2758313 100644 --- a/src/utils/addonExtract.ts +++ b/src/utils/addonExtract.ts @@ -7,17 +7,16 @@ import { errorMessages } from "../types"; export async function dirExistsOrMake(dir: string) { if (!fs.existsSync(dir)) { - await fs.promises.mkdir(dir); + await fs.promises.mkdir(dir, { recursive: true }); return true; } } export async function extractAddon( compressedFilePath: string, - addonFolderPath: string, addonVersionFolderPath: string ) { - await dirExistsOrMake(addonFolderPath); + await dirExistsOrMake(addonVersionFolderPath); if (!(await dirExistsOrMake(addonVersionFolderPath))) { const choice = await vscode.window.showQuickPick(["Yes", "No"], { @@ -47,7 +46,6 @@ export async function extractAddon( return await showErrorMessage(errorMessages, "other", extractAddon, [ compressedFilePath, - addonFolderPath, addonVersionFolderPath, ]); } diff --git a/src/utils/commentManager.ts b/src/utils/commentManager.ts index 4f01df88..73c927de 100644 --- a/src/utils/commentManager.ts +++ b/src/utils/commentManager.ts @@ -1,7 +1,8 @@ import * as vscode from "vscode"; import { addToCache, getFromCache } from "./addonCache"; -import getThreadLocation, { +import { + getThreadLocation, rangeTruncation, stringToRange, } from "./getThreadLocation"; diff --git a/src/utils/getThreadLocation.ts b/src/utils/getThreadLocation.ts index 1d9e0d0e..c40e68ca 100644 --- a/src/utils/getThreadLocation.ts +++ b/src/utils/getThreadLocation.ts @@ -11,7 +11,7 @@ async function readFile(uri: vscode.Uri) { } } -export default async function getThreadLocation(thread: AssayThread) { +export async function getThreadLocation(thread: AssayThread) { const range = rangeToString(thread.range); const { guid, version, filepath } = await getFilepathInfo(thread); return { guid, version, filepath, range: range }; @@ -46,7 +46,7 @@ export async function stringToRange(str: string, uri?: vscode.Uri) { const startLine = parseInt(list[0]); const endLine = list.length > 1 ? parseInt(list[1]) : startLine; - // if given a file uri, set the the end range to eol + // if given a file URI, set the the end range to eol if (uri) { const buffer = await readFile(uri); const content = buffer?.toString()?.split("\n"); diff --git a/test/suite/commands/openFromUrl.test.ts b/test/suite/commands/openFromUrl.test.ts index 4cff9aa2..0d2984b8 100644 --- a/test/suite/commands/openFromUrl.test.ts +++ b/test/suite/commands/openFromUrl.test.ts @@ -17,7 +17,7 @@ describe("openFromUrl.ts", async () => { }); describe("handleUri()", async () => { - it("should do nothing if the action is not review", async () => { + it("should do nothing if the action is not review.", async () => { const uri = { path: "/test-action/test-guid/test-version", }; @@ -29,7 +29,7 @@ describe("openFromUrl.ts", async () => { expect(getRootFolderPathStub.called).to.be.false; }); - it("should fail the stat check and call downloadAndExtract() if the manifest does not exist", async () => { + it("should fail the stat check and call downloadAndExtract() if the manifest does not exist.", async () => { const executeCommandStub = sinon.stub(vscode.commands, 'executeCommand'); executeCommandStub.resolves(); @@ -73,7 +73,7 @@ describe("openFromUrl.ts", async () => { expect(downloadAndExtractStub.called).to.be.true; }); - it("should not fail the stat check and not call downloadAndExtract()", async () => { + it("should not fail the stat check and not call downloadAndExtract().", async () => { const executeCommandStub = sinon.stub(vscode.commands, 'executeCommand'); executeCommandStub.resolves(); @@ -117,7 +117,7 @@ describe("openFromUrl.ts", async () => { }); describe("openWorkspace()", async () => { - it("should open the manifest if the workspace is already open", async () => { + it("should open the manifest if the workspace is already open.", async () => { const context = { globalState: { @@ -162,7 +162,7 @@ describe("openFromUrl.ts", async () => { }); describe("getAddonByUrl", async () => { - it("should receive a result from downloadAndExtract and correctly call openWorkspace", async () => { + it("should receive a result from downloadAndExtract and correctly call openWorkspace.", async () => { const context = { globalState: { update: sinon.stub(), diff --git a/test/suite/utils/AddonExtract.test.ts b/test/suite/utils/AddonExtract.test.ts index f56ed3b7..51a0d640 100644 --- a/test/suite/utils/AddonExtract.test.ts +++ b/test/suite/utils/AddonExtract.test.ts @@ -43,20 +43,19 @@ describe("AddonExtract.ts", async () => { describe("extractAddon()", async () => { it("should extract a new addon, remove the xpi, and make files read only", async () => { + // make a stub for the quickpick and force it to say yes + const showQuickPickStub = sinon.stub(); + showQuickPickStub.onCall(0).returns("Yes"); + sinon.replace(vscode.window, "showQuickPick", showQuickPickStub); + await extractAddon( compressedFilePath, - extractedworkspaceFolder, extractedVersionFolder ); expect(fs.existsSync(extractedworkspaceFolder)).to.be.true; expect(fs.existsSync(extractedVersionFolder)).to.be.true; expect(fs.existsSync(compressedFilePath)).to.be.false; - - const fileStats = fs.statSync( - path.resolve(extractedVersionFolder, "test.txt") - ); - // expect(fileStats.mode).to.equal(0o100444); }); it("should overwrite an existing addon", async () => { @@ -76,7 +75,6 @@ describe("AddonExtract.ts", async () => { await extractAddon( compressedFilePath, - extractedworkspaceFolder, extractedVersionFolder ); @@ -114,7 +112,6 @@ describe("AddonExtract.ts", async () => { try { await extractAddon( compressedFilePath, - extractedworkspaceFolder, extractedVersionFolder ); expect(false).to.be.true; @@ -148,7 +145,6 @@ describe("AddonExtract.ts", async () => { try { await extractAddon( compressedFilePath, - extractedworkspaceFolder, extractedVersionFolder ); expect(false).to.be.true; diff --git a/test/suite/utils/commentManager.test.ts b/test/suite/utils/commentManager.test.ts index eaa12836..f00846cd 100644 --- a/test/suite/utils/commentManager.test.ts +++ b/test/suite/utils/commentManager.test.ts @@ -210,7 +210,7 @@ describe("CommentManager.ts", () => { const expectedLink = `vscode://mozilla.assay/review/guid/version?path=filepath/with/slashes.py#range`; const showInformationMessageStub = sinon.stub(vscode.window, 'showInformationMessage'); - const getThreadLocationStub = sinon.stub(getThreadLocation, 'default').resolves({ + const getThreadLocationStub = sinon.stub(getThreadLocation, 'getThreadLocation').resolves({ guid: 'guid', version: 'version', filepath: 'filepath/with/slashes.py', diff --git a/test/suite/utils/getThreadLocation.test.ts b/test/suite/utils/getThreadLocation.test.ts index f51665ad..e929c4bf 100644 --- a/test/suite/utils/getThreadLocation.test.ts +++ b/test/suite/utils/getThreadLocation.test.ts @@ -4,7 +4,7 @@ import * as sinon from "sinon"; import * as vscode from "vscode"; import { AssayThread, contextValues } from "../../../src/config/comment"; -import getThreadLocation, { rangeToString, rangeTruncation, stringToRange } from "../../../src/utils/getThreadLocation"; +import { getThreadLocation, rangeToString, rangeTruncation, stringToRange } from "../../../src/utils/getThreadLocation"; import * as reviewRootDir from "../../../src/utils/reviewRootDir"; diff --git a/test/suite/utils/revealFile.test.ts b/test/suite/utils/revealFile.test.ts index 53d6c6f0..5e43a837 100644 --- a/test/suite/utils/revealFile.test.ts +++ b/test/suite/utils/revealFile.test.ts @@ -12,17 +12,17 @@ describe("revealFile.ts", async () => { }); describe("revealFile()", () => { - it("should reveal the document located at uri.", async () => { + it("should reveal the document located at URI.", async () => { const showTextDocumentStub = sinon.stub(vscode.window, "showTextDocument"); - const uri = vscode.Uri.parse("index.html"); + const URI = vscode.Uri.parse("index.html"); - await revealFile(uri); + await revealFile(URI); expect(showTextDocumentStub.calledOnce).to.be.true; - expect(showTextDocumentStub.firstCall.args[0]).to.deep.equal(uri); + expect(showTextDocumentStub.firstCall.args[0]).to.deep.equal(URI); }); - it("should reveal the document located at uri and correctly highlight and reveal the desired range", async () => { + it("should reveal the document located at URI and correctly highlight and reveal the desired range", async () => { const range = new vscode.Range(new vscode.Position(24, 0), new vscode.Position(24, 0)); const stringToRangeStub = sinon.stub(getThreadLocation, "stringToRange").resolves(range); const revealRangeStub = sinon.stub(); @@ -32,13 +32,13 @@ describe("revealFile.ts", async () => { selections: [], } as unknown as vscode.TextEditor; - const uri = vscode.Uri.file("index.html"); + const URI = vscode.Uri.file("index.html"); const lineNumber = "#L25"; const showTextDocumentStub = sinon.stub(vscode.window, "showTextDocument"); showTextDocumentStub.resolves(fakeEditor); - await revealFile(uri, lineNumber); + await revealFile(URI, lineNumber); expect(stringToRangeStub.calledOnce).to.be.true; expect(revealRangeStub.calledOnce).to.be.true;