From 3b7c29d1e6df8e1a913b069bcef82134bd629f9a Mon Sep 17 00:00:00 2001 From: Henrik Jess Nielsen Date: Sun, 10 May 2026 18:17:14 +0200 Subject: [PATCH] feat: add landing page at / Clean HTML page describing iLSP, what LSP is, the WebSocket endpoints, and setup instructions for Neovim, IntelliJ and VS Code. Live stats (Bicep modules, pipeline templates, PyPI packages) are rendered from the in-memory catalog at request time. No emojis. Minimal CSS, no external dependencies. --- ilsp/server.py | 297 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 297 insertions(+) diff --git a/ilsp/server.py b/ilsp/server.py index baf5d25..413d060 100644 --- a/ilsp/server.py +++ b/ilsp/server.py @@ -107,11 +107,306 @@ async def _ws_proxy(request: web.Request, host: str, port: int) -> web.WebSocket return ws +_INDEX_HTML = """\ + + + + + + iLSP — Language Server Proxy + + + +
+

iLSP

+ Language Server Proxy + self-hosted +
+ +
+ +

+ iLSP is a WebSocket gateway that exposes language server protocol (LSP) + backends over a single HTTP endpoint. Editors connect via WebSocket and + receive code intelligence — completions, hover, diagnostics — for Bicep, + YAML pipelines, and Python, enriched with organisation-specific catalogs. +

+ +

What is LSP

+

+ The Language Server Protocol + is a standard protocol between editors and language analysis tools. + An editor sends requests (go-to-definition, completion, hover) and receives + structured responses without knowing anything about the language itself. + Any editor that speaks LSP — IntelliJ, Neovim, VS Code, Helix — works + with any LSP server. +

+

+ iLSP wraps existing language servers (Bicep Language Server, + yaml-language-server, pylsp) and injects completion items from + internal catalogs before responses are returned to the editor. +

+ +

Live status

+
+
{bicep_modules}
Bicep modules
+
{pipeline_templates}
Pipeline templates
+
{pypi_packages}
Python packages
+
254
AzDO tasks
+
+

+ Full JSON status: /health +

+ +

WebSocket endpoints

+ + + + + + + + + + + + + + + + + + + +
EndpointLanguage serverExtra completions
wss://ilsp.i80.dk/bicepBicep Language ServerACR module paths, versions, params
wss://ilsp.i80.dk/yamlyaml-language-serverAzDO task schema (254 tasks), pipeline templates
wss://ilsp.i80.dk/pythonpylsp (Jedi)Internal PyPI package stubs
+ +

Editor setup

+ +

Neovim — add to your LSP config (init.lua):

+
-- Bicep
+vim.lsp.start({{
+  name    = "ilsp-bicep",
+  cmd     = {{ "websocat", "wss://ilsp.i80.dk/bicep" }},
+  filetypes = {{ "bicep" }},
+}})
+
+-- YAML pipelines
+vim.lsp.start({{
+  name    = "ilsp-yaml",
+  cmd     = {{ "websocat", "wss://ilsp.i80.dk/yaml" }},
+  filetypes = {{ "yaml" }},
+}})
+ +

+ IntelliJ IDEA / Rider — install the + LSP client plugin, + then add a server under Settings → Languages & Frameworks → Language Servers: +

+
Name:    iLSP Bicep
+Command: websocat wss://ilsp.i80.dk/bicep
+Pattern: *.bicep
+
+Name:    iLSP YAML
+Command: websocat wss://ilsp.i80.dk/yaml
+Pattern: azure-pipelines.yml, *.yaml
+ +
+ websocat bridges a WebSocket to + stdio so editors that expect a local process can connect to a remote LSP. + Install with brew install websocat or + cargo install websocat. +
+ +

VS Code — use the + LSP client + or configure the built-in + vscode-languageclient in a workspace extension, pointing + serverOptions.command at websocat wss://ilsp.i80.dk/bicep. +

+ +

Updating the catalogs

+

+ Catalogs are baked into the Docker image at build time and can also be + refreshed at runtime without restarting. Run the sync scripts from the + iLSP repository and call the reload endpoint: +

+
# Sync Bicep module catalog from ACR and push
+python3 scripts/sync_push_catalogs.py
+
+# Trigger a hot reload without container restart
+curl -X POST https://ilsp.i80.dk/reload
+ +
+ + + + +""" + + async def _build_app() -> web.Application: app = web.Application() + async def index(_: web.Request) -> web.Response: + html = _INDEX_HTML.format( + bicep_modules=len(BicepModuleCatalog._modules), + pipeline_templates=PipelineTemplateCatalog.template_count(), + pypi_packages=len(PypiCatalog._packages), + ) + return web.Response(text=html, content_type="text/html", charset="utf-8") + async def health(_: web.Request) -> web.Response: import shutil + from .yaml_lsp.proxy import _AZDO_SCHEMA_PATH return web.json_response({ "status": "ok", "pypi_packages": len(PypiCatalog._packages), @@ -119,6 +414,7 @@ async def _build_app() -> web.Application: "iac_source_modules": len(BicepModuleCatalog._iac), "yaml_lsp": bool(shutil.which("yaml-language-server")), "pipeline_templates": PipelineTemplateCatalog.template_count(), + "azdo_pipeline_schema": _AZDO_SCHEMA_PATH.exists(), }) async def reload(_: web.Request) -> web.Response: @@ -149,6 +445,7 @@ async def _build_app() -> web.Application: async def yaml_ws(request: web.Request) -> web.WebSocketResponse: return await yaml_ws_handler(request) + app.router.add_get("/", index) app.router.add_get("/health", health) app.router.add_post("/reload", reload) app.router.add_get("/python", python_ws)