Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a JavaScript Walkthrough #151665

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build/filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ module.exports.indentationFilter = [
'!test/monaco/out/**',
'!test/smoke/out/**',
'!extensions/typescript-language-features/test-workspace/**',
'!extensions/typescript-language-features/resources/walkthroughs/**',
'!extensions/markdown-math/notebook-out/**',
'!extensions/vscode-api-tests/testWorkspace/**',
'!extensions/vscode-api-tests/testWorkspace2/**',
Expand Down
63 changes: 62 additions & 1 deletion extensions/typescript-language-features/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@
"onCommand:typescript.fileReferences",
"onCommand:typescript.goToSourceDefinition",
"onTaskType:typescript",
"onLanguage:jsonc"
"onLanguage:jsonc",
"onWalkthrough:jsWelcome"
],
"main": "./out/extension",
"browser": "./dist/browser/extension",
Expand Down Expand Up @@ -1507,6 +1508,66 @@
}
]
}
],
"walkthroughs": [
{
"id": "jsWelcome",
"title": "%walkthroughs.jsWelcome.title%",
"description": "%walkthroughs.jsWelcome.description%",
"when": "isWindows && workspacePlatform == windows || isMac && workspacePlatform == mac || isLinux && workspacePlatform == linux",
"steps": [
{
"id": "walkthroughs.jsWelcome.createHtmlWithScriptTag",
"title": "%walkthroughs.jsWelcome.createHtmlWithScriptTag.title%",
"description": "%walkthroughs.jsWelcome.createHtmlWithScriptTag.description%",
"media": {
"markdown": "resources/walkthroughs/TODO.md"
}
},
{
"id": "walkthroughs.jsWelcome.debugHtmlFile",
"title": "%walkthroughs.jsWelcome.debugHtmlFile.title%",
"description": "%walkthroughs.jsWelcome.debugHtmlFile.description%",
"media": {
"markdown": "resources/walkthroughs/TODO.md"
}
},
{
"id": "walkthroughs.jsWelcome.downloadNode.forMacOrWindows",
"title": "%walkthroughs.jsWelcome.downloadNode.forMacOrWindows.title%",
"description": "%walkthroughs.jsWelcome.downloadNode.forMacOrWindows.description%",
"media": {
"markdown": "resources/walkthroughs/TODO.md"
},
"when": "isWindows || isMac"
},
{
"id": "walkthroughs.jsWelcome.downloadNode.forLinux",
"title": "%walkthroughs.jsWelcome.downloadNode.forLinux.title%",
"description": "%walkthroughs.jsWelcome.downloadNode.forLinux.description%",
"media": {
"markdown": "resources/walkthroughs/TODO.md"
},
"when": "isLinux"
},
{
"id": "walkthroughs.jsWelcome.makeJsFile",
"title": "%walkthroughs.jsWelcome.makeJsFile.title%",
"description": "%walkthroughs.jsWelcome.makeJsFile.description%",
"media": {
"markdown": "resources/walkthroughs/TODO.md"
}
},
{
"id": "walkthroughs.jsWelcome.debugJsFile",
"title": "%walkthroughs.jsWelcome.debugJsFile.title%",
"description": "%walkthroughs.jsWelcome.debugJsFile.description%",
"media": {
"markdown": "resources/walkthroughs/TODO.md"
}
}
]
}
]
},
"repository": {
Expand Down
23 changes: 22 additions & 1 deletion extensions/typescript-language-features/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -190,5 +190,26 @@
"typescript.findAllFileReferences": "Find File References",
"typescript.goToSourceDefinition": "Go to Source Definition",
"configuration.suggest.classMemberSnippets.enabled": "Enable/disable snippet completions for class members. Requires using TypeScript 4.5+ in the workspace",
"configuration.suggest.objectLiteralMethodSnippets.enabled": "Enable/disable snippet completions for methods in object literals. Requires using TypeScript 4.7+ in the workspace"
"configuration.suggest.objectLiteralMethodSnippets.enabled": "Enable/disable snippet completions for methods in object literals. Requires using TypeScript 4.7+ in the workspace",

"walkthroughs.jsWelcome.title": "Get started with JavaScript",
"walkthroughs.jsWelcome.description": "Make the most of Visual Studio Code's first-class JavaScript experience.",

"walkthroughs.jsWelcome.createHtmlWithScriptTag.title": "Create a Web Page",
"walkthroughs.jsWelcome.createHtmlWithScriptTag.description": "The easiest way to start writing JavaScript is inside an HTML file. To do that, we'll need to make an HTML file with a `<script>` tag.\n[Create an HTML file](command:javascript-walkthrough.commands.createHtmlFileWithScriptTag)",

"walkthroughs.jsWelcome.debugHtmlFile.title": "View Your Web Page",
"walkthroughs.jsWelcome.debugHtmlFile.description": "We can save and view your HTML file in a browser. Visual Studio Code makes this easy when you start debugging in an HTML file. Just hit <kbd>F5<kbd> while focused on an HTML file.\n[Start Debugging](command:javascript-walkthrough.commands.debugHtmlFile)",

"walkthroughs.jsWelcome.downloadNode.forMacOrWindows.title": "Install Node.js",
"walkthroughs.jsWelcome.downloadNode.forMacOrWindows.description": "Node.js is an easy way to run JavaScript files directly. You can use it to quickly build command-line apps and servers. It also comes with npm, a package manager which makes reusing and sharing JavaScript code easy.\n[Install Node.js](https://nodejs.org/en/download/)",

"walkthroughs.jsWelcome.downloadNode.forLinux.title": "Install Node.js",
"walkthroughs.jsWelcome.downloadNode.forLinux.description": "Node.js is an easy way to run JavaScript files directly. You can use it to quickly build command-line apps and servers. It also comes with npm, a package manager which makes reusing and sharing JavaScript code easy.\n[Install Node.js](https://nodejs.org/en/download/package-manager/)",

"walkthroughs.jsWelcome.makeJsFile.title": "Create a JavaScript File",
"walkthroughs.jsWelcome.makeJsFile.description": "Writing JavaScript in an HTML file is doable, but often we'll want to write that code in its own file. This lets us include it on multiple pages, or lets us run it right in programs like Node.js.\n[Create a JavaScript File](command:javascript-walkthrough.commands.createJsFile)",

"walkthroughs.jsWelcome.debugJsFile.title": "Run Your JavaScript in Node.js",
"walkthroughs.jsWelcome.debugJsFile.description": "You can run your JavaScript file in Node.js by saving it as a `.mjs` file and running `node your-file-name.mjs`. You can also run it right in Visual Studio Code by hitting <kbd>F5</kbd> and debugging it. When prompted, you can select the Node.js debugger.\n[Start Debugging](command:javascript-walkthrough.commands.debugJsFile)"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# TODO

This page intentionally left TODOish.
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<html>

<head>
<!-- We can change how our page looks with CSS in the <style> tag. -->
<style>
#nameInput,
#output {
font-family: sans-serif;
font-size: 40px;
margin: 10px;
}
</style>
</head>

<!-- We can add the elements of our page in the <body> tag. -->

<body>
<input id="nameInput" type="text" placeholder="What is your name?" autocomplete="off"></input>
<br>
<div id="output"></div>
</body>

<!-- Our JavaScript code will go in a <script> tag. -->
<script>
// Grab our input and output elements.
let nameInput = document.getElementById("nameInput");
let output = document.getElementById("output");

// Write our greeting and focus on the input field.
sayHello();
nameInput.focus()

// Every time our input changes, it will call 'sayHello' again.
nameInput.oninput = sayHello;

function sayHello() {
let name = nameInput.value;
if (name !== "") {
// Add an extra space if we have a name.
name = " " + name;
}

output.textContent = `Hello${name}, nice to meet you!`;
}
</script>

</html>
6 changes: 6 additions & 0 deletions extensions/typescript-language-features/src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { PluginManager } from '../utils/plugins';
import { CommandManager } from './commandManager';
import { ConfigurePluginCommand } from './configurePlugin';
import { JavaScriptGoToProjectConfigCommand, TypeScriptGoToProjectConfigCommand } from './goToProjectConfiguration';
import { CreateHtmlFileWithScriptTagCommand, DebugHtmlFileCommand, CreateNewJSFileCommand, DebugJsFileCommand, JsWalkthroughState } from './jsWalkthrough';
import { LearnMoreAboutRefactoringsCommand } from './learnMoreAboutRefactorings';
import { OpenTsServerLogCommand } from './openTsServerLog';
import { ReloadJavaScriptProjectsCommand, ReloadTypeScriptProjectsCommand } from './reloadProject';
Expand All @@ -22,6 +23,7 @@ export function registerBaseCommands(
lazyClientHost: Lazy<TypeScriptServiceClientHost>,
pluginManager: PluginManager,
activeJsTsEditorTracker: ActiveJsTsEditorTracker,
jsWalkthroughState: JsWalkthroughState
): void {
commandManager.register(new ReloadTypeScriptProjectsCommand(lazyClientHost));
commandManager.register(new ReloadJavaScriptProjectsCommand(lazyClientHost));
Expand All @@ -33,4 +35,8 @@ export function registerBaseCommands(
commandManager.register(new ConfigurePluginCommand(pluginManager));
commandManager.register(new LearnMoreAboutRefactoringsCommand());
commandManager.register(new TSServerRequestCommand(lazyClientHost));
commandManager.register(new CreateHtmlFileWithScriptTagCommand(jsWalkthroughState));
commandManager.register(new DebugHtmlFileCommand(jsWalkthroughState));
commandManager.register(new CreateNewJSFileCommand(jsWalkthroughState));
commandManager.register(new DebugJsFileCommand(jsWalkthroughState));
}
174 changes: 174 additions & 0 deletions extensions/typescript-language-features/src/commands/jsWalkthrough.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as vscode from 'vscode';

import { readFile } from 'fs/promises';
import * as path from 'path';
import { Disposable } from '../utils/dispose';

export class JsWalkthroughState extends Disposable {
exampleHtmlDocument: vscode.TextDocument | undefined = undefined;
exampleJsDocument: vscode.TextDocument | undefined = undefined;

override dispose() {
this.exampleHtmlDocument = undefined;
this.exampleJsDocument = undefined;
}
}

export class CreateHtmlFileWithScriptTagCommand {
public static readonly id = 'javascript-walkthrough.commands.createHtmlFileWithScriptTag';
public readonly id = CreateHtmlFileWithScriptTagCommand.id;

constructor(private walkthroughState: JsWalkthroughState) { }

public execute() {
createHtmlFileWithScriptTag(this.walkthroughState);
}
}

export class DebugHtmlFileCommand {
public static readonly id = 'javascript-walkthrough.commands.debugHtmlFile';
public readonly id = DebugHtmlFileCommand.id;

constructor(private walkthroughState: JsWalkthroughState) { }

public execute() {
debugHtmlFile(this.walkthroughState);
}
}

export class CreateNewJSFileCommand {
public static readonly id = 'javascript-walkthrough.commands.createJsFile';
public readonly id = CreateNewJSFileCommand.id;

constructor(private walkthroughState: JsWalkthroughState) { }

public execute() {
createNewJSFile(this.walkthroughState);
}
}

export class DebugJsFileCommand {
public static readonly id = 'javascript-walkthrough.commands.debugJsFile';
public readonly id = DebugJsFileCommand.id;

constructor(private walkthroughState: JsWalkthroughState) { }

public execute() {
debugJsFile(this.walkthroughState);
}
}

async function createHtmlFileWithScriptTag(walkthroughState: JsWalkthroughState) {
const htmlPath = path.resolve(__dirname, '../../resources/walkthroughs/htmlFileWithScriptTag.html');
const content = await readFile(htmlPath, 'utf8');
const newFile = await vscode.workspace.openTextDocument({
language: 'html',
content,
});
walkthroughState.exampleHtmlDocument = newFile;
//vscode.window.showInformationMessage(process.env.USERNAME!);
const scriptStart = newFile.positionAt(newFile.getText().match(/^<script>$/m)!.index!);
const editor = await vscode.window.showTextDocument(newFile, {
viewColumn: vscode.ViewColumn.Beside,
selection: new vscode.Range(scriptStart, scriptStart),
});
return editor;
}

async function createNewJSFile(walkthroughState: JsWalkthroughState) {
const newFile = await vscode.workspace.openTextDocument({
language: 'javascript',
content: `// Write a message to the console.\nconsole.log('hello world!');`,
});
walkthroughState.exampleJsDocument = newFile;
return vscode.window.showTextDocument(newFile, vscode.ViewColumn.Beside);
}

async function debugHtmlFile(walkthroughState: JsWalkthroughState) {
tryDebugRelevantDocument(walkthroughState.exampleHtmlDocument, 'html', ['.html'], () => createHtmlFileWithScriptTag(walkthroughState));
}

async function debugJsFile(walkthroughState: JsWalkthroughState) {
tryDebugRelevantDocument(walkthroughState.exampleJsDocument, 'javascript', ['.mjs', '.js'], () => createNewJSFile(walkthroughState));
}

type DocSearchResult =
| { kind: 'visible'; editor: vscode.TextEditor }
| { kind: 'hidden'; uri: vscode.Uri }
| { kind: 'not-found' };

async function tryDebugRelevantDocument(lastDocument: vscode.TextDocument | undefined, languageId: string, languageExtensions: [string, ...string[]], createFileAndFocus: () => Promise<vscode.TextEditor>): Promise<void> {
let searchResult!: DocSearchResult;
for (const languageExtension of languageExtensions) {
searchResult = tryFindRelevantDocument(lastDocument, languageId, languageExtension);
if (searchResult.kind !== 'not-found') {
break;
}
}

let editor: vscode.TextEditor;
// If not, make one.
switch (searchResult.kind) {
case 'visible':
// Focus if necessary.
editor = searchResult.editor;
if (vscode.window.activeTextEditor !== editor) {
await vscode.window.showTextDocument(editor.document, {
viewColumn: vscode.ViewColumn.Beside,
});
}
break;
case 'hidden':
editor = await vscode.window.showTextDocument(searchResult.uri, {
viewColumn: vscode.ViewColumn.Beside,
});
break;
case 'not-found':
editor = await createFileAndFocus();
break;
}

await Promise.all([
vscode.commands.executeCommand('workbench.action.debug.start'),
vscode.commands.executeCommand('workbench.debug.action.focusRepl'),
]);

}

/** Tries to find a relevant {@link vscode.TextEditor} or a {@link vscode.Uri} for an open document */
function tryFindRelevantDocument(lastDocument: vscode.TextDocument | undefined, languageId: string, languageExtension: string): DocSearchResult {
let editor: vscode.TextEditor | undefined;

// Try to find the document created from the last step.
if (lastDocument) {
editor = vscode.window.visibleTextEditors.find(editor => editor.document === lastDocument);
}

// If we couldn't find that, find a visible document with the desired language.
editor ??= vscode.window.visibleTextEditors.find(editor => editor.document.languageId === languageId);
if (editor) {
return {
kind: 'visible',
editor,
};
}

// If we still couldn't find that, find a possibly not-visible document.
for (const tabGroup of vscode.window.tabGroups.all) {
for (const tab of tabGroup.tabs) {
if (tab.input instanceof vscode.TabInputText && tab.input.uri.path.endsWith(languageExtension)) {
return {
kind: 'hidden',
uri: tab.input.uri,
};
}
}
}

return { kind: 'not-found' };
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as vscode from 'vscode';
import { Api, getExtensionApi } from './api';
import { CommandManager } from './commands/commandManager';
import { registerBaseCommands } from './commands/index';
import { JsWalkthroughState } from './commands/jsWalkthrough';
import { createLazyClientHost, lazilyActivateClient } from './lazyClientHost';
import { noopRequestCancellerFactory } from './tsServer/cancellation';
import { noopLogDirectoryProvider } from './tsServer/logDirectoryProvider';
Expand Down Expand Up @@ -51,6 +52,9 @@ export function activate(
const activeJsTsEditorTracker = new ActiveJsTsEditorTracker();
context.subscriptions.push(activeJsTsEditorTracker);

const jsWalkthroughState = new JsWalkthroughState();
context.subscriptions.push(jsWalkthroughState);

const versionProvider = new StaticVersionProvider(
new TypeScriptVersion(
TypeScriptVersionSource.Bundled,
Expand All @@ -70,7 +74,7 @@ export function activate(
onCompletionAccepted.fire(item);
});

registerBaseCommands(commandManager, lazyClientHost, pluginManager, activeJsTsEditorTracker);
registerBaseCommands(commandManager, lazyClientHost, pluginManager, activeJsTsEditorTracker, jsWalkthroughState);

// context.subscriptions.push(task.register(lazyClientHost.map(x => x.serviceClient)));

Expand Down
Loading