VSCode Extension with LSP in C#, how to terminate the LSP dll? - language-server-protocol

I have a very simple extension that loads a DLL that will contain my LSP project.
namespace SimpleLsp
{
class Program
{
static async Task Main(string[] args)
{
var server = await OmniSharp.Extensions.LanguageServer.Server.LanguageServer
.From(
options => options
.WithInput(Console.OpenStandardInput())
.WithOutput(Console.OpenStandardOutput())
).ConfigureAwait(false);
await server.WaitForExit;
}
}
}
and the TypeScript extension
import * as vscode from 'vscode';
import path = require('path');
import { workspace } from 'vscode';
import {
LanguageClient,
LanguageClientOptions,
ServerOptions,
TransportKind,
} from "vscode-languageclient/node";
let client: LanguageClient;
export function activate(context: vscode.ExtensionContext) {
console.log('Congratulations, your extension "simplelspextension" is now active!');
const extensionPath = vscode.extensions.getExtension("PauloAboimPinto.simplelspextension")?.extensionPath as string;
const libsFolder = path.join(extensionPath, "libs");
const dllPath = path.join(libsFolder, "simpleLsp.dll");
let serverOptions: ServerOptions = {
run: {
command: "dotnet",
args: [dllPath],
transport: TransportKind.pipe
},
debug: {
command: "dotnet",
args: [dllPath],
transport: TransportKind.pipe,
runtime: ""
}
};
let clientOptions: LanguageClientOptions = {
documentSelector: [
{
pattern: "**/*.xaml",
},
{
pattern: "**/*.axaml",
},
{
pattern: "**/*.csproj",
},
],
synchronize: {
// Notify the server about file changes to '.clientrc files contained in the workspace
fileEvents: workspace.createFileSystemWatcher('**/.axaml')
}
};
client = new LanguageClient(
"simpleLsp",
"Simple Language Server Protocol",
serverOptions,
clientOptions
);
let disposableLsp = client.start();
context.subscriptions.push(disposableLsp);
}
// this method is called when your extension is deactivated
export function deactivate() {
if (!client) {
return undefined;
}
return client.stop();
}
During deactivation I'm sending the stop command to the client, expecting the execution DLL to be terminated, but it's not.
When I close the VSCode I would expect the simpleLsp.dll to be terminated but it's not and I have to terminate it manually.
What I'm missing or doing wrong here?
How I can terminate the execution of the DLL that contains the LSP?
thanks in advance

Related

Vite keeps refreshing the entire page

I migrated from CRA to Vite and it keeps refreshing the entire page.
I saw this issue #7131 on GitHub and that's not my problem.
Here's my vite.config.js:
import { defineConfig, loadEnv } from 'vite'
import react from '#vitejs/plugin-react'
const path = require(`path`)
const aliases = {
'#Form': 'src/Components/Form/Exports',
'#List': 'src/Components/List/Exports',
'#Browse': 'src/Components/Browse/Browse',
'#Tree': 'src/Components/Tree/Exports',
'#Tab': 'src/Components/Tab/Exports',
'#Dashboard': 'src/Components/Dashboard/Dashboard',
'#Panel': 'src/Panel/Panel',
}
const resolvedAliases = Object.fromEntries(
Object.entries(aliases).map(([key, value]) => [key, path.resolve(__dirname, value)]),
)
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, '.')
console.log(env)
const htmlPlugin = () => {
return {
name: "html-transform",
transformIndexHtml(html) {
return html.replace(/%(.*?)%/g, function (match, p1) {
return env[p1]
})
},
}
}
return {
resolve: {
alias: resolvedAliases
},
plugins: [react(), htmlPlugin()],
server: {
host: '0.0.0.0',
hmr: {
clientPort: 443
}
}
}
})
Since my project contains hundreds of components, and it use OAuth and third party authenticators, each reload takes a considerable amount of time (maybe 5 seconds).
This hugely slows down the development.
What should I do to fix this?
What should I do to prevent this?

When I run my bot, the `ready` event doesn't fire but the bot is online

When I run my bot, the ready event doesn't fire but the bot is online. My event handler is in the start method in the Client.ts class,
and I execute the start method in the index.ts file.
My client class: ./classes/Client.ts
import { Client as DiscordClient, ClientOptions, Collection } from 'discord.js';
import fs from 'fs';
import path from 'path';
export class Client extends DiscordClient {
commandarray: any[] = [];
commands: Collection<string, any> = new Collection();
constructor (options: ClientOptions, token: string) {
super(options);
this.login(token);
}
async start() {
//Event Handler
const eventDirectories = await fs.readdirSync('./events');
for (const dir of eventDirectories) {
const eventFiles = await fs.readdirSync(`./events/${dir}`).filter(file => file.endsWith(".ts"));
if (eventFiles.length <= 0)
return console.log("[EVENT HANDLER] - Cannot find any events!");
for (const file of eventFiles) {
const event = require(`../events/${dir}/${file}`);
if (event.once) {
this.once(event.name, (...args) => event.execute(...args, this));
} else {
this.on(event.name, (...args) => event.execute(...args));
}
}
}
// Slash Command Handler
const cmdDirectories = await fs.readdirSync('./commands');
for (const dir of cmdDirectories) {
const cmdFiles = await fs.readdirSync(`./commands/${dir}`).filter(file => file.endsWith(".ts"));
if (cmdFiles.length <= 0)
return console.log("[COMMAND HANDLER] - Cannot find any commands!");
for (const file of cmdFiles) {
const command = require(`../commands/${dir}/${file}`)
await this.commandarray.push(command);
await this.commands.set(command.name, command);
}
}
}
};
My index.ts file: ./index.ts
import { Client } from "./classes/Client";
import { config } from "dotenv";
config();
export const client: Client = new Client({ intents: 515 }, process.env.token!);
client.start();
My ready event: ./events/Client/ready.ts
import { Client } from '../../classes/Client';
export default {
name: 'ready',
once: true,
async execute(client: Client) {
await console.log(`Logged in as ${client.user?.tag}`);
await client.application?.commands.set(client.commandarray);
}
}
Edit, I fixed the first issue thanks to Rahuletto, but found a new one and have updated the question accordingly.
It's a wrong path. You should show ur path structure (file format).
I think it's ../events/Client/ready.ts.
I used export default to export the event
So, instead of using event.<property/function> in the handler, I should use event.default.<property/function>

how to capture nestjs bootstrap errors in a log file

I'm trying to capture all logs(bootstrap, app error messages, db connection error messages) into a single log file in nestjs.
As of now I'm using a custom logger to so. Below is my custom logger code
logger.ts
import * as winston from 'winston';
import * as chalk from 'chalk';
import PrettyError from 'pretty-error';
import { LoggerOptions } from 'winston';
export class LoggerService {
private readonly logger: winston.Logger;
private readonly prettyError = new PrettyError();
public static loggerOptions: LoggerOptions = {
transports: [
new winston.transports.File({
filename: 'logs/mgmtserver-main.log',
format: winston.format.json()
}),
],
};
constructor(private context: string, transport?) {
this.logger = (winston as any).createLogger(LoggerService.loggerOptions);
this.prettyError.skipNodeFiles();
this.prettyError.skipPackage('express', '#nestjs/common', '#nestjs/core');
}
get Logger(): winston.Logger {
return this.logger;
}
static configGlobal(options?: LoggerOptions) {
this.loggerOptions = options;
}
log(message: string): void {
const currentDate = new Date();
this.logger.info(message, {
timestamp: currentDate.toISOString(),
context: this.context,
});
this.formatedLog('info', message);
}
error(message: string, trace?: any): void {
const currentDate = new Date();
this.logger.error(`${message} -> (${trace || 'trace not provided !'})`, {
timestamp: currentDate.toISOString(),
context: this.context,
});
this.formatedLog('error', message, trace);
}
warn(message: string): void {
const currentDate = new Date();
this.logger.warn(message, {
timestamp: currentDate.toISOString(),
context: this.context,
});
this.formatedLog('warn', message);
}
overrideOptions(options: LoggerOptions) {
this.logger.configure(options);
}
// this method just for printing a cool log in your terminal , using chalk
private formatedLog(level: string, message: string, error?): void {
let result = '';
const color = chalk.default;
const currentDate = new Date();
const time = `${currentDate.getHours()}:${currentDate.getMinutes()}:${currentDate.getSeconds()}`;
switch (level) {
case 'info':
result = `[${color.blue('INFO')}] ${color.dim.yellow.bold.underline(time)} [${color.green(
this.context,
)}] ${message}`;
break;
case 'error':
result = `[${color.red('ERR')}] ${color.dim.yellow.bold.underline(time)} [${color.green(
this.context,
)}] ${message}`;
break;
case 'warn':
result = `[${color.yellow('WARN')}] ${color.dim.yellow.bold.underline(time)} [${color.green(
this.context,
)}] ${message}`;
break;
default:
break;
}
console.log(result);
}
}
I'm able to log application erro messages(err, warn, info) using the above logger in any file like below
import { LoggerService } from 'logger';
private readonly logger: LoggerService = new LoggerService(RegistrationService.name);
this.logger.warn('this is a warn message');
my main.ts looks like below
import { ValidationPipe, Logger } from "#nestjs/common";
import { NestFactory } from "#nestjs/core";
import { ConfigService } from '#nestjs/config';
import { AppModule } from "./app.module";
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
import { LoggerService } from "logger";
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: new LoggerService('Main'), abortOnError: false
});
app.enableCors();
await app.listen(3000);
console.log(`Application is running on: ${await app.getUrl()}`);
}
bootstrap();
The issue is I'm not able to capture the Nestfactory.create. bootstrap errors in the log file. They are getting printed on the console but not to log file.
For example, the below bootstrap errors are getting printed on console but not into the log file.
[INFO] 15:12:50 [Main] Starting Nest application...
[ERR] 15:12:50 [Main] Nest cannot create the AuthorisationModule instance.
The module at index [3] of the AuthorisationModule "imports" array is undefined.
Potential causes:
- A circular dependency between modules. Use forwardRef() to avoid it. Read more: https://docs.nestjs.com/fundamentals/circular-dependency
- The module at index [3] is of type "undefined". Check your import statements and the type of the module.
Please help me. Your help is much appreciated.
That is because your logger module isn't initialized until Nest finishes initializing the App context. Your logger will capture all the errors after the your App is running but not before.
However you can make use of node's inbuilt events to log/file these exceptions
process.on('uncaughtException', err => {
console.error('There was an uncaught error', err)
process.exit(1) //mandatory (as per the Node.js docs)
})
or, for Promises,
process.on('unhandledRejection', err => {
console.error('There was an uncaught error', err)
process.exit(1) //mandatory (as per the Node.js docs)
})
https://nodejs.dev/learn/error-handling-in-nodejs#catching-uncaught-exceptions

How do you generate typings for protobuf files for use with GRPC?

I am trying to use GRPC with TypeScript, and I am trying to make sure I have all the types set (rather than just adding my own mapping or using any.
I've gotten as far as with problems I am experiencing noted in the comments.
import { Server, loadPackageDefinition } from "grpc";
import { loadSync } from "#grpc/proto-loader";
const packageDefinition = loadSync(__dirname + "/protos/artifact.proto");
const artifacts = loadPackageDefinition(packageDefinition).artifacts;
// what are the types here?
function SignedUrlPutObject(call, callback) {
}
const server = new Server();
// There's no ArtifactUpload defined in artifacts
server.addService(artifacts.ArtifactUpload.service, { SignedUrlPutObject })
Another approach I tried was to use pbjs and pbts.
"protobuf": "pbjs -t static-module -w commonjs -o protos.js protos/artifact-upload.proto && pbts -o protos.d.ts protos.js",
This generated the typings file, but I can't get it to work with grpc. Here's a github issue I found that may be related https://github.com/protobufjs/protobuf.js/issues/1381
There's 3 main tools you can use:
ts-protoc-gen
#grpc/proto-loader
grpc_tools_node_protoc_ts
I recommend using proto-loader:
npm i #grpc/proto-loader
You can then generate the types like so:
./node_modules/.bin/proto-loader-gen-types --longs=String --enums=String --defaults --oneofs --grpcLib=#grpc/grpc-js --outDir=proto/ proto/*.proto
Here's the proto file I use for this example:
syntax = "proto3";
package example_package;
message ServerMessage {
string server_message = 1;
}
message ClientMessage {
string client_message = 1;
}
service Example {
rpc unaryCall(ClientMessage) returns (ServerMessage) {}
rpc serverStreamingCall(ClientMessage) returns (stream ServerMessage) {}
rpc clientStreamingCall(stream ClientMessage) returns (ServerMessage) {}
rpc bidirectionalStreamingCall(stream ClientMessage) returns (stream ServerMessage) {}
}
Once the types are generated, you can consume them like so:
import * as grpc from '#grpc/grpc-js';
import * as protoLoader from '#grpc/proto-loader';
import { ProtoGrpcType } from './proto/example';
import { ClientMessage } from './proto/example_package/ClientMessage';
import { ExampleHandlers } from './proto/example_package/Example';
import { ServerMessage } from './proto/example_package/ServerMessage';
const host = '0.0.0.0:9090';
const exampleServer: ExampleHandlers = {
unaryCall(
call: grpc.ServerUnaryCall<ClientMessage, ServerMessage>,
callback: grpc.sendUnaryData<ServerMessage>
) {
if (call.request) {
console.log(`(server) Got client message: ${call.request.clientMessage}`);
}
callback(null, {
serverMessage: 'Message from server',
});
},
serverStreamingCall(
call: grpc.ServerWritableStream<ClientMessage, ServerMessage>
) {
call.write({
serverMessage: 'Message from server',
});
},
clientStreamingCall(
call: grpc.ServerReadableStream<ClientMessage, ServerMessage>
) {
call.on('data', (clientMessage: ClientMessage) => {
console.log(
`(server) Got client message: ${clientMessage.clientMessage}`
);
});
},
bidirectionalStreamingCall(
call: grpc.ServerDuplexStream<ClientMessage, ServerMessage>
) {
call.write({
serverMessage: 'Message from server',
});
call.on('data', (clientMessage: ClientMessage) => {
console.log(
`(server) Got client message: ${clientMessage.clientMessage}`
);
});
},
};
function getServer(): grpc.Server {
const packageDefinition = protoLoader.loadSync('./proto/example.proto');
const proto = (grpc.loadPackageDefinition(
packageDefinition
) as unknown) as ProtoGrpcType;
const server = new grpc.Server();
server.addService(proto.example_package.Example.service, exampleServer);
return server;
}
if (require.main === module) {
const server = getServer();
server.bindAsync(
host,
grpc.ServerCredentials.createInsecure(),
(err: Error | null, port: number) => {
if (err) {
console.error(`Server error: ${err.message}`);
} else {
console.log(`Server bound on port: ${port}`);
server.start();
}
}
);
}
I've created various examples of how to use gRPC with TypeScript here: https://github.com/badsyntax/grpc-js-types

How do you to implement a GRPC server in TypeScript?

I am trying to use #grpc/proto-loader to do dynamic code generation of the protobuf files to implement a simple server but in Typescript.
I've gotten as far as
import { Server, loadPackageDefinition, ServerCredentials } from "grpc";
import { loadSync } from "#grpc/proto-loader";
const packageDefinition = loadSync(__dirname + "/protos/ArtifactUpload.proto");
const protoDescriptor = loadPackageDefinition(packageDefinition);
const impl = {
};
const server = new Server();
server.addService(protoDescriptor.ArtifactUpload, impl);
server.bind('0.0.0.0:50051', ServerCredentials.createInsecure());
server.start();
So I have two problems
in the Javascript examples they use protoDescriptor.XXX.service however, there's no service property in protoDescriptor.ArtifactUpload
if I try to add implementation methods in impl, the compiler also fails to compile.
Since the Javascript example works, I am thinking that questions along the line of add new property in Typescript object may be able to add the necessary service type. However, I haven't had luck so far.
My Protobuf is
syntax = "proto3";
service ArtifactUpload {
rpc SignedUrlPutObject (UploadRequest) returns (SignedUrlPutObjectResponse) {}
}
message UploadRequest {
string message = 1;
}
message SignedUrlPutObjectResponse {
string reply = 1;
}
[Updated on 14 May 2021]: TypeScript generation via #grpc/proto-loader is now released with version 0.6.0! I've updated my example here to reflect this. You can now install the latest version of proto loader with npm i #grpc/proto-loader which will contain the TS generation script. The instructions below are still valid.
You can use the proto-loader to generate types.
First, install the proto-loader:
npm i #grpc/proto-loader
You can then generate the types like so:
./node_modules/.bin/proto-loader-gen-types --longs=String --enums=String --defaults --oneofs --grpcLib=#grpc/grpc-js --outDir=proto/ proto/*.proto
Here's the proto file I use for this example:
syntax = "proto3";
package example_package;
message ServerMessage {
string server_message = 1;
}
message ClientMessage {
string client_message = 1;
}
service Example {
rpc unaryCall(ClientMessage) returns (ServerMessage) {}
rpc serverStreamingCall(ClientMessage) returns (stream ServerMessage) {}
rpc clientStreamingCall(stream ClientMessage) returns (ServerMessage) {}
rpc bidirectionalStreamingCall(stream ClientMessage) returns (stream ServerMessage) {}
}
Once the types are generated, you can consume them like so:
import * as grpc from '#grpc/grpc-js';
import * as protoLoader from '#grpc/proto-loader';
import { ProtoGrpcType } from './proto/example';
import { ClientMessage } from './proto/example_package/ClientMessage';
import { ExampleHandlers } from './proto/example_package/Example';
import { ServerMessage } from './proto/example_package/ServerMessage';
const host = '0.0.0.0:9090';
const exampleServer: ExampleHandlers = {
unaryCall(
call: grpc.ServerUnaryCall<ClientMessage, ServerMessage>,
callback: grpc.sendUnaryData<ServerMessage>
) {
if (call.request) {
console.log(`(server) Got client message: ${call.request.clientMessage}`);
}
callback(null, {
serverMessage: 'Message from server',
});
},
serverStreamingCall(
call: grpc.ServerWritableStream<ClientMessage, ServerMessage>
) {
call.write({
serverMessage: 'Message from server',
});
},
clientStreamingCall(
call: grpc.ServerReadableStream<ClientMessage, ServerMessage>
) {
call.on('data', (clientMessage: ClientMessage) => {
console.log(
`(server) Got client message: ${clientMessage.clientMessage}`
);
});
},
bidirectionalStreamingCall(
call: grpc.ServerDuplexStream<ClientMessage, ServerMessage>
) {
call.write({
serverMessage: 'Message from server',
});
call.on('data', (clientMessage: ClientMessage) => {
console.log(
`(server) Got client message: ${clientMessage.clientMessage}`
);
});
},
};
function getServer(): grpc.Server {
const packageDefinition = protoLoader.loadSync('./proto/example.proto');
const proto = (grpc.loadPackageDefinition(
packageDefinition
) as unknown) as ProtoGrpcType;
const server = new grpc.Server();
server.addService(proto.example_package.Example.service, exampleServer);
return server;
}
if (require.main === module) {
const server = getServer();
server.bindAsync(
host,
grpc.ServerCredentials.createInsecure(),
(err: Error | null, port: number) => {
if (err) {
console.error(`Server error: ${err.message}`);
} else {
console.log(`Server bound on port: ${port}`);
server.start();
}
}
);
}
I've created various examples of how to use gRPC with TypeScript here: https://github.com/badsyntax/grpc-js-typescript
I got it working in the end as follows:
In package.json I had the following:
{
...
"scripts": {
"start": "node index.js",
"build": "pbjs -t static-module -w commonjs -o protos.js protos/*.proto && pbts -o protos.d.ts protos.js && tsc",
},
"dependencies": {
"#grpc/proto-loader": "^0.5.5",
"google-protobuf": "^3.13.0",
"grpc": "^1.24.4",
"typescript": "^4.0.5"
},
"devDependencies": {
"#types/node": "^14.14.7",
"protobufjs": "^6.10.1"
}
}
import { Server, loadPackageDefinition, ServerCredentials, GrpcObject, ServiceDefinition, handleUnaryCall } from "grpc";
import { ISignedUrlPutObjectResponse, IUploadRequest, SignedUrlPutObjectResponse } from "./protos";
import { loadSync } from "#grpc/proto-loader";
const packageDefinition = loadSync(__dirname + "/protos/ArtifactUpload.proto");
interface IArtifactUpload {
signedUrlPutObject: handleUnaryCall<IUploadRequest, ISignedUrlPutObjectResponse>;
}
interface ServerDefinition extends GrpcObject {
service: any
}
interface ServerPackage extends GrpcObject {
[name: string]: ServerDefinition
}
const protoDescriptor = loadPackageDefinition(packageDefinition) as ServerPackage;
const server = new Server();
server.addService<IArtifactUpload>(protoDescriptor.ArtifactUpload.service, {
signedUrlPutObject(call, callback) {
console.log(call.request.message);
console.log(callback);
callback(null, SignedUrlPutObjectResponse.create({ reply: "hello " + call.request.message }));
}
});
server.bind('0.0.0.0:50051', ServerCredentials.createInsecure());
server.start();
I use protobufjs to build some of the typings though they are mostly unused as it is not fully compatible with GRPC. However, it does save time with the request and response typings.
I still needed to create the server typings and apply it to the protoDescriptor. Repeating it here for emphasis.
interface IArtifactUpload {
signedUrlPutObject(call: ServerUnaryCall<IUploadRequest>, callback: ArtifactUpload.SignedUrlPutObjectCallback): void;
}
interface ServerDefinition extends GrpcObject {
service: any;
}
interface ServerPackage extends GrpcObject {
[name: string]: ServerDefinition
}
I used any for the service as it was the only one that allowed me to avoid putting in anything specific to IArtifactUpload Ideally the typing for GrpcObject which at present is
export interface GrpcObject {
[name: string]: GrpcObject | typeof Client | ProtobufMessage;
}
should try to provide an object that represents the server.
I linked my solution to https://github.com/protobufjs/protobuf.js/issues/1017#issuecomment-725064230 in case there's a better way that I am missing.

Resources