Skip to content

Commit dd0b687

Browse files
authored
Support Emitter section in init-template when creating TypeSpec project in vscode (#5594)
Support Emitter section in init-template when creating TypeSpec project in vscode fixes: #5426
1 parent ff056eb commit dd0b687

File tree

6 files changed

+235
-20
lines changed

6 files changed

+235
-20
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
changeKind: feature
3+
packages:
4+
- "@typespec/compiler"
5+
- typespec-vscode
6+
---
7+
8+
Support Emitters section in Init Template when creating TypeSpec project in vscode

packages/compiler/src/server/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import {
3939
import { TextDocument, TextEdit } from "vscode-languageserver-textdocument";
4040
import type { CompilerHost, Program, SourceFile, TypeSpecScriptNode } from "../core/index.js";
4141
import { LoadedCoreTemplates } from "../init/core-templates.js";
42-
import { InitTemplate, InitTemplateLibrarySpec } from "../init/init-template.js";
42+
import { EmitterTemplate, InitTemplate, InitTemplateLibrarySpec } from "../init/init-template.js";
4343
import { ScaffoldingConfig } from "../init/scaffold.js";
4444

4545
export type ServerLogLevel = "trace" | "debug" | "info" | "warning" | "error";
@@ -172,3 +172,4 @@ export interface InitProjectContext {
172172
export type InitProjectConfig = ScaffoldingConfig;
173173
export type InitProjectTemplate = InitTemplate;
174174
export type InitProjectTemplateLibrarySpec = InitTemplateLibrarySpec;
175+
export type InitProjectTemplateEmitterTemplate = EmitterTemplate;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { LogLevel } from "rollup";
2+
import vscode from "vscode";
3+
import { normalizePath } from "./path-utils.js";
4+
5+
export interface StartUpMessage {
6+
/**
7+
* the message to show in the popup notification
8+
*/
9+
popupMessage: string;
10+
/**
11+
* the detail logged to the Output window
12+
*/
13+
detail: string;
14+
/**
15+
* the level used to show notification and log
16+
*/
17+
level: LogLevel;
18+
}
19+
20+
/** manage data stored in vscode extension's state (ExtensionContext.globalState/workspaceState) */
21+
export class ExtensionStateManager {
22+
constructor(private vscodeContext: vscode.ExtensionContext) {}
23+
24+
private getValue<T>(key: string, defaultValue: T, isGlobal: boolean): T {
25+
return isGlobal
26+
? this.vscodeContext.globalState.get(key, defaultValue)
27+
: this.vscodeContext.workspaceState.get(key, defaultValue);
28+
}
29+
30+
/**
31+
*
32+
* @param key
33+
* @param value must be JSON stringifyable, set to undefined to delete the key
34+
* @param isGlobal
35+
*/
36+
private setValue<T>(key: string, value: T | undefined, isGlobal: boolean) {
37+
isGlobal
38+
? this.vscodeContext.globalState.update(key, value)
39+
: this.vscodeContext.workspaceState.update(key, value);
40+
}
41+
42+
private getStartUpMessageKey(workspaceFolder: string) {
43+
const ON_START_UP_MESSAGE_KEY_PREFIX = "onStartUpMessage-";
44+
const path = normalizePath(workspaceFolder);
45+
return `${ON_START_UP_MESSAGE_KEY_PREFIX}${path}`;
46+
}
47+
48+
saveStartUpMessage(msg: StartUpMessage, workspaceFolder: string) {
49+
const key = this.getStartUpMessageKey(workspaceFolder);
50+
this.setValue(key, msg, true);
51+
}
52+
loadStartUpMessage(workspaceFolder: string): StartUpMessage | undefined {
53+
const key = this.getStartUpMessageKey(workspaceFolder);
54+
const value = this.getValue<StartUpMessage | undefined>(key, undefined, true);
55+
return value;
56+
}
57+
cleanUpStartUpMessage(workspaceFolder: string) {
58+
const key = this.getStartUpMessageKey(workspaceFolder);
59+
this.setValue(key, undefined, true);
60+
}
61+
}

packages/typespec-vscode/src/extension.ts

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import vscode, { commands, ExtensionContext, TabInputText } from "vscode";
22
import { State } from "vscode-languageclient";
33
import { createCodeActionProvider } from "./code-action-provider.js";
4-
import { ExtensionLogListener } from "./log/extension-log-listener.js";
4+
import { ExtensionStateManager } from "./extension-state-manager.js";
5+
import { ExtensionLogListener, getPopupAction } from "./log/extension-log-listener.js";
56
import logger from "./log/logger.js";
67
import { TypeSpecLogOutputChannel } from "./log/typespec-log-output-channel.js";
78
import { createTaskProvider } from "./task-provider.js";
@@ -12,6 +13,7 @@ import {
1213
RestartServerCommandArgs,
1314
SettingName,
1415
} from "./types.js";
16+
import { isWhitespaceStringOrUndefined } from "./utils.js";
1517
import { createTypeSpecProject } from "./vscode-cmd/create-tsp-project.js";
1618
import { emitCode } from "./vscode-cmd/emit-code/emit-code.js";
1719
import { installCompilerGlobally } from "./vscode-cmd/install-tsp-compiler.js";
@@ -25,6 +27,8 @@ const outputChannel = new TypeSpecLogOutputChannel("TypeSpec");
2527
logger.registerLogListener("extension-log", new ExtensionLogListener(outputChannel));
2628

2729
export async function activate(context: ExtensionContext) {
30+
const stateManager = new ExtensionStateManager(context);
31+
2832
context.subscriptions.push(createTaskProvider());
2933

3034
context.subscriptions.push(createCodeActionProvider());
@@ -99,7 +103,7 @@ export async function activate(context: ExtensionContext) {
99103

100104
context.subscriptions.push(
101105
commands.registerCommand(CommandName.CreateProject, async () => {
102-
await createTypeSpecProject(client);
106+
await createTypeSpecProject(client, stateManager);
103107
}),
104108
);
105109

@@ -143,7 +147,7 @@ export async function activate(context: ExtensionContext) {
143147
return t.input.uri.fsPath.endsWith(".tsp");
144148
}) >= 0
145149
) {
146-
return await vscode.window.withProgress(
150+
await vscode.window.withProgress(
147151
{
148152
title: "Launching TypeSpec language service...",
149153
location: vscode.ProgressLocation.Notification,
@@ -155,6 +159,7 @@ export async function activate(context: ExtensionContext) {
155159
} else {
156160
logger.info("No workspace opened, Skip starting TypeSpec language service.");
157161
}
162+
showStartUpMessages(stateManager);
158163
}
159164

160165
export async function deactivate() {
@@ -169,3 +174,35 @@ async function recreateLSPClient(context: ExtensionContext) {
169174
await client.start();
170175
return client;
171176
}
177+
178+
function showStartUpMessages(stateManager: ExtensionStateManager) {
179+
vscode.workspace.workspaceFolders?.forEach((workspaceFolder) => {
180+
const msg = stateManager.loadStartUpMessage(workspaceFolder.uri.fsPath);
181+
if (msg) {
182+
logger.log("debug", "Start up message found for folder: " + workspaceFolder.uri.fsPath);
183+
if (isWhitespaceStringOrUndefined(msg.detail)) {
184+
logger.log(msg.level, msg.popupMessage, [], {
185+
showPopup: true,
186+
});
187+
} else {
188+
const SHOW_DETAIL = "View Details in Output";
189+
const popupAction = getPopupAction(msg.level);
190+
if (popupAction) {
191+
popupAction(msg.popupMessage, SHOW_DETAIL).then((action) => {
192+
if (action === SHOW_DETAIL) {
193+
outputChannel.show(true);
194+
}
195+
// log the start up message to Output no matter user clicked the button or not
196+
// and there are many logs coming when starting the extension, so
197+
// log the message when the popup is clicked (or disappearing) to make sure these logs are shown at the end of the Output window to catch
198+
// user's attention.
199+
logger.log(msg.level, msg.popupMessage + "\n", [msg.detail]);
200+
});
201+
}
202+
}
203+
} else {
204+
logger.log("debug", "No start up message found for folder: " + workspaceFolder.uri.fsPath);
205+
}
206+
stateManager.cleanUpStartUpMessage(workspaceFolder.uri.fsPath);
207+
});
208+
}

packages/typespec-vscode/src/log/extension-log-listener.ts

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,75 @@
11
import vscode from "vscode";
2-
import { LogItem, LogListener, LogOptions } from "./logger.js";
2+
import { LogItem, LogLevel, LogListener, LogOptions } from "./logger.js";
33

44
export interface ExtensionLogOptions extends LogOptions {
55
/** show the Output window in vscode */
6-
showOutput: boolean;
6+
showOutput?: boolean;
77
/** show the log in vscode popup */
8-
showPopup: boolean;
8+
showPopup?: boolean;
9+
/** the text of the button in the popup notification, default is 'View details in Output' */
10+
popupButtonText?: string;
11+
/** callback when the button in the popup notification is clicked, default is to open the TypeSpec Output */
12+
onPopupButtonClicked?: () => void;
13+
}
14+
15+
export function getPopupAction(loglevel: LogLevel) {
16+
switch (loglevel) {
17+
case "error":
18+
return vscode.window.showErrorMessage;
19+
case "warn":
20+
return vscode.window.showWarningMessage;
21+
case "debug":
22+
case "info":
23+
return vscode.window.showInformationMessage;
24+
default: // trace
25+
return undefined;
26+
}
927
}
1028

1129
export class ExtensionLogListener implements LogListener {
1230
constructor(private outputChannel?: vscode.LogOutputChannel) {}
1331

1432
Log(log: LogItem) {
1533
const VIEW_DETAIL_IN_OUTPUT = "View details in Output";
16-
const { showOutput, showPopup } = log.options ?? { showOutput: false, showPopup: false };
34+
const { showOutput, showPopup, popupButtonText, onPopupButtonClicked } = log.options ?? {
35+
showOutput: false,
36+
showPopup: false,
37+
};
1738
let popupAction: ((msg: string, ...items: string[]) => Thenable<string>) | undefined;
1839
switch (log.level) {
1940
case "error":
2041
this.outputChannel?.error(log.message, ...(log.details ?? []));
21-
popupAction = vscode.window.showErrorMessage;
42+
popupAction = getPopupAction(log.level);
2243
break;
2344
case "trace":
2445
this.outputChannel?.trace(log.message, ...(log.details ?? []));
2546
break;
2647
case "debug":
2748
this.outputChannel?.debug(log.message, ...(log.details ?? []));
28-
popupAction = vscode.window.showInformationMessage;
49+
popupAction = getPopupAction(log.level);
2950
break;
3051
case "info":
3152
this.outputChannel?.info(log.message, ...(log.details ?? []));
32-
popupAction = vscode.window.showInformationMessage;
53+
popupAction = getPopupAction(log.level);
3354
break;
3455
case "warn":
3556
this.outputChannel?.warn(log.message, ...(log.details ?? []));
36-
popupAction = vscode.window.showWarningMessage;
57+
popupAction = getPopupAction(log.level);
3758
break;
3859
}
3960
if (showOutput && this.outputChannel) {
4061
this.outputChannel.show(true /*preserveFocus*/);
4162
}
4263

4364
if (showPopup && popupAction) {
44-
void popupAction(log.message, VIEW_DETAIL_IN_OUTPUT).then((value) => {
45-
if (value === VIEW_DETAIL_IN_OUTPUT) {
46-
this.outputChannel?.show(true /*preserveFocus*/);
65+
const buttonText = popupButtonText ?? VIEW_DETAIL_IN_OUTPUT;
66+
void popupAction(log.message, buttonText).then((value) => {
67+
if (value === buttonText) {
68+
if (onPopupButtonClicked) {
69+
onPopupButtonClicked();
70+
} else {
71+
this.outputChannel?.show(true /*preserveFocus*/);
72+
}
4773
}
4874
});
4975
}

0 commit comments

Comments
 (0)