This commit is contained in:
Jonathan Carter 2020-03-30 22:02:11 -07:00
Родитель c65cf94aad
Коммит 19e0723466
8 изменённых файлов: 96 добавлений и 274 удалений

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

@ -1,12 +1,11 @@
## v0.0.16 (03/30/2020)
- Fixed some bugs with the `CodeTour` tree
- Updated the `CodeTour` tree to display the currently active tour, regardless how it was started (e.g. you open a tour file).
## v0.0.15 (03/29/2020)
- Updated the `CodeTour` tree to only display if the currently open workspace has any tours, or if the user is currently taking a tour. That way, it isn't obtrusive to users that aren't currently using it.
- Updated the `CodeTour: Refresh Tours` command to only show up when the currently opened workspace has any tours.
- Updated the `CodeTour` tree to display the currently active tour, regardless how it was started.
## v0.0.14 (03/26/2020)

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

@ -51,19 +51,13 @@
"command": "codetour.changeTourTitle",
"title": "Change Title"
},
{
"command": "codetour.deleteTour",
"title": "Delete Tour"
},
{
"command": "codetour.deleteTourStep",
"title": "Delete Step"
},
{
"command": "codetour.editTour",
"title": "Edit Tour",
"category": "CodeTour",
"icon": "$(edit)"
"command": "codetour.deleteTour",
"title": "Delete Tour"
},
{
"command": "codetour.editTourAtStep",
@ -73,6 +67,12 @@
"command": "codetour.editTourStep",
"title": "Edit Step"
},
{
"command": "codetour.editTour",
"title": "Edit Tour",
"category": "CodeTour",
"icon": "$(edit)"
},
{
"command": "codetour.endTour",
"title": "End Tour",
@ -83,16 +83,16 @@
"command": "codetour.exportTour",
"title": "Export Tour..."
},
{
"command": "codetour.moveTourStepBack",
"title": "Move Up",
"icon": "$(arrow-up)"
},
{
"command": "codetour.moveTourStepForward",
"title": "Move Down",
"icon": "$(arrow-down)"
},
{
"command": "codetour.moveTourStepBack",
"title": "Move Up",
"icon": "$(arrow-up)"
},
{
"command": "codetour.nextTourStep",
"title": "Next",

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

@ -5,9 +5,7 @@ import {
endCurrentCodeTour,
moveCurrentCodeTourBackward,
moveCurrentCodeTourForward,
startCodeTour,
resumeCurrentCodeTour,
CodeTourComment
startCodeTour
} from "./store/actions";
import { discoverTours } from "./store/provider";
import { CodeTourNode, CodeTourStepNode } from "./tree/nodes";
@ -16,6 +14,7 @@ import { api, RefType } from "./git";
import * as path from "path";
import { getStepFileUri } from "./utils";
import { workspace } from "vscode";
import { focusPlayer, CodeTourComment } from "./player";
interface CodeTourQuickPickItem extends vscode.QuickPickItem {
tour: CodeTour;
}
@ -33,7 +32,7 @@ export function registerCommands() {
return startCodeTour(targetTour, stepNumber, workspaceRoot);
}
let items: CodeTourQuickPickItem[] = store.tours.map(tour => ({
const items: CodeTourQuickPickItem[] = store.tours.map(tour => ({
label: tour.title!,
tour: tour,
detail: tour.description
@ -78,10 +77,7 @@ export function registerCommands() {
}
);
vscode.commands.registerCommand(
`${EXTENSION_NAME}.resumeTour`,
resumeCurrentCodeTour
);
vscode.commands.registerCommand(`${EXTENSION_NAME}.resumeTour`, focusPlayer);
function getTourFileUri(title: string) {
const file = title
@ -225,6 +221,7 @@ export function registerCommands() {
}
}
}
vscode.commands.registerCommand(
`${EXTENSION_NAME}.addTourStep`,
(reply: vscode.CommentReply) => {
@ -460,10 +457,7 @@ export function registerCommands() {
"Delete Tour"
)
) {
if (
store.activeTour &&
node.tour.title === store.activeTour.tour.title
) {
if (store.activeTour && node.tour.id === store.activeTour.tour.id) {
await endCurrentCodeTour();
}

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

@ -1,46 +1,35 @@
import * as vscode from "vscode";
import { registerCommands } from "./commands";
import { EXTENSION_NAME } from "./constants";
import { registerFileSystemProvider } from "./fileSystem";
import { initializeGitApi } from "./git";
import { registerStatusBar } from "./status";
import { store } from "./store";
import {
endCurrentCodeTour,
promptForTour,
startCodeTour
} from "./store/actions";
import { discoverTours } from "./store/provider";
import { registerTreeProvider } from "./tree";
import { initializeGitApi } from "./git";
import { startCodeTour, endCurrentCodeTour } from "./store/actions";
import { registerFileSystemProvider } from "./fileSystem";
async function promptForTour(
workspaceRoot: string,
globalState: vscode.Memento
) {
const key = `${EXTENSION_NAME}:${workspaceRoot}`;
if (store.hasTours && !globalState.get(key)) {
globalState.update(key, true);
if (
await vscode.window.showInformationMessage(
"This workspace has guided tours you can take to get familiar with the codebase.",
"Start CodeTour"
)
) {
vscode.commands.executeCommand(`${EXTENSION_NAME}.startTour`);
}
}
}
export async function activate(context: vscode.ExtensionContext) {
registerCommands();
// If the user has a workspace open, then attempt to discover
// the tours contained within it and optionally prompt the user.
if (vscode.workspace.workspaceFolders) {
const workspaceRoot = vscode.workspace.workspaceFolders[0].uri.toString();
await discoverTours(workspaceRoot);
registerTreeProvider(context.extensionPath);
promptForTour(workspaceRoot, context.globalState);
initializeGitApi();
}
// Regardless if the user has a workspace open,
// we still need to register the following items
// in order to support opening tour files and/or
// enabling other extensions to start a tour.
registerTreeProvider(context.extensionPath);
registerFileSystemProvider();
registerStatusBar();

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

@ -11,19 +11,15 @@ import {
Uri,
workspace
} from "vscode";
import { store, CodeTour, CodeTourStep } from "../store";
import { FS_SCHEME } from "../constants";
import { CodeTour, CodeTourStep, store } from "../store";
export class CodeTourFileSystemProvider implements FileSystemProvider {
private count = 0;
private _onDidChangeFile = new EventEmitter<FileChangeEvent[]>();
public readonly onDidChangeFile: Event<FileChangeEvent[]> = this
._onDidChangeFile.event;
getCurrentTourStep(): [CodeTour, CodeTourStep] {
const tour = store.activeTour?.tour!;
return [tour, tour?.steps[store.activeTour!.step]!];
const tour = store.activeTour!.tour;
return [tour, tour.steps[store.activeTour!.step]];
}
updateTour(tour: CodeTour) {
@ -72,13 +68,19 @@ export class CodeTourFileSystemProvider implements FileSystemProvider {
this.updateTour(tour);
}
// Unimplemented members
private _onDidChangeFile = new EventEmitter<FileChangeEvent[]>();
public readonly onDidChangeFile: Event<FileChangeEvent[]> = this
._onDidChangeFile.event;
async copy?(
source: Uri,
destination: Uri,
options: { overwrite: boolean }
): Promise<void> {
throw FileSystemError.NoPermissions(
"CodeTour doesn't support copying files"
"CodeTour doesn't support copying files."
);
}
@ -90,9 +92,10 @@ export class CodeTourFileSystemProvider implements FileSystemProvider {
async delete(uri: Uri, options: { recursive: boolean }): Promise<void> {
throw FileSystemError.NoPermissions(
"CodeTour doesn't support deleting files"
"CodeTour doesn't support deleting files."
);
}
async readDirectory(uri: Uri): Promise<[string, FileType][]> {
throw FileSystemError.NoPermissions("CodeTour doesnt support directories.");
}

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

@ -1,137 +1,11 @@
import {
commands,
Comment,
CommentAuthorInformation,
CommentMode,
comments,
CommentThread,
CommentThreadCollapsibleState,
MarkdownString,
Range,
TextEditorRevealType,
Uri,
window,
workspace,
TextDocument,
CommentController,
Selection
} from "vscode";
import { commands, Memento, Uri, window } from "vscode";
import { CodeTour, store } from ".";
import { EXTENSION_NAME, FS_SCHEME } from "../constants";
import { reaction } from "mobx";
import { getStepFileUri } from "../utils";
import { startPlayer, stopPlayer } from "../player";
const CAN_EDIT_TOUR_KEY = `${EXTENSION_NAME}:canEditTour`;
const IN_TOUR_KEY = `${EXTENSION_NAME}:inTour`;
const CONTROLLER_ID = "codetour";
const CONTROLLER_LABEL = "CodeTour";
const CONTROLLER_ICON = Uri.parse(
"https://cdn.jsdelivr.net/gh/vsls-contrib/code-tour/images/icon.png"
);
let id = 0;
export class CodeTourComment implements Comment {
public id: string = (++id).toString();
public contextValue: string = "";
public mode: CommentMode = CommentMode.Preview;
public author: CommentAuthorInformation = {
name: CONTROLLER_LABEL,
iconPath: CONTROLLER_ICON
};
constructor(
public body: string | MarkdownString,
public label: string = "",
public parent: CommentThread
) {}
}
let controller: CommentController;
async function showDocument(uri: Uri, range: Range, selection?: Selection) {
const document =
window.visibleTextEditors.find(
editor => editor.document.uri.toString() === uri.toString()
) || (await window.showTextDocument(uri, { preserveFocus: true }));
// TODO: Figure out how to force focus when navigating
// to documents which are already open.
if (selection) {
document.selection = selection;
}
document.revealRange(range, TextEditorRevealType.InCenter);
}
async function renderCurrentStep() {
if (store.activeTour!.thread) {
store.activeTour!.thread.dispose();
}
const currentTour = store.activeTour!.tour;
const currentStep = store.activeTour!.step;
const step = currentTour!.steps[currentStep];
if (!step) {
return;
}
// 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 range = new Range(line, 0, line, 0);
let label = `Step #${currentStep + 1} of ${currentTour!.steps.length}`;
if (currentTour.title) {
label += ` (${currentTour.title})`;
}
const workspaceRoot = store.activeTour!.workspaceRoot
? store.activeTour!.workspaceRoot.toString()
: workspace.workspaceFolders
? workspace.workspaceFolders[0].uri.toString()
: "";
const uri = await getStepFileUri(step, workspaceRoot, currentTour.ref);
store.activeTour!.thread = controller.createCommentThread(uri, range, []);
store.activeTour!.thread.comments = [
new CodeTourComment(step.description, label, store.activeTour!.thread!)
];
const contextValues = [];
if (currentStep > 0) {
contextValues.push("hasPrevious");
}
if (currentStep < currentTour.steps.length - 1) {
contextValues.push("hasNext");
}
store.activeTour!.thread.contextValue = contextValues.join(".");
store.activeTour!.thread.collapsibleState =
CommentThreadCollapsibleState.Expanded;
let selection;
if (step.selection) {
// Adjust the 1-based positions
// to the 0-based positions that
// VS Code's editor uses.
selection = new Selection(
step.selection.start.line - 1,
step.selection.start.character - 1,
step.selection.end.line - 1,
step.selection.end.character - 1
);
} else {
selection = new Selection(range.start, range.end);
}
showDocument(uri, range, selection);
}
export function startCodeTour(
tour: CodeTour,
stepNumber?: number,
@ -139,26 +13,7 @@ export function startCodeTour(
startInEditMode: boolean = false,
canEditTour: boolean = true
) {
if (controller) {
controller.dispose();
}
controller = comments.createCommentController(
CONTROLLER_ID,
CONTROLLER_LABEL
);
// TODO: Correctly limit the commenting ranges
// to files within the workspace root
controller.commentingRangeProvider = {
provideCommentingRanges: (document: TextDocument) => {
if (store.isRecording) {
return [new Range(0, 0, document.lineCount, 0)];
} else {
return null;
}
}
};
startPlayer();
store.activeTour = {
tour,
@ -182,14 +37,7 @@ export async function endCurrentCodeTour() {
commands.executeCommand("setContext", "codetour:recording", false);
}
if (store.activeTour?.thread) {
store.activeTour!.thread.dispose();
store.activeTour!.thread = null;
}
if (controller) {
controller.dispose();
}
stopPlayer();
store.activeTour = null;
commands.executeCommand("setContext", IN_TOUR_KEY, false);
@ -209,27 +57,21 @@ export function moveCurrentCodeTourForward() {
store.activeTour!.step++;
}
export function resumeCurrentCodeTour() {
showDocument(store.activeTour!.thread!.uri, store.activeTour!.thread!.range);
}
export async function promptForTour(
workspaceRoot: string,
globalState: Memento
) {
const key = `${EXTENSION_NAME}:${workspaceRoot}`;
if (store.hasTours && !globalState.get(key)) {
globalState.update(key, true);
reaction(
() => [
store.activeTour
? [
store.activeTour.step,
store.activeTour.tour.title,
store.activeTour.tour.steps.map(step => [
step.title,
step.description,
step.line
])
]
: null
],
() => {
if (store.activeTour) {
renderCurrentStep();
if (
await window.showInformationMessage(
"This workspace has guided tours you can take to get familiar with the codebase.",
"Start CodeTour"
)
) {
commands.executeCommand(`${EXTENSION_NAME}.startTour`);
}
}
}
);

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

@ -1,10 +1,8 @@
import { comparer, runInAction, set } from "mobx";
import * as vscode from "vscode";
import { CodeTour } from ".";
import { store } from ".";
import { VSCODE_DIRECTORY, EXTENSION_NAME } from "../constants";
import { CodeTour, store } from ".";
import { EXTENSION_NAME, VSCODE_DIRECTORY } from "../constants";
import { endCurrentCodeTour } from "./actions";
import { set, runInAction } from "mobx";
import { comparer } from "mobx";
const MAIN_TOUR_FILES = [
`${EXTENSION_NAME}.json`,
@ -17,11 +15,11 @@ const SUB_TOUR_DIRECTORY = `${VSCODE_DIRECTORY}/tours`;
const HAS_TOURS_KEY = `${EXTENSION_NAME}:hasTours`;
export async function discoverTours(workspaceRoot: string): Promise<void> {
const mainTour = await discoverMainTour(workspaceRoot);
const mainTours = await discoverMainTours(workspaceRoot);
const tours = await discoverSubTours(workspaceRoot);
if (mainTour) {
tours.push(mainTour);
if (mainTours) {
tours.push(...mainTours);
}
runInAction(() => {
@ -39,7 +37,7 @@ export async function discoverTours(workspaceRoot: string): Promise<void> {
} else {
// The user deleted the tour
// file that's associated with
// the active tour
// the active tour, so end it
endCurrentCodeTour();
}
}
@ -48,10 +46,9 @@ export async function discoverTours(workspaceRoot: string): Promise<void> {
vscode.commands.executeCommand("setContext", HAS_TOURS_KEY, store.hasTours);
}
async function discoverMainTour(
workspaceRoot: string
): Promise<CodeTour | null> {
for (const tourFile of MAIN_TOUR_FILES) {
async function discoverMainTours(workspaceRoot: string): Promise<CodeTour[]> {
const tours = await Promise.all(
MAIN_TOUR_FILES.map(async tourFile => {
try {
const uri = vscode.Uri.parse(`${workspaceRoot}/${tourFile}`);
const mainTourContent = (
@ -61,9 +58,10 @@ async function discoverMainTour(
tour.id = uri.toString();
return tour;
} catch {}
}
})
);
return null;
return tours.filter(tour => tour);
}
async function discoverSubTours(workspaceRoot: string): Promise<CodeTour[]> {
@ -89,15 +87,15 @@ async function discoverSubTours(workspaceRoot: string): Promise<CodeTour[]> {
}
}
const watcher = vscode.workspace.createFileSystemWatcher(
"**/.vscode/tours/*.json"
);
function updateTours() {
const workspaceRoot = vscode.workspace.workspaceFolders![0].uri.toString();
discoverTours(workspaceRoot);
}
const watcher = vscode.workspace.createFileSystemWatcher(
"**/.vscode/tours/*.json"
);
watcher.onDidChange(updateTours);
watcher.onDidCreate(updateTours);
watcher.onDidDelete(updateTours);

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

@ -7,9 +7,9 @@ import {
TreeItem,
window
} from "vscode";
import { EXTENSION_NAME } from "../constants";
import { store } from "../store";
import { CodeTourNode, CodeTourStepNode, RecordTourNode } from "./nodes";
import { EXTENSION_NAME } from "../constants";
class CodeTourTreeProvider implements TreeDataProvider<TreeItem>, Disposable {
private _disposables: Disposable[] = [];
@ -48,9 +48,9 @@ class CodeTourTreeProvider implements TreeDataProvider<TreeItem>, Disposable {
if (!store.hasTours && !store.activeTour) {
return [new RecordTourNode()];
} else {
const tours = store.tours.map(tour => {
return new CodeTourNode(tour, this.extensionPath);
});
const tours = store.tours.map(
tour => new CodeTourNode(tour, this.extensionPath)
);
if (
store.activeTour &&
@ -107,10 +107,7 @@ export function registerTreeProvider(extensionPath: string) {
() => {
if (store.activeTour) {
treeView.reveal(
new CodeTourStepNode(store.activeTour.tour, store.activeTour!.step),
{
focus: true
}
new CodeTourStepNode(store.activeTour.tour, store.activeTour!.step)
);
} else {
// TODO: Once VS Code supports it, we want