add El LSP skeleton — language server, VSCode extension, syntax highlighting
Implements a Language Server Protocol server for El files over stdin/stdout JSON-RPC. Provides completions (builtins + keywords + document functions), hover documentation, go-to-definition, and full document sync. Also adds a VSCode extension that launches the binary as a child process and a TextMate grammar for .el syntax highlighting. NOTE: el-lsp.el calls __read_n(n: Int) -> String, a seed primitive not yet in el_runtime.c. Build.sh documents the required C implementation; the seed agent must add it before the binary will link.
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
// tools/lsp/vscode-extension/extension.js — El Language VSCode extension
|
||||
//
|
||||
// Launches el-lsp as a child process (stdin/stdout Language Server Protocol)
|
||||
// and connects VSCode's language client to it.
|
||||
//
|
||||
// Prerequisites:
|
||||
// - el-lsp binary built at ../dist/el-lsp (run tools/lsp/build.sh first)
|
||||
// - npm install (installs vscode-languageclient)
|
||||
//
|
||||
// Install for development:
|
||||
// 1. Open the vscode-extension/ folder in VSCode
|
||||
// 2. Press F5 — Extension Development Host launches
|
||||
// 3. Open any .el file — the LSP activates
|
||||
//
|
||||
// To package as a .vsix:
|
||||
// npm install -g @vscode/vsce
|
||||
// cd tools/lsp/vscode-extension && vsce package
|
||||
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const { workspace, window, ExtensionContext } = require('vscode');
|
||||
const {
|
||||
LanguageClient,
|
||||
TransportKind,
|
||||
} = require('vscode-languageclient/node');
|
||||
|
||||
let client;
|
||||
|
||||
/**
|
||||
* activate — called when a .el file is first opened.
|
||||
* Resolves the el-lsp binary path and starts the language client.
|
||||
*
|
||||
* @param {ExtensionContext} context
|
||||
*/
|
||||
function activate(context) {
|
||||
// Resolve el-lsp binary path.
|
||||
// Default: ../dist/el-lsp relative to this extension root.
|
||||
// Override with the "el.lspPath" workspace setting.
|
||||
const config = workspace.getConfiguration('el');
|
||||
const defaultLspPath = path.join(context.extensionPath, '..', 'dist', 'el-lsp');
|
||||
const serverPath = config.get('lspPath', defaultLspPath);
|
||||
|
||||
// ServerOptions: run el-lsp as a child process over stdin/stdout.
|
||||
const serverOptions = {
|
||||
run: {
|
||||
command: serverPath,
|
||||
transport: TransportKind.stdio,
|
||||
},
|
||||
debug: {
|
||||
command: serverPath,
|
||||
transport: TransportKind.stdio,
|
||||
// In debug mode you could add --verbose or redirect stderr to a
|
||||
// log file for inspection. For now debug == run.
|
||||
},
|
||||
};
|
||||
|
||||
// ClientOptions: handle all files with language id "el".
|
||||
const clientOptions = {
|
||||
documentSelector: [
|
||||
{ scheme: 'file', language: 'el' },
|
||||
{ scheme: 'untitled', language: 'el' },
|
||||
],
|
||||
synchronize: {
|
||||
// Re-send full document on save (matches textDocumentSync: 1).
|
||||
fileEvents: workspace.createFileSystemWatcher('**/*.el'),
|
||||
},
|
||||
// Output channel for LSP messages (visible in Output → El Language Server).
|
||||
outputChannelName: 'El Language Server',
|
||||
};
|
||||
|
||||
client = new LanguageClient(
|
||||
'el-language-server',
|
||||
'El Language Server',
|
||||
serverOptions,
|
||||
clientOptions
|
||||
);
|
||||
|
||||
// Start the client (and the server as its child process).
|
||||
client.start();
|
||||
|
||||
// Notify user on activation.
|
||||
window.showInformationMessage('El Language Server started.');
|
||||
}
|
||||
|
||||
/**
|
||||
* deactivate — called when the extension is unloaded.
|
||||
* Stops the language client (sends LSP shutdown + exit).
|
||||
*/
|
||||
function deactivate() {
|
||||
if (!client) {
|
||||
return undefined;
|
||||
}
|
||||
return client.stop();
|
||||
}
|
||||
|
||||
module.exports = { activate, deactivate };
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"comments": {
|
||||
"lineComment": "//"
|
||||
},
|
||||
"brackets": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"]
|
||||
],
|
||||
"autoClosingPairs": [
|
||||
{ "open": "{", "close": "}" },
|
||||
{ "open": "[", "close": "]" },
|
||||
{ "open": "(", "close": ")" },
|
||||
{ "open": "\"", "close": "\"", "notIn": ["string"] }
|
||||
],
|
||||
"surroundingPairs": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
["\"", "\""]
|
||||
],
|
||||
"indentationRules": {
|
||||
"increaseIndentPattern": "\\{[^}]*$",
|
||||
"decreaseIndentPattern": "^\\s*}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "el-language",
|
||||
"displayName": "El Language",
|
||||
"description": "El language support — syntax highlighting, completions, hover, go-to-definition",
|
||||
"version": "0.1.0",
|
||||
"publisher": "neuron-technologies",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"vscode": "^1.75.0"
|
||||
},
|
||||
"categories": [
|
||||
"Programming Languages"
|
||||
],
|
||||
"keywords": [
|
||||
"el",
|
||||
"el-lang",
|
||||
"language-server"
|
||||
],
|
||||
"activationEvents": [
|
||||
"onLanguage:el"
|
||||
],
|
||||
"contributes": {
|
||||
"languages": [
|
||||
{
|
||||
"id": "el",
|
||||
"aliases": [
|
||||
"El",
|
||||
"el"
|
||||
],
|
||||
"extensions": [
|
||||
".el"
|
||||
],
|
||||
"configuration": "./language-configuration.json"
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
{
|
||||
"language": "el",
|
||||
"scopeName": "source.el",
|
||||
"path": "./syntaxes/el.tmGrammar.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"main": "./extension.js",
|
||||
"dependencies": {
|
||||
"vscode-languageclient": "^8.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/vscode": "^1.75.0"
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "echo 'no build step required'",
|
||||
"package": "vsce package"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
|
||||
"name": "El",
|
||||
"scopeName": "source.el",
|
||||
"fileTypes": ["el"],
|
||||
"patterns": [
|
||||
{ "include": "#comments" },
|
||||
{ "include": "#strings" },
|
||||
{ "include": "#function-definition" },
|
||||
{ "include": "#keywords" },
|
||||
{ "include": "#types" },
|
||||
{ "include": "#constants" },
|
||||
{ "include": "#numbers" },
|
||||
{ "include": "#operators" },
|
||||
{ "include": "#function-call" },
|
||||
{ "include": "#identifiers" }
|
||||
],
|
||||
"repository": {
|
||||
|
||||
"comments": {
|
||||
"name": "comment.line.double-slash.el",
|
||||
"match": "//.*$"
|
||||
},
|
||||
|
||||
"strings": {
|
||||
"name": "string.quoted.double.el",
|
||||
"begin": "\"",
|
||||
"end": "\"",
|
||||
"patterns": [
|
||||
{
|
||||
"name": "constant.character.escape.el",
|
||||
"match": "\\\\[nrt\\\\\"']"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"function-definition": {
|
||||
"comment": "Highlight 'fn name(' — the name gets entity.name.function scope",
|
||||
"match": "\\bfn\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\(",
|
||||
"captures": {
|
||||
"0": { "name": "meta.function.el" },
|
||||
"1": { "name": "entity.name.function.el" }
|
||||
}
|
||||
},
|
||||
|
||||
"keywords": {
|
||||
"patterns": [
|
||||
{
|
||||
"comment": "Control-flow keywords",
|
||||
"name": "keyword.control.el",
|
||||
"match": "\\b(if|else|while|for|in|return|match)\\b"
|
||||
},
|
||||
{
|
||||
"comment": "Declaration keywords",
|
||||
"name": "keyword.declaration.el",
|
||||
"match": "\\b(fn|let|type|enum|import|from|as)\\b"
|
||||
},
|
||||
{
|
||||
"comment": "Reserved future keywords",
|
||||
"name": "keyword.other.el",
|
||||
"match": "\\b(cgi|vessel|activate|where|sealed|with|test|seed|assert|protocol|impl|retry|times|fallback|reason|parallel|trace|requires|deploy|to|via|target|manager|engine|accessor)\\b"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"types": {
|
||||
"comment": "Built-in El types",
|
||||
"name": "support.type.el",
|
||||
"match": "\\b(String|Int|Float|Bool|Void|Any|Map|List)\\b"
|
||||
},
|
||||
|
||||
"constants": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "constant.language.boolean.el",
|
||||
"match": "\\b(true|false)\\b"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"numbers": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "constant.numeric.float.el",
|
||||
"match": "\\b[0-9]+\\.[0-9]+\\b"
|
||||
},
|
||||
{
|
||||
"name": "constant.numeric.integer.el",
|
||||
"match": "\\b[0-9]+\\b"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"operators": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "keyword.operator.comparison.el",
|
||||
"match": "(==|!=|<=|>=|<|>)"
|
||||
},
|
||||
{
|
||||
"name": "keyword.operator.logical.el",
|
||||
"match": "(&&|\\|\\||!)"
|
||||
},
|
||||
{
|
||||
"name": "keyword.operator.assignment.el",
|
||||
"match": "(?<![=!<>])=(?!=)"
|
||||
},
|
||||
{
|
||||
"name": "keyword.operator.arithmetic.el",
|
||||
"match": "[+\\-*/%]"
|
||||
},
|
||||
{
|
||||
"name": "keyword.operator.arrow.el",
|
||||
"match": "->"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"function-call": {
|
||||
"comment": "Highlight function calls: name( — name gets entity.name.function scope",
|
||||
"match": "\\b([a-zA-Z_][a-zA-Z0-9_]*)\\s*(?=\\()",
|
||||
"captures": {
|
||||
"1": { "name": "entity.name.function.call.el" }
|
||||
}
|
||||
},
|
||||
|
||||
"identifiers": {
|
||||
"comment": "General identifiers — lower priority than other rules",
|
||||
"name": "variable.other.el",
|
||||
"match": "\\b[a-zA-Z_][a-zA-Z0-9_]*\\b"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user