Skip to content

Contribute to the extension

Josh Tynjala edited this page Jan 25, 2018 · 6 revisions

Extension Architecture

The extension is divided into two parts.

  1. The entry point launched by VSCode is src/ts/extension.ts, written in TypeScript. This part is mainly used to launch the Java language server in a separate process. There are a few things that the language server protocol can't do, like define commands, so there is some extra stuff in there too. However, the most important part of extension.ts is spawning the new process.

  2. The part written in Java that is spawned in a separate process is the language server. When VSCode needs to do something that requires language intelligence (like completion, signature help, find references, jump to symbol, etc.), it makes requests to the language server. It uses a special socket protocol for communication, which is implemented by the Eclipse LSP4J Java library, so it's all behind the scenes. Our job is simply to write implementations for methods defined by LSP4J's interfaces. These methods will get called automatically when VSCode makes a request.

Java classes

Here's what each of the Java classes do:

com.nextgenactionscript.vscode.Main

This is the entry point of the JAR. It sets up the socket that communicates with VSCode and instantiates the ActionScriptLanguageServer class.

com.nextgenactionscript.vscode.ActionScriptLanguageServer

This class implements the org.eclipse.lsp4j.services.LanguageServer interface defined by LSP4J. It's purpose is mostly to tell VSCode about the language server's capabilities.

  • The initialize() method is used to declare which feature the language server supports.
  • getWorkspaceService() returns an object that mostly delegates to the TextDocumentService (see below), where the bulk of everything happens. This could probably be a little cleaner, but it works well enough.
  • getTextDocumentService() returns com.nextgenactionscript.vscode.ActionScriptTextDocumentService, an implementation of TextDocumentService.

com.nextgenactionscript.vscode.ActionScriptTextDocumentService

This class implements the org.eclipse.lsp4j.services.TextDocumentService interface defined by LSP4J. It's methods will be called when VSCode makes requests over the language server protocol. The purpose of methods like completion(), signatureHelp(), definition(), references(), and rename() (among others) should be pretty obvious.

From the compiler, this class creates a Workspace and a RoyaleProject. It parses the asconfig.json file to get the compiler configuration options. To get the definitions, ActionScriptTextDocumentService creates invisible compilation units for the main class(es) in an app, and the compiler finds all of its dependencies and creates regular compilation units automatically. Everything stays in memory between requests, and VSCode tells the language server when any ActionScript files are added, removed, or changed. This results in calls to fileAdded(), fileRemoved(), and fileChanged() on the Workspace, which will update only the compilation units that have changed. Making a new Workspace and starting fresh with every request from VSCode does not offer good enough performance for larger projects.

com.nextgenactionscript.vscode.LanguageServerFileSpecGetter

Implements the org.apache.flex.compiler.common.IFileSpecificationGetter interface from the compiler. Returns an implementation of org.apache.flex.compiler.filespecs.IFileSpecification for a specific file path. If a file is open in memory, it returns a org.apache.flex.compiler.filespecs.StringFileSpecification, and if it's only on the file system, it's a regular org.apache.flex.compiler.filespecs.FileSpecification. VSCode notifies the language server about files in memory with didOpen(), didClose(), didSave(), and didChange() on the ActionScriptTextDocumentService class.

com.nextgenactionscript.asconfig.*

The classes in this package store data from asconfig.json.

Build the project

You will need Apache Maven, which will automatically download dependencies and build the extension. To debug the extension, you need Visual Studio Code, obviously.

Open the root folder of the repository in Visual Studio Code. Switch to the Debug view. Make sure that "Launch Extension" is selected in the drop-down. Click the green play button to start debugging in VSCode's Extension Host. VSCode will automatically build the project before launching.

To build from the command line, run the following command in the root folder of the repository:

mvn clean package -s settings-template.xml

Running the extension during development

  1. Open the Debug view in Visual Studio Code.

  2. Select Launch Extension in the drop down.

  3. Press the green play button to start debugging. You may also press the F5 key.

Debugging the Java code

When you run the extension in the Visual Studio Code debugger, you can launch the Java process with the following extra argument (requires modification to extension.ts:

-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

This allows remote debugging over port 5005.

In IntelliJ IDEA, you can add a new Remote configuration to your module with the following settings:

  • Transport: Socket
  • Debugger Mode: Attach
  • Host: localhost
  • Port: 5005

After launching the extension, start debugging in IntelliJ IDEA. It should attach to the running Java process.

Clone this wiki locally