merge tools/lsp (full) — 184 completions, hover, go-to-def, live diagnostics, enhanced VSCode extension
This commit is contained in:
@@ -155,6 +155,39 @@ el_val_t readline(void) {
|
|||||||
return el_wrap_str(el_strdup(buf));
|
return el_wrap_str(el_strdup(buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* __read_n — read exactly n bytes from stdin.
|
||||||
|
* Allocates a buffer of size n+1, calls fread(buf, 1, n, stdin) to read
|
||||||
|
* exactly n raw bytes (including \r, \n, NUL, etc.), null-terminates, and
|
||||||
|
* returns the buffer as an El String. Returns "" on EOF or I/O error.
|
||||||
|
*
|
||||||
|
* Used by the El LSP server to read JSON-RPC message bodies after parsing
|
||||||
|
* the Content-Length header. readline() cannot be used for the body because
|
||||||
|
* it stops at the first \n and LSP JSON bodies are not newline-terminated. */
|
||||||
|
el_val_t __read_n(el_val_t nv) {
|
||||||
|
int64_t n = EL_INT(nv);
|
||||||
|
if (n <= 0) return el_wrap_str(el_strdup(""));
|
||||||
|
char* buf = malloc((size_t)n + 1);
|
||||||
|
if (!buf) { fputs("el_runtime: __read_n: out of memory\n", stderr); return el_wrap_str(el_strdup("")); }
|
||||||
|
size_t got = fread(buf, 1, (size_t)n, stdin);
|
||||||
|
buf[got] = '\0';
|
||||||
|
if (got == 0) { free(buf); return el_wrap_str(el_strdup("")); }
|
||||||
|
/* Track in arena so the allocation is freed when the request ends. */
|
||||||
|
el_arena_track(buf);
|
||||||
|
return el_wrap_str(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* __print_raw — write a string to stdout without any modification.
|
||||||
|
* Unlike println/print (which call puts/fputs and may add newlines or flush
|
||||||
|
* in platform-specific ways), this uses fwrite with the exact byte count so
|
||||||
|
* that embedded \r\n pairs in LSP Content-Length headers survive intact. */
|
||||||
|
void __print_raw(el_val_t sv) {
|
||||||
|
const char* s = EL_CSTR(sv);
|
||||||
|
if (!s) return;
|
||||||
|
size_t len = strlen(s);
|
||||||
|
fwrite(s, 1, len, stdout);
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
/* ── String builtins ─────────────────────────────────────────────────────── */
|
/* ── String builtins ─────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
el_val_t el_str_concat(el_val_t av, el_val_t bv) {
|
el_val_t el_str_concat(el_val_t av, el_val_t bv) {
|
||||||
|
|||||||
@@ -80,6 +80,16 @@ void println(el_val_t s);
|
|||||||
void print(el_val_t s);
|
void print(el_val_t s);
|
||||||
el_val_t readline(void);
|
el_val_t readline(void);
|
||||||
|
|
||||||
|
/* __read_n — read exactly n bytes from stdin; returns String of length n.
|
||||||
|
* Returns "" on EOF or read error. Used by the El LSP server to read
|
||||||
|
* JSON-RPC message bodies after the Content-Length header is parsed. */
|
||||||
|
el_val_t __read_n(el_val_t n);
|
||||||
|
|
||||||
|
/* __print_raw — write a string to stdout using fwrite + fflush, preserving
|
||||||
|
* embedded \r\n byte pairs as-is. Used by the El LSP server to emit
|
||||||
|
* Content-Length framed JSON-RPC messages. */
|
||||||
|
void __print_raw(el_val_t s);
|
||||||
|
|
||||||
/* ── String builtins ─────────────────────────────────────────────────────── */
|
/* ── String builtins ─────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
el_val_t el_str_concat(el_val_t a, el_val_t b);
|
el_val_t el_str_concat(el_val_t a, el_val_t b);
|
||||||
|
|||||||
@@ -2049,6 +2049,9 @@ fn builtin_arity(name: String) -> Int {
|
|||||||
if str_eq(name, "println") { return 1 }
|
if str_eq(name, "println") { return 1 }
|
||||||
if str_eq(name, "print") { return 1 }
|
if str_eq(name, "print") { return 1 }
|
||||||
if str_eq(name, "readline") { return 0 }
|
if str_eq(name, "readline") { return 0 }
|
||||||
|
// LSP seed primitives
|
||||||
|
if str_eq(name, "__read_n") { return 1 }
|
||||||
|
if str_eq(name, "__print_raw") { return 1 }
|
||||||
// String
|
// String
|
||||||
if str_eq(name, "el_str_concat") { return 2 }
|
if str_eq(name, "el_str_concat") { return 2 }
|
||||||
if str_eq(name, "str_eq") { return 2 }
|
if str_eq(name, "str_eq") { return 2 }
|
||||||
|
|||||||
@@ -0,0 +1,198 @@
|
|||||||
|
# El LSP — Language Server for El
|
||||||
|
|
||||||
|
Full Language Server Protocol implementation for the El programming language.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
| Feature | Status |
|
||||||
|
|---------|--------|
|
||||||
|
| Syntax highlighting | Full TextMate grammar |
|
||||||
|
| Completions | Builtins (130+), user-defined fns, keywords, types |
|
||||||
|
| Hover | Signatures + descriptions for all builtins and user fns |
|
||||||
|
| Go-to-definition | Jump to `fn name(` in the open document |
|
||||||
|
| Diagnostics | Unclosed braces/parens/brackets, unterminated strings |
|
||||||
|
| Document sync | Full (re-sends entire document on every change) |
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- The `elc` compiler binary at `dist/platform/elc` (built from the repo root)
|
||||||
|
- `cc` (clang or gcc), `libcurl`, `pthreads`
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From the el repo root:
|
||||||
|
./tools/lsp/build.sh
|
||||||
|
|
||||||
|
# Or with a custom elc path:
|
||||||
|
ELC=/path/to/elc ./tools/lsp/build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Output: `tools/lsp/dist/el-lsp`
|
||||||
|
|
||||||
|
### Install system-wide
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo cp tools/lsp/dist/el-lsp /usr/local/bin/el-lsp
|
||||||
|
```
|
||||||
|
|
||||||
|
## VSCode Extension
|
||||||
|
|
||||||
|
### Development install (recommended)
|
||||||
|
|
||||||
|
1. Build the binary first (see above).
|
||||||
|
2. Install the npm dependency:
|
||||||
|
```bash
|
||||||
|
cd tools/lsp/vscode-extension
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
3. Open `tools/lsp/vscode-extension/` in VSCode.
|
||||||
|
4. Press **F5** — this launches the Extension Development Host.
|
||||||
|
5. Open any `.el` file in the dev host window.
|
||||||
|
|
||||||
|
### Package as .vsix
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g @vscode/vsce
|
||||||
|
cd tools/lsp/vscode-extension
|
||||||
|
vsce package
|
||||||
|
# Produces: el-language-1.0.0.vsix
|
||||||
|
```
|
||||||
|
|
||||||
|
Install the .vsix:
|
||||||
|
```bash
|
||||||
|
code --install-extension el-language-1.0.0.vsix
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
| Setting | Default | Description |
|
||||||
|
|---------|---------|-------------|
|
||||||
|
| `el.lspPath` | (bundled) | Path to `el-lsp` binary. Empty = use `../dist/el-lsp`. |
|
||||||
|
| `el.trace.server` | `off` | LSP message tracing. Set to `verbose` to see all messages. |
|
||||||
|
|
||||||
|
## Neovim / other editors
|
||||||
|
|
||||||
|
Any editor that supports LSP can use `el-lsp`. Example configuration for
|
||||||
|
Neovim with `nvim-lspconfig`:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local lspconfig = require('lspconfig')
|
||||||
|
local configs = require('lspconfig.configs')
|
||||||
|
|
||||||
|
if not configs.el then
|
||||||
|
configs.el = {
|
||||||
|
default_config = {
|
||||||
|
cmd = { 'el-lsp' },
|
||||||
|
filetypes = { 'el' },
|
||||||
|
root_dir = lspconfig.util.root_pattern('.git', '*.el'),
|
||||||
|
settings = {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
lspconfig.el.setup({})
|
||||||
|
```
|
||||||
|
|
||||||
|
Add to `ftdetect/el.vim`:
|
||||||
|
```vim
|
||||||
|
au BufRead,BufNewFile *.el set filetype=el
|
||||||
|
```
|
||||||
|
|
||||||
|
## Wire protocol
|
||||||
|
|
||||||
|
El LSP communicates over stdin/stdout using standard LSP framing:
|
||||||
|
|
||||||
|
```
|
||||||
|
Content-Length: <N>\r\n
|
||||||
|
\r\n
|
||||||
|
<N bytes of UTF-8 JSON>
|
||||||
|
```
|
||||||
|
|
||||||
|
The `__read_n(n: Int) -> String` primitive in `el_runtime.c` reads exactly
|
||||||
|
`n` bytes from stdin — needed because `readline()` stops at `\n` and LSP
|
||||||
|
JSON bodies are not newline-terminated. `__print_raw(s: String)` writes with
|
||||||
|
`fwrite + fflush` to preserve embedded `\r\n` in headers.
|
||||||
|
|
||||||
|
## Smoke test
|
||||||
|
|
||||||
|
After building, verify the server responds to `initialize`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 - << 'PY'
|
||||||
|
import subprocess, json
|
||||||
|
|
||||||
|
def frame(obj):
|
||||||
|
body = json.dumps(obj).encode()
|
||||||
|
return f"Content-Length: {len(body)}\r\n\r\n".encode() + body
|
||||||
|
|
||||||
|
def read_response(proc):
|
||||||
|
hdr = b""
|
||||||
|
while not hdr.endswith(b"\r\n\r\n"):
|
||||||
|
hdr += proc.stdout.read(1)
|
||||||
|
cl = int([l for l in hdr.decode().split("\r\n") if "Content-Length" in l][0].split(": ")[1])
|
||||||
|
return json.loads(proc.stdout.read(cl))
|
||||||
|
|
||||||
|
proc = subprocess.Popen(["./dist/el-lsp"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||||
|
proc.stdin.write(frame({"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{}}}))
|
||||||
|
proc.stdin.flush()
|
||||||
|
r = read_response(proc)
|
||||||
|
print("Server name:", r["result"]["serverInfo"]["name"])
|
||||||
|
print("Capabilities:", list(r["result"]["capabilities"].keys()))
|
||||||
|
proc.stdin.write(frame({"jsonrpc":"2.0","method":"exit","params":{}}))
|
||||||
|
proc.stdin.flush()
|
||||||
|
proc.wait()
|
||||||
|
print("OK")
|
||||||
|
PY
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
el-lsp.el
|
||||||
|
lsp_read_message() reads header bytes one-by-one, then __read_n(body_len)
|
||||||
|
lsp_write_message() __print_raw("Content-Length: N\r\n\r\n" + json)
|
||||||
|
lsp_dispatch() routes method string to handler
|
||||||
|
lsp_builtin_catalog() [String] of "name|signature|description" entries
|
||||||
|
lsp_extract_fns() scan source for "fn name(" patterns
|
||||||
|
lsp_word_at() expand identifier under cursor
|
||||||
|
lsp_compute_diagnostics() scan for unclosed brackets + unterminated strings
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
```
|
||||||
|
tools/lsp/
|
||||||
|
el-lsp.el LSP server source (El language)
|
||||||
|
build.sh Build script
|
||||||
|
README.md This file
|
||||||
|
dist/
|
||||||
|
el-lsp Compiled binary (after build)
|
||||||
|
el-lsp.c Generated C (after build)
|
||||||
|
vscode-extension/
|
||||||
|
extension.js Extension entry point
|
||||||
|
package.json Extension manifest
|
||||||
|
language-configuration.json Bracket matching, comment config
|
||||||
|
syntaxes/
|
||||||
|
el.tmGrammar.json TextMate syntax grammar
|
||||||
|
.vscode/
|
||||||
|
launch.json F5 debug configuration
|
||||||
|
tasks.json Pre-launch npm install task
|
||||||
|
```
|
||||||
|
|
||||||
|
## Runtime additions
|
||||||
|
|
||||||
|
Two new primitives added to `el-compiler/runtime/`:
|
||||||
|
|
||||||
|
### `__read_n(n: Int) -> String`
|
||||||
|
Reads exactly `n` bytes from stdin using `fread`. Returns `""` on EOF.
|
||||||
|
Required for reading JSON-RPC message bodies.
|
||||||
|
|
||||||
|
### `__print_raw(s: String) -> Void`
|
||||||
|
Writes a string to stdout using `fwrite + fflush`. Preserves embedded
|
||||||
|
`\r\n` bytes exactly. Required for LSP Content-Length headers.
|
||||||
|
|
||||||
|
Both are declared in `el_runtime.h` and registered in the `builtin_arity`
|
||||||
|
table in `el-compiler/src/codegen.el`.
|
||||||
+86
-85
@@ -1,124 +1,125 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# tools/lsp/build.sh — Build the El LSP server binary.
|
# tools/lsp/build.sh — Build the El LSP server binary end-to-end.
|
||||||
|
#
|
||||||
|
# Pipeline:
|
||||||
|
# 1. Compile el-lsp.el → C source (using elc)
|
||||||
|
# 2. Compile C source → binary (using cc with el_runtime.c)
|
||||||
#
|
#
|
||||||
# Output: tools/lsp/dist/el-lsp
|
# Output: tools/lsp/dist/el-lsp
|
||||||
#
|
#
|
||||||
# Usage:
|
|
||||||
# cd tools/lsp && ./build.sh
|
|
||||||
# EL_HOME=/path/to/el ./build.sh # if el root is elsewhere
|
|
||||||
# ELC=/path/to/custom/elc ./build.sh # use a specific compiler binary
|
|
||||||
#
|
|
||||||
# Prerequisites:
|
# Prerequisites:
|
||||||
# - elc binary (either on PATH or ELC env var)
|
# - dist/platform/elc (the El self-hosted compiler)
|
||||||
# - el-compiler/runtime/el_runtime.c
|
# - el-compiler/runtime/el_runtime.c + el_runtime.h
|
||||||
# - cc (clang or gcc), libcurl, pthreads
|
# - cc (clang or gcc), libcurl, pthreads
|
||||||
#
|
#
|
||||||
# NOTE — seed primitive __read_n:
|
# Usage:
|
||||||
# el-lsp.el calls __read_n(n: Int) -> String, which reads exactly n bytes
|
# cd <el-root>
|
||||||
# from stdin. This primitive must be present in el_runtime.c and declared
|
# ./tools/lsp/build.sh
|
||||||
# in el_runtime.h for the compiled binary to work correctly.
|
|
||||||
#
|
#
|
||||||
# Until __read_n is added to the runtime, the LSP will compile and link
|
# Or from anywhere:
|
||||||
# successfully but lsp_read_message() will silently use the __read_n symbol
|
# EL_HOME=/path/to/el-root ./tools/lsp/build.sh
|
||||||
# — causing a linker error. The seed agent must:
|
|
||||||
# 1. Add el_val_t __read_n(el_val_t n) to el-compiler/runtime/el_runtime.c
|
|
||||||
# 2. Declare el_val_t __read_n(el_val_t n) in el-compiler/runtime/el_runtime.h
|
|
||||||
# 3. Add "__read_n" with arity 1 to the builtin_arity table in
|
|
||||||
# el-compiler/src/codegen.el
|
|
||||||
# 4. Rebuild the elc binary
|
|
||||||
#
|
|
||||||
# Implementation sketch for __read_n in C:
|
|
||||||
#
|
|
||||||
# el_val_t __read_n(el_val_t n) {
|
|
||||||
# int64_t count = (int64_t)n;
|
|
||||||
# if (count <= 0) return EL_STR("");
|
|
||||||
# char* buf = malloc(count + 1);
|
|
||||||
# if (!buf) return EL_STR("");
|
|
||||||
# size_t total = 0;
|
|
||||||
# while (total < (size_t)count) {
|
|
||||||
# size_t got = fread(buf + total, 1, (size_t)count - total, stdin);
|
|
||||||
# if (got == 0) break;
|
|
||||||
# total += got;
|
|
||||||
# }
|
|
||||||
# buf[total] = '\0';
|
|
||||||
# return EL_STR(buf);
|
|
||||||
# }
|
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd "$(dirname "$0")"
|
|
||||||
|
|
||||||
# ── Locate el root ─────────────────────────────────────────────────────────────
|
# ── Locate el root ─────────────────────────────────────────────────────────
|
||||||
EL_HOME="${EL_HOME:-$(cd ../.. && pwd)}"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
EL_HOME="${EL_HOME:-$(cd "${SCRIPT_DIR}/../.." && pwd)}"
|
||||||
|
|
||||||
ELC="${ELC:-${EL_HOME}/dist/platform/elc}"
|
ELC="${ELC:-${EL_HOME}/dist/platform/elc}"
|
||||||
RUNTIME_DIR="${EL_HOME}/el-compiler/runtime"
|
RUNTIME_DIR="${EL_HOME}/el-compiler/runtime"
|
||||||
|
LSP_DIR="${SCRIPT_DIR}"
|
||||||
|
OUT_DIR="${LSP_DIR}/dist"
|
||||||
|
|
||||||
|
# ── Validate prerequisites ─────────────────────────────────────────────────
|
||||||
|
echo "==> Checking prerequisites..."
|
||||||
|
echo " EL_HOME = ${EL_HOME}"
|
||||||
|
echo " elc = ${ELC}"
|
||||||
|
echo " runtime = ${RUNTIME_DIR}"
|
||||||
|
|
||||||
# ── Validate prerequisites ─────────────────────────────────────────────────────
|
|
||||||
if [ ! -x "${ELC}" ]; then
|
if [ ! -x "${ELC}" ]; then
|
||||||
echo "error: elc not found at ${ELC}" >&2
|
echo "error: elc not found at ${ELC}" >&2
|
||||||
echo " Set ELC=/path/to/elc or EL_HOME=/path/to/el-root" >&2
|
echo " Build it first: cd <el-root> && make (or compile elc-bootstrap.c)" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -f "${RUNTIME_DIR}/el_runtime.c" ]; then
|
if [ ! -f "${RUNTIME_DIR}/el_runtime.c" ]; then
|
||||||
echo "error: el_runtime.c not found at ${RUNTIME_DIR}/el_runtime.c" >&2
|
echo "error: el_runtime.c not found at ${RUNTIME_DIR}" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p dist
|
if [ ! -f "${LSP_DIR}/el-lsp.el" ]; then
|
||||||
|
echo "error: el-lsp.el not found at ${LSP_DIR}" >&2
|
||||||
# ── Concatenate runtime modules + LSP source ───────────────────────────────────
|
|
||||||
# Load order follows runtime/manifest.el: string → math → state → env →
|
|
||||||
# fs → exec → time → json → http → then our LSP source.
|
|
||||||
RUNTIME_SRCS=(
|
|
||||||
"${EL_HOME}/runtime/string.el"
|
|
||||||
"${EL_HOME}/runtime/math.el"
|
|
||||||
"${EL_HOME}/runtime/state.el"
|
|
||||||
"${EL_HOME}/runtime/env.el"
|
|
||||||
"${EL_HOME}/runtime/fs.el"
|
|
||||||
"${EL_HOME}/runtime/exec.el"
|
|
||||||
"${EL_HOME}/runtime/time.el"
|
|
||||||
"${EL_HOME}/runtime/json.el"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check that runtime modules exist
|
|
||||||
MISSING=0
|
|
||||||
for f in "${RUNTIME_SRCS[@]}"; do
|
|
||||||
if [ ! -f "${f}" ]; then
|
|
||||||
echo "warning: runtime module not found: ${f}" >&2
|
|
||||||
MISSING=$((MISSING + 1))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if [ "${MISSING}" -gt 0 ]; then
|
|
||||||
echo "error: ${MISSING} runtime module(s) missing — cannot build" >&2
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
COMBINED="dist/el-lsp-combined.el"
|
mkdir -p "${OUT_DIR}"
|
||||||
echo "==> Combining sources..."
|
|
||||||
cat "${RUNTIME_SRCS[@]}" el-lsp.el > "${COMBINED}"
|
|
||||||
echo " ${COMBINED}"
|
|
||||||
|
|
||||||
# ── Compile El → C ─────────────────────────────────────────────────────────────
|
# ── Compile El → C ─────────────────────────────────────────────────────────
|
||||||
C_OUT="dist/el-lsp.c"
|
C_OUT="${OUT_DIR}/el-lsp.c"
|
||||||
echo "==> Compiling El → C..."
|
echo "==> Compiling El → C..."
|
||||||
"${ELC}" "${COMBINED}" > "${C_OUT}"
|
echo " ${LSP_DIR}/el-lsp.el → ${C_OUT}"
|
||||||
echo " ${C_OUT}"
|
|
||||||
|
|
||||||
# ── Compile C → binary ─────────────────────────────────────────────────────────
|
# el-lsp.el uses only standard El builtins (no import statements needed).
|
||||||
BIN="dist/el-lsp"
|
# The elc compiler emits #include "el_runtime.h" at the top of the output.
|
||||||
|
"${ELC}" "${LSP_DIR}/el-lsp.el" > "${C_OUT}"
|
||||||
|
|
||||||
|
echo " Done ($(wc -l < "${C_OUT}") lines of C)."
|
||||||
|
|
||||||
|
# ── Compile C → binary ─────────────────────────────────────────────────────
|
||||||
|
BIN="${OUT_DIR}/el-lsp"
|
||||||
echo "==> Compiling C → binary..."
|
echo "==> Compiling C → binary..."
|
||||||
|
echo " ${C_OUT} + el_runtime.c → ${BIN}"
|
||||||
|
|
||||||
cc -std=c11 -O2 \
|
cc -std=c11 -O2 \
|
||||||
-I "${RUNTIME_DIR}" \
|
-I "${RUNTIME_DIR}" \
|
||||||
-o "${BIN}" \
|
-o "${BIN}" \
|
||||||
"${C_OUT}" "${RUNTIME_DIR}/el_runtime.c" \
|
"${C_OUT}" "${RUNTIME_DIR}/el_runtime.c" \
|
||||||
-lcurl -lpthread
|
-lcurl -lpthread
|
||||||
echo " ${BIN}"
|
|
||||||
|
|
||||||
|
echo " Done."
|
||||||
|
|
||||||
|
# ── Summary ────────────────────────────────────────────────────────────────
|
||||||
echo
|
echo
|
||||||
echo "==> Build complete: ${BIN}"
|
echo "==> Build complete."
|
||||||
echo
|
echo
|
||||||
echo " Run as LSP server (editors connect via stdin/stdout):"
|
echo " Binary : ${BIN}"
|
||||||
echo " ${BIN}"
|
echo " Size : $(du -sh "${BIN}" | cut -f1)"
|
||||||
echo
|
echo
|
||||||
echo " Test with a synthetic LSP initialize request:"
|
echo " Install system-wide:"
|
||||||
echo " echo -e 'Content-Length: 97\\r\\n\\r\\n{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"capabilities\":{}}}' | ${BIN}"
|
echo " sudo cp ${BIN} /usr/local/bin/el-lsp"
|
||||||
|
echo
|
||||||
|
echo " Quick smoke test (initialize + shutdown):"
|
||||||
|
cat << 'SMOKETEST'
|
||||||
|
python3 - << 'PY'
|
||||||
|
import subprocess, json
|
||||||
|
|
||||||
|
def frame(body):
|
||||||
|
b = body.encode()
|
||||||
|
return f"Content-Length: {len(b)}\r\n\r\n".encode() + b
|
||||||
|
|
||||||
|
def send(proc, obj):
|
||||||
|
proc.stdin.write(frame(json.dumps(obj)))
|
||||||
|
proc.stdin.flush()
|
||||||
|
|
||||||
|
def recv(proc):
|
||||||
|
hdr = b""
|
||||||
|
while not hdr.endswith(b"\r\n\r\n"):
|
||||||
|
hdr += proc.stdout.read(1)
|
||||||
|
cl = int([l for l in hdr.decode().split("\r\n") if l.startswith("Content-Length")][0].split(": ")[1])
|
||||||
|
return json.loads(proc.stdout.read(cl))
|
||||||
|
|
||||||
|
import sys, os
|
||||||
|
bin_path = sys.argv[1] if len(sys.argv) > 1 else "./dist/el-lsp"
|
||||||
|
proc = subprocess.Popen([bin_path], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||||
|
send(proc, {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{}}})
|
||||||
|
r = recv(proc)
|
||||||
|
print("initialize:", r.get("result", {}).get("serverInfo", {}).get("name"), "OK" if "result" in r else "FAIL")
|
||||||
|
send(proc, {"jsonrpc":"2.0","id":2,"method":"shutdown","params":{}})
|
||||||
|
r = recv(proc)
|
||||||
|
print("shutdown:", "OK" if r.get("result") is None else "FAIL")
|
||||||
|
send(proc, {"jsonrpc":"2.0","method":"exit","params":{}})
|
||||||
|
proc.wait()
|
||||||
|
print("exit: OK")
|
||||||
|
PY
|
||||||
|
SMOKETEST
|
||||||
|
|
||||||
|
|||||||
+807
-765
File diff suppressed because it is too large
Load Diff
+31
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Launch Extension (Extension Development Host)",
|
||||||
|
"type": "extensionHost",
|
||||||
|
"request": "launch",
|
||||||
|
"args": [
|
||||||
|
"--extensionDevelopmentPath=${workspaceFolder}"
|
||||||
|
],
|
||||||
|
"outFiles": [
|
||||||
|
"${workspaceFolder}/**/*.js"
|
||||||
|
],
|
||||||
|
"preLaunchTask": "npm: install",
|
||||||
|
"env": {
|
||||||
|
"EL_LSP_LOG": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Attach to el-lsp process",
|
||||||
|
"type": "node",
|
||||||
|
"request": "attach",
|
||||||
|
"port": 6009,
|
||||||
|
"restart": true,
|
||||||
|
"timeout": 10000,
|
||||||
|
"outFiles": [
|
||||||
|
"${workspaceFolder}/**/*.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"type": "npm",
|
||||||
|
"script": "install",
|
||||||
|
"label": "npm: install",
|
||||||
|
"detail": "Install vscode-languageclient dependency",
|
||||||
|
"group": "build",
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "silent"
|
||||||
|
},
|
||||||
|
"problemMatcher": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,91 +1,137 @@
|
|||||||
// tools/lsp/vscode-extension/extension.js — El Language VSCode extension
|
// tools/lsp/vscode-extension/extension.js
|
||||||
//
|
//
|
||||||
// Launches el-lsp as a child process (stdin/stdout Language Server Protocol)
|
// El Language VSCode extension — Language Client
|
||||||
// and connects VSCode's language client to it.
|
|
||||||
//
|
//
|
||||||
// Prerequisites:
|
// Launches the el-lsp binary as a child process and connects VSCode's
|
||||||
// - el-lsp binary built at ../dist/el-lsp (run tools/lsp/build.sh first)
|
// Language Client to it over stdin/stdout (JSON-RPC 2.0 + Content-Length).
|
||||||
// - npm install (installs vscode-languageclient)
|
|
||||||
//
|
//
|
||||||
// Install for development:
|
// Capabilities provided by el-lsp:
|
||||||
// 1. Open the vscode-extension/ folder in VSCode
|
// - textDocumentSync (full)
|
||||||
// 2. Press F5 — Extension Development Host launches
|
// - completionProvider — builtins + user fns + keywords
|
||||||
// 3. Open any .el file — the LSP activates
|
// - hoverProvider — signatures + descriptions
|
||||||
|
// - definitionProvider — go-to-def for user-defined fns
|
||||||
|
// - diagnostics — unclosed braces/parens, unterminated strings
|
||||||
//
|
//
|
||||||
// To package as a .vsix:
|
// Setup (development):
|
||||||
|
// 1. cd tools/lsp && ./build.sh (builds dist/el-lsp)
|
||||||
|
// 2. cd vscode-extension && npm install (installs vscode-languageclient)
|
||||||
|
// 3. Open vscode-extension/ in VSCode
|
||||||
|
// 4. Press F5 (launches Extension Development Host)
|
||||||
|
// 5. Open any .el file
|
||||||
|
//
|
||||||
|
// Package as .vsix:
|
||||||
// npm install -g @vscode/vsce
|
// npm install -g @vscode/vsce
|
||||||
// cd tools/lsp/vscode-extension && vsce package
|
// cd tools/lsp/vscode-extension && vsce package
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
const { workspace, window, ExtensionContext } = require('vscode');
|
const { workspace, window, ExtensionContext } = require('vscode');
|
||||||
const {
|
const {
|
||||||
LanguageClient,
|
LanguageClient,
|
||||||
TransportKind,
|
TransportKind,
|
||||||
} = require('vscode-languageclient/node');
|
} = require('vscode-languageclient/node');
|
||||||
|
|
||||||
|
/** @type {LanguageClient | undefined} */
|
||||||
let client;
|
let client;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* activate — called when a .el file is first opened.
|
* activate — entry point called when any .el file is first opened.
|
||||||
* Resolves the el-lsp binary path and starts the language client.
|
|
||||||
*
|
*
|
||||||
* @param {ExtensionContext} context
|
* @param {ExtensionContext} context
|
||||||
*/
|
*/
|
||||||
function activate(context) {
|
function activate(context) {
|
||||||
// Resolve el-lsp binary path.
|
// ── Resolve el-lsp binary ────────────────────────────────────────────
|
||||||
// Default: ../dist/el-lsp relative to this extension root.
|
|
||||||
// Override with the "el.lspPath" workspace setting.
|
|
||||||
const config = workspace.getConfiguration('el');
|
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.
|
// Default path: ../dist/el-lsp relative to the extension root.
|
||||||
|
// Override with the "el.lspPath" workspace/user setting.
|
||||||
|
const defaultLspPath = path.join(context.extensionPath, '..', 'dist', 'el-lsp');
|
||||||
|
const serverPath = config.get('lspPath', defaultLspPath);
|
||||||
|
|
||||||
|
if (!fs.existsSync(serverPath)) {
|
||||||
|
window.showErrorMessage(
|
||||||
|
`El Language Server binary not found at: ${serverPath}\n` +
|
||||||
|
`Run tools/lsp/build.sh to build it, then reload VSCode.\n` +
|
||||||
|
`Or set "el.lspPath" in settings to the correct path.`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Server options (start el-lsp as child process) ───────────────────
|
||||||
|
/** @type {import('vscode-languageclient/node').ServerOptions} */
|
||||||
const serverOptions = {
|
const serverOptions = {
|
||||||
run: {
|
run: {
|
||||||
command: serverPath,
|
command: serverPath,
|
||||||
transport: TransportKind.stdio,
|
transport: TransportKind.stdio,
|
||||||
|
options: { env: { ...process.env } },
|
||||||
},
|
},
|
||||||
debug: {
|
debug: {
|
||||||
command: serverPath,
|
command: serverPath,
|
||||||
transport: TransportKind.stdio,
|
transport: TransportKind.stdio,
|
||||||
// In debug mode you could add --verbose or redirect stderr to a
|
options: {
|
||||||
// log file for inspection. For now debug == run.
|
env: { ...process.env },
|
||||||
|
// Redirect el-lsp stderr to VSCode's Output panel in debug mode.
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// ClientOptions: handle all files with language id "el".
|
// ── Client options ───────────────────────────────────────────────────
|
||||||
|
/** @type {import('vscode-languageclient/node').LanguageClientOptions} */
|
||||||
const clientOptions = {
|
const clientOptions = {
|
||||||
|
// Activate for all .el files (file:// and untitled: schemes).
|
||||||
documentSelector: [
|
documentSelector: [
|
||||||
{ scheme: 'file', language: 'el' },
|
{ scheme: 'file', language: 'el' },
|
||||||
{ scheme: 'untitled', language: 'el' },
|
{ scheme: 'untitled', language: 'el' },
|
||||||
],
|
],
|
||||||
synchronize: {
|
synchronize: {
|
||||||
// Re-send full document on save (matches textDocumentSync: 1).
|
// Fire fileEvents so the client resends on disk changes outside VSCode.
|
||||||
fileEvents: workspace.createFileSystemWatcher('**/*.el'),
|
fileEvents: workspace.createFileSystemWatcher('**/*.el'),
|
||||||
},
|
},
|
||||||
// Output channel for LSP messages (visible in Output → El Language Server).
|
|
||||||
outputChannelName: 'El Language Server',
|
outputChannelName: 'El Language Server',
|
||||||
|
// Trace LSP messages to the Output panel for debugging.
|
||||||
|
// Set "el.trace.server": "verbose" in settings to enable.
|
||||||
|
traceOutputChannel: window.createOutputChannel('El LSP Trace'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ── Create and start client ──────────────────────────────────────────
|
||||||
client = new LanguageClient(
|
client = new LanguageClient(
|
||||||
'el-language-server',
|
'el-language-server',
|
||||||
'El Language Server',
|
'El Language Server',
|
||||||
serverOptions,
|
serverOptions,
|
||||||
clientOptions
|
clientOptions,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Start the client (and the server as its child process).
|
// Register the client so it is disposed when the extension deactivates.
|
||||||
client.start();
|
context.subscriptions.push(client);
|
||||||
|
|
||||||
// Notify user on activation.
|
client.start().then(() => {
|
||||||
window.showInformationMessage('El Language Server started.');
|
// Show a discrete status bar item while the server is active.
|
||||||
|
const status = window.createStatusBarItem(1);
|
||||||
|
status.text = '$(symbol-misc) El LSP';
|
||||||
|
status.tooltip = 'El Language Server is running';
|
||||||
|
status.command = 'el.restartServer';
|
||||||
|
status.show();
|
||||||
|
context.subscriptions.push(status);
|
||||||
|
}).catch((err) => {
|
||||||
|
window.showErrorMessage(`El Language Server failed to start: ${err.message}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Commands ─────────────────────────────────────────────────────────
|
||||||
|
context.subscriptions.push(
|
||||||
|
require('vscode').commands.registerCommand('el.restartServer', () => {
|
||||||
|
if (client) {
|
||||||
|
client.stop().then(() => client.start());
|
||||||
|
window.showInformationMessage('El Language Server restarted.');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* deactivate — called when the extension is unloaded.
|
* deactivate — called when the extension is unloaded.
|
||||||
* Stops the language client (sends LSP shutdown + exit).
|
* Sends LSP shutdown + exit to the server process.
|
||||||
*/
|
*/
|
||||||
function deactivate() {
|
function deactivate() {
|
||||||
if (!client) {
|
if (!client) {
|
||||||
|
|||||||
@@ -1,20 +1,29 @@
|
|||||||
{
|
{
|
||||||
"name": "el-language",
|
"name": "el-language",
|
||||||
"displayName": "El Language",
|
"displayName": "El Language",
|
||||||
"description": "El language support — syntax highlighting, completions, hover, go-to-definition",
|
"description": "El language support — syntax highlighting, completions, hover, go-to-definition, and diagnostics",
|
||||||
"version": "0.1.0",
|
"version": "1.0.0",
|
||||||
"publisher": "neuron-technologies",
|
"publisher": "neuron-technologies",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"icon": "icon.png",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/neuron-technologies/foundation"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.75.0"
|
"vscode": "^1.75.0"
|
||||||
},
|
},
|
||||||
"categories": [
|
"categories": [
|
||||||
"Programming Languages"
|
"Programming Languages",
|
||||||
|
"Linters",
|
||||||
|
"Other"
|
||||||
],
|
],
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"el",
|
"el",
|
||||||
"el-lang",
|
"el-lang",
|
||||||
"language-server"
|
"language-server",
|
||||||
|
"lsp",
|
||||||
|
"neuron"
|
||||||
],
|
],
|
||||||
"activationEvents": [
|
"activationEvents": [
|
||||||
"onLanguage:el"
|
"onLanguage:el"
|
||||||
@@ -23,13 +32,8 @@
|
|||||||
"languages": [
|
"languages": [
|
||||||
{
|
{
|
||||||
"id": "el",
|
"id": "el",
|
||||||
"aliases": [
|
"aliases": ["El", "el-lang"],
|
||||||
"El",
|
"extensions": [".el", ".elh"],
|
||||||
"el"
|
|
||||||
],
|
|
||||||
"extensions": [
|
|
||||||
".el"
|
|
||||||
],
|
|
||||||
"configuration": "./language-configuration.json"
|
"configuration": "./language-configuration.json"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -39,6 +43,29 @@
|
|||||||
"scopeName": "source.el",
|
"scopeName": "source.el",
|
||||||
"path": "./syntaxes/el.tmGrammar.json"
|
"path": "./syntaxes/el.tmGrammar.json"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"configuration": {
|
||||||
|
"type": "object",
|
||||||
|
"title": "El Language",
|
||||||
|
"properties": {
|
||||||
|
"el.lspPath": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Absolute path to the el-lsp binary. Leave empty to use the binary bundled with the extension (../dist/el-lsp)."
|
||||||
|
},
|
||||||
|
"el.trace.server": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["off", "messages", "verbose"],
|
||||||
|
"default": "off",
|
||||||
|
"description": "Trace LSP messages between VSCode and el-lsp (visible in Output → El LSP Trace)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"command": "el.restartServer",
|
||||||
|
"title": "El: Restart Language Server"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"main": "./extension.js",
|
"main": "./extension.js",
|
||||||
@@ -46,10 +73,12 @@
|
|||||||
"vscode-languageclient": "^8.1.0"
|
"vscode-languageclient": "^8.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/vscode": "^1.75.0"
|
"@types/vscode": "^1.75.0",
|
||||||
|
"@vscode/vsce": "^2.22.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"vscode:prepublish": "echo 'no build step required'",
|
"vscode:prepublish": "echo 'no transpile step required'",
|
||||||
"package": "vsce package"
|
"package": "vsce package",
|
||||||
|
"install-dev": "npm install && code --install-extension el-language-1.0.0.vsix 2>/dev/null || true"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,16 @@
|
|||||||
{ "include": "#comments" },
|
{ "include": "#comments" },
|
||||||
{ "include": "#strings" },
|
{ "include": "#strings" },
|
||||||
{ "include": "#function-definition" },
|
{ "include": "#function-definition" },
|
||||||
{ "include": "#keywords" },
|
{ "include": "#builtin-functions" },
|
||||||
|
{ "include": "#keywords-control" },
|
||||||
|
{ "include": "#keywords-declaration" },
|
||||||
|
{ "include": "#keywords-other" },
|
||||||
{ "include": "#types" },
|
{ "include": "#types" },
|
||||||
|
{ "include": "#type-annotations" },
|
||||||
{ "include": "#constants" },
|
{ "include": "#constants" },
|
||||||
{ "include": "#numbers" },
|
{ "include": "#numbers" },
|
||||||
{ "include": "#operators" },
|
{ "include": "#operators" },
|
||||||
|
{ "include": "#range-operator" },
|
||||||
{ "include": "#function-call" },
|
{ "include": "#function-call" },
|
||||||
{ "include": "#identifiers" }
|
{ "include": "#identifiers" }
|
||||||
],
|
],
|
||||||
@@ -29,44 +34,68 @@
|
|||||||
"patterns": [
|
"patterns": [
|
||||||
{
|
{
|
||||||
"name": "constant.character.escape.el",
|
"name": "constant.character.escape.el",
|
||||||
"match": "\\\\[nrt\\\\\"']"
|
"match": "\\\\[nrt\\\\\"'0]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "constant.character.escape.hex.el",
|
||||||
|
"match": "\\\\x[0-9a-fA-F]{2}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
"function-definition": {
|
"function-definition": {
|
||||||
"comment": "Highlight 'fn name(' — the name gets entity.name.function scope",
|
"comment": "fn keyword + function name — name gets entity.name.function",
|
||||||
"match": "\\bfn\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\(",
|
"match": "\\b(fn)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*(?=\\()",
|
||||||
"captures": {
|
"captures": {
|
||||||
"0": { "name": "meta.function.el" },
|
"1": { "name": "keyword.declaration.function.el" },
|
||||||
"1": { "name": "entity.name.function.el" }
|
"2": { "name": "entity.name.function.el" }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"keywords": {
|
"builtin-functions": {
|
||||||
"patterns": [
|
"comment": "Well-known El builtins get support.function scope for distinct colour",
|
||||||
{
|
"name": "support.function.builtin.el",
|
||||||
"comment": "Control-flow keywords",
|
"match": "\\b(println|print|readline|str_eq|str_len|str_concat|str_slice|str_contains|str_starts_with|str_ends_with|str_replace|str_to_upper|str_to_lower|str_trim|str_lstrip|str_rstrip|str_index_of|str_last_index_of|str_split|str_split_lines|str_split_n|str_join|str_char_at|str_char_code|str_pad_left|str_pad_right|str_repeat|str_reverse|str_strip_prefix|str_strip_suffix|str_strip_chars|str_count|str_count_lines|str_find_chars|str_upper|str_lower|int_to_str|str_to_int|float_to_str|str_to_float|int_to_float|float_to_int|format_float|decimal_round|bool_to_str|el_abs|el_max|el_min|math_sqrt|math_log|math_ln|math_sin|math_cos|math_pi|el_list_empty|el_list_append|el_list_len|el_list_get|el_list_clone|list_push|list_push_front|list_join|list_range|el_map_get|el_map_set|el_get_field|state_set|state_get|state_del|state_keys|json_get|json_get_string|json_get_int|json_get_float|json_get_bool|json_get_raw|json_set|json_parse|json_stringify|json_array_len|json_array_get|json_array_get_string|fs_read|fs_write|fs_list|fs_exists|fs_mkdir|http_get|http_post|http_post_json|http_get_with_headers|http_post_with_headers|http_serve|http_serve_v2|http_response|url_encode|url_decode|time_now|time_now_utc|sleep_secs|sleep_ms|time_format|time_add|time_diff|now|unix_seconds|unix_millis|uuid_new|uuid_v4|env|args|exit_program|exec_command|exec_capture|sha256_hex|hmac_sha256_hex|base64_encode|base64_decode|base64url_encode|base64url_decode|llm_call|llm_call_system|llm_call_agentic|llm_vision|llm_models|llm_register_tool|engram_node|engram_get_node|engram_strengthen|engram_forget|engram_search|engram_connect|engram_activate|engram_save|engram_load|engram_node_count|engram_edge_count|engram_neighbors|engram_search_json|engram_stats_json|dharma_connect|dharma_send|dharma_activate|dharma_emit|dharma_field|dharma_peers|native_list_empty|native_list_append|native_list_len|native_list_get|native_list_clone|native_string_chars|native_int_to_str)\\b(?=\\s*\\()"
|
||||||
"name": "keyword.control.el",
|
},
|
||||||
"match": "\\b(if|else|while|for|in|return|match)\\b"
|
|
||||||
},
|
"keywords-control": {
|
||||||
{
|
"name": "keyword.control.el",
|
||||||
"comment": "Declaration keywords",
|
"match": "\\b(if|else|while|for|in|return|match|break|continue)\\b"
|
||||||
"name": "keyword.declaration.el",
|
},
|
||||||
"match": "\\b(fn|let|type|enum|import|from|as)\\b"
|
|
||||||
},
|
"keywords-declaration": {
|
||||||
{
|
"name": "keyword.declaration.el",
|
||||||
"comment": "Reserved future keywords",
|
"match": "\\b(fn|let|type|enum|import|from|as|extern)\\b"
|
||||||
"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"
|
|
||||||
}
|
"keywords-other": {
|
||||||
]
|
"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": {
|
"types": {
|
||||||
"comment": "Built-in El types",
|
"comment": "Built-in El primitive types",
|
||||||
"name": "support.type.el",
|
"name": "support.type.primitive.el",
|
||||||
"match": "\\b(String|Int|Float|Bool|Void|Any|Map|List)\\b"
|
"match": "\\b(String|Int|Float|Bool|Void|Any|Instant|Duration|Map|List)\\b"
|
||||||
|
},
|
||||||
|
|
||||||
|
"type-annotations": {
|
||||||
|
"comment": "Highlight type annotations after : in let and fn params",
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"match": ":\\s*([A-Z][a-zA-Z0-9_<>, ]*)(?=\\s*[,)={\\]]|\\s*->)",
|
||||||
|
"captures": {
|
||||||
|
"1": { "name": "support.type.el" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": "->\\s*([A-Z][a-zA-Z0-9_<>, ]*)",
|
||||||
|
"captures": {
|
||||||
|
"0": { "name": "keyword.operator.arrow.el" },
|
||||||
|
"1": { "name": "support.type.return.el" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
"constants": {
|
"constants": {
|
||||||
@@ -74,6 +103,10 @@
|
|||||||
{
|
{
|
||||||
"name": "constant.language.boolean.el",
|
"name": "constant.language.boolean.el",
|
||||||
"match": "\\b(true|false)\\b"
|
"match": "\\b(true|false)\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "constant.language.null.el",
|
||||||
|
"match": "\\bnull\\b"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -82,24 +115,33 @@
|
|||||||
"patterns": [
|
"patterns": [
|
||||||
{
|
{
|
||||||
"name": "constant.numeric.float.el",
|
"name": "constant.numeric.float.el",
|
||||||
"match": "\\b[0-9]+\\.[0-9]+\\b"
|
"match": "-?\\b[0-9]+\\.[0-9]+\\b"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "constant.numeric.integer.el",
|
"name": "constant.numeric.integer.el",
|
||||||
"match": "\\b[0-9]+\\b"
|
"match": "-?\\b[0-9]+\\b"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"range-operator": {
|
||||||
|
"name": "keyword.operator.range.el",
|
||||||
|
"match": "\\.\\."
|
||||||
|
},
|
||||||
|
|
||||||
"operators": {
|
"operators": {
|
||||||
"patterns": [
|
"patterns": [
|
||||||
{
|
{
|
||||||
"name": "keyword.operator.comparison.el",
|
"name": "keyword.operator.comparison.el",
|
||||||
"match": "(==|!=|<=|>=|<|>)"
|
"match": "(==|!=|<=|>=)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keyword.operator.comparison.el",
|
||||||
|
"match": "(?<![<>-])([<>])(?![>=])"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "keyword.operator.logical.el",
|
"name": "keyword.operator.logical.el",
|
||||||
"match": "(&&|\\|\\||!)"
|
"match": "(&&|\\|\\||!(?!=))"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "keyword.operator.assignment.el",
|
"name": "keyword.operator.assignment.el",
|
||||||
@@ -117,7 +159,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"function-call": {
|
"function-call": {
|
||||||
"comment": "Highlight function calls: name( — name gets entity.name.function scope",
|
"comment": "Function calls: identifier immediately followed by (",
|
||||||
"match": "\\b([a-zA-Z_][a-zA-Z0-9_]*)\\s*(?=\\()",
|
"match": "\\b([a-zA-Z_][a-zA-Z0-9_]*)\\s*(?=\\()",
|
||||||
"captures": {
|
"captures": {
|
||||||
"1": { "name": "entity.name.function.call.el" }
|
"1": { "name": "entity.name.function.call.el" }
|
||||||
@@ -125,7 +167,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"identifiers": {
|
"identifiers": {
|
||||||
"comment": "General identifiers — lower priority than other rules",
|
"comment": "Catch-all for remaining identifiers",
|
||||||
"name": "variable.other.el",
|
"name": "variable.other.el",
|
||||||
"match": "\\b[a-zA-Z_][a-zA-Z0-9_]*\\b"
|
"match": "\\b[a-zA-Z_][a-zA-Z0-9_]*\\b"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user