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

Do not start TypeSpec Language Server when there is no workspace opened #5413

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- typespec-vscode
---

Do not start TypeSpec Language Server when there is no workspace opened
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: internal
packages:
- "@typespec/compiler"
---

Use inspect instead of Json.stringify when logging object in server log
4 changes: 2 additions & 2 deletions packages/compiler/src/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { mkdir, writeFile } from "fs/promises";
import inspector from "inspector";
import { join } from "path";
import { fileURLToPath } from "url";
import { inspect } from "util";
import { TextDocument } from "vscode-languageserver-textdocument";
import {
ApplyWorkspaceEditParams,
Expand Down Expand Up @@ -50,8 +51,7 @@ function main() {
let detail: string | undefined = undefined;
let fullMessage = message;
if (log.detail) {
detail =
typeof log.detail === "string" ? log.detail : JSON.stringify(log.detail, undefined, 2);
detail = typeof log.detail === "string" ? log.detail : inspect(log.detail, undefined, 2);
fullMessage = `${message}:\n${detail}`;
}

Expand Down
35 changes: 21 additions & 14 deletions packages/typespec-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ export async function activate(context: ExtensionContext) {
async (args: RestartServerCommandArgs | undefined): Promise<TspLanguageClient> => {
return vscode.window.withProgress(
{
title: "Restarting TypeSpec language service...",
title: args?.notificationMessage ?? "Restarting TypeSpec language service...",
location: vscode.ProgressLocation.Notification,
},
async () => {
if (args?.forceRecreate === true) {
logger.info("Forcing to recreate TypeSpec LSP server...");
return await recreateLSPClient(context, args?.popupRecreateLspError);
return await recreateLSPClient(context);
}
if (client && client.state === State.Running) {
await client.restart();
Expand All @@ -65,7 +65,7 @@ export async function activate(context: ExtensionContext) {
logger.info(
"TypeSpec LSP server is not running which is not expected, try to recreate and start...",
);
return recreateLSPClient(context, args?.popupRecreateLspError);
return recreateLSPClient(context);
}
},
);
Expand Down Expand Up @@ -97,26 +97,33 @@ export async function activate(context: ExtensionContext) {
}),
);

return await vscode.window.withProgress(
{
title: "Launching TypeSpec language service...",
location: vscode.ProgressLocation.Notification,
},
async () => {
await recreateLSPClient(context);
},
);
// Only try to start language server when some workspace has been opened
// because the LanguageClient class will popup error notification in vscode directly if failing to start
// which will be confusing to user if no workspace is opened (i.e. in Create TypeSpec project scenario)
if ((vscode.workspace.workspaceFolders?.length ?? 0) > 0) {
return await vscode.window.withProgress(
{
title: "Launching TypeSpec language service...",
location: vscode.ProgressLocation.Notification,
},
async () => {
await recreateLSPClient(context);
},
);
} else {
logger.info("No workspace opened, Skip starting TypeSpec language service.");
}
}

export async function deactivate() {
await client?.stop();
}

async function recreateLSPClient(context: ExtensionContext, showPopupWhenError?: boolean) {
async function recreateLSPClient(context: ExtensionContext) {
logger.info("Recreating TypeSpec LSP server...");
const oldClient = client;
client = await TspLanguageClient.create(context, outputChannel);
await oldClient?.stop();
await client.start(showPopupWhenError ?? (vscode.workspace.workspaceFolders?.length ?? 0) > 0);
await client.start();
return client;
}
7 changes: 4 additions & 3 deletions packages/typespec-vscode/src/tsp-language-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,10 @@ export class TspLanguageClient {
}
}

async start(showPopupWhenError: boolean): Promise<void> {
async start(): Promise<void> {
try {
if (this.client.needsStart()) {
// please be aware that this method would popup error notification in vscode directly
await this.client.start();
logger.info("TypeSpec server started");
} else {
Expand All @@ -162,13 +163,13 @@ export class TspLanguageClient {
" - TypeSpec server path is configured with https://github.com/microsoft/typespec#installing-vs-code-extension.",
].join("\n"),
[],
{ showOutput: false, showPopup: showPopupWhenError },
{ showOutput: false, showPopup: true },
);
logger.error("Error detail", [e]);
} else {
logger.error("Unexpected error when starting TypeSpec server", [e], {
showOutput: false,
showPopup: showPopupWhenError,
showPopup: true,
});
}
}
Expand Down
26 changes: 25 additions & 1 deletion packages/typespec-vscode/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,36 @@ export interface InstallGlobalCliCommandArgs {
confirm: boolean;
confirmTitle?: string;
confirmPlaceholder?: string;
/**
* set to true to disable popup notification and show output channel when running the command
*/
silentMode?: boolean;
}

export interface RestartServerCommandArgs {
/**
* whether to recreate TspLanguageClient instead of just restarting it
*/
forceRecreate: boolean;
popupRecreateLspError: boolean;
notificationMessage?: string;
}

export const enum ResultCode {
Success = "success",
Fail = "fail",
Cancelled = "cancelled",
Timeout = "timeout",
}

interface SuccessResult<T> {
code: ResultCode.Success;
value: T;
details?: any;
}

interface UnsuccessResult {
code: ResultCode.Fail | ResultCode.Cancelled | ResultCode.Timeout;
details?: any;
}

export type Result<T> = SuccessResult<T> | UnsuccessResult;
9 changes: 5 additions & 4 deletions packages/typespec-vscode/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { CancellationToken } from "vscode";
import { Executable } from "vscode-languageclient/node.js";
import logger from "./log/logger.js";
import { isUrl } from "./path-utils.js";
import { ResultCode } from "./types.js";

export async function isFile(path: string) {
try {
Expand Down Expand Up @@ -278,8 +279,8 @@ export function spawnExecution(
}

/**
* if the operation is cancelled, the promise will be rejected with reason==="cancelled"
* if the operation is timeout, the promise will be rejected with reason==="timeout"
* if the operation is cancelled, the promise will be rejected with {@link ResultCode.Cancelled}
* if the operation is timeout, the promise will be rejected with {@link ResultCode.Timeout}
*
* @param action
* @param token
Expand All @@ -293,10 +294,10 @@ export function createPromiseWithCancelAndTimeout<T>(
) {
return new Promise<T>((resolve, reject) => {
token.onCancellationRequested(() => {
reject("cancelled");
reject(ResultCode.Cancelled);
});
setTimeout(() => {
reject("timeout");
reject(ResultCode.Timeout);
}, timeoutInMs);
action.then(resolve, reject);
});
Expand Down
Loading
Loading