diff --git a/.vscode/launch.json b/.vscode/launch.json index 7dca8e1e8..1c118d3b9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,6 +12,20 @@ "request": "launch", "type": "extensionHost" }, + { + "env": { + "LF_LS_PORT": "7670" + }, + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "name": "Launch Extension (Socket)", + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ], + "request": "launch", + "type": "extensionHost" + }, { "args": [ "--extensionDevelopmentPath=${workspaceFolder}", diff --git a/lingua-franca b/lingua-franca index 9d863b312..c23c41116 160000 --- a/lingua-franca +++ b/lingua-franca @@ -1 +1 @@ -Subproject commit 9d863b31291c4018b4e626318228f3250ead3bb3 +Subproject commit c23c41116ad5e0fb461f72c1d1c479b8d6230968 diff --git a/src/build_lds.ts b/src/build_lds.ts index 44111f8ed..ba9a071e1 100644 --- a/src/build_lds.ts +++ b/src/build_lds.ts @@ -12,6 +12,7 @@ import simpleGit, { SimpleGit } from 'simple-git' import { Command, OptionValues } from 'commander' import * as config from './config' import { exit } from 'process'; +import { execSync } from 'child_process' import { bold, green, red } from 'colorette' import which from 'which' import { javacVersionChecker, VersionCheckResult } from './version_checker'; @@ -34,30 +35,17 @@ function getOpts() { } /** - * Copy jars produced by the Maven build. + * Copy jar produced by the Gradle build. */ -function copyJars() { +function copyJar() { if (fs.existsSync(config.libDirPath)) { rimraf.sync(config.libDirPath); } fs.mkdirSync(config.libDirPath); // Copy the LDS jar. - fs.copyFileSync(config.ldsJarFile, + fs.copyFileSync(config.sourceLdsJarFile, path.join(config.libDirPath, config.ldsJarName)) - - // Copy SWT plugins, needed by LDS. - fs.readdirSync(config.swtJarsDirPath).forEach( - (name: string) => { - let found = name.match(config.swtJarRegex) - if (found !== null) { - // Copy file, strip version numbers. - fs.copyFileSync(path.join(config.swtJarsDirPath, name), - path.join(config.libDirPath, - name.replace(found.groups.version, ''))) - } - } - ) } /** @@ -129,14 +117,13 @@ async function build() { } else { await fetchDeps(opts).catch((err) => { console.log(err); process.exit(1)}) } - const mvn = (require('maven')).create({ - cwd: repo - }); - console.log("> starting Maven build...") - mvn.execute(['clean', 'package', '-P', 'lds', '-U'], { 'skipTests' : 'true' }) - .then(() => { - copyJars() - }); + console.log("> starting Gradle build...") + try { + execSync("./gradlew generateLanguageDiagramServer", { cwd: repo, stdio: 'inherit' }); + copyJar(); + } catch(e) { + console.error(e); + } } /** diff --git a/src/config.ts b/src/config.ts index 4e350bb93..f53cf1bf5 100644 --- a/src/config.ts +++ b/src/config.ts @@ -57,12 +57,6 @@ export const rtiVersion: Version = new Version('0.0.0'); /** Name of the Language and Diagram Server jar. */ export const ldsJarName = 'lflang-lds.jar'; -/** Regex for matching SWT jars and capturing their version number. */ -export const swtJarRegex = /org\.eclipse\.swt\..+(?\.x86.+)\.jar$/; - -/** Name of the Language and Diagram Server package. */ -export const pkgName = 'org.lflang.lds'; - /** Name of the Lingua Franca repo. */ export const repoName = 'lingua-franca'; @@ -78,21 +72,7 @@ export const baseDirPath = path.resolve(path.dirname(require.main.filename), '.. /** Absolute path to the directory in which to put the jar files. */ export const libDirPath = path.resolve(baseDirPath, libDirName); -/** Absolute path to the directory in which to find the SWT jar files. */ -export const swtJarsDirPath = path.resolve(baseDirPath, - path.join(repoName, pkgName, 'target', 'repository', 'plugins')); - /** Absolute path to the language and diagram server jar. */ -export const ldsJarFile = path.resolve(baseDirPath, - path.join(repoName, pkgName, 'target', 'exe', ldsJarName)); - -/** Dictionary mapping OSes to the names of their corresponding SWT jars. */ -export const swtJarsByOs = defaultDict('org.eclipse.swt.gtk.linux.jar')({ - 'win32': 'org.eclipse.swt.win32.win32.jar', - 'darwin': 'org.eclipse.swt.cocoa.macosx.jar' -}); - -/** Dictionary mapping OSes to their corresponding classpath separators. */ -export const classPathSeparatorsByOs = defaultDict(':')({ - 'win32': ';', -}); +// TODO handle version in file name +export const sourceLdsJarFile = path.resolve(baseDirPath, + path.join(repoName, 'org.lflang.diagram', 'build', 'libs', 'org.lflang.diagram-0.3.1-SNAPSHOT-lds.jar')); diff --git a/src/extension.ts b/src/extension.ts index 4934eac6c..7bb42c8f4 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -6,13 +6,15 @@ import * as fs from 'fs'; import { Trace } from 'vscode-jsonrpc'; import * as vscode from 'vscode'; -import { LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient'; +import { connect, NetConnectOpts, Socket } from 'net'; +import { LanguageClient, LanguageClientOptions, ServerOptions, StreamInfo } from 'vscode-languageclient'; import { legend, semanticTokensProvider } from './highlight'; import * as config from './config'; import { registerBuildCommands } from './build_commands'; import * as checkDependencies from './check_dependencies'; let client: LanguageClient; +let socket: Socket export async function activate(context: vscode.ExtensionContext) { @@ -30,20 +32,8 @@ export async function activate(context: vscode.ExtensionContext) { (vscode.window.showErrorMessage) () )) return; - const jarInLibDir = filename => context.asAbsolutePath(path.join(config.libDirName, filename)); - const ldsJar = jarInLibDir(config.ldsJarName); - const swtJar = jarInLibDir(config.swtJarsByOs[os.platform()]); - console.assert(fs.existsSync(ldsJar)); - const javaArgs = [ - '-cp', - ldsJar + config.classPathSeparatorsByOs[os.platform()] + swtJar, - 'org.lflang.diagram.lsp.LanguageDiagramServer' - ]; - - const serverOptions: ServerOptions = { - run : { command: 'java', args: javaArgs }, - debug: { command: 'java', args: javaArgs, options: { env: createDebugEnv() } } - }; + + const serverOptions: ServerOptions = createServerOptions(context); const clientOptions: LanguageClientOptions = { documentSelector: ['lflang'], @@ -68,6 +58,48 @@ export async function activate(context: vscode.ExtensionContext) { registerBuildCommands(context, client); } +/** + * Depending on the launch configuration, returns {@link ServerOptions} that either + * connects to a socket or starts the LS as a process. It uses a socket if the + * environment variable `LF_LS_PORT` is present. Otherwise it runs the jar located + * at `lib/lflang-lds.jar`. + */ + function createServerOptions(context: vscode.ExtensionContext): ServerOptions { + // Connect to language server via socket if a port is specified as an env variable + if (typeof process.env.LF_LS_PORT !== 'undefined') { + const connectionInfo: NetConnectOpts = { + port: parseInt(process.env.LF_LS_PORT, 10), + }; + console.log('Connecting to language server on port: ', connectionInfo.port); + + return async () => { + socket = connect(connectionInfo); + const result: StreamInfo = { + writer: socket, + reader: socket, + }; + return result; + }; + } else { // Start LDS Jar + const ldsJar = context.asAbsolutePath(path.join(config.libDirName, config.ldsJarName)); + + console.log('Spawning the language server as a process.'); + console.assert(fs.existsSync(ldsJar)); + + return { + run: { + command: 'java', + args: ['-Djava.awt.headless=true', '-jar', ldsJar] + }, + debug: { + command: 'java', + args: ['-Djava.awt.headless=true', '-jar', ldsJar], + options: { env: createDebugEnv() } + }, + }; + } +} + function createDebugEnv() { return Object.assign({ JAVA_OPTS:'-Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=8000,suspend=n,quiet=y' @@ -78,5 +110,12 @@ export function deactivate(): Thenable | undefined { if (!client) { return undefined; } - return client.stop(); + if (socket) { + // Don't call client.stop when we are connected via socket for development. + // That call will end the LS server, leading to a bad dev experience. + socket.end(); + return; + } else { + return client.stop(); + } }