This commit is contained in:
Jonathan Carter 2020-06-30 18:08:06 -07:00
Родитель b14dcce6ba
Коммит 7e2b517df7
12 изменённых файлов: 392 добавлений и 189 удалений

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

@ -1,3 +1,11 @@
## v0.0.34 (06/27/2020)
- Updated the tour recorder, to allow you to edit the line associated with a step
- Updated the tour recorder, to allow you to add a tour step from an editor selection
- Added the ability to record a new tour that is saved to an arbitrary location on disk, as opposed to the `.tours` directory of the opened workspace.
- Added new extensibility APIs to record and playback tours for external workspaces (e.g. GistPad repo editing).
- Updated the `CodeTour` tree to always show when you're taking a tour, even if you don't have a workspace open.
## v0.0.33 (06/18/2020)
- Fixed an issue where CodeTour overrode the JSON language type
@ -75,7 +83,7 @@
- Introduced support for embedding shell commands in a tour step (e.g. `>> npm run compile`), which allows you to add more interactivity to a tour.
- Added support for including VS Code `command:` links within your tour step comments (e.g. `[Start Tour](command:codetour.startTour)`), in order to automate arbitrary workbench actions.
- Tours can now be organized within sub-directories of the `.vscode/tours` drectory, and can now also be places withtin a root-level `.tours` folder.
- Tours can now be organized within sub-directories of the `.vscode/tours` directory, and can now also be places withtin a root-level `.tours` folder.
- Added the `exportTour` to the API that is exposed by this extension
## v0.0.19 (04/06/2020)

162
README.md
Просмотреть файл

@ -8,89 +8,15 @@ CodeTour is a Visual Studio Code extension, which allows you to record and playb
In order to get started, install the [CodeTour extension](https://aka.ms/codetour), and then following one of the following guides, depending on whether you want to record or playback a tour:
- [Starting Tours](#starting-tours)
- [Navigating Tours](#navigating-tours)
- [Recording Tours](#recording-tours)
- [Exporting Tours](#exporting-tours)
- [Starting Tours](#starting-tours)
- [Navigating Tours](#navigating-tours)
- [Reference](#reference)
## Starting Tours
In order to start a tour, simply open up a codebase that has one or more tours. If this is the first time you've ever opened this codebase, you'll be presented with a toast notification asking if you'd like to take a tour of it.
<img width="400px" src="https://user-images.githubusercontent.com/116461/76691619-d0ada080-6609-11ea-81bf-c1f022dbff43.png" />
Otherwise, you can manually start a tour via any of the following methods:
1. Selecting a tour (or specific step) in the [`CodeTour` view](#tree-view) in the `Explorer` activity tab
<img width="250px" src="https://user-images.githubusercontent.com/116461/76164362-8610bd80-610b-11ea-9621-4ba2d47a8a52.png" />
1. Running the `CodeTour: Start Tour` [command](#contributed-commands), and selecting the tour you'd like to take
<img width="800px" src="https://user-images.githubusercontent.com/116461/76151694-7b531b80-606c-11ea-96a6-0655eb6ab4e6.gif" />
If the current workspace only has a single code tour, then this command will automatically start that tour. Otherwise, you'll be presented with a list of tours to select from.
### Opening Tours
In addition to taking tours that are part of the currently open workspace, you can also open a tour file that someone else sent you and/or you created yourself. Simply run the `CodeTour: Open Tour File...` command and/or click the folder icon in the title bar of the `CodeTour` tree view.
> Note: The `CodeTour` tree view only appears if the currently opened workspace has any tours and/or you're currently taking a tour.
Additionally, if someone has [exported](#exporting-tours) a tour, and uploaded it to a publically accessible location, they can send you the URL, and you can open it by running the `CodeTour: Open Tour URL...` command.
### Tour Markers
As you explore a codebase, you might encounter a "tour marker", which displays the CodeTour icon in the file gutter. This indicates that a line of code participates in a tour for the open workspace, which makes it easier to discover tours that might be relevant to what you're currently working on. When you see a marker, simply hover over the line and click the `Start Tour` link in the hover tooltip. This will start the tour that's associated with this line of code, at the specific step.
<img width="800px" src="https://user-images.githubusercontent.com/116461/78101204-9aa74500-739b-11ea-8a1e-dea923910524.gif" />
If you want to disable tour markers, you can perform one of the following actions:
- Run the `CodeTour: Hide Tour Markers` command
- Click the "eye icon" in the title bar of the `CodeTour` tree view
- Set the `codetour.showMarkers` configuration setting to `false`. _Note that the above two actions do this for you automatically._
<!--
### Notebook View
In addition to taking a tour through a series of files, you can also view a tour as a "notebook", which displays the tour's steps within a single document. Simply right-click a tour in the `CodeTour` tree and select `View Notebook`.
<img width="700px" src="https://user-images.githubusercontent.com/116461/79699658-bd63a580-8245-11ea-81cc-6208e2784acf.gif" />
-->
## Navigating Tours
Once you've started a tour, the comment UI will guide you, and includes navigation actions that allow you to perform the following:
- `Move Previous` - Navigate to the previous step in the current tour. This command is visible for step #2 and later.
- `Move Next` - Navigate to the next step in the current tour. This command is visible for all steps but the last one in a tour.
- `Edit Tour` - Begin editing the current tour (see [authoring](#authoring-tours) for details). Note that not all tours are editable, so depending on how you started the tour, you may or may not see this action.
- `End Tour` - End the current tour and remove the comment UI.
<img width="500px" src="https://user-images.githubusercontent.com/116461/76151723-ca00b580-606c-11ea-9bd5-81c1d9352cef.png" />
Additionally, you can use the `ctrl+right` / `ctrl+left` (Windows/Linux) and `cmd+right` / `cmd+left` (macOS) keyboard shortcuts to move forwards and backwards in the tour. The `CodeTour` tree view and status bar is also kept in sync with your current tour/step, to help the developer easily understand where they're at in the context of the broader tour.
<img width="800px" src="https://user-images.githubusercontent.com/116461/76807453-a1319c00-67a1-11ea-9b88-7e448072f33d.gif" />
If you navigate away from the current step and need to resume, you can do that via any of the following actions:
- Right-clicking the active tour in the `CodeTour` tree and selecting `Resume Tour`
- Clicking the `CodeTour` status bar item
- Running the `CodeTour: Resume Tour` command in the command palette
At any time, you can end the current code tour by means of one of the following actions:
- Click the stop button (the red square) in the current step comment
- Click the stop button next to the active tour in the `CodeTour` tree
- Running the `CodeTour: End Tour` command in the command palette
## Recording Tours
If you'd like to record a code tour for your codebase, you can simply click the `+` button in the `CodeTour` tree view (if it's visible) and/or run the `CodeTour: Record Tour` command. This will start the tour recorder, which allows you to begin opening files, clicking the "comment bar" for the line you want to annotate, and then adding the respective description (including markdown!). Add as many steps as you want, and then when done, simply click the stop tour action (the red square button). You can also create [directory steps](#directory-steps), or [content steps](#content-steps) to add an introductory or intermediate explainations to a tour.
If you'd like to record a code tour for your codebase, you can simply click the `+` button in the `CodeTour` tree view (if it's visible) and/or run the `CodeTour: Record Tour` command. This will start the tour recorder, which allows you to begin opening files, clicking the "comment bar" for the line you want to annotate, and then adding the respective description (including markdown!). Add as many steps as you want, and then when done, simply click the stop tour action (the red square button). You can also create [directory steps](#directory-steps), [selection steps](#text-selection), or [content steps](#content-steps) in order to add an introductory or intermediate explanations to a tour.
While you're recording, the `CodeTour` [tree view](#tree-view) will display the currently recorded tour, and it's current set of steps. You can tell which tour is being recorded because it will have a microphone icon to the left of its name.
@ -119,7 +45,7 @@ By default, each step is associated with the line of code you created the commen
<img width="800px" src="https://user-images.githubusercontent.com/116461/76705627-b96cc280-669e-11ea-982a-d754c4f001aa.gif" />
If you need to tweak the selection that's associated with a step, simply edit the step, reset the selection and then save it.
If you need to tweak the selection that's associated with a step, simply edit the step, reset the selection and then save it. Furthermore, if you want to create a step from a selection, simply highlight a span a code, right-click the editor and select `Add CodeTour Step`.
### Re-arranging steps
@ -261,7 +187,7 @@ For an example, refer to the `.tours/tree.tour` file of this repository.
By default, when you record a tour, it is written to the currently open workspace. This makes it easy to check-in the tour and share it with the rest of the team. However, there may be times where you want to record a tour for yourself, or a tour to help explain a one-off to someone, and in those situations, you might not want to check the tour into the repo.
So support this, after you finish recording a tour, you can right-click it in the `CodeTour` tree and select `Export Tour...`. This will allow you to save the tour to a new location, and then you can delete the tour file from your repo. Furthermore, when you export a tour, the tour file itself will embed the contents of all files needed by the tour, which ensures that someone can play it back, regardless if the have the respective code available locally. This enables a powerful form of collaboration.
To support this scenario, when you start recording a new tour, you can click the `Save tour as...` button in the upper-right side of the dialog that asks for the title of the tour. This wll allow you to select the file that the new tour will be written to, so that it isn't persisted to the workspace. Furthermore, you can record a tour as usual, and then when done, you can right-click it in the `CodeTour` tree and select `Export Tour...`. This will allow you to save the tour to a new location, and then you can delete the tour file from your repo. When you export a tour, the tour file itself will embed the contents of all files needed by the tour, which ensures that someone can play it back, regardless if the have the respective code available locally. This enables a powerful form of collaboration.
<img width="700px" src="https://user-images.githubusercontent.com/116461/77705325-9682be00-6f7c-11ea-9532-6975b19b8fcb.gif" />
@ -271,6 +197,80 @@ If you install the [GistPad](https://aka.ms/gistpad) extension, then you'll see
Once a tour is exported as a gist, you can right-click the `main.tour` file in the `GistPad` tree, and select `Copy GitHub URL`. If you send that to someone, and they run the `CodeTour: Open Tour URL...` command, then they'll be able to take the exact same tour, regardless if they have the code locally available or not.
## Starting Tours
In order to start a tour, simply open up a codebase that has one or more tours. If this is the first time you've ever opened this codebase, you'll be presented with a toast notification asking if you'd like to take a tour of it.
<img width="400px" src="https://user-images.githubusercontent.com/116461/76691619-d0ada080-6609-11ea-81bf-c1f022dbff43.png" />
Otherwise, you can manually start a tour via any of the following methods:
1. Selecting a tour (or specific step) in the [`CodeTour` view](#tree-view) in the `Explorer` activity tab
<img width="250px" src="https://user-images.githubusercontent.com/116461/76164362-8610bd80-610b-11ea-9621-4ba2d47a8a52.png" />
1. Running the `CodeTour: Start Tour` [command](#contributed-commands), and selecting the tour you'd like to take
<img width="800px" src="https://user-images.githubusercontent.com/116461/76151694-7b531b80-606c-11ea-96a6-0655eb6ab4e6.gif" />
If the current workspace only has a single code tour, then this command will automatically start that tour. Otherwise, you'll be presented with a list of tours to select from.
### Opening Tours
In addition to taking tours that are part of the currently open workspace, you can also open a tour file that someone else sent you and/or you created yourself. Simply run the `CodeTour: Open Tour File...` command and/or click the folder icon in the title bar of the `CodeTour` tree view.
> Note: The `CodeTour` tree view only appears if the currently opened workspace has any tours and/or you're currently taking a tour.
Additionally, if someone has [exported](#exporting-tours) a tour, and uploaded it to a publically accessible location, they can send you the URL, and you can open it by running the `CodeTour: Open Tour URL...` command.
### Tour Markers
As you explore a codebase, you might encounter a "tour marker", which displays the CodeTour icon in the file gutter. This indicates that a line of code participates in a tour for the open workspace, which makes it easier to discover tours that might be relevant to what you're currently working on. When you see a marker, simply hover over the line and click the `Start Tour` link in the hover tooltip. This will start the tour that's associated with this line of code, at the specific step.
<img width="800px" src="https://user-images.githubusercontent.com/116461/78101204-9aa74500-739b-11ea-8a1e-dea923910524.gif" />
If you want to disable tour markers, you can perform one of the following actions:
- Run the `CodeTour: Hide Tour Markers` command
- Click the "eye icon" in the title bar of the `CodeTour` tree view
- Set the `codetour.showMarkers` configuration setting to `false`. _Note that the above two actions do this for you automatically._
<!--
### Notebook View
In addition to taking a tour through a series of files, you can also view a tour as a "notebook", which displays the tour's steps within a single document. Simply right-click a tour in the `CodeTour` tree and select `View Notebook`.
<img width="700px" src="https://user-images.githubusercontent.com/116461/79699658-bd63a580-8245-11ea-81cc-6208e2784acf.gif" />
-->
## Navigating Tours
Once you've started a tour, the comment UI will guide you, and includes navigation actions that allow you to perform the following:
- `Move Previous` - Navigate to the previous step in the current tour. This command is visible for step #2 and later.
- `Move Next` - Navigate to the next step in the current tour. This command is visible for all steps but the last one in a tour.
- `Edit Tour` - Begin editing the current tour (see [authoring](#authoring-tours) for details). Note that not all tours are editable, so depending on how you started the tour, you may or may not see this action.
- `End Tour` - End the current tour and remove the comment UI.
<img width="500px" src="https://user-images.githubusercontent.com/116461/76151723-ca00b580-606c-11ea-9bd5-81c1d9352cef.png" />
Additionally, you can use the `ctrl+right` / `ctrl+left` (Windows/Linux) and `cmd+right` / `cmd+left` (macOS) keyboard shortcuts to move forwards and backwards in the tour. The `CodeTour` tree view and status bar is also kept in sync with your current tour/step, to help the developer easily understand where they're at in the context of the broader tour.
<img width="800px" src="https://user-images.githubusercontent.com/116461/76807453-a1319c00-67a1-11ea-9b88-7e448072f33d.gif" />
If you navigate away from the current step and need to resume, you can do that via any of the following actions:
- Right-clicking the active tour in the `CodeTour` tree and selecting `Resume Tour`
- Clicking the `CodeTour` status bar item
- Running the `CodeTour: Resume Tour` command in the command palette
At any time, you can end the current code tour by means of one of the following actions:
- Click the stop button (the red square) in the current step comment
- Click the stop button next to the active tour in the `CodeTour` tree
- Running the `CodeTour: End Tour` command in the command palette
## Reference
The following sections describe the VS Code integrations that the CodeTour extension contributes (e.g. tree, status bar, settings):
@ -330,8 +330,8 @@ In addition to the available commands, the Code Tour extension also contributes
In order to enable other extensions to contribute/manage their own code tours, the CodeTour extension exposes an API with the following methods:
- `startTour(tour: CodeTour, stepNumber: number, workspaceRoot: Uri, startInEditMode: boolean = false, canEditTour: boolean): void` - Starts the specified tour, at a specific step, and using a specific workspace root to resolve relative file paths. Additionally, you can specify whether the tour should be started in edit/record mode or not, as well as whether the tour should be editable. Once the tour has been started, the end-user can use the status bar, command palette, key bindings and comment UI to navigate and edit the tour, just like a "normal" tour.
- `endCurrentTour(): void` - Ends the currently running tour (if there is one). Note that this is simply a programatic way to end the tour, and the end-user can also choose to end the tour using either the command palette (running the `CodeTour: End Tour` command) or comment UI (clicking the red square, stop icon) as usual.
- `exportTour(tour: CodeTour): Promise<string>` - Exports a `CodeTour` instance into a fully-embedded tour file, that can then be written to some persistent storage (e.g. a GitHub Gist).
- `startTour(tour: CodeTour, stepNumber: number, workspaceRoot: Uri, startInEditMode: boolean = false, canEditTour: boolean): void` - Starts the specified tour, at a specific step, and using a specific workspace root to resolve relative file paths. Additionally, you can specify whether the tour should be started in edit/record mode or not, as well as whether the tour should be editable. Once the tour has been started, the end-user can use the status bar, command palette, key bindings and comment UI to navigate and edit the tour, just like a "normal" tour.

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

@ -53,6 +53,10 @@
"command": "codetour.addDirectoryStep",
"title": "Add CodeTour Step"
},
{
"command": "codetour.addSelectionStep",
"title": "Add CodeTour Step"
},
{
"command": "codetour.addTourStep",
"title": "Add Step to Tour",
@ -66,6 +70,10 @@
"command": "codetour.changeTourRef",
"title": "Change Git Ref"
},
{
"command": "codetour.changeTourStepLine",
"title": "Change Line"
},
{
"command": "codetour.changeTourStepTitle",
"title": "Change Title"
@ -228,6 +236,10 @@
"command": "codetour.addDirectoryStep",
"when": "false"
},
{
"command": "codetour.addSelectionStep",
"when": "false"
},
{
"command": "codetour.addTourStep",
"when": "false"
@ -236,6 +248,10 @@
"command": "codetour.changeTourRef",
"when": "false"
},
{
"command": "codetour.changeTourStepLine",
"when": "false"
},
{
"command": "codetour.changeTourStepTitle",
"when": "false"
@ -336,7 +352,12 @@
{
"command": "codetour.moveTourStepForward",
"group": "move@2",
"when": "commentController == codetour && && codetour:canEditTour commentThread =~ /hasNext/"
"when": "commentController == codetour && codetour:canEditTour commentThread =~ /hasNext/"
},
{
"command": "codetour.changeTourStepLine",
"group": "mutate@1",
"when": "commentController == codetour && codetour:canEditTour"
},
{
"command": "codetour.deleteTourStep",
@ -490,6 +511,13 @@
"command": "codetour.addDirectoryStep",
"when": "codetour:recording && explorerResourceIsFolder"
}
],
"editor/context": [
{
"command": "codetour.addSelectionStep",
"when": "codetour:recording && editorHasSelection",
"group": "codetour@1"
}
]
},
"views": {
@ -497,7 +525,7 @@
{
"id": "codetour.tours",
"name": "CodeTour",
"when": "workspaceFolderCount != 0"
"when": "workspaceFolderCount != 0 || codetour:inTour"
}
]
},

22
src/api.ts Normal file
Просмотреть файл

@ -0,0 +1,22 @@
import { ExtensionContext } from "vscode";
import {
endCurrentCodeTour,
exportTour,
onDidEndTour,
promptForTour,
recordTour,
selectTour,
startCodeTour
} from "./store/actions";
export function initializeApi(context: ExtensionContext) {
return {
endCurrentTour: endCurrentCodeTour,
exportTour,
onDidEndTour,
promptForTour: promptForTour.bind(null, context.globalState),
recordTour,
startTour: startCodeTour,
selectTour
};
}

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

@ -1,4 +1,5 @@
import * as vscode from "vscode";
import { initializeApi } from "./api";
import { registerCommands } from "./commands";
import { registerFileSystemProvider } from "./fileSystem";
import { registerTextDocumentContentProvider } from "./fileSystem/documentProvider";
@ -7,12 +8,7 @@ import { registerDecorators } from "./player/decorator";
import { registerStatusBar } from "./player/status";
import { registerCompletionProvider } from "./recorder/completionProvider";
import { store } from "./store";
import {
endCurrentCodeTour,
exportTour,
promptForTour,
startCodeTour
} from "./store/actions";
import { promptForTour } from "./store/actions";
import { discoverTours } from "./store/provider";
import { registerTreeProvider } from "./tree";
@ -51,9 +47,5 @@ export async function activate(context: vscode.ExtensionContext) {
registerStatusBar();
registerCompletionProvider();
return {
startTour: startCodeTour,
exportTour,
endCurrentTour: endCurrentCodeTour
};
return initializeApi(context);
}

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

@ -8,13 +8,11 @@ import {
exportTour,
moveCurrentCodeTourBackward,
moveCurrentCodeTourForward,
selectTour,
startCodeTour
} from "../store/actions";
import { CodeTourNode } from "../tree/nodes";
import { readUriContents } from "../utils";
interface CodeTourQuickPickItem extends vscode.QuickPickItem {
tour: CodeTour;
}
let terminal: vscode.Terminal | null;
export function registerPlayerCommands() {
@ -34,9 +32,17 @@ export function registerPlayerCommands() {
vscode.commands.registerCommand(
`${EXTENSION_NAME}.startTourByTitle`,
async (title: string, stepNumber?: number) => {
const tour = store.tours.find(tour => tour.title === title);
const tours = store.activeTour?.tours || store.tours;
const tour = tours.find(tour => tour.title === title);
if (tour) {
startCodeTour(tour, stepNumber && --stepNumber);
startCodeTour(
tour,
stepNumber && --stepNumber,
store.activeTour?.workspaceRoot,
undefined,
undefined,
store.activeTour?.tours
);
}
}
);
@ -45,7 +51,14 @@ export function registerPlayerCommands() {
vscode.commands.registerCommand(
`${EXTENSION_NAME}.navigateToStep`,
async (stepNumber: number) => {
startCodeTour(store.activeTour!.tour, --stepNumber);
startCodeTour(
store.activeTour!.tour,
--stepNumber,
store.activeTour?.workspaceRoot,
undefined,
undefined,
store.activeTour?.tours
);
}
);
@ -77,30 +90,22 @@ export function registerPlayerCommands() {
async (
tour?: CodeTour | CodeTourNode,
stepNumber?: number,
workspaceRoot?: vscode.Uri
workspaceRoot?: vscode.Uri,
tours?: CodeTour[]
) => {
if (tour) {
const targetTour = tour instanceof CodeTourNode ? tour.tour : tour;
return startCodeTour(targetTour, stepNumber, workspaceRoot);
return startCodeTour(
targetTour,
stepNumber,
workspaceRoot,
undefined,
undefined,
tours
);
}
const items: CodeTourQuickPickItem[] = store.tours.map(tour => ({
label: tour.title!,
tour: tour,
detail: tour.description
}));
if (items.length === 1) {
return startCodeTour(items[0].tour);
}
const response = await vscode.window.showQuickPick(items, {
placeHolder: "Select the tour to start..."
});
if (response) {
startCodeTour(response.tour);
}
selectTour(store.tours, workspaceRoot);
}
);

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

@ -71,7 +71,8 @@ export class CodeTourComment implements Comment {
return `[${title}](command:codetour.navigateToStep?${stepNumber} "Navigate to step #${stepNumber}")`;
}
const tour = store.tours.find(tour => tour.title === tourTitle);
const tours = store.activeTour?.tours || store.tours;
const tour = tours.find(tour => tour.title === tourTitle);
if (tour) {
const args = [tourTitle];
@ -166,7 +167,12 @@ async function renderCurrentStep() {
// Adjust the line number, to allow the user to specify
// them in 1-based format, not 0-based
const line = step.line ? step.line - 1 : 2000;
const line = step.line
? step.line - 1
: step.selection
? step.selection.end.line - 1
: 2000;
const range = new Range(line, 0, line, 0);
let label = `Step #${currentStep + 1} of ${currentTour!.steps.length}`;

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

@ -1,11 +1,17 @@
import { action, comparer, runInAction } from "mobx";
import * as path from "path";
import * as vscode from "vscode";
import { workspace } from "vscode";
import { EXTENSION_NAME, FS_SCHEME_CONTENT } from "../constants";
import { api, RefType } from "../git";
import { CodeTourComment } from "../player";
import { CodeTour, store } from "../store";
import { endCurrentCodeTour, startCodeTour } from "../store/actions";
import {
endCurrentCodeTour,
exportTour,
onDidEndTour,
startCodeTour
} from "../store/actions";
import { CodeTourNode, CodeTourStepNode } from "../tree/nodes";
import { getActiveWorkspacePath, getRelativePath } from "../utils";
@ -16,8 +22,12 @@ export function registerRecorderCommands() {
.replace(/\s/g, "-")
.replace(/[^\w\d-_]/g, "");
const prefix = workspaceRoot.path.endsWith("/")
? workspaceRoot.path
: `${workspaceRoot.path}/`;
return workspaceRoot.with({
path: `${workspaceRoot.path}/.tours/${file}.tour`
path: `${prefix}.tours/${file}.tour`
});
}
@ -34,12 +44,18 @@ export function registerRecorderCommands() {
async function writeTourFile(
workspaceRoot: vscode.Uri,
title: string,
title: string | vscode.Uri,
ref?: string
): Promise<CodeTour> {
const uri = getTourFileUri(workspaceRoot, title);
const uri =
typeof title === "string" ? getTourFileUri(workspaceRoot, title) : title;
const tour = { title, steps: [] };
const tourTitle =
typeof title === "string"
? title
: path.basename(title.path).replace(".tour", "");
const tour = { title: tourTitle, steps: [] };
if (ref && ref !== "HEAD") {
(tour as any).ref = ref;
}
@ -53,24 +69,19 @@ export function registerRecorderCommands() {
// @ts-ignore
return tour as CodeTour;
}
interface WorkspaceQuickPickItem extends vscode.QuickPickItem {
uri: vscode.Uri;
}
const REENTER_TITLE_RESPONSE = "Re-enter title";
vscode.commands.registerCommand(
`${EXTENSION_NAME}.recordTour`,
async (placeHolderTitle?: string) => {
const title = await vscode.window.showInputBox({
prompt: "Specify the title of the tour",
value: placeHolderTitle
});
async function recordTourInternal(
tourTitle: string | vscode.Uri,
workspaceRoot?: vscode.Uri
) {
if (!workspaceRoot) {
workspaceRoot = workspace.workspaceFolders![0].uri;
if (!title) {
return;
}
let workspaceRoot = workspace.workspaceFolders![0].uri;
if (workspace.workspaceFolders!.length > 1) {
const items: WorkspaceQuickPickItem[] = workspace.workspaceFolders!.map(
({ name, uri }) => ({
@ -89,11 +100,14 @@ export function registerRecorderCommands() {
workspaceRoot = response.uri;
}
}
if (typeof tourTitle === "string") {
const tourExists = await checkIfTourExists(workspaceRoot, tourTitle);
const tourExists = await checkIfTourExists(workspaceRoot, title);
if (tourExists) {
const response = await vscode.window.showErrorMessage(
`This workspace already includes a tour with the title "${title}."`,
`This workspace already includes a tour with the title "${tourTitle}."`,
REENTER_TITLE_RESPONSE,
"Overwrite existing tour"
);
@ -101,7 +115,8 @@ export function registerRecorderCommands() {
if (response === REENTER_TITLE_RESPONSE) {
return vscode.commands.executeCommand(
`${EXTENSION_NAME}.recordTour`,
title
workspaceRoot,
tourTitle
);
} else if (!response) {
// If the end-user closes the error
@ -109,36 +124,76 @@ export function registerRecorderCommands() {
return;
}
}
}
const ref = await promptForTourRef(workspaceRoot);
const tour = await writeTourFile(workspaceRoot, title, ref);
const ref = await promptForTourRef(workspaceRoot);
const tour = await writeTourFile(workspaceRoot, tourTitle, ref);
startCodeTour(tour);
startCodeTour(tour, 0, workspaceRoot, true);
store.isRecording = true;
await vscode.commands.executeCommand(
"setContext",
"codetour:recording",
true
);
vscode.window.showInformationMessage(
"CodeTour recording started! Begin creating steps by opening a file, clicking the + button to the left of a line of code, and then adding the appropriate comments."
);
}
if (
await vscode.window.showInformationMessage(
"CodeTour recording started! Begin creating steps by opening a file, clicking the + button to the left of a line of code, and then adding the appropriate comments.",
"Cancel"
)
) {
const uri = vscode.Uri.parse(tour.id);
vscode.workspace.fs.delete(uri);
vscode.commands.registerCommand(
`${EXTENSION_NAME}.recordTour`,
async (workspaceRoot?: vscode.Uri, placeHolderTitle?: string) => {
const inputBox = vscode.window.createInputBox();
inputBox.title =
"Specify the title of the tour, or save it to a specific location";
inputBox.placeholder = placeHolderTitle;
inputBox.buttons = [
{
iconPath: new vscode.ThemeIcon("save-as"),
tooltip: "Save tour as..."
}
];
endCurrentCodeTour();
store.isRecording = false;
vscode.commands.executeCommand(
"setContext",
"codetour:recording",
false
);
}
inputBox.onDidAccept(async () => {
inputBox.hide();
if (!inputBox.value) {
return;
}
recordTourInternal(inputBox.value, workspaceRoot);
});
inputBox.onDidTriggerButton(async button => {
inputBox.hide();
const uri = await vscode.window.showSaveDialog({
filters: {
Tours: ["tour"]
},
saveLabel: "Save Tour"
});
if (!uri) {
return;
}
const disposeEndTourHandler = onDidEndTour(async tour => {
if (tour.id === decodeURIComponent(uri.toString())) {
disposeEndTourHandler.dispose();
if (
await vscode.window.showInformationMessage(
"Would you like to export this tour?",
"Export Tour"
)
) {
const content = await exportTour(tour);
vscode.workspace.fs.writeFile(uri, Buffer.from(content));
}
}
});
recordTourInternal(uri, workspaceRoot);
});
inputBox.show();
}
);
@ -225,6 +280,25 @@ export function registerRecorderCommands() {
})
);
vscode.commands.registerTextEditorCommand(
`${EXTENSION_NAME}.addSelectionStep`,
action(async (editor: vscode.TextEditor) => {
const stepNumber = ++store.activeTour!.step;
const tour = store.activeTour!.tour;
const workspaceRoot = getActiveWorkspacePath();
const file = getRelativePath(workspaceRoot, editor.document.uri.path);
tour.steps.splice(stepNumber, 0, {
file,
selection: getStepSelection(),
description: ""
});
saveTour(tour);
})
);
vscode.commands.registerCommand(
`${EXTENSION_NAME}.addTourStep`,
action((reply: vscode.CommentReply) => {
@ -481,6 +555,25 @@ export function registerRecorderCommands() {
}
);
vscode.commands.registerCommand(
`${EXTENSION_NAME}.changeTourStepLine`,
async (comment: CodeTourComment) => {
const step = store.activeTour!.tour.steps[store.activeTour!.step];
const response = await vscode.window.showInputBox({
prompt: `Enter the new line # for this tour step (Leave blank to use the selection/document end)`,
value: step.line?.toString() || ""
});
if (response) {
step.line = Number(response);
} else {
delete step.line;
}
saveTour(store.activeTour!.tour);
}
);
vscode.commands.registerCommand(
`${EXTENSION_NAME}.changeTourRef`,
async (node: CodeTourNode) => {

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

@ -1,4 +1,4 @@
import { commands, Memento, Uri, window } from "vscode";
import { commands, EventEmitter, Memento, Uri, window } from "vscode";
import { CodeTour, store } from ".";
import { EXTENSION_NAME, FS_SCHEME, FS_SCHEME_CONTENT } from "../constants";
import { startPlayer, stopPlayer } from "../player";
@ -13,12 +13,16 @@ const CAN_EDIT_TOUR_KEY = `${EXTENSION_NAME}:canEditTour`;
const IN_TOUR_KEY = `${EXTENSION_NAME}:inTour`;
const RECORDING_KEY = `${EXTENSION_NAME}:recording`;
const _onDidEndTour = new EventEmitter<CodeTour>();
export const onDidEndTour = _onDidEndTour.event;
export function startCodeTour(
tour: CodeTour,
stepNumber?: number,
workspaceRoot?: Uri,
startInEditMode: boolean = false,
canEditTour: boolean = true
canEditTour: boolean = true,
tours?: CodeTour[]
) {
startPlayer();
@ -30,7 +34,8 @@ export function startCodeTour(
tour,
step: stepNumber ? stepNumber : tour.steps.length ? 0 : -1,
workspaceRoot,
thread: null
thread: null,
tours
};
commands.executeCommand("setContext", IN_TOUR_KEY, true);
@ -42,7 +47,36 @@ export function startCodeTour(
}
}
export async function selectTour(
tours: CodeTour[],
workspaceRoot?: Uri
): Promise<boolean> {
const items: any[] = tours.map(tour => ({
label: tour.title!,
tour: tour,
detail: tour.description
}));
if (items.length === 1) {
startCodeTour(items[0].tour, 0, workspaceRoot, false, true, tours);
return true;
}
const response = await window.showQuickPick(items, {
placeHolder: "Select the tour to start..."
});
if (response) {
startCodeTour(response.tour, 0, workspaceRoot, false, true, tours);
return true;
}
return false;
}
export async function endCurrentCodeTour() {
_onDidEndTour.fire(store.activeTour!.tour);
if (store.isRecording) {
store.isRecording = false;
commands.executeCommand("setContext", RECORDING_KEY, false);
@ -71,10 +105,13 @@ export function moveCurrentCodeTourForward() {
store.activeTour!.step++;
}
export async function promptForTour(globalState: Memento) {
const workspaceKey = getWorkspaceKey();
const key = `${EXTENSION_NAME}:${workspaceKey}`;
if (store.hasTours && !globalState.get(key)) {
export async function promptForTour(
globalState: Memento,
workspaceRoot: Uri = getWorkspaceKey(),
tours: CodeTour[] = store.tours
): Promise<boolean> {
const key = `${EXTENSION_NAME}:${workspaceRoot}`;
if (tours.length > 0 && !globalState.get(key)) {
globalState.update(key, true);
if (
@ -83,15 +120,18 @@ export async function promptForTour(globalState: Memento) {
"Start CodeTour"
)
) {
const primaryTour = store.tours.find(tour => tour.isPrimary);
const primaryTour = tours.find(tour => tour.isPrimary);
if (primaryTour) {
startCodeTour(primaryTour);
startCodeTour(primaryTour, 0, workspaceRoot, false, undefined, tours);
return true;
} else {
commands.executeCommand(`${EXTENSION_NAME}.startTour`);
return selectTour(tours, workspaceRoot);
}
}
}
return false;
}
export async function exportTour(tour: CodeTour) {
@ -121,3 +161,7 @@ export async function exportTour(tour: CodeTour) {
return JSON.stringify(newTour, null, 2);
}
export async function recordTour(workspaceRoot: Uri) {
commands.executeCommand(`${EXTENSION_NAME}.recordTour`, workspaceRoot);
}

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

@ -49,6 +49,14 @@ export interface ActiveTour {
// In order to resolve relative file
// paths, we need to know the workspace root
workspaceRoot?: Uri;
// In order to resolve inter-tour
// links, the active tour might need
// the context of its sibling tours, if
// they're coming from somewhere other
// then the active workspace (e.g. a
// GistPad-managed repo).
tours?: CodeTour[];
}
export interface Store {

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

@ -81,17 +81,16 @@ export class CodeTourStepNode extends TreeItem {
const step = tour.steps[stepNumber];
const workspaceRoot =
store.activeTour &&
store.activeTour.tour.id === tour.id &&
store.activeTour.workspaceRoot
? store.activeTour.workspaceRoot
: undefined;
let workspaceRoot, tours;
if (store.activeTour && store.activeTour.tour.id === tour.id) {
workspaceRoot = store.activeTour.workspaceRoot;
tours = store.activeTour.tours;
}
this.command = {
command: `${EXTENSION_NAME}.startTour`,
title: "Start Tour",
arguments: [tour, stepNumber, workspaceRoot]
arguments: [tour, stepNumber, workspaceRoot, tours]
};
let resourceUri;

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

@ -76,19 +76,17 @@ export function getActiveWorkspacePath() {
}
export function getWorkspaceKey() {
return (
workspace.workspaceFile || workspace.workspaceFolders![0].uri.toString()
);
return workspace.workspaceFile || workspace.workspaceFolders![0].uri;
}
export function getWorkspacePath(tour: CodeTour) {
return getWorkspaceUri(tour)?.toString() || "";
}
export function getWorkspaceUri(tour: CodeTour) {
export function getWorkspaceUri(tour: CodeTour): Uri | undefined {
const tourUri = Uri.parse(tour.id);
return (
workspace.getWorkspaceFolder(tourUri)?.uri ||
workspace.workspaceFolders![0].uri
(workspace.workspaceFolders && workspace.workspaceFolders[0].uri)
);
}