@softvisio/cli
Version:
Softisio CLI Tool
221 lines (172 loc) • 6.42 kB
JavaScript
import "#core/stream";
import childProcess from "node:child_process";
import fs from "node:fs";
import net from "node:net";
import path from "node:path";
import url from "node:url";
import File from "#core/file";
import Server from "#core/http/server";
import openUrl from "#core/open-url";
import { TmpFile } from "#core/tmp";
import { lintFile } from "#lib/lint";
import Api from "#lib/rpc/api";
import * as constants from "#lib/rpc/constants";
export default class RpcServer {
// public
async start ( { daemon } = {} ) {
var res;
const api = new Api();
// exit current server
await api.call( "exit" );
// start daemon
if ( daemon ) {
const proc = childProcess.spawn( process.argv[ 0 ], [ process.argv[ 1 ], "lsp", "start", "--no-daemon" ], {
"detached": true,
"stdio": "ignore",
} );
proc.unref();
return result( 200 );
}
// start server
else {
res = await this.#startLspServer();
if ( !res.ok ) return res;
res = await this.#startApiServer();
if ( !res.ok ) return res;
return new Promise( resolve => {} );
}
}
// protected
async [ "_API_exit" ] () {
process.exit();
}
async [ "_API_initialize" ] ( data ) {
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initializeresult
return result( 200, {
"serverInfo": {
"name": "softvisio",
},
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#serverCapabilities
"capabilities": {
"positionEncoding": "utf8",
// "textDocumentSync": {},
// "documentFormattingProvider": true,
// "documentRangeFormattingProvider": false,
},
} );
}
async [ "_API_softvisio/lint-file" ] ( data ) {
if ( data.path == null || data.path === "" ) data.path = "temp";
const file = new File( {
"path": path.isAbsolute( data.path )
? data.path
: path.join( data.cwd, data.path ),
"buffer": data.buffer,
} );
return lintFile( file, {
"action": data.action,
"type": data.type,
"processUnsupportedTypes": true,
} );
}
async [ "_API_softvisio/open-browser" ] ( data ) {
if ( process.platform !== "win32" ) return;
data.data.replaceAll( "\t", " " );
data.data.replaceAll( "&", "&" );
data.data.replaceAll( "<", "<" );
data.data.replaceAll( ">", ">" );
data.data.replaceAll( `"`, """ );
data.data.replaceAll( "'", "'" );
if ( data.font ) data.font = data.font.split( ":" )[ 0 ].replaceAll( "_", " " );
const html = `
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=${ data.encoding }">
</head>
<body>
<pre style="font-family: '${ data.font }' !important; font-size: 12pt; white-space: pre-wrap;">
${ data.data }
</pre>
</body>
</html>
`;
const tmp = new TmpFile( { "extname": ".html" } );
fs.writeFileSync( tmp.path, html );
openUrl( url.pathToFileURL( tmp.path ) );
return result( 200 );
}
// private
async #startApiServer () {
const server = new Server().ws( "/*", {
"maxPayloadLength": constants.maxPayloadSize,
"onConnect": connection => {
connection.on( "message", async ( connection, data, isBinary ) => {
try {
const msg = JSON.parse( Buffer.from( data ) );
if ( msg.method ) {
msg.method = msg.method.replace( /^\/v\d+\//, "" );
}
const res = await this.#processMessage( msg );
if ( !res ) return;
connection.send( JSON.stringify( res.toJsonRpc( msg.id ) ), false );
}
catch {}
} );
},
} );
return server.start( {
"address": constants.host,
"port": constants.apiPort,
} );
}
async #startLspServer () {
const server = net.createServer( async socket => {
while ( true ) {
try {
const headers = await socket.readHttpHeaders();
if ( !headers ) break;
const length = headers.match( /content-length: *(\d+)/im )?.[ 1 ];
if ( !length ) break;
const data = await socket.readChunk( Number( length ) );
if ( !data ) break;
const msg = JSON.parse( data );
// shutdown
if ( msg.method === "shutdown" ) {
break;
}
else {
const res = await this.#processMessage( msg );
if ( !res ) continue;
if ( msg.method.startsWith( "softvisio/" ) ) {
this.#sendLspResponse( socket, msg.id, res );
}
else {
this.#sendLspResponse( socket, msg.id, res.data );
}
}
}
catch {}
}
} );
server.listen( constants.lspPort, constants.host );
return result( 200 );
}
async #processMessage ( msg ) {
if ( !msg.id ) return;
if ( !msg.method ) return;
const method = "_API_" + msg.method;
if ( !this[ method ] ) {
return result( [ 404, "API method not found" ] );
}
if ( !Array.isArray( msg.params ) ) msg.params = [ msg.params ];
return this[ method ]( ...msg.params );
}
#sendLspResponse ( socket, id, result ) {
const data = Buffer.from( JSON.stringify( {
"jsonrpc": "2.0",
id,
result,
} ) );
socket.write( `Content-Length: ${ data.length }\r\n\r\n${ data.toString() }` );
}
}