diff --git a/ide/base/client/src/client/client.browser.ts b/ide/base/client/src/client/client.browser.ts new file mode 100644 index 00000000..d85596df --- /dev/null +++ b/ide/base/client/src/client/client.browser.ts @@ -0,0 +1,42 @@ +import { Languages } from '@blockception/ide-shared'; +import { LanguageClient, LanguageClientOptions } from 'vscode-languageclient/browser'; +import { Manager } from '../manager/manager'; +import * as vscode from 'vscode'; + +export function setupClientBrowser(context: vscode.ExtensionContext): void { + console.log('starting minecraft language client (browser/web worker)'); + + // Include vscode-vfs scheme used by vscode.dev / github.dev + const clientOptions: LanguageClientOptions = { + documentSelector: [ + { scheme: 'file', language: Languages.McFunctionIdentifier }, + { scheme: 'vscode-vfs', language: Languages.McFunctionIdentifier }, + { scheme: 'file', language: Languages.McLanguageIdentifier }, + { scheme: 'vscode-vfs', language: Languages.McLanguageIdentifier }, + { scheme: 'file', language: Languages.JsonIdentifier }, + { scheme: 'vscode-vfs', language: Languages.JsonIdentifier }, + { scheme: 'file', language: Languages.JsonCIdentifier }, + { scheme: 'vscode-vfs', language: Languages.JsonCIdentifier }, + { scheme: 'file', language: Languages.McProjectIdentifier }, + { scheme: 'vscode-vfs', language: Languages.McProjectIdentifier }, + { scheme: 'file', language: Languages.McMolangIdentifier }, + { scheme: 'vscode-vfs', language: Languages.McMolangIdentifier }, + ], + }; + + // Use Uri.joinPath on context.extensionUri — NOT context.asAbsolutePath — + // because asAbsolutePath is not available in the browser extension host. + const serverUri = vscode.Uri.joinPath(context.extensionUri, 'lsp', 'server.browser.js'); + const worker = new Worker(serverUri.toString(/* skipEncoding= */ true)); + + Manager.Client = new LanguageClient( + 'languageBlockceptionMinecraftClient', + 'LSP-BC Minecraft', + clientOptions, + worker, + ); + + Manager.Client.start().then(() => { + vscode.commands.executeCommand('setContext', 'ext:is_active', true); + }); +} diff --git a/ide/base/client/src/index.ts b/ide/base/client/src/index.ts index 83796e41..0a401a45 100644 --- a/ide/base/client/src/index.ts +++ b/ide/base/client/src/index.ts @@ -1,4 +1,5 @@ export * from './client'; +export * from './client/client.browser'; export * from './code'; export * from './commands'; export * from './console'; diff --git a/ide/base/server/src/index.ts b/ide/base/server/src/index.ts index 3bd52b06..e40433d3 100644 --- a/ide/base/server/src/index.ts +++ b/ide/base/server/src/index.ts @@ -1 +1,2 @@ export * from './lsp/server/setup'; +export * from './lsp/server/setup.browser'; diff --git a/ide/base/server/src/lsp/server/setup.browser.ts b/ide/base/server/src/lsp/server/setup.browser.ts new file mode 100644 index 00000000..a978cca9 --- /dev/null +++ b/ide/base/server/src/lsp/server/setup.browser.ts @@ -0,0 +1,13 @@ +// Browser-specific server setup — uses vscode-languageserver/browser +// instead of vscode-languageserver/node. +import { BrowserMessageReader, BrowserMessageWriter, createConnection, ProposedFeatures } from 'vscode-languageserver/browser'; +import { LSPConfig } from '../config/config'; +import { setupServerCore } from './setup.core'; + +export function setupServerBrowser(config: LSPConfig): void { + // self is the DedicatedWorkerGlobalScope inside the Web Worker + const messageReader = new BrowserMessageReader(self as DedicatedWorkerGlobalScope); + const messageWriter = new BrowserMessageWriter(self as DedicatedWorkerGlobalScope); + const connection = createConnection(ProposedFeatures.all, messageReader, messageWriter); + setupServerCore(connection, config); +} diff --git a/ide/base/server/src/lsp/server/setup.core.ts b/ide/base/server/src/lsp/server/setup.core.ts new file mode 100644 index 00000000..1cd5a5e6 --- /dev/null +++ b/ide/base/server/src/lsp/server/setup.core.ts @@ -0,0 +1,115 @@ +import { Connection, BulkRegistration, InitializeResult } from 'vscode-languageserver'; + +import { CodeActionService } from '../code-action/service'; +import { CodeLensService } from '../code-lens/service'; +import { CommandService } from '../commands/service'; +import { DataSetService } from '../dataset/service'; +import { CompletionService } from '../completion/service'; +import { ConfigurationService } from '../configuration/service'; +import { Database } from '../database/database'; +import { DiagnoserService } from '../diagnostics/service'; +import { DocumentManager, IDocumentManager } from '../documents/manager'; +import { ExtensionContext } from '../extension'; +import { FormatService } from '../format/service'; +import { ExtendedLogger } from '../logger/logger'; +import { DocumentProcessor, PackProcessor, WorkspaceProcessor } from '../process'; +import { DefinitionService, ImplementationService, ReferenceService, TypeDefinitionService } from '../references'; +import { SemanticsServer } from '../semantics/service'; +import { CapabilityBuilder } from '../services/capabilities'; +import { ServiceManager } from '../services/collection'; +import { SignatureService } from '../signatures/service'; +import { DocumentSymbolService } from '../symbols/document-service'; +import { WorkspaceSymbolService } from '../symbols/workspace-service'; +import { LSPConfig } from '../config/config'; + +export function setupServerCore(connection: Connection, config: LSPConfig): void { + const logger = new ExtendedLogger(connection.console); + const manager = new ServiceManager(logger); + const extension = new ExtensionContext(config, connection, manager, logger, {} as IDocumentManager, {} as Database); + const documents = new DocumentManager(logger, extension); + const database = new Database(logger, documents); + extension.documents = documents; + extension.database = database; + + const diagnoserService = new DiagnoserService(logger, extension, documents); + const documentProcessor = new DocumentProcessor(logger, extension, diagnoserService); + const packProcessor = new PackProcessor(logger, extension, documentProcessor); + const workspaceProcessor = new WorkspaceProcessor(logger, extension, packProcessor); + + manager + // Essentials + .add(new ConfigurationService(logger, extension), documents, database) + // Non Essentials + .add( + diagnoserService, + documentProcessor, + packProcessor, + workspaceProcessor, + new CodeActionService(logger, extension), + new CodeLensService(logger, extension), + new CommandService(logger, extension), + new DataSetService(logger, extension), + new CompletionService(logger, extension), + new DefinitionService(logger, extension), + new DocumentSymbolService(logger, extension), + new FormatService(logger, extension), + new ImplementationService(logger, extension), + new ReferenceService(logger, extension), + new SemanticsServer(logger, extension), + new SignatureService(logger, extension), + new TypeDefinitionService(logger, extension), + new WorkspaceSymbolService(logger, extension), + ); + + logger.info('starting minecraft server'); + + //Initialize + connection.onInitialize((params, token, workDoneProgress) => { + workDoneProgress.begin('initializing', 0); + + logger.info('Initializing minecraft server', { version: config.version }); + const result: InitializeResult = { + serverInfo: { + name: 'bc-minecraft-language-server', + version: config.version, + }, + capabilities: {}, + }; + const builder = new CapabilityBuilder(result.capabilities); + + extension.parseClientCapabilities(params.capabilities); + manager.onInitialize(builder, params, token, workDoneProgress); + + result.capabilities = builder.result(); + + workDoneProgress.done(); + return result; + }); + + // This handler provides diagnostics + connection.onInitialized(async () => { + logger.info('Initialized minecraft server', { version: config.version }); + manager.setupHandlers(connection); + + //Registers any follow ups + const register = BulkRegistration.create(); + manager.dynamicRegister(register); + await connection.client.register(register); + + return manager.start(); + }); + + // On shutdown handler + connection.onShutdown(() => { + logger.info('Shutting down server'); + manager.stop(); + }); + + connection.onExit(() => { + logger.info('exiting server'); + manager.dispose(); + }); + + // Listen on the connection + connection.listen(); +} diff --git a/ide/base/server/src/lsp/server/setup.ts b/ide/base/server/src/lsp/server/setup.ts index 1a16af14..68a2d884 100644 --- a/ide/base/server/src/lsp/server/setup.ts +++ b/ide/base/server/src/lsp/server/setup.ts @@ -1,118 +1,10 @@ -import { BulkRegistration, createConnection, InitializeResult, ProposedFeatures } from 'vscode-languageserver/node'; -import { CodeActionService } from '../code-action/service'; -import { CodeLensService } from '../code-lens/service'; -import { CommandService } from '../commands/service'; -import { DataSetService } from '../dataset/service'; -import { CompletionService } from '../completion/service'; -import { ConfigurationService } from '../configuration/service'; -import { Database } from '../database/database'; -import { DiagnoserService } from '../diagnostics/service'; -import { DocumentManager, IDocumentManager } from '../documents/manager'; -import { ExtensionContext } from '../extension'; -import { FormatService } from '../format/service'; -import { ExtendedLogger } from '../logger/logger'; -import { DocumentProcessor, PackProcessor, WorkspaceProcessor } from '../process'; -import { DefinitionService, ImplementationService, ReferenceService, TypeDefinitionService } from '../references'; -import { SemanticsServer } from '../semantics/service'; -import { CapabilityBuilder } from '../services/capabilities'; -import { ServiceManager } from '../services/collection'; -import { SignatureService } from '../signatures/service'; -import { DocumentSymbolService } from '../symbols/document-service'; -import { WorkspaceSymbolService } from '../symbols/workspace-service'; +import { createConnection, ProposedFeatures } from 'vscode-languageserver/node'; import { LSPConfig } from '../config/config'; +import { setupServerCore } from './setup.core'; -export function setupServer(config: LSPConfig) { +export function setupServer(config: LSPConfig): void { // Create a connection for the server, using Node's IPC as a transport. // Also include all preview / proposed LSP features. const connection = createConnection(ProposedFeatures.all); - - const logger = new ExtendedLogger(connection.console); - const manager = new ServiceManager(logger); - const extension = new ExtensionContext(config, connection, manager, logger, {} as IDocumentManager, {} as Database); - const documents = new DocumentManager(logger, extension); - const database = new Database(logger, documents); - extension.documents = documents; - extension.database = database; - - const diagnoserService = new DiagnoserService(logger, extension, documents); - const documentProcessor = new DocumentProcessor(logger, extension, diagnoserService); - const packProcessor = new PackProcessor(logger, extension, documentProcessor); - const workspaceProcessor = new WorkspaceProcessor(logger, extension, packProcessor); - - manager - // Essentials - .add(new ConfigurationService(logger, extension), documents, database) - // Non Essentials - .add( - diagnoserService, - documentProcessor, - packProcessor, - workspaceProcessor, - new CodeActionService(logger, extension), - new CodeLensService(logger, extension), - new CommandService(logger, extension), - new DataSetService(logger, extension), - new CompletionService(logger, extension), - new DefinitionService(logger, extension), - new DocumentSymbolService(logger, extension), - new FormatService(logger, extension), - new ImplementationService(logger, extension), - new ReferenceService(logger, extension), - new SemanticsServer(logger, extension), - new SignatureService(logger, extension), - new TypeDefinitionService(logger, extension), - new WorkspaceSymbolService(logger, extension), - ); - - logger.info('starting minecraft server'); - - //Initialize - connection.onInitialize((params, token, workDoneProgress) => { - workDoneProgress.begin('initializing', 0); - - logger.info('Initializing minecraft server', { version: config.version }); - const result: InitializeResult = { - serverInfo: { - name: 'bc-minecraft-language-server', - version: config.version, - }, - capabilities: {}, - }; - const builder = new CapabilityBuilder(result.capabilities); - - extension.parseClientCapabilities(params.capabilities); - manager.onInitialize(builder, params, token, workDoneProgress); - - result.capabilities = builder.result(); - - workDoneProgress.done(); - return result; - }); - - // This handler provides diagnostics - connection.onInitialized(async () => { - logger.info('Initialized minecraft server', { version: config.version }); - manager.setupHandlers(connection); - - //Registers any follow ups - const register = BulkRegistration.create(); - manager.dynamicRegister(register); - await connection.client.register(register); - - return manager.start(); - }); - - // On shutdown handler - connection.onShutdown(() => { - logger.info('Shutting down server'); - manager.stop(); - }); - - connection.onExit(() => { - logger.info('exiting server'); - manager.dispose(); - }); - - // Listen on the connection - connection.listen(); + setupServerCore(connection, config); } diff --git a/ide/vscode/package.json b/ide/vscode/package.json index 7bbb9692..39e3af6d 100644 --- a/ide/vscode/package.json +++ b/ide/vscode/package.json @@ -45,7 +45,8 @@ "vscode": "^1.69.2" }, "extensionKind": [ - "workspace" + "workspace", + "web" ], "devDependencies": { "@eslint/js": "^10.0.1", @@ -59,7 +60,9 @@ "ts-loader": "^9.5.4", "typescript": "^5.9.2", "typescript-eslint": "^8.57.2", - "webpack": "^5.105.4" + "webpack": "^5.105.4", + "@vscode/test-web": "^0.0.51", + "path-browserify": "^1.0.1" }, "dependencies": { "bc-minecraft-lsp": "*", @@ -91,7 +94,9 @@ "prewebpack": "npm run storeversion && npm run cp-schemas", "webpack:client": "webpack --mode production --config ./webpack.client.js", "webpack:server": "webpack --mode production --config ./webpack.server.js", - "webpack": "npm run webpack:client && npm run webpack:server" + "webpack": "npm run webpack:client && npm run webpack:server && npm run webpack:browser", + "webpack:browser": "webpack --mode production --config ./webpack.browser.js", + "chrome": "npm run webpack:browser && vscode-test-web --browserType=chromium --extensionDevelopmentPath=. ." }, "contributes": { "configuration": [ @@ -1587,5 +1592,6 @@ ] } ] - } + }, + "browser": "./lsp/client.browser.js" } diff --git a/ide/vscode/src/client.browser.ts b/ide/vscode/src/client.browser.ts new file mode 100644 index 00000000..924c83c7 --- /dev/null +++ b/ide/vscode/src/client.browser.ts @@ -0,0 +1,14 @@ +import * as vscode from 'vscode'; +import { setupClientBrowser, Manager } from 'bc-minecraft-lsp-client'; + +export function activate(context: vscode.ExtensionContext): void { + setupClientBrowser(context); +} + +export function deactivate(): Thenable | undefined { + console.log('stopping minecraft language client (browser)'); + if (!Manager.Client) { + return undefined; + } + return Manager.Client.stop(); +} diff --git a/ide/vscode/src/server.browser.ts b/ide/vscode/src/server.browser.ts new file mode 100644 index 00000000..f4081912 --- /dev/null +++ b/ide/vscode/src/server.browser.ts @@ -0,0 +1,4 @@ +import { setupServerBrowser } from 'bc-minecraft-lsp'; +import { Version } from './version'; + +setupServerBrowser({ version: Version }); diff --git a/ide/vscode/webpack.browser.js b/ide/vscode/webpack.browser.js new file mode 100644 index 00000000..185b8c62 --- /dev/null +++ b/ide/vscode/webpack.browser.js @@ -0,0 +1,58 @@ +//@ts-check +'use strict'; + +const path = require('path'); + +/** + * @param {{ entry: string, filename: string, libraryTarget: string, library?: string }} opts + * @returns {import('webpack').Configuration} + */ +function makeBrowserConfig({ entry, filename, libraryTarget, library }) { + return { + context: path.join(__dirname), + mode: 'none', + target: 'webworker', // web extensions run in a webworker context + + entry, + output: { + path: path.resolve(__dirname, 'lsp'), + filename, + libraryTarget, + ...(library ? { library } : {}), + devtoolModuleFilenameTemplate: '../[resource-path]', + }, + devtool: 'nosources-source-map', + externals: { + vscode: 'commonjs vscode', + }, + resolve: { + mainFields: ['module', 'main'], + extensions: ['.ts', '.js'], + fallback: { + path: require.resolve('path-browserify'), + }, + }, + module: { + rules: [ + { test: /\.ts$/, exclude: /node_modules/, use: [{ loader: 'ts-loader' }] }, + ], + }, + performance: { + hints: false, + }, + }; +} + +module.exports = [ + makeBrowserConfig({ + entry: './src/client.browser.ts', + filename: 'client.browser.js', + libraryTarget: 'commonjs', + }), + makeBrowserConfig({ + entry: './src/server.browser.ts', + filename: 'server.browser.js', + libraryTarget: 'var', + library: 'serverExportVar', + }), +];