Skip to content

Commit 33a69f9

Browse files
authored
feat: add vscode extension (#109)
1 parent 5e36866 commit 33a69f9

34 files changed

+1393
-21
lines changed

.vscode/launch.json

+12-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,19 @@
55
"command": "./node_modules/.bin/astro dev",
66
"name": "Development Server",
77
"request": "launch",
8-
"cwd": "${workspaceFolder}/template",
8+
"cwd": "${workspaceFolder}/packages/template",
99
"type": "node-terminal"
10+
},
11+
{
12+
"name": "Run extension",
13+
"type": "extensionHost",
14+
"request": "launch",
15+
"runtimeExecutable": "${execPath}",
16+
"args": [
17+
"--extensionDevelopmentPath=${workspaceRoot}/extensions/vscode",
18+
"--folder-uri=${workspaceRoot}/packages/template"
19+
],
20+
"outFiles": ["${workspaceRoot}/extensions/vscode/dist/**/*.js"]
1021
}
1122
]
1223
}

extensions/vscode/.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
out
2+
dist
3+
node_modules
4+
.vscode-test/
5+
*.vsix

extensions/vscode/.npmrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
enable-pre-post-scripts = true

extensions/vscode/.vscode-test.mjs

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { defineConfig } from '@vscode/test-cli';
2+
3+
export default defineConfig({
4+
files: 'out/test/**/*.test.js',
5+
});

extensions/vscode/.vscodeignore

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.vscode/**
2+
.vscode-test/**
3+
src/**
4+
.gitignore
5+
.yarnrc
6+
vsc-extension-quickstart.md
7+
**/tsconfig.json
8+
**/.eslintrc.json
9+
**/*.map
10+
**/*.ts
11+
**/.vscode-test.*
12+
node_modules

extensions/vscode/CHANGELOG.md

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Changelog
2+
3+
All notable changes to the "tutorialkit" extension will be documented in this file.
4+
5+
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
6+
7+
## [0.0.11]
8+
9+
- Remove the error toast appearing when loading the extension with no workspace present
10+
11+
## [0.0.10]
12+
13+
- Relax the vscode engine requirement to allow versions as low as 1.80.0
14+
- Update the extension icon
15+
16+
## [0.0.9]
17+
18+
- Initial release

extensions/vscode/LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 StackBlitz, Inc <https://stackblitz.com>
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

extensions/vscode/README.md

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# TutorialKit Code Extension
2+
3+
**Status**: Preview
4+
5+
## Features
6+
7+
- Viewing lessons, chapters and parts in the side panel
8+
- Creating new lessons and chapters
9+
10+
<img src="https://github.com/stackblitz/tutorialkit-extension/blob/main/resources/tutorialkit-screenshot.png?raw=true" width="920" alt="TutorialKit screenshot" />
11+
12+
## Installation
13+
14+
1. Open VS Code
15+
2. Select _Extensions_ in the left vertical toolbar
16+
3. Search for `tutorialkit`
17+
4. Click on _Install_
18+
19+
## Description
20+
21+
When designing the content architecture across folders and files, we aimed to make it as plain and clear as possible. However, even the best file system organization doesn't perfectly translate to a tutorial's structure of lessons and chapters.
22+
23+
We wanted to make navigating between the different parts of your courseware or creating new lessons even easier than traversing a file system.
24+
25+
Therefore, we've built a companion Visual Studio Code extension for tutorial authors. The extension visually represents your tutorial structure and allows you to create new chapters and lessons. When you navigate through the displayed structure, the extension automatically focuses on the corresponding folders and files in your file explorer, giving you a quick way to jump to the content you want to edit.

extensions/vscode/build.mjs

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import * as esbuild from 'esbuild';
2+
import fs from 'node:fs';
3+
import { execa } from 'execa';
4+
5+
const production = process.argv.includes('--production');
6+
const watch = process.argv.includes('--watch');
7+
8+
async function main() {
9+
const ctx = await esbuild.context({
10+
entryPoints: ['src/extension.ts'],
11+
bundle: true,
12+
format: 'cjs',
13+
minify: production,
14+
sourcemap: !production,
15+
sourcesContent: false,
16+
platform: 'node',
17+
outfile: 'dist/extension.js',
18+
external: ['vscode'],
19+
logLevel: 'silent',
20+
plugins: [
21+
/* add to the end of plugins array */
22+
esbuildProblemMatcherPlugin,
23+
],
24+
});
25+
26+
if (watch) {
27+
await Promise.all([
28+
ctx.watch(),
29+
execa('tsc', ['--noEmit', '--watch', '--project', 'tsconfig.json'], { stdio: 'inherit', preferLocal: true }),
30+
]);
31+
} else {
32+
await ctx.rebuild();
33+
await ctx.dispose();
34+
35+
if (production) {
36+
// rename name in package json to match extension name on store:
37+
const pkgJSON = JSON.parse(fs.readFileSync('./package.json', { encoding: 'utf8' }));
38+
39+
pkgJSON.name = 'tutorialkit';
40+
41+
fs.writeFileSync('./package.json', JSON.stringify(pkgJSON, undefined, 2), 'utf8');
42+
}
43+
}
44+
}
45+
46+
/**
47+
* @type {import('esbuild').Plugin}
48+
*/
49+
const esbuildProblemMatcherPlugin = {
50+
name: 'esbuild-problem-matcher',
51+
setup(build) {
52+
build.onStart(() => {
53+
console.log('[watch] build started');
54+
});
55+
build.onEnd((result) => {
56+
result.errors.forEach(({ text, location }) => {
57+
console.error(`✘ [ERROR] ${text}`);
58+
console.error(` ${location.file}:${location.line}:${location.column}:`);
59+
});
60+
console.log('[watch] build finished');
61+
});
62+
},
63+
};
64+
65+
main().catch((error) => {
66+
console.error(error);
67+
process.exit(1);
68+
});

extensions/vscode/package.json

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
{
2+
"name": "tutorialkit-vscode",
3+
"displayName": "TutorialKit",
4+
"description": "TutorialKit support in VS Code",
5+
"icon": "resources/tutorialkit-icon.png",
6+
"publisher": "StackBlitz",
7+
"version": "0.0.11",
8+
"engines": {
9+
"vscode": "^1.80.0"
10+
},
11+
"repository": {
12+
"url": "https://github.com/stackblitz/tutorialkit"
13+
},
14+
"categories": [
15+
"Other"
16+
],
17+
"activationEvents": [
18+
"onStartupFinished"
19+
],
20+
"main": "./dist/extension.js",
21+
"contributes": {
22+
"commands": [
23+
{
24+
"command": "tutorialkit.select-tutorial",
25+
"title": "Select Tutorial",
26+
"icon": "$(book)"
27+
},
28+
{
29+
"command": "tutorialkit.add-lesson",
30+
"title": "Add Lesson"
31+
},
32+
{
33+
"command": "tutorialkit.add-chapter",
34+
"title": "Add Chapter"
35+
},
36+
{
37+
"command": "tutorialkit.add-part",
38+
"title": "Add Part"
39+
},
40+
{
41+
"command": "tutorialkit.refresh",
42+
"title": "Refresh Lessons",
43+
"icon": "$(refresh)"
44+
}
45+
],
46+
"viewsWelcome": [
47+
{
48+
"view": "tutorialkit-lessons-tree",
49+
"id": "tutorialkit-splashscreen",
50+
"contents": "",
51+
"title": "Tutorial",
52+
"description": "TutorialKit",
53+
"when": "!tutorialkit:initialized"
54+
},
55+
{
56+
"view": "tutorialkit-lessons-tree",
57+
"id": "tutorialkit-splashscreen",
58+
"contents": "Looks like there is no TutorialKit project in this workspace.\n[Rescan workspace](command:tutorialkit.initialize?true)",
59+
"title": "Tutorial",
60+
"description": "TutorialKit",
61+
"when": "tutorialkit:initialized && !tutorialkit:multiple-tutorials"
62+
},
63+
{
64+
"view": "tutorialkit-lessons-tree",
65+
"id": "tutorialkit-multiple-tutorials",
66+
"contents": "Welcome to TutorialKit!\nLooks like there is more than one tutorial in your workspace.\n[Select a tutorial](command:tutorialkit.select-tutorial)",
67+
"title": "Tutorial",
68+
"description": "TutorialKit",
69+
"when": "tutorialkit:multiple-tutorials"
70+
}
71+
],
72+
"views": {
73+
"explorer": [
74+
{
75+
"id": "tutorialkit-lessons-tree",
76+
"name": "Tutorial",
77+
"visibility": "visible",
78+
"initialSize": 3
79+
}
80+
]
81+
},
82+
"menus": {
83+
"view/title": [
84+
{
85+
"command": "tutorialkit.select-tutorial",
86+
"when": "view == tutorialkit-lessons-tree && tutorialkit:multiple-tutorials",
87+
"group": "navigation"
88+
},
89+
{
90+
"command": "tutorialkit.refresh",
91+
"when": "view == tutorialkit-lessons-tree && tutorialkit:tree",
92+
"group": "navigation"
93+
}
94+
],
95+
"view/item/context": [
96+
{
97+
"command": "tutorialkit.add-lesson",
98+
"when": "view == tutorialkit-lessons-tree && viewItem == chapter"
99+
},
100+
{
101+
"command": "tutorialkit.add-chapter",
102+
"when": "view == tutorialkit-lessons-tree && viewItem == part"
103+
}
104+
]
105+
}
106+
},
107+
"scripts": {
108+
"__esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=dist/extension.js --external:vscode --format=cjs --platform=node",
109+
"__dev": "pnpm run esbuild-base -- --sourcemap --watch",
110+
"__vscode:prepublish": "pnpm run esbuild-base -- --minify",
111+
"__build": "vsce package",
112+
"compile": "pnpm run check-types && node build.mjs",
113+
"check-types": "tsc --noEmit",
114+
"dev": "node build.mjs --watch",
115+
"vscode:prepublish": "pnpm run package",
116+
"package": "pnpm run check-types && node build.mjs --production"
117+
},
118+
"dependencies": {
119+
"case-anything": "^3.1.0",
120+
"gray-matter": "^4.0.3"
121+
},
122+
"devDependencies": {
123+
"@types/mocha": "^10.0.6",
124+
"@types/node": "20.14.11",
125+
"@types/vscode": "^1.80.0",
126+
"@vscode/test-cli": "^0.0.9",
127+
"@vscode/test-electron": "^2.4.0",
128+
"esbuild": "^0.21.5",
129+
"execa": "^9.2.0",
130+
"typescript": "^5.4.5"
131+
}
132+
}
Loading
Loading
Loading
Loading
3.28 KB
Loading
Loading
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export function newCommand<F extends (...args: any[]) => Promise<any>>(fn: F) {
2+
return async function (this: any, ...args: Parameters<F>): Promise<Awaited<ReturnType<F>> | undefined> {
3+
try {
4+
return await fn.apply(this, args);
5+
} catch (error: unknown) {
6+
if (error instanceof CancelError) {
7+
return undefined;
8+
}
9+
10+
throw error;
11+
}
12+
};
13+
}
14+
15+
export class CancelError extends Error {
16+
constructor() {
17+
super('operation cancelled');
18+
}
19+
}

0 commit comments

Comments
 (0)