JavaScript and Node.js walkthrough (#157965)

* Initial contents for JavaScript walkthrough.

* Just suggest a `.js` file.

* Switch the walkthrough to be a Node.js walkthrough.

* Remove leftover file from HTML walkthrough.

* Add basic detection mechanism for node.

* Don't check for a Node install until the user runs debug.

* Add "learn more" link.

* Some assets.

* Remove "run" section.

* Add a "try debugging anyway" option just in case.

* Remove "view terminal" command in run & debug.

* More copy.

* Remove unused command.

* Update icon and themed walkthrough SVGs

* Default to not showing the extension.

* Replace icon

* Delete TODO.md

* jsWelcome -> nodejsWelcome

Co-authored-by: David Dossett <ddossett@microsoft.com>
Co-authored-by: Matt Bierner <matb@microsoft.com>
This commit is contained in:
Daniel Rosenwasser 2022-09-14 09:53:37 -07:00 committed by GitHub
parent 37f08d1195
commit 0cbcb1b1ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 618 additions and 4 deletions

View file

@ -82,6 +82,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/**',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View file

@ -68,7 +68,8 @@
"onCommand:typescript.fileReferences",
"onCommand:typescript.goToSourceDefinition",
"onTaskType:typescript",
"onLanguage:jsonc"
"onLanguage:jsonc",
"onWalkthrough:nodejsWelcome"
],
"main": "./out/extension",
"browser": "./dist/browser/extension",
@ -1524,6 +1525,58 @@
}
]
}
],
"walkthroughs": [
{
"id": "nodejsWelcome",
"title": "%walkthroughs.nodejsWelcome.title%",
"description": "%walkthroughs.nodejsWelcome.description%",
"when": "false",
"steps": [
{
"id": "walkthroughs.nodejsWelcome.downloadNode.forMacOrWindows",
"title": "%walkthroughs.nodejsWelcome.downloadNode.forMacOrWindows.title%",
"description": "%walkthroughs.nodejsWelcome.downloadNode.forMacOrWindows.description%",
"media": {
"svg": "resources/walkthroughs/install-node-js.svg"
},
"when": "isWindows || isMac"
},
{
"id": "walkthroughs.nodejsWelcome.downloadNode.forLinux",
"title": "%walkthroughs.nodejsWelcome.downloadNode.forLinux.title%",
"description": "%walkthroughs.nodejsWelcome.downloadNode.forLinux.description%",
"media": {
"svg": "resources/walkthroughs/install-node-js.svg"
},
"when": "isLinux"
},
{
"id": "walkthroughs.nodejsWelcome.makeJsFile",
"title": "%walkthroughs.nodejsWelcome.makeJsFile.title%",
"description": "%walkthroughs.nodejsWelcome.makeJsFile.description%",
"media": {
"svg": "resources/walkthroughs/create-a-js-file.svg"
}
},
{
"id": "walkthroughs.nodejsWelcome.debugJsFile",
"title": "%walkthroughs.nodejsWelcome.debugJsFile.title%",
"description": "%walkthroughs.nodejsWelcome.debugJsFile.description%",
"media": {
"svg": "resources/walkthroughs/debug-and-run.svg"
}
},
{
"id": "walkthroughs.nodejsWelcome.learnMoreAboutJs",
"title": "%walkthroughs.nodejsWelcome.learnMoreAboutJs.title%",
"description": "%walkthroughs.nodejsWelcome.learnMoreAboutJs.description%",
"media": {
"svg": "resources/walkthroughs/learn-more.svg"
}
}
]
}
]
},
"repository": {

View file

@ -191,5 +191,25 @@
"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.nodejsWelcome.title": "Get started with JavaScript and Node.js",
"walkthroughs.nodejsWelcome.description": "Make the most of Visual Studio Code's first-class JavaScript experience.",
"walkthroughs.nodejsWelcome.downloadNode.forMacOrWindows.title": "Install Node.js",
"walkthroughs.nodejsWelcome.downloadNode.forMacOrWindows.description": "Node.js is an easy way to run JavaScript code. 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.nodejsWelcome.downloadNode.forLinux.title": "Install Node.js",
"walkthroughs.nodejsWelcome.downloadNode.forLinux.description": "Node.js is an easy way to run JavaScript code. 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.nodejsWelcome.makeJsFile.title": "Create a JavaScript File",
"walkthroughs.nodejsWelcome.makeJsFile.description": "Let's write our first JavaScript file. We'll have to create a new file and save it with the ``.js`` extension at the end of the file name.\n[Create a JavaScript File](command:javascript-walkthrough.commands.createJsFile)",
"walkthroughs.nodejsWelcome.debugJsFile.title": "Run and Debug your JavaScript",
"walkthroughs.nodejsWelcome.debugJsFile.description": "Once you've installed Node.js, you can run JavaScript programs at a terminal by entering ``node your-file-name.js``\nAnother easy way to run Node.js programs is by using VS Code's debugger which lets you run your code, pause at different points, and help you understand what's going on step-by-step.\n[Start Debugging](command:javascript-walkthrough.commands.debugJsFile)",
"walkthroughs.nodejsWelcome.debugJsFile.altText": "Debug and run your JavaScript code in Node.js with Visual Studio Code.",
"walkthroughs.nodejsWelcome.learnMoreAboutJs.title": "Explore More",
"walkthroughs.nodejsWelcome.learnMoreAboutJs.description": "Want to get more comfortable with JavaScript, Node.js, and VS Code? Be sure to check out our docs!\nWe've got lots of resources for learning [JavaScript](https://code.visualstudio.com/docs/nodejs/working-with-javascript) and [Node.js](https://code.visualstudio.com/docs/nodejs/nodejs-tutorial).\n\n[Learn More](https://code.visualstudio.com/docs/nodejs/nodejs-tutorial)",
"walkthroughs.nodejsWelcome.learnMoreAboutJs.altText": "Learn more about JavaScript and Node.js in Visual Studio Code."
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 36 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -10,6 +10,7 @@ import { PluginManager } from '../utils/plugins';
import { CommandManager } from './commandManager';
import { ConfigurePluginCommand } from './configurePlugin';
import { JavaScriptGoToProjectConfigCommand, TypeScriptGoToProjectConfigCommand } from './goToProjectConfiguration';
import { CreateNewJSFileCommand, DebugJsFileCommand, JsWalkthroughState } from './jsWalkthrough';
import { LearnMoreAboutRefactoringsCommand } from './learnMoreAboutRefactorings';
import { OpenTsServerLogCommand } from './openTsServerLog';
import { ReloadJavaScriptProjectsCommand, ReloadTypeScriptProjectsCommand } from './reloadProject';
@ -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));
@ -33,4 +35,6 @@ export function registerBaseCommands(
commandManager.register(new ConfigurePluginCommand(pluginManager));
commandManager.register(new LearnMoreAboutRefactoringsCommand());
commandManager.register(new TSServerRequestCommand(lazyClientHost));
commandManager.register(new CreateNewJSFileCommand(jsWalkthroughState));
commandManager.register(new DebugJsFileCommand(jsWalkthroughState));
}

View file

@ -0,0 +1,187 @@
/*---------------------------------------------------------------------------------------------
* 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 * as nls from 'vscode-nls';
import * as cp from 'child_process';
import { Disposable } from '../utils/dispose';
const localize = nls.loadMessageBundle();
export async function nodeWasResolvable(): Promise<boolean> {
let execStr: string;
switch (process.platform) {
case 'win32':
execStr = 'where node';
break;
case 'aix':
case 'cygwin':
case 'darwin':
case 'freebsd':
case 'haiku':
case 'linux':
case 'netbsd':
case 'openbsd':
case 'sunos':
execStr = 'which node';
break;
default:
return false;
}
return new Promise(resolve => {
cp.exec(execStr, { windowsHide: true }, err => {
resolve(!err);
});
});
}
export class JsWalkthroughState extends Disposable {
exampleJsDocument: vscode.TextDocument | undefined = undefined;
override dispose() {
this.exampleJsDocument = undefined;
}
}
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);
}
}
export class NodeInstallationFoundCommand {
public static readonly id = 'javascript-walkthrough.commands.nodeInstallationFound';
public readonly id = NodeInstallationFoundCommand.id;
public execute() { }
}
async function createNewJSFile(walkthroughState: JsWalkthroughState) {
const newFile = await vscode.workspace.openTextDocument({
language: 'javascript',
content: `// Write a message to the console.\nconsole.log('hello world!');\n`,
});
walkthroughState.exampleJsDocument = newFile;
return vscode.window.showTextDocument(newFile, vscode.ViewColumn.Beside);
}
async function debugJsFile(walkthroughState: JsWalkthroughState) {
const hasNode = await nodeWasResolvable();
if (!hasNode) {
const reloadResponse = localize('reloadWindowForNode', 'Reload VS Code');
const debugAnywayResponse = localize('nodeNotFoundDebugAnyway', 'Try Debugging Anyway');
const dismissResponse = localize('nodeNotFoundDismissDialog', 'Dismiss');
const response = await vscode.window.showErrorMessage(
// The message
localize('noNodeInstallFound', 'We couldn\'t find Node.js on this computer. If you just installed it, you might need to reload VS Code.'),
// The options
reloadResponse,
debugAnywayResponse,
dismissResponse,
);
if (response === undefined || response === dismissResponse) {
return;
}
if (response === reloadResponse) {
vscode.commands.executeCommand('workbench.action.reloadWindow');
return;
}
}
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' };
}

View file

@ -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';
@ -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,
@ -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)));

View file

@ -8,6 +8,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 { ExperimentationService } from './experimentationService';
import { createLazyClientHost, lazilyActivateClient } from './lazyClientHost';
import { nodeRequestCancellerFactory } from './tsServer/cancellation.electron';
@ -38,6 +39,9 @@ export function activate(
const activeJsTsEditorTracker = new ActiveJsTsEditorTracker();
context.subscriptions.push(activeJsTsEditorTracker);
const jsWalkthroughState = new JsWalkthroughState();
context.subscriptions.push(jsWalkthroughState);
const lazyClientHost = createLazyClientHost(context, onCaseInsensitiveFileSystem(), {
pluginManager,
commandManager,
@ -51,7 +55,7 @@ export function activate(
onCompletionAccepted.fire(item);
});
registerBaseCommands(commandManager, lazyClientHost, pluginManager, activeJsTsEditorTracker);
registerBaseCommands(commandManager, lazyClientHost, pluginManager, activeJsTsEditorTracker, jsWalkthroughState);
// Currently no variables in use.
new ExperimentationService(context);