doneOn => completionEvents

implement onLink, onEvent, onCommand, extensionInstalled, stepSelected
ref #122570
This commit is contained in:
Jackson Kearl 2021-05-12 17:48:51 -07:00
parent 1f2077f7e6
commit 4c63cdf2fa
No known key found for this signature in database
GPG key ID: DA09A59C409FC400
5 changed files with 123 additions and 60 deletions

View file

@ -117,9 +117,9 @@ export interface IWalkthroughStep {
readonly id: string;
readonly title: string;
readonly description: string | undefined;
readonly media:
| { path: string | { dark: string, light: string, hc: string }, altText: string }
| { path: string, },
readonly media: { path: string | { dark: string, light: string, hc: string }, altText?: string }
readonly completionEvents?: string[];
/** @deprecated use `completionEvents: 'onCommand:...'` */
readonly doneOn?: { command: string };
readonly when?: string;
}

View file

@ -460,6 +460,7 @@ export class GettingStartedPage extends EditorPane {
stepElement.classList.add('expanded');
stepElement.setAttribute('aria-expanded', 'true');
this.buildMediaComponent(id);
this.gettingStartedService.progressByEvent('stepSelected:' + id);
} else {
this.editorInput.selectedStep = undefined;
}
@ -488,7 +489,8 @@ export class GettingStartedPage extends EditorPane {
const path = joinPath(base, src);
const transformed = asWebviewUri({
isExtensionDevelopmentDebug: this.environmentService.isExtensionDevelopment,
...this.environmentService,
webviewResourceRoot: this.environmentService.webviewResourceRoot,
webviewCspSource: this.environmentService.webviewCspSource,
remote: { authority: undefined },
}, this.webviewID, path).toString();
return `src="${transformed}"`;
@ -889,6 +891,10 @@ export class GettingStartedPage extends EditorPane {
}
this.openerService.open(command, { allowCommands: true });
if (!isCommand && node.href.startsWith('https://')) {
this.gettingStartedService.progressByEvent('onLink:' + node.href);
}
}, null, this.detailsPageDisposables);
if (isCommand) {

View file

@ -50,7 +50,7 @@ export const walkthroughsExtensionPoint = ExtensionsRegistry.registerExtensionPo
defaultSnippets: [{
body: {
'id': '$1', 'title': '$2', 'description': '$3',
'doneOn': { 'command': '$5' },
'completionEvents': ['$5'],
'media': { 'path': '$6', 'type': '$7' }
}
}],
@ -122,8 +122,38 @@ export const walkthroughsExtensionPoint = ExtensionsRegistry.registerExtensionPo
}
]
},
completionEvents: {
description: localize('walkthroughs.steps.completionEvents', "Events that should trigger this step to become checked off. If empty or not defined, the step will check off when any of the step's buttons or links are clicked; if the step has no buttons or links it will check on when it is selected."),
type: 'array',
items: {
type: 'string',
defaultSnippets: [
{
label: 'onCommand',
description: localize('walkthroughs.steps.completionEvents.onCommand', 'Check off step when a given command is executed anywhere in VS Code.'),
body: 'onCommand:${1:commandId}'
},
{
label: 'onLink',
description: localize('walkthroughs.steps.completionEvents.onLink', 'Check off step when a given link is opened via a Getting Started step.'),
body: 'onLink:${2:linkId}'
},
{
label: 'extensionInstalled',
description: localize('walkthroughs.steps.completionEvents.extensionInstalled', 'Check off step when an extension with the given id is installed. If the extension is already installed, the step will start off checked.'),
body: 'extensionInstalled:${3:extensionId}'
},
{
label: 'stepSelected',
description: localize('walkthroughs.steps.completionEvents.stepSelected', 'Check off step as soon as it is selected.'),
body: 'stepSelected'
},
]
}
},
doneOn: {
description: localize('walkthroughs.steps.doneOn', "Signal to mark step as complete."),
deprecationMessage: localize('walkthroughs.steps.doneOn.deprecation', "doneOn is deprecated, use completionEvents"),
type: 'object',
required: ['command'],
defaultSnippets: [{ 'body': { command: '$1' } }],

View file

@ -28,10 +28,11 @@ import { GettingStartedInput } from 'vs/workbench/contrib/welcome/gettingStarted
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { GettingStartedPage } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { LinkedText, parseLinkedText } from 'vs/base/common/linkedText';
import { ILink, LinkedText, parseLinkedText } from 'vs/base/common/linkedText';
import { walkthroughsExtensionPoint } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedExtensionPoint';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { dirname } from 'vs/base/common/path';
import { coalesce, flatten } from 'vs/base/common/arrays';
export const IGettingStartedService = createDecorator<IGettingStartedService>('gettingStartedService');
@ -52,7 +53,9 @@ export interface IGettingStartedStep {
category: GettingStartedCategory | string
when: ContextKeyExpression
order: number
doneOn: { commandExecuted: string, eventFired?: never } | { eventFired: string, commandExecuted?: never }
/** @deprecated */
doneOn?: { commandExecuted: string, eventFired?: never } | { eventFired: string, commandExecuted?: never }
completionEvents: string[]
media:
| { type: 'image', path: { hc: URI, light: URI, dark: URI }, altText: string }
| { type: 'markdown', path: URI, base: URI, root: URI }
@ -151,8 +154,8 @@ export class GettingStartedService extends Disposable implements IGettingStarted
private memento: Memento;
private stepProgress: Record<string, StepProgress>;
private commandListeners = new Map<string, string[]>();
private eventListeners = new Map<string, string[]>();
private sessionEvents = new Set<string>();
private completionListeners = new Map<string, Set<string>>();
private gettingStartedContributions = new Map<string, IGettingStartedCategory>();
private steps = new Map<string, IGettingStartedStep>();
@ -186,17 +189,22 @@ export class GettingStartedService extends Disposable implements IGettingStarted
removed.forEach(e => this.unregisterExtensionContributions(e.description));
});
this._register(this.commandService.onDidExecuteCommand(command => this.progressByCommand(command.commandId)));
this._register(this.commandService.onDidExecuteCommand(command => this.progressByEvent(`onCommand:${command.commandId}`)));
this.extensionManagementService.getInstalled().then(installed => {
installed.forEach(ext => this.progressByEvent(`extensionInstalled:${ext.identifier.id.toLowerCase()}`));
});
this._register(this.extensionManagementService.onDidInstallExtension(async e => {
if (await this.hostService.hadLastFocus()) {
this.sessionInstalledExtensions.add(e.identifier.id);
}
this.progressByEvent(`extensionInstalled:${e.identifier.id.toLowerCase()}`);
}));
if (userDataAutoSyncEnablementService.isEnabled()) { this.progressByEvent('sync-enabled'); }
if (userDataAutoSyncEnablementService.isEnabled()) { this.progressByEvent('onEvent:sync-enabled'); }
this._register(userDataAutoSyncEnablementService.onDidChangeEnablement(() => {
if (userDataAutoSyncEnablementService.isEnabled()) { this.progressByEvent('sync-enabled'); }
if (userDataAutoSyncEnablementService.isEnabled()) { this.progressByEvent('onEvent:sync-enabled'); }
}));
startEntries.forEach(async (entry, index) => {
@ -221,6 +229,7 @@ export class GettingStartedService extends Disposable implements IGettingStarted
this.getStepOverrides(step, category.id);
return ({
...step,
completionEvents: step.completionEvents ?? [],
description: parseDescription(step.description),
category: category.id,
order: index,
@ -389,9 +398,7 @@ export class GettingStartedService extends Disposable implements IGettingStarted
return ({
description, media,
doneOn: step.doneOn?.command
? { commandExecuted: step.doneOn.command }
: { eventFired: 'markDone:' + fullyQualifiedID },
completionEvents: step.completionEvents?.filter(x => typeof x === 'string') ?? [],
id: fullyQualifiedID,
title: step.title,
when: ContextKeyExpr.deserialize(step.when) ?? ContextKeyExpr.true(),
@ -456,20 +463,69 @@ export class GettingStartedService extends Disposable implements IGettingStarted
}
private registerDoneListeners(step: IGettingStartedStep) {
if (step.doneOn.commandExecuted) {
const existing = this.commandListeners.get(step.doneOn.commandExecuted);
if (existing) { existing.push(step.id); }
else {
this.commandListeners.set(step.doneOn.commandExecuted, [step.id]);
if (step.doneOn) {
if (step.doneOn.commandExecuted) { step.completionEvents.push(`onCommand:${step.doneOn.commandExecuted}`); }
if (step.doneOn.eventFired) { step.completionEvents.push(`onEvent:${step.doneOn.eventFired}`); }
}
if (!step.completionEvents.length) {
step.completionEvents = coalesce(flatten(
step.description
.filter(linkedText => linkedText.nodes.length === 1) // only buttons
.map(linkedText =>
linkedText.nodes
.filter(((node): node is ILink => typeof node !== 'string'))
.map(({ href }) => {
if (href.startsWith('command:')) {
return 'onCommand:' + href.slice('command:'.length, href.includes('?') ? href.indexOf('?') : undefined);
}
if (href.startsWith('https://')) {
return 'onLink:' + href;
}
return undefined;
}))));
}
if (!step.completionEvents.length) {
step.completionEvents.push('stepSelected');
}
for (let event of step.completionEvents) {
const [_, eventType, argument] = /^([^:]*):?(.*)$/.exec(event) ?? [];
if (!eventType) {
console.error(`Unknown completionEvent ${event} when registering step ${step.id}`);
continue;
}
switch (eventType) {
case 'onLink': case 'onEvent': break;
case 'stepSelected':
event = eventType + ':' + step.id;
break;
case 'onCommand':
event = eventType + ':' + argument.replace(/^toSide:/, '');
break;
case 'extensionInstalled':
event = eventType + ':' + argument.toLowerCase();
break;
default:
console.error(`Unknown completionEvent ${event} when registering step ${step.id}`);
continue;
}
this.registerCompletionListener(event, step);
if (this.sessionEvents.has(event)) {
this.progressStep(step.id);
}
}
if (step.doneOn.eventFired) {
const existing = this.eventListeners.get(step.doneOn.eventFired);
if (existing) { existing.push(step.id); }
else {
this.eventListeners.set(step.doneOn.eventFired, [step.id]);
}
}
private registerCompletionListener(event: string, step: IGettingStartedStep) {
if (!this.completionListeners.has(event)) {
this.completionListeners.set(event, new Set());
}
this.completionListeners.get(event)?.add(step.id);
}
getCategories(): IGettingStartedCategoryWithProgress[] {
@ -539,14 +595,9 @@ export class GettingStartedService extends Disposable implements IGettingStarted
this._onDidProgressStep.fire(this.getStepProgress(step));
}
private progressByCommand(command: string) {
const listening = this.commandListeners.get(command) ?? [];
listening.forEach(id => this.progressStep(id));
}
progressByEvent(event: string): void {
const listening = this.eventListeners.get(event) ?? [];
listening.forEach(id => this.progressStep(id));
this.sessionEvents.add(event);
this.completionListeners.get(event)?.forEach(id => this.progressStep(id));
}
private registerStartEntry(categoryDescriptor: IGettingStartedStartEntryDescriptor): void {

View file

@ -19,7 +19,7 @@ export type BuiltinGettingStartedStep = {
id: string
title: string,
description: string,
doneOn: { commandExecuted: string, eventFired?: never } | { eventFired: string, commandExecuted?: never, }
completionEvents?: string[]
when?: string,
media:
| { type: 'image', path: string | { hc: string, light: string, dark: string }, altText: string }
@ -130,28 +130,24 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
id: 'runProjectStep',
title: localize('gettingStarted.runProject.title', "Build & run your app"),
description: localize('gettingStarted.runProject.description', "Build, run & debug your code in the cloud, right from the browser.\n[Start Debugging](command:workbench.action.debug.selectandstart)"),
doneOn: { commandExecuted: 'workbench.action.debug.selectandstart' },
media: { type: 'image', altText: 'Node.js project running debug mode and paused.', path: 'runProject.png' },
},
{
id: 'forwardPortsStep',
title: localize('gettingStarted.forwardPorts.title', "Access your running application"),
description: localize('gettingStarted.forwardPorts.description', "Ports running within your codespace are automatically forwarded to the web, so you can open them in your browser.\n[Show Ports Panel](command:~remote.forwardedPorts.focus)"),
doneOn: { commandExecuted: '~remote.forwardedPorts.focus' },
media: { type: 'image', altText: 'Ports panel.', path: 'forwardPorts.png' },
},
{
id: 'pullRequests',
title: localize('gettingStarted.pullRequests.title', "Pull requests at your fingertips"),
description: localize('gettingStarted.pullRequests.description', "Bring your GitHub workflow closer to your code, so you can review pull requests, add comments, merge branches, and more.\n[Open GitHub View](command:workbench.view.extension.github-pull-requests)"),
doneOn: { commandExecuted: 'workbench.view.extension.github-pull-requests' },
media: { type: 'image', altText: 'Preview for reviewing a pull request.', path: 'pullRequests.png' },
},
{
id: 'remoteTerminal',
title: localize('gettingStarted.remoteTerminal.title', "Run tasks in the integrated terminal"),
description: localize('gettingStarted.remoteTerminal.description', "Perform quick command-line tasks using the built-in terminal.\n[Focus Terminal](command:terminal.focus)"),
doneOn: { commandExecuted: 'terminal.focus' },
media: { type: 'image', altText: 'Remote terminal showing npm commands.', path: 'remoteTerminal.png' },
},
{
@ -159,7 +155,6 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
title: localize('gettingStarted.openVSC.title', "Develop remotely in VS Code"),
description: localize('gettingStarted.openVSC.description', "Access the power of your cloud development environment from your local VS Code. Set it up by installing the GitHub Codespaces extension and connecting your GitHub account.\n[Open in VS Code](command:github.codespaces.openInStable)"),
when: 'isWeb',
doneOn: { commandExecuted: 'github.codespaces.openInStable' },
media: {
type: 'image', altText: 'Preview of the Open in VS Code command.', path: {
dark: 'dark/openVSC.png',
@ -185,14 +180,12 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
id: 'pickColorTheme',
title: localize('gettingStarted.pickColor.title', "Customize the look with themes"),
description: localize('gettingStarted.pickColor.description', "Pick a color theme to match your taste and mood while coding.\n[Pick a Theme](command:workbench.action.selectTheme)"),
doneOn: { commandExecuted: 'workbench.action.selectTheme' },
media: { type: 'image', altText: 'Color theme preview for dark and light theme.', path: 'colorTheme.png', }
},
{
id: 'findLanguageExtensions',
title: localize('gettingStarted.findLanguageExts.title', "Code in any language"),
description: localize('gettingStarted.findLanguageExts.description', "VS Code supports over 50+ programming languages. While many are built-in, others can be easily installed as extensions in one click.\n[Browse Language Extensions](command:workbench.extensions.action.showLanguageExtensions)"),
doneOn: { commandExecuted: 'workbench.extensions.action.showLanguageExtensions' },
media: {
type: 'image', altText: 'Language extensions', path: {
dark: 'dark/languageExtensions.png',
@ -205,7 +198,6 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
id: 'keymaps',
title: localize('gettingStarted.keymaps.title', "Switch from other editors"),
description: localize('gettingStarted.keymaps.description', "Bring your favorite keyboard shortcuts from other editors into VS Code with keymaps.\n[Browse Keymap Extensions](command:workbench.extensions.action.showRecommendedKeymapExtensions)"),
doneOn: { commandExecuted: 'workbench.extensions.action.showRecommendedKeymapExtensions' },
media: {
type: 'image', altText: 'List of keymap extensions.', path: {
dark: 'dark/keymaps.png',
@ -219,7 +211,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
title: localize('gettingStarted.settingsSync.title', "Sync your favorite setup"),
description: localize('gettingStarted.settingsSync.description', "Never lose the perfect VS Code setup! Settings Sync will back up and share settings, keybindings & extensions across several VS Code instances.\n[Enable Settings Sync](command:workbench.userDataSync.actions.turnOn)"),
when: 'syncStatus != uninitialized',
doneOn: { eventFired: 'sync-enabled' },
completionEvents: ['onEvent:sync-enabled'],
media: {
type: 'image', altText: 'The "Turn on Sync" entry in the settings gear menu.', path: {
dark: 'dark/settingsSync.png',
@ -233,7 +225,6 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
title: localize('gettingStarted.setup.OpenFolder.title', "Open your project folder"),
description: localize('gettingStarted.setup.OpenFolder.description', "Open a project folder to start coding!\n[Pick a Folder](command:workbench.action.files.openFileFolder)"),
when: 'isMac && workspaceFolderCount == 0',
doneOn: { commandExecuted: 'workbench.action.files.openFileFolder' },
media: {
type: 'image', altText: 'Explorer view showing buttons for opening folder and cloning repository.', path: {
dark: 'dark/openFolder.png',
@ -247,7 +238,6 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
title: localize('gettingStarted.setup.OpenFolder.title', "Open your project folder"),
description: localize('gettingStarted.setup.OpenFolder.description2', "Open a project folder to start coding!\n[Pick a Folder](command:workbench.action.files.openFolder)"),
when: '!isMac && workspaceFolderCount == 0',
doneOn: { commandExecuted: 'workbench.action.files.openFolder' },
media: {
type: 'image', altText: 'Explorer view showing buttons for opening folder and cloning repository.', path: {
dark: 'dark/openFolder.png',
@ -261,7 +251,6 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
title: localize('gettingStarted.quickOpen.title', "Quick open files"),
description: localize('gettingStarted.quickOpen.description', "Navigate between files in an instant with one keystroke. Tip: Open multiple files by pressing the right arrow key.\n[Quick Open a File](command:toSide:workbench.action.quickOpen)"),
when: 'workspaceFolderCount != 0',
doneOn: { commandExecuted: 'workbench.action.quickOpen' },
media: {
type: 'image', altText: 'Go to file in quick search.', path: {
dark: 'dark/openFolder.png',
@ -286,7 +275,6 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
id: 'commandPaletteTask',
title: localize('gettingStarted.commandPalette.title', "Find & run commands"),
description: localize('gettingStarted.commandPalette.description', "The easiest way to find everything VS Code can do. If you're ever looking for a feature or a shortcut, check here first!\n[Open Command Palette](command:workbench.action.showCommands)"),
doneOn: { commandExecuted: 'workbench.action.showCommands' },
media: {
type: 'image', altText: 'Command Palette overlay for searching and executing commands.', path: {
dark: 'dark/commandPalette.png',
@ -300,7 +288,6 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
title: localize('gettingStarted.terminal.title', "Convenient built-in terminal"),
description: localize('gettingStarted.terminal.description', "Quickly run shell commands and monitor build output, right next to your code.\n[Show Terminal Panel](command:workbench.action.terminal.toggleTerminal)"),
when: 'remoteName != codespaces && !terminalIsOpen',
doneOn: { commandExecuted: 'workbench.action.terminal.toggleTerminal' },
media: {
type: 'image', altText: 'Integrated terminal running a few npm commands', path: {
dark: 'dark/terminal.png',
@ -313,7 +300,6 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
id: 'extensions',
title: localize('gettingStarted.extensions.title', "Limitless extensibility"),
description: localize('gettingStarted.extensions.description', "Extensions are VS Code's power-ups. They range from handy productivity hacks, expanding out-of-the-box features, to adding completely new capabilities.\n[Browse Recommended Extensions](command:workbench.extensions.action.showRecommendedExtensions)"),
doneOn: { commandExecuted: 'workbench.extensions.action.showRecommendedExtensions' },
media: {
type: 'image', altText: 'VS Code extension marketplace with featured language extensions', path: {
dark: 'dark/extensions.png',
@ -326,7 +312,6 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
id: 'settings',
title: localize('gettingStarted.settings.title', "Tune your settings"),
description: localize('gettingStarted.settings.description', "Tweak every aspect of VS Code and your extensions to your liking. Commonly used settings are listed first to get you started.\n[Tweak my Settings](command:toSide:workbench.action.openSettings)"),
doneOn: { commandExecuted: 'workbench.action.openSettings' },
media: {
type: 'image', altText: 'VS Code Settings', path: {
dark: 'dark/settings.png',
@ -339,7 +324,6 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
id: 'videoTutorial',
title: localize('gettingStarted.videoTutorial.title', "Lean back and learn"),
description: localize('gettingStarted.videoTutorial.description', "Watch the first in a series of short & practical video tutorials for VS Code's key features.\n[Watch Tutorial](https://aka.ms/vscode-getting-started-video)"),
doneOn: { eventFired: 'linkOpened:https://aka.ms/vscode-getting-started-video' },
media: { type: 'image', altText: 'VS Code Settings', path: 'tutorialVideo.png' },
}
]
@ -358,7 +342,6 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
id: 'playground',
title: localize('gettingStarted.playground.title', "Redefine your editing skills"),
description: localize('gettingStarted.playground.description', "Want to code faster and smarter? Practice powerful code editing features in the interactive playground.\n[Open Interactive Playground](command:toSide:workbench.action.showInteractivePlayground)"),
doneOn: { commandExecuted: 'workbench.action.showInteractivePlayground' },
media: {
type: 'image', altText: 'Interactive Playground.', path: {
dark: 'dark/playground.png',
@ -371,7 +354,6 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
id: 'splitview',
title: localize('gettingStarted.splitview.title', "Side by side editing"),
description: localize('gettingStarted.splitview.description', "Make the most of your screen estate by opening files side by side, vertically and horizontally.\n[Split Editor](command:workbench.action.splitEditor)"),
doneOn: { commandExecuted: 'workbench.action.splitEditor' },
media: {
type: 'image', altText: 'Multiple editors in split view.', path: {
dark: 'dark/splitview.png',
@ -385,7 +367,6 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
title: localize('gettingStarted.debug.title', "Watch your code in action"),
description: localize('gettingStarted.debug.description', "Accelerate your edit, build, test, and debug loop by setting up a launch configuration.\n[Run your Project](command:workbench.action.debug.selectandstart)"),
when: 'workspaceFolderCount != 0',
doneOn: { commandExecuted: 'workbench.action.debug.selectandstart' },
media: {
type: 'image', altText: 'Run and debug view.', path: {
dark: 'dark/debug.png',
@ -399,7 +380,6 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
title: localize('gettingStarted.scm.title', "Track your code with Git"),
description: localize('gettingStarted.scmClone.description', "Set up the built-in version control for your project to track your changes and collaborate with others.\n[Clone Repository](command:git.clone)"),
when: 'config.git.enabled && !git.missing && workspaceFolderCount == 0',
doneOn: { commandExecuted: 'git.clone' },
media: {
type: 'image', altText: 'Source Control view.', path: {
dark: 'dark/scm.png',
@ -413,7 +393,6 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
title: localize('gettingStarted.scm.title', "Track your code with Git"),
description: localize('gettingStarted.scmSetup.description', "Set up the built-in version control for your project to track your changes and collaborate with others.\n[Initialize Git Repository](command:git.init)"),
when: 'config.git.enabled && !git.missing && workspaceFolderCount != 0 && gitOpenRepositoryCount == 0',
doneOn: { commandExecuted: 'git.init' },
media: {
type: 'image', altText: 'Source Control view.', path: {
dark: 'dark/scm.png',
@ -425,9 +404,8 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
{
id: 'scm',
title: localize('gettingStarted.scm.title', "Track your code with Git"),
description: localize('gettingStarted.scm.description', "No more looking up Git commands! Git and GitHub workflows are seamlessly integrated.[Open Source Control](command:workbench.view.scm)"),
description: localize('gettingStarted.scm.description', "No more looking up Git commands! Git and GitHub workflows are seamlessly integrated.\n[Open Source Control](command:workbench.view.scm)"),
when: 'config.git.enabled && !git.missing && workspaceFolderCount != 0 && gitOpenRepositoryCount != 0 && activeViewlet != \'workbench.view.scm\'',
doneOn: { commandExecuted: 'workbench.view.scm.focus' },
media: {
type: 'image', altText: 'Source Control view.', path: {
dark: 'dark/scm.png',
@ -441,7 +419,6 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
title: localize('gettingStarted.tasks.title', "Automate your project tasks"),
when: 'workspaceFolderCount != 0',
description: localize('gettingStarted.tasks.description', "Create tasks for your common workflows and enjoy the integrated experience of running scripts and automatically checking results.\n[Run Auto-detected Tasks](command:workbench.action.tasks.runTask)"),
doneOn: { commandExecuted: 'workbench.action.tasks.runTask' },
media: {
type: 'image', altText: 'Task runner.', path: {
dark: 'dark/tasks.png',
@ -454,7 +431,6 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
id: 'shortcuts',
title: localize('gettingStarted.shortcuts.title', "Customize your shortcuts"),
description: localize('gettingStarted.shortcuts.description', "Once you have discovered your favorite commands, create custom keyboard shortcuts for instant access.\n[Keyboard Shortcuts](command:toSide:workbench.action.openGlobalKeybindings)"),
doneOn: { commandExecuted: 'workbench.action.openGlobalKeybindings' },
media: {
type: 'image', altText: 'Interactive shortcuts.', path: {
dark: 'dark/shortcuts.png',