Adding support for VFS with query strings

This commit is contained in:
Jonathan Carter 2020-05-24 11:14:19 -07:00
Родитель 87e8d99e19
Коммит c55d9e7f91
12 изменённых файлов: 87 добавлений и 73 удалений

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

@ -2,6 +2,10 @@
- Added the following commands to the command link completion list: `Run build task`, `Run task` and `Run test task`.
- Fixed a bug where command links didn't work, if the command included multiple "components" to the name (e.g. `workbench.action.tasks.build`).
- Fixed a bug where tours weren't being discovered for virtual file systems that include a query string in their workspace path.
- Fixed a bug where tours that included content-only steps couldn't be exported.
- Fixed the open/export tour commands to correctly look for `*.tour` files.
- Fixed a bug where the `CodeTour: Record Tour` command was being displayed without having any workspaces open.
## v0.0.27 (05/22/2020)

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

@ -154,11 +154,6 @@
"category": "CodeTour",
"icon": "$(add)"
},
{
"command": "codetour.refreshTours",
"title": "Refresh Tours",
"category": "CodeTour"
},
{
"command": "codetour.resumeTour",
"title": "Resume Tour",
@ -214,8 +209,8 @@
"when": "codetour:inTour && codetour:recording"
},
{
"command": "codetour.refreshTours",
"when": "codetour:hasTours"
"command": "codetour.recordTour",
"when": "workspaceFolderCount != 0"
},
{
"command": "codetour.resumeTour",

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

@ -1,7 +1,7 @@
import * as vscode from "vscode";
import { CodeTour } from "../store";
import { getStepFileUri, getWorkspacePath } from "../utils";
import { EXTENSION_NAME, SMALL_ICON_URL } from "../constants";
import { CodeTour } from "../store";
import { getStepFileUri, getWorkspaceUri } from "../utils";
class CodeTourNotebookProvider implements vscode.NotebookProvider {
async resolveNotebook(editor: vscode.NotebookEditor): Promise<void> {
@ -11,15 +11,15 @@ class CodeTourNotebookProvider implements vscode.NotebookProvider {
cellEditable: false
};
let contents = (
let contents = new TextDecoder().decode(
await vscode.workspace.fs.readFile(editor.document.uri)
).toString();
);
let tour = <CodeTour>JSON.parse(contents);
tour.id = editor.document.uri.toString();
let steps: any[] = [];
const workspaceRoot = getWorkspacePath(tour);
const workspaceRoot = getWorkspaceUri(tour);
for (let item of tour.steps) {
const uri = await getStepFileUri(item, workspaceRoot, tour.ref);

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

@ -134,7 +134,7 @@ export function registerPlayerCommands() {
async () => {
const uri = await vscode.window.showOpenDialog({
filters: {
Tours: ["json"]
Tours: ["tour"]
},
canSelectFolders: false,
canSelectMany: false,
@ -146,9 +146,13 @@ export function registerPlayerCommands() {
}
try {
const contents = await vscode.workspace.fs.readFile(uri[0]);
const tour = JSON.parse(contents.toString());
const contents = new TextDecoder().decode(
await vscode.workspace.fs.readFile(uri[0])
);
const tour = JSON.parse(contents);
tour.id = uri[0].toString();
startCodeTour(tour);
} catch {
vscode.window.showErrorMessage(
@ -186,7 +190,7 @@ export function registerPlayerCommands() {
async (node: CodeTourNode) => {
const uri = await vscode.window.showSaveDialog({
filters: {
Tours: ["json"]
Tours: ["tour"]
},
saveLabel: "Export Tour"
});
@ -196,7 +200,8 @@ export function registerPlayerCommands() {
}
const contents = await exportTour(node.tour);
vscode.workspace.fs.writeFile(uri, new Buffer(contents));
const bytes = new TextEncoder().encode(contents);
vscode.workspace.fs.writeFile(uri, bytes);
}
);

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

@ -2,7 +2,7 @@ import { reaction } from "mobx";
import * as vscode from "vscode";
import { FS_SCHEME_CONTENT, ICON_URL } from "../constants";
import { CodeTour, CodeTourStep, store } from "../store";
import { getStepFileUri, getWorkspacePath } from "../utils";
import { getStepFileUri, getWorkspaceUri } from "../utils";
const DISABLED_SCHEMES = [FS_SCHEME_CONTENT, "comment"];
@ -27,7 +27,7 @@ async function getTourSteps(
const tourSteps = await Promise.all(
steps.map(async ([tour, step, stepNumber]) => {
const workspaceRoot = getWorkspacePath(tour);
const workspaceRoot = getWorkspaceUri(tour);
const uri = await getStepFileUri(step, workspaceRoot);
if (

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

@ -18,7 +18,7 @@ import {
} from "vscode";
import { SMALL_ICON_URL } from "../constants";
import { store } from "../store";
import { getActiveWorkspacePath, getFileUri, getStepFileUri } from "../utils";
import { getFileUri, getStepFileUri } from "../utils";
const CONTROLLER_ID = "codetour";
const CONTROLLER_LABEL = "CodeTour";
@ -131,7 +131,7 @@ async function renderCurrentStep() {
label += ` (${currentTour.title})`;
}
const workspaceRoot = getActiveWorkspacePath();
const workspaceRoot = store.activeTour?.workspaceRoot;
const uri = await getStepFileUri(step, workspaceRoot, currentTour.ref);
store.activeTour!.thread = controller!.createCommentThread(uri, range, []);
@ -176,7 +176,7 @@ async function renderCurrentStep() {
await showDocument(uri, range, selection);
if (step.directory) {
const directoryUri = getFileUri(workspaceRoot, step.directory);
const directoryUri = getFileUri(step.directory, workspaceRoot);
commands.executeCommand("revealInExplorer", directoryUri);
}
}

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

@ -96,14 +96,11 @@ export async function exportTour(tour: CodeTour) {
newTour.steps = await Promise.all(
newTour.steps.map(async step => {
if (step.contents && step.uri) {
if (step.contents || step.uri || !step.file) {
return step;
}
const workspaceRoot = workspace.workspaceFolders
? workspace.workspaceFolders[0].uri.toString()
: "";
const workspaceRoot = getWorkspaceUri(tour);
const stepFileUri = await getStepFileUri(step, workspaceRoot, tour.ref);
const stepFileContents = await workspace.fs.readFile(stepFileUri);

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

@ -9,12 +9,17 @@ export interface CodeTourStepPosition {
export interface CodeTourStep {
title?: string;
description: string;
// If any of the following are set, then only
// one of them can be, since these properties
// indicate the "type" of step.
file?: string;
directory?: string;
contents?: string;
uri?: string;
line?: number;
selection?: { start: CodeTourStepPosition; end: CodeTourStepPosition };
contents?: string;
}
export interface CodeTour {

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

@ -5,7 +5,6 @@ import { EXTENSION_NAME, VSCODE_DIRECTORY } from "../constants";
import { endCurrentCodeTour } from "./actions";
const MAIN_TOUR_FILES = [".tour", `${VSCODE_DIRECTORY}/main.tour`];
const SUB_TOUR_DIRECTORIES = [`${VSCODE_DIRECTORY}/tours`, `.tours`];
const HAS_TOURS_KEY = `${EXTENSION_NAME}:hasTours`;
@ -13,9 +12,8 @@ const HAS_TOURS_KEY = `${EXTENSION_NAME}:hasTours`;
export async function discoverTours(): Promise<void> {
const tours = await Promise.all(
vscode.workspace.workspaceFolders!.map(async workspaceFolder => {
const workspaceRoot = workspaceFolder.uri.toString();
const mainTours = await discoverMainTours(workspaceRoot);
const tours = await discoverSubTours(workspaceRoot);
const mainTours = await discoverMainTours(workspaceFolder.uri);
const tours = await discoverSubTours(workspaceFolder.uri);
if (mainTours) {
tours.push(...mainTours);
@ -51,14 +49,19 @@ export async function discoverTours(): Promise<void> {
vscode.commands.executeCommand("setContext", HAS_TOURS_KEY, store.hasTours);
}
async function discoverMainTours(workspaceRoot: string): Promise<CodeTour[]> {
async function discoverMainTours(
workspaceUri: vscode.Uri
): Promise<CodeTour[]> {
const tours = await Promise.all(
MAIN_TOUR_FILES.map(async tourFile => {
try {
const uri = vscode.Uri.parse(`${workspaceRoot}/${tourFile}`);
const mainTourContent = (
const uri = workspaceUri.with({
path: `${workspaceUri.path}/${tourFile}`
});
const mainTourContent = new TextDecoder().decode(
await vscode.workspace.fs.readFile(uri)
).toString();
);
const tour = JSON.parse(mainTourContent);
tour.id = uri.toString();
return tour;
@ -69,20 +72,23 @@ async function discoverMainTours(workspaceRoot: string): Promise<CodeTour[]> {
return tours.filter(tour => tour);
}
async function readTourDirectory(tourDirectory: string): Promise<CodeTour[]> {
async function readTourDirectory(uri: vscode.Uri): Promise<CodeTour[]> {
try {
const uri = vscode.Uri.parse(tourDirectory);
const tourFiles = await vscode.workspace.fs.readDirectory(uri);
const tours = await Promise.all(
tourFiles.map(async ([file, type]) => {
const fileUri = uri.with({
path: `${uri.path}/${file}`
});
if (type === vscode.FileType.File) {
return readTourFile(tourDirectory, file);
return readTourFile(fileUri);
} else {
return readTourDirectory(`${tourDirectory}/${file}`);
return readTourDirectory(fileUri);
}
})
);
// @ts-ignore
return tours.flat().filter(tour => tour);
} catch {
return [];
@ -90,25 +96,27 @@ async function readTourDirectory(tourDirectory: string): Promise<CodeTour[]> {
}
async function readTourFile(
directory: string,
file: string
tourUri: vscode.Uri
): Promise<CodeTour | undefined> {
try {
const tourUri = vscode.Uri.parse(`${directory}/${file}`);
const tourContent = (
const tourContent = new TextDecoder().decode(
await vscode.workspace.fs.readFile(tourUri)
).toString();
);
const tour = JSON.parse(tourContent);
tour.id = tourUri.toString();
return tour;
} catch {}
}
async function discoverSubTours(workspaceRoot: string): Promise<CodeTour[]> {
async function discoverSubTours(workspaceUri: vscode.Uri): Promise<CodeTour[]> {
const tours = await Promise.all(
SUB_TOUR_DIRECTORIES.map(directory =>
readTourDirectory(`${workspaceRoot}/${directory}`)
)
SUB_TOUR_DIRECTORIES.map(directory => {
const uri = workspaceUri.with({
path: `${workspaceUri.path}/${directory}`
});
return readTourDirectory(uri);
})
);
return tours.flat();

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

@ -2,7 +2,7 @@ import * as path from "path";
import { ThemeIcon, TreeItem, TreeItemCollapsibleState, Uri } from "vscode";
import { CONTENT_URI, EXTENSION_NAME, FS_SCHEME } from "../constants";
import { CodeTour, store } from "../store";
import { getFileUri, getWorkspacePath } from "../utils";
import { getFileUri, getWorkspaceUri } from "../utils";
function isRecording(tour: CodeTour) {
return (
@ -101,10 +101,10 @@ export class CodeTourStepNode extends TreeItem {
resourceUri = Uri.parse(`${FS_SCHEME}://current/${step.file}`);
} else if (step.file || step.directory) {
const resourceRoot = workspaceRoot
? workspaceRoot.toString()
: getWorkspacePath(tour);
? workspaceRoot
: getWorkspaceUri(tour);
resourceUri = getFileUri(resourceRoot, step.directory || step.file!);
resourceUri = getFileUri(step.directory || step.file!, resourceRoot);
} else {
resourceUri = CONTENT_URI;
}

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

@ -4,28 +4,27 @@ import { CONTENT_URI, FS_SCHEME } from "./constants";
import { api } from "./git";
import { CodeTour, CodeTourStep, store } from "./store";
export function getFileUri(workspaceRoot: string, file: string) {
const uriPrefix = workspaceRoot.endsWith("/")
? workspaceRoot
: `${workspaceRoot}/`;
let uri = Uri.parse(`${uriPrefix}${file}`);
if (file.startsWith("..")) {
// path.join/normalize will resolve relative paths (e.g. replacing
// ".." with the actual directories), but it also messes up
// non-file based schemes. So we parse the workspace root and
// then replace it's path with a "joined" version of _only_ the path.
uri = uri.with({
path: path.normalize(uri.path)
});
export function getFileUri(file: string, workspaceRoot?: Uri) {
if (!workspaceRoot) {
return Uri.parse(file);
}
return uri;
const rootPath = workspaceRoot.path.endsWith("/")
? workspaceRoot.path
: `${workspaceRoot.path}/`;
// path.join/normalize will resolve relative paths (e.g. replacing
// ".." with the actual directories), but it also messes up
// non-file based schemes. So we parse the workspace root and
// then replace it's path with a normalized version of _only_ the path.
return workspaceRoot.with({
path: path.normalize(`${rootPath}${file}`)
});
}
export async function getStepFileUri(
step: CodeTourStep,
workspaceRoot: string,
workspaceRoot?: Uri,
ref?: string
): Promise<Uri> {
let uri;
@ -34,7 +33,7 @@ export async function getStepFileUri(
} else if (step.uri || step.file) {
uri = step.uri
? Uri.parse(step.uri)
: getFileUri(workspaceRoot, step.file!);
: getFileUri(step.file!, workspaceRoot);
if (api && ref && ref !== "HEAD") {
const repo = api.getRepository(uri);
@ -72,5 +71,6 @@ export function getWorkspacePath(tour: CodeTour) {
}
export function getWorkspaceUri(tour: CodeTour) {
return workspace.getWorkspaceFolder(Uri.parse(tour.id))?.uri;
const tourUri = Uri.parse(tour.id);
return workspace.getWorkspaceFolder(tourUri)?.uri;
}

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

@ -3,7 +3,7 @@
"module": "commonjs",
"target": "es6",
"outDir": "out",
"lib": ["ES2019"],
"lib": ["ES2019", "DOM"],
"sourceMap": true,
"rootDir": "src",
"strict": true,