From 3a5b1b5cae084145400dc0f1190f80308e4682c8 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Sat, 27 Mar 2021 00:09:44 +0000 Subject: [PATCH] Addressing misc. bugs --- .vscode/settings.json | 5 +- CHANGELOG.md | 12 ++- README.md | 25 +++--- package-lock.json | 13 ++- package.json | 8 +- src/commands.ts | 10 --- src/extension.ts | 46 ++-------- src/player/commands.ts | 2 +- src/player/decorator.ts | 89 ++++++++----------- .../fileSystem/documentProvider.ts | 2 +- src/{ => player}/fileSystem/index.ts | 4 +- src/player/index.ts | 78 ++++++++++------ src/{ => player}/tree/index.ts | 6 +- src/{ => player}/tree/nodes.ts | 8 +- src/recorder/commands.ts | 32 ++++--- src/recorder/index.ts | 8 ++ src/recorder/watcher.ts | 67 ++++++++++++++ src/store/provider.ts | 15 ++-- 18 files changed, 249 insertions(+), 181 deletions(-) delete mode 100644 src/commands.ts rename src/{ => player}/fileSystem/documentProvider.ts (91%) rename src/{ => player}/fileSystem/index.ts (96%) rename src/{ => player}/tree/index.ts (97%) rename src/{ => player}/tree/nodes.ts (92%) create mode 100644 src/recorder/index.ts create mode 100644 src/recorder/watcher.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index c3d1f4c..7946d69 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,6 +9,5 @@ "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.organizeImports": true - }, - "codetour.recordMode": "pattern" -} \ No newline at end of file + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 16da32e..df68f8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/README.md b/README.md index 14231c8..97478ef 100644 --- a/README.md +++ b/README.md @@ -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! @@ -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 diff --git a/package-lock.json b/package-lock.json index c70c2fc..2d8e02b 100644 --- a/package-lock.json +++ b/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", diff --git a/package.json b/package.json index 03c54ba..f26bc8c 100644 --- a/package.json +++ b/package.json @@ -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" } -} \ No newline at end of file +} diff --git a/src/commands.ts b/src/commands.ts deleted file mode 100644 index e8a1611..0000000 --- a/src/commands.ts +++ /dev/null @@ -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(); -} diff --git a/src/extension.ts b/src/extension.ts index 557e4b3..15a0ad6 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -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); } diff --git a/src/player/commands.ts b/src/player/commands.ts index d75ac1b..1b2851f 100644 --- a/src/player/commands.ts +++ b/src/player/commands.ts @@ -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; diff --git a/src/player/decorator.ts b/src/player/decorator.ts index 99fa705..4f828ab 100644 --- a/src/player/decorator.ts +++ b/src/player/decorator.ts @@ -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 { 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 + ); } diff --git a/src/fileSystem/documentProvider.ts b/src/player/fileSystem/documentProvider.ts similarity index 91% rename from src/fileSystem/documentProvider.ts rename to src/player/fileSystem/documentProvider.ts index 9954c8a..02035d6 100644 --- a/src/fileSystem/documentProvider.ts +++ b/src/player/fileSystem/documentProvider.ts @@ -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 { diff --git a/src/fileSystem/index.ts b/src/player/fileSystem/index.ts similarity index 96% rename from src/fileSystem/index.ts rename to src/player/fileSystem/index.ts index ed69402..da64cfb 100644 --- a/src/fileSystem/index.ts +++ b/src/player/fileSystem/index.ts @@ -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; diff --git a/src/player/index.ts b/src/player/index.ts index d0450db..f807af7 100644 --- a/src/player/index.ts +++ b/src/player/index.ts @@ -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+(?