add bare bones for a lsp

This commit is contained in:
bQUARKz 2026-05-05 13:39:24 +01:00
parent 6b60fa9d33
commit 52211ea02c
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
12 changed files with 1131 additions and 393 deletions

View File

@ -0,0 +1,3 @@
plugins {
id("gradle.java-library-conventions")
}

View File

@ -0,0 +1,9 @@
plugins {
id("gradle.java-library-conventions")
}
dependencies {
implementation(project(":prometeu-infra"))
implementation("org.eclipse.lsp4j:org.eclipse.lsp4j:1.0.0")
}

View File

@ -0,0 +1,271 @@
package p.studio.lsp;
import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.jsonrpc.Launcher;
import org.eclipse.lsp4j.launch.LSPLauncher;
import org.eclipse.lsp4j.services.*;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public final class PrometeuStudioLspMain {
private static final String HOST = "127.0.0.1";
private static final int PORT = 7777;
public static void main(String[] args) throws Exception {
InetAddress bindAddress = InetAddress.getByName(HOST);
try (ServerSocket serverSocket = new ServerSocket(PORT, 50, bindAddress)) {
System.out.println("Prometeu Studio LSP listening on " + HOST + ":" + PORT);
while (true) {
Socket socket = serverSocket.accept();
Thread clientThread = new Thread(() -> handleClient(socket));
clientThread.setName("prometeu-lsp-client");
clientThread.setDaemon(true);
clientThread.start();
}
}
}
private static void handleClient(Socket socket) {
try {
System.out.println("VS Code connected: " + socket.getRemoteSocketAddress());
InputStream input = socket.getInputStream();
OutputStream output = socket.getOutputStream();
PrometeuLanguageServer server = new PrometeuLanguageServer();
Launcher<LanguageClient> launcher =
LSPLauncher.createServerLauncher(server, input, output);
LanguageClient client = launcher.getRemoteProxy();
server.connect(client);
launcher.startListening();
} catch (Exception e) {
System.err.println("LSP client failed: " + e.getMessage());
e.printStackTrace();
}
}
private static final class PrometeuLanguageServer implements LanguageServer, LanguageClientAware {
private LanguageClient client;
private final TextDocumentService textDocumentService = new PrometeuTextDocumentService(this);
private final WorkspaceService workspaceService = new PrometeuWorkspaceService();
private boolean shutdownRequested = false;
@Override
public void connect(LanguageClient client) {
this.client = client;
if (textDocumentService instanceof PrometeuTextDocumentService service) {
service.connect(client);
}
}
public LanguageClient client() {
return client;
}
@Override
public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
ServerCapabilities capabilities = new ServerCapabilities();
TextDocumentSyncOptions syncOptions = new TextDocumentSyncOptions();
syncOptions.setOpenClose(true);
syncOptions.setChange(TextDocumentSyncKind.Full);
SaveOptions saveOptions = new SaveOptions();
saveOptions.setIncludeText(true);
syncOptions.setSave(saveOptions);
capabilities.setTextDocumentSync(syncOptions);
capabilities.setHoverProvider(true);
InitializeResult result = new InitializeResult(capabilities);
ServerInfo serverInfo = new ServerInfo();
serverInfo.setName("Prometeu Studio LSP");
serverInfo.setVersion("0.1.0");
result.setServerInfo(serverInfo);
return CompletableFuture.completedFuture(result);
}
@Override
public void initialized(InitializedParams params) {
if (client != null) {
client.logMessage(new MessageParams(
MessageType.Info,
"Hello from Prometeu Studio LSP"
));
}
}
@Override
public CompletableFuture<Object> shutdown() {
shutdownRequested = true;
return CompletableFuture.completedFuture(null);
}
@Override
public void exit() {
System.out.println("LSP client requested exit. shutdownRequested=" + shutdownRequested);
// Importantíssimo:
// Como isso está dentro do Studio, NÃO use System.exit().
}
@Override
public TextDocumentService getTextDocumentService() {
return textDocumentService;
}
@Override
public WorkspaceService getWorkspaceService() {
return workspaceService;
}
}
private static final class PrometeuTextDocumentService implements TextDocumentService {
private final PrometeuLanguageServer server;
private LanguageClient client;
private PrometeuTextDocumentService(PrometeuLanguageServer server) {
this.server = server;
}
private void connect(LanguageClient client) {
this.client = client;
}
@Override
public void didOpen(DidOpenTextDocumentParams params) {
String uri = params.getTextDocument().getUri();
String text = params.getTextDocument().getText();
System.out.println("didOpen: " + uri);
publishHelloDiagnostic(uri, text);
}
@Override
public void didChange(DidChangeTextDocumentParams params) {
String uri = params.getTextDocument().getUri();
String text = "";
if (!params.getContentChanges().isEmpty()) {
text = params.getContentChanges().get(0).getText();
}
System.out.println("didChange: " + uri);
publishHelloDiagnostic(uri, text);
}
@Override
public void didClose(DidCloseTextDocumentParams params) {
String uri = params.getTextDocument().getUri();
System.out.println("didClose: " + uri);
if (client != null) {
client.publishDiagnostics(new PublishDiagnosticsParams(uri, List.of()));
}
}
@Override
public void didSave(DidSaveTextDocumentParams params) {
String uri = params.getTextDocument().getUri();
System.out.println("didSave: " + uri);
if (client != null) {
client.logMessage(new MessageParams(
MessageType.Info,
"Prometeu Studio saw save: " + uri
));
}
}
@Override
public CompletableFuture<Hover> hover(HoverParams params) {
MarkupContent content = new MarkupContent();
content.setKind(MarkupKind.MARKDOWN);
content.setValue("""
**Prometeu Studio LSP**
Hello from Java + LSP4J.
This hover came from the Studio process.
""");
Hover hover = new Hover();
hover.setContents(content);
return CompletableFuture.completedFuture(hover);
}
private void publishHelloDiagnostic(String uri, String text) {
if (client == null) {
return;
}
Diagnostic diagnostic = new Diagnostic();
diagnostic.setRange(new Range(
new Position(0, 0),
new Position(0, Math.max(1, firstLineLength(text)))
));
diagnostic.setSeverity(DiagnosticSeverity.Information);
diagnostic.setSource("Prometeu Studio");
diagnostic.setMessage("Hello from Prometeu Studio LSP");
client.publishDiagnostics(new PublishDiagnosticsParams(
uri,
List.of(diagnostic)
));
}
private int firstLineLength(String text) {
if (text == null || text.isEmpty()) {
return 1;
}
int newline = text.indexOf('\n');
if (newline < 0) {
return text.length();
}
return newline;
}
}
private static final class PrometeuWorkspaceService implements WorkspaceService {
@Override
public void didChangeConfiguration(DidChangeConfigurationParams params) {
System.out.println("didChangeConfiguration");
}
@Override
public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) {
System.out.println("didChangeWatchedFiles: " + params.getChanges().size() + " changes");
}
}
}

View File

@ -5,9 +5,13 @@ plugins {
rootProject.name = "prometeu-studio" rootProject.name = "prometeu-studio"
include("prometeu-infra") include("prometeu-infra")
include("prometeu-packer:prometeu-packer-api") include("prometeu-packer:prometeu-packer-api")
include("prometeu-packer:prometeu-packer-v1") include("prometeu-packer:prometeu-packer-v1")
include("prometeu-lsp:prometeu-lsp-api")
include("prometeu-lsp:prometeu-lsp-v1")
include("prometeu-compiler:frontends:prometeu-frontend-pbs") include("prometeu-compiler:frontends:prometeu-frontend-pbs")
include("prometeu-compiler:prometeu-compiler-core") include("prometeu-compiler:prometeu-compiler-core")
include("prometeu-compiler:prometeu-build-pipeline") include("prometeu-compiler:prometeu-build-pipeline")

File diff suppressed because it is too large Load Diff

35
tools/vscode-extension/.gitignore vendored Normal file
View File

@ -0,0 +1,35 @@
# Dependencies
node_modules/
# Build output
out/
dist/
# VS Code package output
*.vsix
# TypeScript
*.tsbuildinfo
# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Test / coverage
coverage/
.vscode-test/
# Environment / local config
.env
.env.*
!.env.example
# OS files
.DS_Store
Thumbs.db
# Editor local state
.vscode/settings.json
.vscode/*.log

View File

@ -0,0 +1,17 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Prometeu VS Code Extension",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"preLaunchTask": "npm: compile"
}
]
}

View File

@ -0,0 +1,12 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "compile",
"group": "build",
"problemMatcher": "$tsc",
"label": "npm: compile"
}
]
}

139
tools/vscode-extension/package-lock.json generated Normal file
View File

@ -0,0 +1,139 @@
{
"name": "prometeu-vscode-extension",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "prometeu-vscode-extension",
"version": "0.1.0",
"dependencies": {
"vscode-languageclient": "^9.0.1"
},
"devDependencies": {
"@types/node": "^20.0.0",
"@types/vscode": "^1.90.0",
"typescript": "^5.0.0"
},
"engines": {
"vscode": "^1.90.0"
}
},
"node_modules/@types/node": {
"version": "20.19.39",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz",
"integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/@types/vscode": {
"version": "1.118.0",
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.118.0.tgz",
"integrity": "sha512-Ah6eTlqDcwIMELEVwQMO++rJAFBRz/oLluLD/vWdYrH1KuI9kfpaM+7pg0OvvascgcJy+ghLCERAYouM4QbzGw==",
"dev": true,
"license": "MIT"
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/minimatch": {
"version": "5.1.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
"integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
},
"node_modules/vscode-jsonrpc": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz",
"integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/vscode-languageclient": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-9.0.1.tgz",
"integrity": "sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==",
"license": "MIT",
"dependencies": {
"minimatch": "^5.1.0",
"semver": "^7.3.7",
"vscode-languageserver-protocol": "3.17.5"
},
"engines": {
"vscode": "^1.82.0"
}
},
"node_modules/vscode-languageserver-protocol": {
"version": "3.17.5",
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz",
"integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==",
"license": "MIT",
"dependencies": {
"vscode-jsonrpc": "8.2.0",
"vscode-languageserver-types": "3.17.5"
}
},
"node_modules/vscode-languageserver-types": {
"version": "3.17.5",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz",
"integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==",
"license": "MIT"
}
}
}

View File

@ -0,0 +1,69 @@
{
"name": "prometeu-vscode-extension",
"displayName": "Prometeu VS Code Extension",
"description": "VS Code bridge for Prometeu Studio.",
"version": "0.1.0",
"private": true,
"engines": {
"vscode": "^1.90.0"
},
"categories": [
"Programming Languages"
],
"activationEvents": [
"onLanguage:pbs"
],
"main": "./out/extension.js",
"contributes": {
"languages": [
{
"id": "pbs",
"aliases": [
"PBS",
"Prometeu Base Script"
],
"extensions": [
".pbs",
".barrel"
]
}
],
"commands": [
{
"command": "prometeu.connectStudioLsp",
"title": "Prometeu: Connect to Studio LSP"
},
{
"command": "prometeu.restartStudioLsp",
"title": "Prometeu: Restart Studio LSP"
}
],
"configuration": {
"title": "Prometeu",
"properties": {
"prometeu.studio.host": {
"type": "string",
"default": "127.0.0.1",
"description": "Prometeu Studio LSP host."
},
"prometeu.studio.port": {
"type": "number",
"default": 7777,
"description": "Prometeu Studio LSP TCP port."
}
}
}
},
"scripts": {
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./"
},
"dependencies": {
"vscode-languageclient": "^9.0.1"
},
"devDependencies": {
"@types/node": "^20.0.0",
"@types/vscode": "^1.90.0",
"typescript": "^5.0.0"
}
}

View File

@ -0,0 +1,159 @@
import * as net from "node:net";
import * as vscode from "vscode";
import {
LanguageClient,
LanguageClientOptions,
ServerOptions,
StreamInfo
} from "vscode-languageclient/node";
let client: LanguageClient | undefined;
let output: vscode.OutputChannel;
export function activate(context: vscode.ExtensionContext): void {
output = vscode.window.createOutputChannel("Prometeu LSP");
output.appendLine("Prometeu VS Code extension activated.");
const connectCommand = vscode.commands.registerCommand(
"prometeu.connectStudioLsp",
async () => {
await startClient(context);
}
);
const restartCommand = vscode.commands.registerCommand(
"prometeu.restartStudioLsp",
async () => {
await stopClient();
await startClient(context);
}
);
context.subscriptions.push(output, connectCommand, restartCommand);
void startClient(context);
}
export async function deactivate(): Promise<void> {
await stopClient();
}
async function startClient(context: vscode.ExtensionContext): Promise<void> {
if (client) {
output.appendLine("Prometeu LSP client is already running.");
return;
}
const config = vscode.workspace.getConfiguration("prometeu.studio");
const host = config.get<string>("host", "127.0.0.1");
const port = config.get<number>("port", 7777);
output.show(true);
output.appendLine(`Connecting to Prometeu Studio LSP at ${host}:${port}...`);
const serverOptions: ServerOptions = async (): Promise<StreamInfo> => {
return new Promise<StreamInfo>((resolve, reject) => {
const socket = net.createConnection({ host, port });
let settled = false;
const connectTimeout = setTimeout(() => {
if (settled) {
return;
}
settled = true;
socket.destroy();
reject(new Error("Connection timed out."));
}, 3000);
socket.once("connect", () => {
if (settled) {
return;
}
settled = true;
clearTimeout(connectTimeout);
output.appendLine("TCP connection established with Prometeu Studio.");
resolve({
reader: socket,
writer: socket
});
});
socket.once("error", (error) => {
if (settled) {
return;
}
settled = true;
clearTimeout(connectTimeout);
reject(error);
});
});
};
const clientOptions: LanguageClientOptions = {
documentSelector: [
{
scheme: "file",
language: "pbs"
}
],
synchronize: {
fileEvents: [
vscode.workspace.createFileSystemWatcher("**/*.pbs")
]
},
initializationOptions: {
clientKind: "vscode",
prometeuClientVersion: context.extension.packageJSON.version
},
outputChannel: output
};
client = new LanguageClient(
"prometeuStudioLsp",
"Prometeu Studio LSP",
serverOptions,
clientOptions
);
try {
await client.start();
output.appendLine("Prometeu Studio LSP client started.");
vscode.window.showInformationMessage("Connected to Prometeu Studio LSP.");
} catch (error) {
client = undefined;
const message = error instanceof Error ? error.message : String(error);
output.appendLine(`Failed to start Prometeu LSP client: ${message}`);
vscode.window.showErrorMessage(`Could not connect to Prometeu Studio LSP: ${message}`);
}
}
async function stopClient(): Promise<void> {
if (!client) {
return;
}
const currentClient = client;
client = undefined;
output.appendLine("Stopping Prometeu Studio LSP client...");
try {
await currentClient.stop();
output.appendLine("Prometeu Studio LSP client stopped.");
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
output.appendLine(`Failed to stop Prometeu LSP client cleanly: ${message}`);
}
}

View File

@ -0,0 +1,20 @@
{
"compilerOptions": {
"module": "Node16",
"target": "ES2022",
"lib": [
"ES2022"
],
"outDir": "out",
"rootDir": "src",
"sourceMap": true,
"strict": true,
"moduleResolution": "Node16",
"esModuleInterop": true,
"skipLibCheck": true
},
"exclude": [
"node_modules",
".vscode-test"
]
}