This commit is contained in:
Jonathan Carter 2020-03-14 21:14:44 -07:00
Родитель 54169ee0fb
Коммит 9ab64d7136
8 изменённых файлов: 81 добавлений и 135 удалений

Просмотреть файл

@ -1,6 +1,7 @@
## v0.0.8 (03/14/2020)
- Added the ability to associate a tour with a specific Git tag and/or commit, in order to enable it to be resilient to code changes
- Updated the tour recorder so that tours are automatically saved upon creation, and on each step/change
## v0.0.7 (03/14/2020)

Просмотреть файл

@ -3,7 +3,7 @@
"displayName": "CodeTour",
"description": "VS Code extension that allows you to record and playback guided tours of codebases, directly within the editor",
"publisher": "vsls-contrib",
"version": "0.0.7",
"version": "0.0.8",
"repository": {
"type": "git",
"url": "https://github.com/vsls-contrib/codetour"
@ -110,13 +110,6 @@
"title": "Resume Tour",
"category": "CodeTour"
},
{
"command": "codetour.saveTour",
"title": "Save Tour",
"category": "CodeTour",
"icon": "$(save)",
"enablement": "!commentThreadIsEmpty"
},
{
"command": "codetour.saveTourStep",
"title": "Save Step"
@ -138,10 +131,6 @@
"command": "codetour.resumeTour",
"when": "codetour:inTour"
},
{
"command": "codetour.saveTour",
"when": "codetour:recording"
},
{
"command": "codetour.startTour",
"when": "codetour:hasTours"
@ -202,20 +191,15 @@
"group": "inline@2",
"when": "commentController == codetour && commentThread =~ /hasNext/"
},
{
"command": "codetour.editTour",
"group": "inline@3",
"when": "commentController == codetour && !codetour:recording"
},
{
"command": "codetour.saveTour",
"group": "inline@3",
"when": "commentController == codetour && codetour:recording"
},
{
"command": "codetour.endTour",
"group": "inline@4",
"group": "inline@3",
"when": "commentController == codetour"
},
{
"command": "codetour.editTour",
"group": "inline@4",
"when": "commentController == codetour && !codetour:recording"
}
],
"comments/commentThread/context": [
@ -262,35 +246,25 @@
}
],
"view/item/context": [
{
"command": "codetour.saveTour",
"when": "viewItem =~ /codetour.tour.recording/",
"group": "inline@1"
},
{
"command": "codetour.endTour",
"when": "viewItem =~ /codetour.tour(.recording)?.active/",
"group": "inline@2"
"group": "inline@1"
},
{
"command": "codetour.startTour",
"when": "viewItem == codetour.tour",
"group": "inline@3"
},
{
"command": "codetour.saveTour",
"when": "viewItem =~ /codetour.tour.recording/",
"group": "active@1"
"group": "inline@2"
},
{
"command": "codetour.resumeTour",
"when": "viewItem =~ /codetour.tour(.recording)?.active$/",
"group": "active@2"
"group": "active@1"
},
{
"command": "codetour.endTour",
"when": "viewItem =~ /codetour.tour(.recording)?.active$/",
"group": "active@3"
"group": "active@2"
},
{
"command": "codetour.startTour",

Просмотреть файл

@ -77,6 +77,29 @@ export function registerCommands() {
resumeCurrentCodeTour
);
async function writeTourFile(title: string, ref?: string): Promise<CodeTour> {
const file = title
.toLocaleLowerCase()
.replace(/\s/g, "-")
.replace(/[^\w\d-_]/g, "");
const workspaceRoot = vscode.workspace.workspaceFolders![0].uri.toString();
const uri = vscode.Uri.parse(`${workspaceRoot}/.vscode/tours/${file}.json`);
const tour = { title, steps: [] };
if (ref && ref !== "HEAD") {
(tour as any).ref = ref;
}
const tourContent = JSON.stringify(tour, null, 2);
await vscode.workspace.fs.writeFile(uri, new Buffer(tourContent));
(tour as any).id = uri.toString();
// @ts-ignore
return tour as CodeTour;
}
vscode.commands.registerCommand(`${EXTENSION_NAME}.recordTour`, async () => {
const title = await vscode.window.showInputBox({
prompt: "Specify the title of the tour"
@ -86,9 +109,10 @@ export function registerCommands() {
return;
}
const description = await vscode.window.showInputBox({
prompt: "(Optional) Specify the description of the tour"
});
const ref = await promptForTourRef();
const tour = await writeTourFile(title, ref);
startCodeTour(tour);
store.isRecording = true;
await vscode.commands.executeCommand(
@ -97,19 +121,15 @@ export function registerCommands() {
true
);
startCodeTour({
id: "",
title,
description,
steps: []
});
if (
await vscode.window.showInformationMessage(
"Code tour recording started. Start creating steps by clicking the + button to the left of each line of code.",
"Cancel"
)
) {
const uri = vscode.Uri.parse(tour.id);
vscode.workspace.fs.delete(uri);
endCurrentCodeTour();
store.isRecording = false;
vscode.commands.executeCommand("setContext", "codetour:recording", false);
@ -139,6 +159,8 @@ export function registerCommands() {
tour.steps.splice(stepNumber, 0, step);
saveTour(tour);
let label = `Step #${stepNumber + 1} of ${tour.steps.length}`;
const contextValues = [];
@ -202,6 +224,8 @@ export function registerCommands() {
store.activeTour!.step
].description = content;
saveTour(store.activeTour!.tour);
comment.parent.comments = comment.parent.comments.map(cmt => {
if ((cmt as CodeTourComment).id === comment.id) {
cmt.mode = vscode.CommentMode.Preview;
@ -212,13 +236,15 @@ export function registerCommands() {
}
);
function saveTourIfNeccessary(tour: CodeTour) {
if (tour.id) {
const uri = vscode.Uri.parse(tour.id);
delete tour.id;
const tourContent = JSON.stringify(tour, null, 2);
vscode.workspace.fs.writeFile(uri, new Buffer(tourContent));
}
async function saveTour(tour: CodeTour) {
const uri = vscode.Uri.parse(tour.id);
const newTour = {
...tour
};
delete newTour.id;
const tourContent = JSON.stringify(newTour, null, 2);
return vscode.workspace.fs.writeFile(uri, new Buffer(tourContent));
}
async function updateTourProperty(tour: CodeTour, property: string) {
@ -235,7 +261,7 @@ export function registerCommands() {
// @ts-ignore
tour[property] = propertyValue;
saveTourIfNeccessary(tour);
saveTour(tour);
}
function moveStep(
@ -260,13 +286,13 @@ export function registerCommands() {
// the tour play along with it as well.
if (
store.activeTour &&
tour.id === store.activeTour.id &&
tour.id === store.activeTour.tour.id &&
stepNumber === store.activeTour.step
) {
store.activeTour.step += movement;
}
saveTourIfNeccessary(tour);
saveTour(tour);
}
vscode.commands.registerCommand(
@ -292,8 +318,7 @@ export function registerCommands() {
vscode.commands.registerCommand(
`${EXTENSION_NAME}.changeTourRef`,
async (node: CodeTourNode) => {
const uri = vscode.Uri.parse(node.tour.id!);
const ref = await promptForTourRef(uri);
const ref = await promptForTourRef();
if (ref) {
if (ref === "HEAD") {
delete node.tour.ref;
@ -302,7 +327,7 @@ export function registerCommands() {
}
}
saveTourIfNeccessary(node.tour);
saveTour(node.tour);
}
);
@ -353,19 +378,16 @@ export function registerCommands() {
tour.steps.splice(step, 1);
if (
store.activeTour &&
tour.title === store.activeTour.tour.title &&
step === store.activeTour.step
) {
if (tour.steps.length === 0) {
store.activeTour!.step = -1;
} else if (step > 0) {
if (store.activeTour && store.activeTour.tour.id === tour.id) {
if (
step <= store.activeTour!.step &&
(store.activeTour!.step > 0 || tour.steps.length === 0)
) {
store.activeTour!.step--;
}
}
saveTourIfNeccessary(tour);
saveTour(tour);
}
}
);
@ -374,9 +396,8 @@ export function registerCommands() {
ref?: string;
}
async function promptForTourRef(
uri: vscode.Uri
): Promise<string | undefined> {
async function promptForTourRef(): Promise<string | undefined> {
const uri = vscode.workspace.workspaceFolders![0].uri;
const repository = api.getRepository(uri);
if (!repository) {
return;
@ -430,38 +451,4 @@ export function registerCommands() {
return response.ref;
}
}
vscode.commands.registerCommand(`${EXTENSION_NAME}.saveTour`, async () => {
const file = store
.activeTour!.tour.title.toLocaleLowerCase()
.replace(/\s/g, "-")
.replace(/[^\w\d-_]/g, "");
const tour = store.activeTour!.tour;
delete tour.id;
const workspaceRoot = vscode.workspace.workspaceFolders![0].uri.toString();
const uri = vscode.Uri.parse(`${workspaceRoot}/.vscode/tours/${file}.json`);
const ref = await promptForTourRef(uri);
if (ref && ref !== "HEAD") {
tour.ref = ref;
}
const tourContent = JSON.stringify(tour, null, 2);
vscode.workspace.fs.writeFile(uri, new Buffer(tourContent));
vscode.commands.executeCommand("setContext", "codetour:recording", false);
store.isRecording = false;
vscode.window.showInformationMessage(
`The "${
store.activeTour!.tour.title
}" code tour was saved to to ".vscode/tours/${file}.json"`
);
store.activeTour!.thread!.dispose();
store.activeTour = null;
endCurrentCodeTour();
});
}

Просмотреть файл

@ -142,7 +142,6 @@ export function startCodeTour(tour: CodeTour, stepNumber?: number) {
};
store.activeTour = {
id: tour.id,
tour,
step: stepNumber ? stepNumber : tour.steps.length ? 0 : -1,
thread: null
@ -151,24 +150,10 @@ export function startCodeTour(tour: CodeTour, stepNumber?: number) {
commands.executeCommand("setContext", IN_TOUR_KEY, true);
}
const KEEP_RECORDING_RESPONSE = "Continue Recording";
export async function endCurrentCodeTour() {
if (
store.isRecording &&
store.activeTour &&
store.activeTour.tour.steps.length > 0
) {
const response = await window.showInformationMessage(
"Are you sure you want to exit the current recording?",
"Exit",
KEEP_RECORDING_RESPONSE
);
if (response === KEEP_RECORDING_RESPONSE) {
return;
} else {
store.isRecording = false;
commands.executeCommand("setContext", "codetour:recording", false);
}
if (store.isRecording) {
store.isRecording = false;
commands.executeCommand("setContext", "codetour:recording", false);
}
if (store.activeTour?.thread) {

Просмотреть файл

@ -20,10 +20,6 @@ export interface CodeTour {
}
export interface ActiveTour {
// When a tour is being recorded, and hasn't
// been saved yet, it won't have an ID
id?: string;
tour: CodeTour;
step: number;

Просмотреть файл

@ -4,6 +4,7 @@ import { store } from ".";
import { VSCODE_DIRECTORY, EXTENSION_NAME } from "../constants";
import { endCurrentCodeTour } from "./actions";
import { set } from "mobx";
import { comparer } from "mobx";
const MAIN_TOUR_FILES = [
`${EXTENSION_NAME}.json`,
@ -26,12 +27,14 @@ export async function discoverTours(workspaceRoot: string): Promise<void> {
store.tours = tours.sort((a, b) => a.title.localeCompare(b.title));
if (store.activeTour) {
const tour = tours.find(tour => tour.id === store.activeTour!.id);
const tour = tours.find(tour => tour.id === store.activeTour!.tour.id);
if (tour) {
// Since the active tour could be already observed,
// we want to update it in place with the new properties.
set(store.activeTour.tour, tour);
if (!comparer.structural(store.activeTour.tour, tour)) {
// Since the active tour could be already observed,
// we want to update it in place with the new properties.
set(store.activeTour.tour, tour);
}
} else {
// The user deleted the tour
// file that's associated with

Просмотреть файл

@ -48,7 +48,7 @@ class CodeTourTreeProvider implements TreeDataProvider<TreeItem>, Disposable {
const isRecording =
store.isRecording &&
store.activeTour &&
store.activeTour.id === tour.id;
store.activeTour.tour.id === tour.id;
return new CodeTourNode(tour, this.extensionPath, isRecording!);
});

Просмотреть файл

@ -31,7 +31,7 @@ export class CodeTourNode extends TreeItem {
contextValues.push("recording");
}
const isActive = store.activeTour && tour.id === store.activeTour?.id;
const isActive = store.activeTour && tour.id === store.activeTour?.tour.id;
if (isActive) {
contextValues.push("active");
}