This commit is contained in:
Jonathan Carter 2021-03-27 00:09:44 +00:00 коммит произвёл GitHub
Родитель 24ce401600
Коммит 3a5b1b5cae
18 изменённых файлов: 249 добавлений и 181 удалений

5
.vscode/settings.json поставляемый
Просмотреть файл

@ -9,6 +9,5 @@
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
},
"codetour.recordMode": "pattern"
}
}
}

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

@ -1,7 +1,17 @@
## Upcoming
- Automatically updating a tour file as the associated code changes
## v0.0.48 (03/26/2021)
- The pattern record mode is now automatically used when you create a new tour, and select `None` for the git ref
- Added keybindings for starting and ending tours
- Fixed an issue with using quotes in a shell command
- Fixed a bug with code fences that used a multi-word language (e.g. `codefusion html`)
## v0.0.47 (03/10/2021)
- Introduced the new `CodeTour: Record Mode` setting, that allows you to create tours that are associated with code via regex patterns, in addition to line numbers.
- Added keybindings for starting and ending tours
## v0.0.46 (03/09/2021)

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

@ -1,6 +1,6 @@
# CodeTour 🗺️
CodeTour is a Visual Studio Code extension, which allows you to record and playback guided walkthroughs of your codebases. It's like a virtual brownbag, or table of contents, that can make it easier to onboard (or re-board!) to a new project/feature area, visualize bug reports, or understand the context of a code review/PR change. A "code tour" is simply a series of interactive steps, each of which are associated with a specific directory, or file/line, and include a description of the respective code. This allows developers to clone a repo, and then immediately start **learning it**, without needing to refer to a `CONTRIBUTING.md` file and/or rely on help from others. Tours can either be checked into a repo, to enable sharing with other contributors, or [exported](#exporting-tours) to a "tour file", which allows anyone to replay the same tour, without having to clone any code to do it!
CodeTour is a Visual Studio Code extension, which allows you to record and playback guided walkthroughs of your codebases. It's like a table of contents, that can make it easier to onboard (or re-board!) to a new project/feature area, visualize bug reports, or understand the context of a code review/PR change. A "code tour" is simply a series of interactive steps, each of which are associated with a specific directory, or file/line, and include a description of the respective code. This allows developers to clone a repo, and then immediately start **learning it**, without needing to refer to a `CONTRIBUTING.md` file and/or rely on help from others. Tours can either be checked into a repo, to enable sharing with other contributors, or [exported](#exporting-tours) to a "tour file", which allows anyone to replay the same tour, without having to clone any code to do it!
<img width="800px" src="https://user-images.githubusercontent.com/116461/76165260-c6c00500-6112-11ea-9cda-0a6cb9b72e8f.gif" />
@ -161,7 +161,7 @@ When you record a tour, you'll be asked which git "ref" to associate it with. Th
You can choose to associate with the tour with the following ref types:
- `None` - The tour isn't associated with any ref, and therefore, the file/line numbers in the tour might get out of sync over time. The benefit of this option is that it enables the code to be edited as part of the tour, since the tour will walk the user through whichever branch/commit they have checked out.
- `None` - The tour isn't associated with any ref. When you select this option, tour steps will be associated with code via a regex pattern, as opposed to a line number. This ensures that they can retain resilient if the attached line is moved around the file. The benefit of this option is that it enables the code to be edited as part of the tour, since the tour will walk the user through whichever branch/commit they have checked out (e.g. interactive tutorials).
- `Current Branch` - The tour is restricted to the current branch. This can have the same resiliency challenges as `None`, but, it allows you to maintain a special branch for your tours that can be versioned seperately. If the end-user has the associated branch checked out, then the tour will enable them to make edits to files as its taken. Otherwise, the tour will replay with read-only files.
- `Current Commit` - The tour is restricted to the current commit, and therefore, will never get out of sync. If the end-user's `HEAD` points at the specified commit, then the tour will enable them to make edits to files as its taken. Otherwise, the tour will replay with read-only files.
- Tags - The tour is restricted to the selected tag, and therefore, will never get out of sync. The repo's entire list of tags will be displayed, which allows you to easily select one.
@ -201,7 +201,7 @@ Within the `.tours` (or `.vscode/tours`) directory, you can organize your tour f
- `directory` - The path of a directory (relative to the workspace root) that this step is associated with. _Note: This property takes precedence over the `file` property, and so will "win" if both are present._
- `uri` - An absolute URI that this step is associated with. Note that `uri` and `file` are mutually exclusive, so only set one per step
- `line` - The 1-based line number that this step is associated with
- `pattern` - A regular expression to associate the step with. This is only considered when the line property isn't set, and allows you to associate steps with line content as opposed to ordinal.
- `pattern` - A regular expression to associate the step with. This is only considered when the `line` property isn't set, and allows you to associate steps with line content as opposed to ordinal.
- `title` - An optional title, which will be displayed as the step name in the `CodeTour` tree view.
- `commands` - An array of VS Code command strings, that indicate the name of a command (e.g. `codetour.endTour`) and any optional parameters to pass to it, specified as a query string array (eg. `codetour.endTour?[2]`).
- `view` - The ID of a VS Code view that will be automatically focused when this step is navigated to.
@ -346,19 +346,22 @@ In addition to the `CodeTour` tree view and the status bar item, the CodeTour ex
The `CodeTour` extension contributes the following settings:
- `codetour.promptForWorkspaceTours` - Specifies whether or not to display a notification when opening a workspace with tours for the first time.
- `codetour.showMarkers` - Specifies whether or not to show [tour markers](#tour-markers). Defaults to `true`.
- `Codetour > Prompt For Workspace Tours` - Specifies whether or not to display a notification when opening a workspace with tours for the first time.
- `Codetour > Record Mode` - Specifies how you want to associate tour steps to code when you're recording a new tour. Can either be `lineNumber` or `pattern`. Defaults to `lineNumber`.
- `Codetour > Show Markers` - Specifies whether or not to show [tour markers](#tour-markers). Defaults to `true`.
### Keybindings
In addition to the available commands, the Code Tour extension also contributes the following commands, which are active while you're currently taking a tour:
| Windows/Linux | macOS | Description |
|-|-|-|
| `ctrl+right` | `cmd+right` | Move to the next step in the tour |
| `ctrl+left` | `cmd+left` | Move to the previous step in the tour |
| `ctrl+down ctrl+down` | `cmd+down cmd+down` | End the current tour |
| `ctrl+up ctrl+up` | `cmd+up cmd+up` | Start new tour |
| Windows/Linux | macOS | Description |
| --------------------- | ------------------- | ------------------------------------- |
| `ctrl+right` | `cmd+right` | Move to the next step in the tour |
| `ctrl+left` | `cmd+left` | Move to the previous step in the tour |
| `ctrl+down ctrl+down` | `cmd+down cmd+down` | End the current tour |
| `ctrl+up ctrl+up` | `cmd+up cmd+up` | Start new tour |
## Extension API

13
package-lock.json сгенерированный
Просмотреть файл

@ -1,6 +1,6 @@
{
"name": "codetour",
"version": "0.0.46",
"version": "0.0.47",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -42,6 +42,12 @@
"integrity": "sha512-8RkBivJrDCyPpBXhVZcjh7cQxVBSmRk9QM7hOketZzp6Tg79c0N8kkpAIito9bnJ3HCVCHVYz+KHTEbfQNfeVQ==",
"dev": true
},
"@types/throttle-debounce": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-2.1.0.tgz",
"integrity": "sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ==",
"dev": true
},
"@webassemblyjs/ast": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
@ -4378,6 +4384,11 @@
"worker-farm": "^1.7.0"
}
},
"throttle-debounce": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz",
"integrity": "sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg=="
},
"through2": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",

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

@ -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.47",
"version": "0.0.48",
"author": {
"name": "Microsoft Corporation"
},
@ -604,7 +604,7 @@
},
{
"command": "codetour.startTour",
"when": "!terminalFocus",
"when": "!textInputFocus && !terminalFocus",
"key": "ctrl+up ctrl+up",
"mac": "cmd+up cmd+up"
}
@ -621,10 +621,12 @@
"dependencies": {
"axios": "^0.19.2",
"mobx": "^5.14.2",
"throttle-debounce": "^3.0.1",
"vsls": "^1.0.2532"
},
"devDependencies": {
"@types/node": "^8.10.25",
"@types/throttle-debounce": "^2.1.0",
"ts-loader": "^7.0.4",
"tslint": "^5.8.0",
"typescript": "^3.1.4",
@ -645,4 +647,4 @@
"arrowParens": "avoid",
"trailingComma": "none"
}
}
}

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

@ -1,10 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { registerPlayerCommands } from "./player/commands";
import { registerRecorderCommands } from "./recorder/commands";
export function registerCommands() {
registerPlayerCommands();
registerRecorderCommands();
}

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

@ -3,60 +3,24 @@
import * as vscode from "vscode";
import { initializeApi } from "./api";
import { registerCommands } from "./commands";
import { registerFileSystemProvider } from "./fileSystem";
import { registerTextDocumentContentProvider } from "./fileSystem/documentProvider";
import { initializeGitApi } from "./git";
import { registerLiveShareModule } from "./liveShare";
import { registerDecorators } from "./player/decorator";
import { registerStatusBar } from "./player/status";
import { registerCompletionProvider } from "./recorder/completionProvider";
import { store } from "./store";
import { registerPlayerModule } from "./player";
import { registerRecorderModule } from "./recorder";
import { promptForTour } from "./store/actions";
import { discoverTours } from "./store/provider";
import { initializeStorage } from "./store/storage";
import { registerTreeProvider } from "./tree";
import { updateMarkerTitles } from "./utils";
export async function activate(context: vscode.ExtensionContext) {
registerCommands();
initializeStorage(context);
registerPlayerModule(context);
registerRecorderModule();
registerLiveShareModule();
// 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) {
await discoverTours();
promptForTour(context.globalState);
registerDecorators();
store.showMarkers = vscode.workspace
.getConfiguration("codetour")
.get("showMarkers", true);
vscode.commands.executeCommand(
"setContext",
"codetour:showingMarkers",
store.showMarkers
);
initializeGitApi();
registerLiveShareModule();
}
// 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();
registerTextDocumentContentProvider();
registerStatusBar();
registerCompletionProvider();
updateMarkerTitles();
return initializeApi(context);
}

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

@ -16,7 +16,7 @@ import {
startCodeTour
} from "../store/actions";
import { progress } from "../store/storage";
import { CodeTourNode } from "../tree/nodes";
import { CodeTourNode } from "./tree/nodes";
import { readUriContents } from "../utils";
let terminal: vscode.Terminal | null;

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

@ -17,8 +17,8 @@ const TOUR_DECORATOR = vscode.window.createTextEditorDecorationType({
rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed
});
async function getTourSteps(
document: vscode.TextDocument
export async function getTourSteps(
editor: vscode.TextEditor
): Promise<CodeTourStepTuple[]> {
const steps: CodeTourStepTuple[] = store.tours.flatMap(tour =>
tour.steps.map(
@ -26,15 +26,24 @@ async function getTourSteps(
)
);
const contents = editor.document.getText();
const tourSteps = await Promise.all(
steps.map(async ([tour, step, stepNumber]) => {
const workspaceRoot = getWorkspaceUri(tour);
const uri = await getStepFileUri(step, workspaceRoot);
if (
uri.toString().localeCompare(document.uri.toString()) === 0
) {
return [tour, step, stepNumber];
if (uri.toString().localeCompare(editor.document.uri.toString()) === 0) {
let line;
if (step.line) {
line = step.line - 1;
} else if (step.pattern) {
const match = contents.match(new RegExp(step.pattern, "m"));
if (match) {
line = editor.document.positionAt(match.index!).line;
}
}
return [tour, step, stepNumber, line];
}
})
);
@ -54,7 +63,9 @@ function registerHoverProvider() {
return;
}
const tourSteps = store.activeEditorSteps.filter(([,,,line]) => line === position.line);
const tourSteps = store.activeEditorSteps.filter(
([, , , line]) => line === position.line
);
const hovers = tourSteps.map(([tour, _, stepNumber]) => {
const args = encodeURIComponent(JSON.stringify([tour.id, stepNumber]));
const command = `command:codetour._startTourById?${args}`;
@ -70,39 +81,21 @@ function registerHoverProvider() {
});
}
async function updateDecorations(editor: vscode.TextEditor) {
if (DISABLED_SCHEMES.includes(editor.document.uri.scheme)) {
export async function updateDecorations(
editor: vscode.TextEditor | undefined = vscode.window.activeTextEditor
) {
if (!editor || DISABLED_SCHEMES.includes(editor.document.uri.scheme)) {
return;
}
const tourSteps = await getTourSteps(editor.document);
if (tourSteps.length === 0) {
store.activeEditorSteps = await getTourSteps(editor);
if (store.activeEditorSteps.length === 0) {
return clearDecorations(editor);
}
const contents = editor.document.getText();
// @ts-ignore
store.activeEditorSteps = (await Promise.all(tourSteps.map(
async (tourStep) => {
let line, step = tourStep[1];
if (step.line) {
line = step.line - 1;
} else if (step.pattern) {
const match = contents.match(new RegExp(step.pattern, "m"));
if (match) {
line = editor.document.positionAt(match.index!).line;
}
}
if (line) {
tourStep.push(line);
return tourStep;
}
}
))).filter(tourStep => tourStep);
const ranges = store.activeEditorSteps!.map(([,,,line]) => new vscode.Range(line!, 0, line!, 1000));
const ranges = store.activeEditorSteps!.map(
([, , , line]) => new vscode.Range(line!, 0, line!, 1000)
);
editor.setDecorations(TOUR_DECORATOR, ranges);
}
@ -135,32 +128,26 @@ export async function registerDecorators() {
})
);
/*disposables.push(vscode.workspace.onDidChangeTextDocument(e => {
if (!store.activeEditorSteps) {
return;
}
e.contentChanges.map(async (change) => {
const tourSteps = store.activeEditorSteps!.filter(([,,,line]) => line === change.range.start.line);
for (let [tour, step] of tourSteps) {
step.pattern = change.text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').trim();
await saveTour(tour);
}
})
}));*/
if (activeEditor) {
updateDecorations(activeEditor);
}
} else if (activeEditor) {
clearDecorations(activeEditor);
disposables.forEach(disposable => disposable.dispose());
hoverProviderDisposable = undefined;
disposables = [];
}
}
);
store.showMarkers = vscode.workspace
.getConfiguration("codetour")
.get("showMarkers", true);
vscode.commands.executeCommand(
"setContext",
"codetour:showingMarkers",
store.showMarkers
);
}

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

@ -2,7 +2,7 @@
// Licensed under the MIT License.
import * as vscode from "vscode";
import { FS_SCHEME_CONTENT } from "../constants";
import { FS_SCHEME_CONTENT } from "../../constants";
class CodeTourTextDocumentContentProvider
implements vscode.TextDocumentContentProvider {

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

@ -14,8 +14,8 @@ import {
Uri,
workspace
} from "vscode";
import { FS_SCHEME } from "../constants";
import { CodeTour, CodeTourStep, store } from "../store";
import { FS_SCHEME } from "../../constants";
import { CodeTour, CodeTourStep, store } from "../../store";
export class CodeTourFileSystemProvider implements FileSystemProvider {
private count = 0;

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

@ -11,6 +11,7 @@ import {
comments,
CommentThread,
CommentThreadCollapsibleState,
ExtensionContext,
MarkdownString,
Range,
Selection,
@ -22,6 +23,7 @@ import {
} from "vscode";
import { SMALL_ICON_URL } from "../constants";
import { CodeTour, store } from "../store";
import { initializeStorage } from "../store/storage";
import {
getActiveStepMarker,
getActiveTourNumber,
@ -30,6 +32,12 @@ import {
getStepLabel,
getTourTitle
} from "../utils";
import { registerPlayerCommands } from "./commands";
import { registerDecorators } from "./decorator";
import { registerFileSystemProvider } from "./fileSystem";
import { registerTextDocumentContentProvider } from "./fileSystem/documentProvider";
import { registerStatusBar } from "./status";
import { registerTreeProvider } from "./tree";
const CONTROLLER_ID = "codetour";
const CONTROLLER_LABEL = "CodeTour";
@ -39,13 +47,17 @@ let id = 0;
const SHELL_SCRIPT_PATTERN = /^>>\s+(?<script>.*)$/gm;
const COMMAND_PATTERN = /(?<commandPrefix>\(command:[\w+\.]+\?)(?<params>\[[^\]]+\])/gm;
const TOUR_REFERENCE_PATTERN = /(?:\[(?<linkTitle>[^\]]+)\])?\[(?=\s*[^\]\s])(?<tourTitle>[^\]#]+)?(?:#(?<stepNumber>\d+))?\](?!\()/gm;
const CODE_FENCE_PATTERN = /```\w+\s+([^`]+)\s+```/gm;
const CODE_FENCE_PATTERN = /```[^\n]+\n([^`]+)```/gm;
export function generatePreviewContent(content: string) {
return content
.replace(SHELL_SCRIPT_PATTERN, (_, script) => {
const args = encodeURIComponent(JSON.stringify([script]));
return `> [${script}](command:codetour.sendTextToTerminal?${args} "Run \\"${script}\\" in a terminal")`;
const s = `> [${script}](command:codetour.sendTextToTerminal?${args} "Run \\"${script.replace(
/"/g,
"'"
)}\\" in a terminal")`;
return s;
})
.replace(COMMAND_PATTERN, (_, commandPrefix, params) => {
const args = encodeURIComponent(JSON.stringify(JSON.parse(params)));
@ -247,7 +259,10 @@ async function renderCurrentStep() {
store.activeTour!.thread = controller!.createCommentThread(uri, range, []);
const mode = store.isRecording && store.isEditing ? CommentMode.Editing : CommentMode.Preview;
const mode =
store.isRecording && store.isEditing
? CommentMode.Editing
: CommentMode.Preview;
let content = step.description;
let hasPreviousStep = currentStep > 0;
@ -400,27 +415,38 @@ async function showDocument(uri: Uri, range: Range, selection?: Selection) {
document.revealRange(range, TextEditorRevealType.InCenter);
}
// Watch for changes to the active tour property,
// and automatically re-render the current step in response.
reaction(
() => [
store.activeTour
? [
store.activeTour.step,
store.activeTour.tour.title,
store.activeTour.tour.steps.map(step => [
step.title,
step.description,
step.line,
step.directory,
step.view
])
]
: null
],
() => {
if (store.activeTour) {
renderCurrentStep();
export function registerPlayerModule(context: ExtensionContext) {
registerPlayerCommands();
registerTreeProvider(context.extensionPath);
registerFileSystemProvider();
registerTextDocumentContentProvider();
registerStatusBar();
registerDecorators();
initializeStorage(context);
// Watch for changes to the active tour property,
// and automatically re-render the current step in response.
reaction(
() => [
store.activeTour
? [
store.activeTour.step,
store.activeTour.tour.title,
store.activeTour.tour.steps.map(step => [
step.title,
step.description,
step.line,
step.directory,
step.view
])
]
: null
],
() => {
if (store.activeTour) {
renderCurrentStep();
}
}
}
);
);
}

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

@ -11,9 +11,9 @@ import {
TreeItem,
window
} from "vscode";
import { EXTENSION_NAME } from "../constants";
import { generatePreviewContent } from "../player";
import { store } from "../store";
import { EXTENSION_NAME } from "../../constants";
import { generatePreviewContent } from "..";
import { store } from "../../store";
import { CodeTourNode, CodeTourStepNode } from "./nodes";
class CodeTourTreeProvider implements TreeDataProvider<TreeItem>, Disposable {

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

@ -8,10 +8,10 @@ import {
TreeItemCollapsibleState,
Uri
} from "vscode";
import { CONTENT_URI, EXTENSION_NAME, FS_SCHEME } from "../constants";
import { CodeTour, store } from "../store";
import { progress } from "../store/storage";
import { getFileUri, getStepLabel, getWorkspaceUri } from "../utils";
import { CONTENT_URI, EXTENSION_NAME, FS_SCHEME } from "../../constants";
import { CodeTour, store } from "../../store";
import { progress } from "../../store/storage";
import { getFileUri, getStepLabel, getWorkspaceUri } from "../../utils";
function isRecording(tour: CodeTour) {
return (

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

@ -8,6 +8,7 @@ import { workspace } from "vscode";
import { EXTENSION_NAME, FS_SCHEME_CONTENT } from "../constants";
import { api, RefType } from "../git";
import { CodeTourComment } from "../player";
import { CodeTourNode, CodeTourStepNode } from "../player/tree/nodes";
import { CodeTour, CodeTourStep, store } from "../store";
import {
EDITING_KEY,
@ -16,7 +17,6 @@ import {
onDidEndTour,
startCodeTour
} from "../store/actions";
import { CodeTourNode, CodeTourStepNode } from "../tree/nodes";
import { getActiveWorkspacePath, getRelativePath } from "../utils";
export async function saveTour(tour: CodeTour) {
@ -25,6 +25,8 @@ export async function saveTour(tour: CodeTour) {
$schema: "https://aka.ms/codetour-schema",
...tour
};
// @ts-ignore
delete newTour.id;
newTour.steps.forEach(step => {
delete step.markerTitle;
@ -33,7 +35,7 @@ export async function saveTour(tour: CodeTour) {
const tourContent = JSON.stringify(newTour, null, 2);
const bytes = new TextEncoder().encode(tourContent);
return vscode.workspace.fs.writeFile(uri, bytes);
await vscode.workspace.fs.writeFile(uri, bytes);
}
export function registerRecorderCommands() {
@ -153,10 +155,10 @@ export function registerRecorderCommands() {
}
let ref;
const mode = vscode.workspace
.getConfiguration("codetour")
.get("recordMode", "lineNumber");
.getConfiguration("codetour")
.get("recordMode", "lineNumber");
if (mode === "lineNumber") {
ref = await promptForTourRef(workspaceRoot);
@ -291,8 +293,8 @@ export function registerRecorderCommands() {
store.activeTour!.step = stepNumber;
} else {
stepNumber = ++store.activeTour!.step;
}
}
const tour = store.activeTour!.tour;
tour.steps.splice(stepNumber, 0, {
@ -378,15 +380,17 @@ export function registerRecorderCommands() {
const mode = vscode.workspace
.getConfiguration("codetour")
.get("recordMode", "lineNumber");
.get("recordMode");
if (mode === "lineNumber") {
if (mode === "pattern" || tour.ref === undefined) {
const contents = vscode.window.activeTextEditor?.document.lineAt(
thread.range.start
).text;
step.pattern = contents!.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").trim();
} else if (mode === "lineNumber" || tour.ref !== undefined) {
step.line = thread!.range.start.line + 1;
} else if (mode === "pattern") {
const contents = vscode.window.activeTextEditor?.document.lineAt(thread.range.start).text;
step.pattern = contents!.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').trim();
}
store.activeTour!.step++;
const stepNumber = store.activeTour!.step;
@ -437,7 +441,7 @@ export function registerRecorderCommands() {
true
);
await vscode.commands.executeCommand("setContext", EDITING_KEY, true);
if (node instanceof CodeTourNode) {
startCodeTour(node.tour);
} else if (store.activeTour) {

8
src/recorder/index.ts Normal file
Просмотреть файл

@ -0,0 +1,8 @@
import { registerRecorderCommands } from "./commands";
import { registerCompletionProvider } from "./completionProvider";
export function registerRecorderModule() {
registerRecorderCommands();
registerCompletionProvider();
//registerEditorWatcher();
}

67
src/recorder/watcher.ts Normal file
Просмотреть файл

@ -0,0 +1,67 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { reaction } from "mobx";
import { debounce } from "throttle-debounce";
import * as vscode from "vscode";
import { store } from "../store";
import { saveTour } from "./commands";
const debouncedSaveTour = debounce(5000, false, saveTour);
const changeWatcher = async (e: vscode.TextDocumentChangeEvent) => {
if (!store.activeEditorSteps) {
return;
}
const impactedSteps = store.activeEditorSteps!.filter(
([, step, , line]) =>
step.pattern &&
e.contentChanges.some(change => line === change.range.start.line)
);
if (impactedSteps.length === 0) {
return;
}
await Promise.all(
e.contentChanges.map(async () => {
for (let [tour, step, , line] of impactedSteps) {
const changedText = e.document.lineAt(line!).text;
// If the text is empty, then that means the user
// delete the step line, but we can't delete the
// steo, since we don't know if it was a delete
// or if theyy're doing a cut/paste of the line.
if (changedText === "") {
continue;
}
const newPattern = changedText
.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
.trim();
if (newPattern !== step.pattern) {
step.pattern = newPattern;
await debouncedSaveTour(tour);
}
}
})
);
};
let disposable: vscode.Disposable;
function initializeWatcher() {
if (disposable) {
disposable.dispose();
}
if (store.tours.length > 0) {
disposable = vscode.workspace.onDidChangeTextDocument(changeWatcher);
}
}
export async function registerEditorWatcher() {
reaction(() => store.tours.length, initializeWatcher);
initializeWatcher();
}

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

@ -51,6 +51,8 @@ export async function discoverTours(): Promise<void> {
});
vscode.commands.executeCommand("setContext", HAS_TOURS_KEY, store.hasTours);
updateMarkerTitles();
}
async function discoverMainTours(
@ -115,17 +117,12 @@ async function discoverSubTours(workspaceUri: vscode.Uri): Promise<CodeTour[]> {
return tours.flat();
}
async function discoverToursAndUpdateTitles() {
await discoverTours();
updateMarkerTitles();
}
vscode.workspace.onDidChangeWorkspaceFolders(discoverToursAndUpdateTitles);
vscode.workspace.onDidChangeWorkspaceFolders(discoverTours);
const watcher = vscode.workspace.createFileSystemWatcher(
"**/{.vscode/tours,.tours}/**/*.{json,tour}"
);
watcher.onDidChange(discoverToursAndUpdateTitles);
watcher.onDidCreate(discoverToursAndUpdateTitles);
watcher.onDidDelete(discoverToursAndUpdateTitles);
watcher.onDidChange(discoverTours);
watcher.onDidCreate(discoverTours);
watcher.onDidDelete(discoverTours);