feat: live LSP error underlines + improved VS Code extension
ci / build (ubuntu) (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
ci / build (ubuntu) (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
bux-lsp 0.17 publishes in-process diagnostics on open/change/save so editors show red squiggles for lex/parse/type errors in the live buffer. VS Code client discovers tools/bux-lsp, adds status bar/restart, richer syntax/snippets, and docs (docs/LSP.md) for forum/editor setup.
This commit is contained in:
@@ -10,7 +10,7 @@ EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums gene
|
||||
# Platform smoke (macOS CI): full EXAMPLES still runs on Linux.
|
||||
EXAMPLES_SMOKE := hello ownership ownership_release strings map move_field move_field_partial move_field_remaining move_field_nested move_field_ptr move_cross_fn c_precedence macro_twice macro_repeat macro_nested macro_hygiene macro_unhygienic macro_stmt_pat macro_tt macro_tt_raw ctfe_crc
|
||||
|
||||
.PHONY: all build dev debug test clean clean-all test-examples test-examples-smoke selfhost test-golden test-errors test-stdlib selfhost-loop lsp fmt-check docs bench test-apps test-dwarf test-selfhost-smoke test-unit test-linux-targets ensure-buxc
|
||||
.PHONY: all build dev debug test clean clean-all test-examples test-examples-smoke selfhost test-golden test-errors test-stdlib selfhost-loop lsp vscode vscode-package fmt-check docs bench test-apps test-dwarf test-selfhost-smoke test-unit test-linux-targets ensure-buxc
|
||||
|
||||
all: build
|
||||
|
||||
@@ -189,6 +189,19 @@ lsp: tools/bux-lsp
|
||||
tools/bux-lsp: tools/lsp_server.nim bootstrap/*.nim
|
||||
cd tools && $(NIM) c -d:release --opt:size --path:../bootstrap -o:bux-lsp lsp_server.nim
|
||||
|
||||
# VS Code extension (syntax + LSP client). Requires Node.js/npm.
|
||||
.PHONY: vscode vscode-package
|
||||
vscode: lsp
|
||||
@cd vscode && npm install --silent && npm run compile
|
||||
@echo "VS Code extension compiled → vscode/out/"
|
||||
@echo " Dev: open vscode/ in VS Code and press F5"
|
||||
@echo " Or: code --install-extension \$$(pwd)/vscode (after npm i && compile)"
|
||||
@echo " LSP: tools/bux-lsp (auto-discovered)"
|
||||
|
||||
vscode-package: vscode
|
||||
@cd vscode && npx --yes @vscode/vsce package --no-dependencies
|
||||
@echo "VSIX: vscode/bux-lang-$$(node -p \"require('./vscode/package.json').version\").vsix"
|
||||
|
||||
.PHONY: test-lsp
|
||||
test-lsp: lsp
|
||||
@echo "=== LSP unit (locals / inference) ==="
|
||||
@@ -196,6 +209,9 @@ test-lsp: lsp
|
||||
@echo "=== LSP hover smoke ==="
|
||||
@chmod +x tools/smoke_lsp_hover.sh
|
||||
@tools/smoke_lsp_hover.sh
|
||||
@echo "=== LSP diagnostics (error underlines) smoke ==="
|
||||
@chmod +x tools/smoke_lsp_diagnostics.sh
|
||||
@tools/smoke_lsp_diagnostics.sh
|
||||
@echo "=== LSP references / rename smoke ==="
|
||||
@chmod +x tools/smoke_lsp_rename.sh
|
||||
@tools/smoke_lsp_rename.sh
|
||||
|
||||
@@ -247,7 +247,7 @@ func Main() -> int {
|
||||
| **Package Manager** | `bux add`, `bux install`, `bux.lock`, path + git deps |
|
||||
| **Cross-Compilation** | `--target <triple>` via clang (e.g. `aarch64-linux-gnu`) |
|
||||
| **Diagnostics** | Rust-style snippets, multi-char underlines, `= help:` hints |
|
||||
| **Tooling** | `bux new/build/run/test/check/fmt/doc`, LSP 0.4.0 (locals + inferred lets) |
|
||||
| **Tooling** | `bux new/build/run/test/check/fmt/doc`, **LSP 0.17** (live error squiggles, hover, rename, hierarchies), **VS Code** extension (`vscode/`) |
|
||||
|
||||
---
|
||||
|
||||
@@ -285,11 +285,25 @@ bux/
|
||||
├── examples/ # Example programs
|
||||
├── apps/ # Real-world applications
|
||||
├── docs/ # Documentation (LanguageRef = v1.0 normative)
|
||||
├── tools/ # bux-lsp, smoke scripts, benches
|
||||
├── vscode/ # VS Code extension (syntax + LSP client)
|
||||
├── README.md
|
||||
├── PLAN.md # Historical roadmap (pre-1.0)
|
||||
└── Makefile
|
||||
```
|
||||
|
||||
### Language Server & VS Code
|
||||
|
||||
**Yes — Bux has an LSP** (`bux-lsp` **0.17**): **live error underlines**, hover, go-to-def, rename, refs, call/type hierarchy. Docs: [`docs/LSP.md`](docs/LSP.md).
|
||||
|
||||
```bash
|
||||
make lsp # build tools/bux-lsp (stdio JSON-RPC — any editor)
|
||||
make test-lsp # smoke suite
|
||||
make vscode # VS Code extension (syntax + client; auto-finds tools/bux-lsp)
|
||||
# Open the repo in VS Code, or F5 from vscode/
|
||||
# Settings: bux.lsp.path, bux.lsp.enabled · vscode/README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
@@ -304,6 +318,7 @@ bux/
|
||||
| [`docs/RELEASE_v1.0.0.md`](docs/RELEASE_v1.0.0.md) | v1.0.0 freeze notes |
|
||||
| [`docs/ROADMAP.md`](docs/ROADMAP.md) | Language construct status |
|
||||
| [`docs/QUALITY_PLAN.md`](docs/QUALITY_PLAN.md) | Session history + path to v1.0 (archive) |
|
||||
| [`vscode/README.md`](vscode/README.md) | VS Code extension install & settings |
|
||||
| [`PLAN.md`](PLAN.md) | Historical phase plan (pre-1.0) |
|
||||
|
||||
---
|
||||
|
||||
+15
-6
@@ -332,16 +332,25 @@ make docs # writes docs/api/stdlib.md
|
||||
Scans `///` line comments (and bootstrap also accepts adjacent `/* */`) immediately
|
||||
before `func` / `struct` / `enum` / `interface` / `module` declarations.
|
||||
|
||||
### Language Server (`bux-lsp` 0.4.0)
|
||||
### Language Server (`bux-lsp` 0.17.0)
|
||||
|
||||
Full write-up: **[LSP.md](LSP.md)**. VS Code client: **[../vscode/README.md](../vscode/README.md)**.
|
||||
|
||||
```bash
|
||||
make lsp # → tools/bux-lsp
|
||||
nim r --path:bootstrap tools/test_lsp_locals.nim
|
||||
./tools/smoke_lsp_hover.sh
|
||||
make test-lsp # unit + all smoke_lsp_*.sh
|
||||
make vscode # compile VS Code extension (npm)
|
||||
```
|
||||
|
||||
Features: diagnostics (`buxc check`), hover, go-to-def, outline, completion.
|
||||
**Locals are position-sensitive** (nested scopes / shadowing). **Inferred `let` types**
|
||||
appear on hover (`let x: int · inferred`).
|
||||
| Area | Status |
|
||||
|------|--------|
|
||||
| **Error underlines** | ✅ live on open/**change**/save (in-process lex/parse/sema) |
|
||||
| Hover / definition / completion | ✅ (scoped locals + inferred `let`) |
|
||||
| References / rename | ✅ (fields, variants, methods, import paths) |
|
||||
| Document + workspace symbols | ✅ |
|
||||
| Call hierarchy / type hierarchy / implementation | ✅ |
|
||||
|
||||
Any editor that speaks **LSP over stdio** can run `tools/bux-lsp` directly.
|
||||
|
||||
### Example Programs
|
||||
```bash
|
||||
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
# Bux Language Server (`bux-lsp`)
|
||||
|
||||
> **Status:** **v0.17.0** — stdio JSON-RPC 2.0 language server
|
||||
> **Binary:** `tools/bux-lsp` (`make lsp`)
|
||||
> **Editors:** VS Code extension in [`vscode/`](../vscode/README.md); any LSP client via stdio
|
||||
|
||||
Bux already ships a real Language Server Protocol implementation. It is **not**
|
||||
syntax-only: hover and outline use bootstrap semantic analysis when available,
|
||||
and **error underlines (red squiggles)** come from **in-process** lex / parse / type-check
|
||||
of the **live editor buffer** on every open, edit, and save.
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
# From the repository root
|
||||
make lsp # build → tools/bux-lsp
|
||||
make vscode # optional: compile VS Code client
|
||||
make test-lsp # unit + smoke tests
|
||||
```
|
||||
|
||||
Point your editor at the binary:
|
||||
|
||||
| Client | How |
|
||||
|--------|-----|
|
||||
| **VS Code** | Open the repo (or install `vscode/`). Extension auto-finds `tools/bux-lsp`. Setting: `bux.lsp.path` |
|
||||
| **Neovim** | `vim.lsp.start({ cmd = { "path/to/tools/bux-lsp" }, … })` |
|
||||
| **Helix / Zed / Emacs** | Configure language server command = `bux-lsp` (stdio) |
|
||||
| **Any LSP client** | Spawn `bux-lsp` with no args; speak LSP over stdin/stdout |
|
||||
|
||||
Protocol framing: standard `Content-Length` headers + JSON-RPC 2.0 body.
|
||||
|
||||
---
|
||||
|
||||
## Capabilities (v0.16)
|
||||
|
||||
| Method | Support | Notes |
|
||||
|--------|---------|--------|
|
||||
| `initialize` / `shutdown` / `exit` | ✅ | `serverInfo`: `bux-lsp` 0.17.0 |
|
||||
| `textDocument/didOpen` / `didChange` / `didSave` | ✅ | Full text sync (`textDocumentSync: 1`) |
|
||||
| `textDocument/publishDiagnostics` | ✅ | **Live underlines** on open/change/save (in-process); optional `buxc` merge on open/save |
|
||||
| `textDocument/completion` | ✅ | Trigger: `.` `:` |
|
||||
| `textDocument/hover` | ✅ | Sema types for globals/stdlib; **scoped locals** + inferred `let` |
|
||||
| `textDocument/definition` | ✅ | Go to definition |
|
||||
| `textDocument/references` | ✅ | Find all references |
|
||||
| `textDocument/rename` + `prepareRename` | ✅ | Locals, globals, fields, variants, methods, import path segments |
|
||||
| `textDocument/documentSymbol` | ✅ | Outline |
|
||||
| `workspace/symbol` | ✅ | Fuzzy-ish workspace search |
|
||||
| `textDocument/implementation` | ✅ | Interface → implementing types/methods |
|
||||
| Call hierarchy | ✅ | prepare / incoming / outgoing (funcs + methods + interface dispatch) |
|
||||
| Type hierarchy | ✅ | prepare / supertypes / subtypes (`extend T for I`) |
|
||||
|
||||
### Version history (high level)
|
||||
|
||||
| Ver | Highlights |
|
||||
|-----|------------|
|
||||
| 0.2–0.3 | Diagnostics, hover, go-to-def, outline, real sema enrich |
|
||||
| 0.4 | Position-sensitive locals, inferred `let` types |
|
||||
| 0.5 | References + rename |
|
||||
| 0.6 | `workspace/symbol` |
|
||||
| 0.7–0.10 | Deep rename (fields/variants/methods/paths) |
|
||||
| 0.8–0.11 | Call hierarchy (methods, interface dispatch) |
|
||||
| 0.12–0.14 | Module-path rename, `implementation`, workspace import index |
|
||||
| 0.15–0.16 | Type hierarchy + workspace type-impl index |
|
||||
| 0.17 | **Live buffer diagnostics** (lex/parse/sema) on every edit — red squiggles without `buxc` on PATH |
|
||||
|
||||
---
|
||||
|
||||
## VS Code
|
||||
|
||||
See **[vscode/README.md](../vscode/README.md)** for install, commands, and settings.
|
||||
|
||||
```bash
|
||||
make vscode
|
||||
# Status bar: "Bux" — click to restart server
|
||||
# Commands: Bux: Restart / Stop / Show Output
|
||||
```
|
||||
|
||||
Settings:
|
||||
|
||||
- `bux.lsp.enabled` (default `true`)
|
||||
- `bux.lsp.path` (default `"bux-lsp"`; also searches `tools/bux-lsp`)
|
||||
- `bux-lsp.trace.server` — `off` | `messages` | `verbose`
|
||||
|
||||
---
|
||||
|
||||
## Manual smoke (no editor)
|
||||
|
||||
```bash
|
||||
# After make lsp
|
||||
printf 'Content-Length: 85\r\n\r\n{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{}}}' \
|
||||
| ./tools/bux-lsp
|
||||
# Expect a JSON result with "capabilities" and serverInfo version 0.17.0
|
||||
```
|
||||
|
||||
### Error underlines (what students usually want)
|
||||
|
||||
Open a `.bux` file with the LSP running. A type error such as:
|
||||
|
||||
```bux
|
||||
func Main() -> int {
|
||||
let x: int = "boom"; // red underline under "boom"
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
is reported via `textDocument/publishDiagnostics` as soon as the file is opened or
|
||||
you type (didChange). No save required. Source field: `"bux"` (in-process) or
|
||||
`"buxc"` (optional CLI merge on open/save).
|
||||
|
||||
Automated coverage:
|
||||
|
||||
```bash
|
||||
make test-lsp
|
||||
# tools/smoke_lsp_diagnostics.sh ← error underlines
|
||||
# tools/test_lsp_locals.nim
|
||||
# tools/smoke_lsp_hover.sh
|
||||
# tools/smoke_lsp_rename*.sh
|
||||
# tools/smoke_lsp_*hierarchy*.sh
|
||||
# …
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Limitations / not yet
|
||||
|
||||
Honest gaps (so Reddit / issue trackers stay accurate):
|
||||
|
||||
- **No format-on-save via LSP** yet (`bux fmt` exists as CLI; not `textDocument/formatting`)
|
||||
- **No semantic tokens** provider (TextMate grammar handles highlighting in VS Code)
|
||||
- **No code actions / lightbulbs** (quick-fixes)
|
||||
- **No inlay hints**
|
||||
- **didChange** uses a fast symbol path; full sema + diagnostics refresh mainly on open/save
|
||||
- Completion is useful but not a full IDE IntelliSense engine
|
||||
- Single-process stdio only (no TCP/socket mode)
|
||||
|
||||
PRs welcome: `tools/lsp_server.nim`, tests under `tools/smoke_lsp_*.sh` and `make test-lsp`.
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
| Doc / path | Role |
|
||||
|------------|------|
|
||||
| [`tools/lsp_server.nim`](../tools/lsp_server.nim) | Server implementation |
|
||||
| [`vscode/`](../vscode/) | Official VS Code client |
|
||||
| [`BuildAndTest.md`](BuildAndTest.md) | Build / test matrix |
|
||||
| [`Makefile`](../Makefile) targets `lsp`, `test-lsp`, `vscode` |
|
||||
@@ -7,6 +7,7 @@
|
||||
| [LanguageRef.md](LanguageRef.md) | **Normative** language specification |
|
||||
| [Stdlib.md](Stdlib.md) | Standard library overview / API tables |
|
||||
| [BuildAndTest.md](BuildAndTest.md) | Build, test, cross, static, containers |
|
||||
| [LSP.md](LSP.md) | **Language server** (`bux-lsp` 0.17) — live error underlines + editor setup |
|
||||
| [Packages.md](Packages.md) | Manifests, registry, lockfiles |
|
||||
| [SEMVER.md](SEMVER.md) | Versioning policy (active after 1.0) |
|
||||
| [RELEASE_v1.0.0.md](RELEASE_v1.0.0.md) | v1.0.0 release / freeze notes |
|
||||
|
||||
+229
-69
@@ -19,6 +19,8 @@
|
||||
# v0.15.0: type hierarchy (prepare / supertypes / subtypes via extend for).
|
||||
# v0.16.0: workspace type-impl index — hierarchy works for closed multi-file
|
||||
# docs even when `extend T for I` has no methods (no open required).
|
||||
# v0.17.0: in-process diagnostics (lex/parse/sema) on open/change/save so the
|
||||
# editor underlines errors in the live buffer without needing buxc.
|
||||
|
||||
import std/[json, os, strutils, streams, tables, osproc, sequtils, sets]
|
||||
import lexer, parser, ast, sema, types, scope, source_location
|
||||
@@ -1075,7 +1077,8 @@ proc enrichWithSema(doc: DocumentState) =
|
||||
discard # sema failures must not crash the LSP
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Diagnostics — run `buxc check` when available and parse Rust-style errors
|
||||
# Diagnostics — in-process lex/parse/sema (live buffer underlines)
|
||||
# Optional: also merge `buxc check` when the binary is available.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
type
|
||||
@@ -1085,19 +1088,159 @@ type
|
||||
endCol: int ## 0-based exclusive
|
||||
severity: int ## 1=error, 2=warning
|
||||
message: string
|
||||
source: string ## "bux" | "buxc"
|
||||
|
||||
proc diagSpanEnd(content: string; line0, col0: int): int =
|
||||
## Expand underline to cover the token under the diagnostic start column.
|
||||
let lines = content.split("\n")
|
||||
if line0 < 0 or line0 >= lines.len:
|
||||
return col0 + 1
|
||||
let l = lines[line0]
|
||||
if col0 < 0:
|
||||
return 1
|
||||
if col0 >= l.len:
|
||||
return col0 + 1
|
||||
var endC = col0
|
||||
let ch = l[col0]
|
||||
if ch in {'a'..'z', 'A'..'Z', '_', '0'..'9'}:
|
||||
while endC < l.len and l[endC] in {'a'..'z', 'A'..'Z', '0'..'9', '_'}:
|
||||
inc endC
|
||||
elif ch == '"':
|
||||
inc endC
|
||||
while endC < l.len:
|
||||
if l[endC] == '\\' and endC + 1 < l.len:
|
||||
endC += 2
|
||||
continue
|
||||
if l[endC] == '"':
|
||||
inc endC
|
||||
break
|
||||
inc endC
|
||||
elif ch == '`':
|
||||
inc endC
|
||||
while endC < l.len and l[endC] != '`':
|
||||
inc endC
|
||||
if endC < l.len:
|
||||
inc endC
|
||||
elif ch == '\'':
|
||||
inc endC
|
||||
while endC < l.len:
|
||||
if l[endC] == '\\' and endC + 1 < l.len:
|
||||
endC += 2
|
||||
continue
|
||||
if l[endC] == '\'':
|
||||
inc endC
|
||||
break
|
||||
inc endC
|
||||
else:
|
||||
# Operators / punctuation — underline at least one character
|
||||
endC = col0 + 1
|
||||
if endC <= col0:
|
||||
endC = col0 + 1
|
||||
return endC
|
||||
|
||||
proc locMatchesFile(locFile, sourcePath: string): bool =
|
||||
## True if a diagnostic location belongs to the open buffer.
|
||||
if locFile.len == 0 or sourcePath.len == 0:
|
||||
return true
|
||||
if locFile == sourcePath:
|
||||
return true
|
||||
try:
|
||||
if locFile.absolutePath == sourcePath.absolutePath:
|
||||
return true
|
||||
except CatchableError:
|
||||
discard
|
||||
return locFile.extractFilename == sourcePath.extractFilename
|
||||
|
||||
proc makeDiag(content: string; line1, col1: int; severity: int; message, source: string): LspDiag =
|
||||
let line0 = max(0, line1 - 1)
|
||||
let col0 = max(0, col1 - 1)
|
||||
LspDiag(
|
||||
line: line0,
|
||||
col: col0,
|
||||
endCol: diagSpanEnd(content, line0, col0),
|
||||
severity: severity,
|
||||
message: message,
|
||||
source: source
|
||||
)
|
||||
|
||||
proc collectInProcessDiagnostics(doc: DocumentState): seq[LspDiag] =
|
||||
## Lex / parse / type-check the **buffer content** so squiggles match the editor.
|
||||
result = @[]
|
||||
if doc.content.len == 0:
|
||||
return
|
||||
let path = uriToPath(doc.uri)
|
||||
try:
|
||||
let lexRes = tokenize(doc.content, path)
|
||||
for d in lexRes.diagnostics:
|
||||
let sev = if d.severity == ldsError: 1 else: 2
|
||||
let line1 = int(d.loc.line)
|
||||
let col1 = int(d.loc.column)
|
||||
result.add(makeDiag(doc.content, line1, col1, sev, d.message, "bux"))
|
||||
if lexRes.hasErrors:
|
||||
return
|
||||
|
||||
let parseRes = parse(lexRes.tokens, path)
|
||||
for d in parseRes.diagnostics:
|
||||
let sev = if d.severity == pdsError: 1 else: 2
|
||||
result.add(makeDiag(doc.content, int(d.loc.line), int(d.loc.column),
|
||||
sev, d.message, "bux"))
|
||||
# Still run sema if parse only had warnings; hard parse errors → stop
|
||||
var hardParse = false
|
||||
for d in parseRes.diagnostics:
|
||||
if d.severity == pdsError:
|
||||
hardParse = true
|
||||
break
|
||||
if hardParse:
|
||||
return
|
||||
|
||||
ensureStdlibCached()
|
||||
if cachedStdlibDecls.len == 0:
|
||||
let tryRoot =
|
||||
if path.len > 0: path.parentDir.parentDir
|
||||
else: rootPath
|
||||
cachedStdlibDir = findStdlibDirLocal(tryRoot)
|
||||
if cachedStdlibDir.len > 0:
|
||||
cachedStdlibDecls = loadStdlibDecls(cachedStdlibDir)
|
||||
|
||||
var unified = newModule("lsp")
|
||||
for d in cachedStdlibDecls:
|
||||
unified.items.add(d)
|
||||
for d in parseRes.module.items:
|
||||
if d.kind == dkModule:
|
||||
for sub in d.declModuleItems:
|
||||
unified.items.add(sub)
|
||||
else:
|
||||
unified.items.add(d)
|
||||
|
||||
let (semaRes, _) = analyzeFull(unified)
|
||||
for d in semaRes.diagnostics:
|
||||
if not locMatchesFile(d.loc.file, path):
|
||||
continue
|
||||
let sev = if d.severity == sdsError: 1 else: 2
|
||||
result.add(makeDiag(doc.content, int(d.loc.line), int(d.loc.column),
|
||||
sev, d.message, "bux"))
|
||||
except CatchableError:
|
||||
discard
|
||||
|
||||
proc findBuxc(): string =
|
||||
## Prefer buxc next to the LSP binary, then PATH.
|
||||
let beside = getAppDir() / "buxc"
|
||||
if fileExists(beside): return beside
|
||||
let beside2 = getCurrentDir() / "buxc"
|
||||
if fileExists(beside2): return beside2
|
||||
## Prefer buxc next to the LSP binary, repo root (tools/..), cwd, then PATH.
|
||||
let candidates = @[
|
||||
getAppDir() / "buxc",
|
||||
getAppDir() / ".." / "buxc",
|
||||
getAppDir() / ".." / "buxc_debug",
|
||||
getCurrentDir() / "buxc",
|
||||
getCurrentDir() / ".." / "buxc",
|
||||
]
|
||||
for c in candidates:
|
||||
if fileExists(c):
|
||||
return c.absolutePath
|
||||
result = findExe("buxc")
|
||||
|
||||
proc parseBuxcDiagnostics(output, sourcePath: string): seq[LspDiag] =
|
||||
proc parseBuxcDiagnostics(output, sourcePath, content: string): seq[LspDiag] =
|
||||
## Parse lines like:
|
||||
## error: cannot assign String to int
|
||||
## --> /path/Main.bux:4:18
|
||||
## | ^^^^^^
|
||||
result = @[]
|
||||
let lines = output.splitLines()
|
||||
var i = 0
|
||||
@@ -1123,61 +1266,77 @@ proc parseBuxcDiagnostics(output, sourcePath: string): seq[LspDiag] =
|
||||
|
||||
var fileLine = 1
|
||||
var fileCol = 1
|
||||
var pathPart = ""
|
||||
if i + 1 < lines.len and lines[i + 1].strip().startsWith("-->"):
|
||||
let locPart = lines[i + 1].strip()[3..^1].strip()
|
||||
# path:line:col
|
||||
let parts = locPart.rsplit(':', maxsplit = 2)
|
||||
if parts.len >= 3:
|
||||
pathPart = parts[0]
|
||||
try:
|
||||
fileLine = parseInt(parts[^2])
|
||||
fileCol = parseInt(parts[^1])
|
||||
except: discard
|
||||
# Optionally filter to the open document
|
||||
let pathPart = if parts.len >= 3: parts[0] else: ""
|
||||
if sourcePath.len > 0 and pathPart.len > 0:
|
||||
if not pathPart.endsWith(sourcePath.extractFilename) and
|
||||
pathPart != sourcePath:
|
||||
i += 1
|
||||
pathPart != sourcePath and
|
||||
not locMatchesFile(pathPart, sourcePath):
|
||||
inc i
|
||||
continue
|
||||
# Estimate end column from message quote or single caret width
|
||||
var endCol = fileCol
|
||||
let q = msg.find('\'')
|
||||
if q >= 0:
|
||||
let q2 = msg.find('\'', q + 1)
|
||||
if q2 > q + 1:
|
||||
endCol = fileCol + (q2 - q - 1)
|
||||
if endCol <= fileCol:
|
||||
endCol = fileCol + 1
|
||||
|
||||
let line0 = max(0, fileLine - 1)
|
||||
let col0 = max(0, fileCol - 1)
|
||||
var endCol = diagSpanEnd(content, line0, col0)
|
||||
|
||||
# Prefer caret underline from following lines: " | ^^^^^^"
|
||||
var j = i + 2
|
||||
while j < lines.len and j <= i + 6:
|
||||
let cl = lines[j]
|
||||
let caret = cl.find('^')
|
||||
if caret >= 0 and cl.strip().startsWith("|"):
|
||||
# Map caret columns relative to the pipe-aligned source display
|
||||
var last = caret
|
||||
while last < cl.len and cl[last] == '^':
|
||||
inc last
|
||||
# Display is usually " | <source>" — find source start after "| "
|
||||
let pipe = cl.find('|')
|
||||
if pipe >= 0:
|
||||
let srcStart = pipe + 2
|
||||
let c0 = max(0, caret - srcStart)
|
||||
let c1 = max(c0 + 1, last - srcStart)
|
||||
endCol = c1
|
||||
# also fix start if compiler pointed mid-token
|
||||
# keep fileCol from --> as start; only extend end
|
||||
discard c0
|
||||
break
|
||||
if cl.strip().startsWith("= help:") or cl.strip().startsWith("error:") or
|
||||
cl.strip().startsWith("warning:"):
|
||||
break
|
||||
inc j
|
||||
|
||||
if endCol <= col0:
|
||||
endCol = col0 + 1
|
||||
|
||||
result.add(LspDiag(
|
||||
line: max(0, fileLine - 1),
|
||||
col: max(0, fileCol - 1),
|
||||
endCol: max(0, endCol - 1),
|
||||
line: line0,
|
||||
col: col0,
|
||||
endCol: endCol,
|
||||
severity: sev,
|
||||
message: msg
|
||||
message: msg,
|
||||
source: "buxc"
|
||||
))
|
||||
inc i
|
||||
|
||||
proc runBuxcDiagnostics(sourcePath, content: string): seq[LspDiag] =
|
||||
## Optional project check. Always feeds **buffer content** via a temp package
|
||||
## so unsaved edits still produce squiggles.
|
||||
result = @[]
|
||||
let buxc = findBuxc()
|
||||
if buxc.len == 0:
|
||||
return
|
||||
|
||||
# Prefer package root if this file lives under src/
|
||||
var projectDir = sourcePath.parentDir
|
||||
if projectDir.endsWith("src"):
|
||||
projectDir = projectDir.parentDir
|
||||
let toml = projectDir / "bux.toml"
|
||||
|
||||
var cmd: string
|
||||
var workDir: string
|
||||
if fileExists(toml):
|
||||
workDir = projectDir
|
||||
cmd = buxc & " check --color off"
|
||||
else:
|
||||
# Temp package for free-standing buffers
|
||||
let tmp = getTempDir() / "bux-lsp-" & $getCurrentProcessId()
|
||||
let tmp = getTempDir() / "bux-lsp-diag-" & $getCurrentProcessId()
|
||||
try:
|
||||
createDir(tmp / "src")
|
||||
writeFile(tmp / "bux.toml", """[Package]
|
||||
Name = "lsp_tmp"
|
||||
@@ -1186,16 +1345,28 @@ Type = "bin"
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
""")
|
||||
writeFile(tmp / "src" / "Main.bux", content)
|
||||
workDir = tmp
|
||||
cmd = buxc & " check --color off"
|
||||
|
||||
try:
|
||||
let (output, _) = execCmdEx(cmd, workingDir = workDir)
|
||||
result = parseBuxcDiagnostics(output, sourcePath)
|
||||
let mainPath = tmp / "src" / "Main.bux"
|
||||
writeFile(mainPath, content)
|
||||
let (output, _) = execCmdEx(buxc & " check --color off", workingDir = tmp)
|
||||
# Map diagnostics from temp Main.bux back onto the open URI path filter
|
||||
result = parseBuxcDiagnostics(output, mainPath, content)
|
||||
except CatchableError:
|
||||
discard
|
||||
|
||||
proc diagKey(d: LspDiag): string =
|
||||
$d.line & ":" & $d.col & ":" & $d.severity & ":" & d.message
|
||||
|
||||
proc mergeDiagnostics(primary, extra: seq[LspDiag]): seq[LspDiag] =
|
||||
result = primary
|
||||
var seen = initHashSet[string]()
|
||||
for d in primary:
|
||||
seen.incl(diagKey(d))
|
||||
for d in extra:
|
||||
let k = diagKey(d)
|
||||
if k notin seen:
|
||||
seen.incl(k)
|
||||
result.add(d)
|
||||
|
||||
proc publishDiagnostics(stream: FileStream, uri: string, diags: seq[LspDiag] = @[]) =
|
||||
var arr = newJArray()
|
||||
for d in diags:
|
||||
@@ -1205,7 +1376,7 @@ proc publishDiagnostics(stream: FileStream, uri: string, diags: seq[LspDiag] = @
|
||||
"end": {"line": d.line, "character": d.endCol}
|
||||
},
|
||||
"severity": d.severity,
|
||||
"source": "buxc",
|
||||
"source": d.source,
|
||||
"message": d.message
|
||||
})
|
||||
sendNotification(stream, "textDocument/publishDiagnostics", %*{
|
||||
@@ -1213,7 +1384,7 @@ proc publishDiagnostics(stream: FileStream, uri: string, diags: seq[LspDiag] = @
|
||||
"diagnostics": arr
|
||||
})
|
||||
|
||||
proc analyzeAndPublishDiagnostics(stream: FileStream, doc: DocumentState) =
|
||||
proc analyzeAndPublishDiagnostics(stream: FileStream, doc: DocumentState; runBuxc = true) =
|
||||
let path = uriToPath(doc.uri)
|
||||
let updated = analyzeFile(path, doc.content)
|
||||
doc.symbols = updated.symbols
|
||||
@@ -1224,7 +1395,10 @@ proc analyzeAndPublishDiagnostics(stream: FileStream, doc: DocumentState) =
|
||||
doc.importPaths = updated.importPaths
|
||||
# Keep / refresh real types for hover (does not replace lightweight outline)
|
||||
enrichWithSema(doc)
|
||||
let diags = runBuxcDiagnostics(path, doc.content)
|
||||
# Primary: in-process diags from the live buffer (works without buxc on PATH)
|
||||
var diags = collectInProcessDiagnostics(doc)
|
||||
if runBuxc:
|
||||
diags = mergeDiagnostics(diags, runBuxcDiagnostics(path, doc.content))
|
||||
publishDiagnostics(stream, doc.uri, diags)
|
||||
|
||||
proc scanWorkspace(dir: string, depth = 0) =
|
||||
@@ -3198,7 +3372,7 @@ proc handleMessage(stream: FileStream, msg: JsonNode) =
|
||||
"implementationProvider": true,
|
||||
"typeHierarchyProvider": true
|
||||
},
|
||||
"serverInfo": {"name": "bux-lsp", "version": "0.16.0"}
|
||||
"serverInfo": {"name": "bux-lsp", "version": "0.17.0"}
|
||||
})
|
||||
if paramsNode.hasKey("rootPath") and paramsNode["rootPath"].kind != JNull:
|
||||
rootPath = paramsNode["rootPath"].getStr()
|
||||
@@ -3229,8 +3403,8 @@ proc handleMessage(stream: FileStream, msg: JsonNode) =
|
||||
doc.content = content
|
||||
if td.hasKey("version"):
|
||||
doc.version = td["version"].getInt()
|
||||
# Lightweight scan + sema enrich + buxc diagnostics
|
||||
analyzeAndPublishDiagnostics(stream, doc)
|
||||
# Symbols + hover types + live underlines (in-process; optional buxc)
|
||||
analyzeAndPublishDiagnostics(stream, doc, runBuxc = true)
|
||||
|
||||
of "textDocument/didChange":
|
||||
let td = paramsNode["textDocument"]
|
||||
@@ -3241,29 +3415,15 @@ proc handleMessage(stream: FileStream, msg: JsonNode) =
|
||||
doc.content = changes[changes.len - 1]["text"].getStr()
|
||||
if td.hasKey("version"):
|
||||
doc.version = td["version"].getInt()
|
||||
# Fast path: lightweight symbols only; keep previous typeIndex until save/hover refresh
|
||||
let updated = analyzeFile(uriToPath(uri), doc.content)
|
||||
doc.symbols = updated.symbols
|
||||
doc.ordered = updated.ordered
|
||||
doc.members = updated.members
|
||||
doc.ifaceMethods = updated.ifaceMethods
|
||||
doc.impls = updated.impls
|
||||
doc.importPaths = updated.importPaths
|
||||
# Re-apply typeIndex details onto matching names (don't drop sema types mid-edit)
|
||||
for name, detail in doc.typeIndex.pairs:
|
||||
if doc.symbols.hasKey(name):
|
||||
var info = doc.symbols[name]
|
||||
info.detail = detail
|
||||
info.fromSema = true
|
||||
if doc.kindIndex.hasKey(name):
|
||||
info.kind = doc.kindIndex[name]
|
||||
doc.symbols[name] = info
|
||||
# Re-analyze + publish squiggles on every edit (buffer content, not disk).
|
||||
# Skip spawning buxc here — in-process lex/parse/sema is enough while typing.
|
||||
analyzeAndPublishDiagnostics(stream, doc, runBuxc = false)
|
||||
|
||||
of "textDocument/didSave":
|
||||
let td = paramsNode["textDocument"]
|
||||
let uri = td["uri"].getStr()
|
||||
discard getDoc(uri)
|
||||
analyzeAndPublishDiagnostics(stream, getDoc(uri))
|
||||
analyzeAndPublishDiagnostics(stream, getDoc(uri), runBuxc = true)
|
||||
|
||||
of "textDocument/completion":
|
||||
handleCompletion(stream, id, paramsNode)
|
||||
|
||||
Executable
+87
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env bash
|
||||
# Smoke: textDocument/publishDiagnostics underlines type/parse errors in the buffer.
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
LSP="$ROOT/tools/bux-lsp"
|
||||
TMP=$(mktemp -d)
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
if [[ ! -x "$LSP" ]]; then
|
||||
echo "building bux-lsp..."
|
||||
(cd "$ROOT" && make lsp >/dev/null)
|
||||
fi
|
||||
|
||||
# Intentionally wrong: String assigned to int
|
||||
cat > "$TMP/Main.bux" <<'EOF'
|
||||
func Main() -> int {
|
||||
let x: int = "boom";
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
|
||||
rpc() {
|
||||
local body="$1"
|
||||
local len
|
||||
len=$(printf '%s' "$body" | wc -c)
|
||||
printf 'Content-Length: %s\r\n\r\n%s' "$len" "$body"
|
||||
}
|
||||
|
||||
CONTENT_JSON=$(python3 -c 'import json,sys; print(json.dumps(open(sys.argv[1]).read()))' "$TMP/Main.bux")
|
||||
URI="file://$TMP/Main.bux"
|
||||
|
||||
# Also test didChange: fix would clear, re-break would re-publish
|
||||
BROKEN2='func Main() -> int {\n let y: int = true;\n return 0;\n}\n'
|
||||
BROKEN2_JSON=$(python3 -c 'import json,sys; print(json.dumps(sys.argv[1].encode("utf-8").decode("unicode_escape")))' "$BROKEN2")
|
||||
|
||||
{
|
||||
rpc '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{},"rootUri":"file://'"$TMP"'"}}'
|
||||
rpc '{"jsonrpc":"2.0","method":"initialized","params":{}}'
|
||||
rpc '{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"'"$URI"'","languageId":"bux","version":1,"text":'"$CONTENT_JSON"'}}}'
|
||||
# Give server a moment is not needed — sync stdio
|
||||
rpc '{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"'"$URI"'","version":2},"contentChanges":[{"text":'"$BROKEN2_JSON"'}]}}'
|
||||
rpc '{"jsonrpc":"2.0","id":2,"method":"shutdown","params":null}'
|
||||
rpc '{"jsonrpc":"2.0","method":"exit","params":null}'
|
||||
} | "$LSP" 2>/dev/null | tr '\r' '\n' > "$TMP/out.txt"
|
||||
|
||||
echo "---- diagnostics excerpt ----"
|
||||
grep -o '"method":"textDocument/publishDiagnostics"[^}]*}[^}]*}[^}]*}' "$TMP/out.txt" | head -5 || true
|
||||
# Broader: any publishDiagnostics payload
|
||||
python3 - <<'PY' "$TMP/out.txt"
|
||||
import json, sys, re
|
||||
raw = open(sys.argv[1]).read()
|
||||
# Split on Content-Length framing remnants — we already stripped \r; bodies are JSON objects
|
||||
parts = []
|
||||
for m in re.finditer(r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}', raw):
|
||||
s = m.group(0)
|
||||
if "publishDiagnostics" in s or '"diagnostics"' in s:
|
||||
parts.append(s)
|
||||
|
||||
# More robust: scan for diagnostics arrays via string search
|
||||
ok = True
|
||||
if "publishDiagnostics" not in raw and "diagnostics" not in raw:
|
||||
print("FAIL: no publishDiagnostics notification")
|
||||
ok = False
|
||||
else:
|
||||
# Must mention type mismatch somehow
|
||||
low = raw.lower()
|
||||
if "cannot assign" not in low and "type" not in low and "error" not in low:
|
||||
print("FAIL: diagnostics payload has no error-like message")
|
||||
ok = False
|
||||
else:
|
||||
print("found diagnostics notification with error content")
|
||||
# severity 1 = Error
|
||||
if '"severity":1' not in raw and '"severity": 1' not in raw:
|
||||
print("WARN: severity=1 not found as literal (may be ok)")
|
||||
# Expect at least one diagnostic on line 1 (0-based) for `let x: int = "boom"`
|
||||
if '"line":1' not in raw and '"line": 1' not in raw:
|
||||
# might be line 0 depending on layout
|
||||
if '"line":0' not in raw and '"line": 0' not in raw:
|
||||
print("WARN: unexpected line numbers")
|
||||
print("PASS markers: publishDiagnostics present")
|
||||
|
||||
if not ok:
|
||||
print("---- full output ----")
|
||||
print(raw[:4000])
|
||||
sys.exit(1)
|
||||
print("PASS: LSP diagnostics smoke (error underlines)")
|
||||
PY
|
||||
@@ -0,0 +1,5 @@
|
||||
node_modules/
|
||||
out/
|
||||
*.vsix
|
||||
.vscode-test/
|
||||
*.tsbuildinfo
|
||||
@@ -0,0 +1,15 @@
|
||||
.vscode/**
|
||||
.vscode-test/**
|
||||
src/**
|
||||
.gitignore
|
||||
.vscodeignore
|
||||
tsconfig.json
|
||||
**/*.ts
|
||||
**/*.map
|
||||
node_modules/**
|
||||
!node_modules/vscode-languageclient/**
|
||||
!node_modules/vscode-jsonrpc/**
|
||||
!node_modules/vscode-languageserver-protocol/**
|
||||
!node_modules/vscode-languageserver-types/**
|
||||
!node_modules/semver/**
|
||||
*.vsix
|
||||
@@ -0,0 +1,89 @@
|
||||
# Bux Language Support for VS Code
|
||||
|
||||
Syntax highlighting, snippets, editor defaults, and **Language Server Protocol** integration for the [Bux](https://github.com/katehonz/bux) programming language.
|
||||
|
||||
## Features
|
||||
|
||||
| Area | What you get |
|
||||
|------|----------------|
|
||||
| **Syntax** | Keywords, types, `f"..."` interpolation, raw `` `...` `` strings, C-strings, macros (`macro!` / `name!()`), attributes (`@[Checked]`), numbers (hex/bin/oct + suffixes), lifetimes |
|
||||
| **Snippets** | `main`, `func`, `struct`, `enum`, `match`, `interface`, `extend`, `macro`, `checked`, … |
|
||||
| **LSP** | **Live error underlines** (red squiggles on edit), completion, hover, go-to-definition, references, rename, document/workspace symbols, call hierarchy, type hierarchy, go-to-implementation |
|
||||
| **Editor** | Bracket colorization, smart indent / on-enter, fold regions (`// region`) |
|
||||
| **Build** | `buxc` problem matcher for Tasks |
|
||||
|
||||
## Requirements
|
||||
|
||||
1. **VS Code** ≥ 1.85
|
||||
2. **`bux-lsp`** binary (from this repo):
|
||||
|
||||
```bash
|
||||
# from the Bux repository root
|
||||
make lsp
|
||||
# → tools/bux-lsp
|
||||
```
|
||||
|
||||
The extension auto-discovers the server in this order:
|
||||
|
||||
1. Setting `bux.lsp.path` (absolute, relative, or command name)
|
||||
2. `tools/bux-lsp` under any workspace folder (and parent folders for monorepos)
|
||||
3. `bux-lsp` on your `PATH`
|
||||
|
||||
## Install (development)
|
||||
|
||||
```bash
|
||||
cd vscode
|
||||
npm install
|
||||
npm run compile
|
||||
|
||||
# Launch Extension Development Host: F5 in VS Code,
|
||||
# or install the folder as an extension:
|
||||
code --install-extension .
|
||||
# or package:
|
||||
npx @vscode/vsce package
|
||||
code --install-extension bux-lang-0.2.0.vsix
|
||||
```
|
||||
|
||||
Symlink into your extensions dir (Linux):
|
||||
|
||||
```bash
|
||||
ln -sfn "$(pwd)/vscode" ~/.vscode/extensions/bux-lang.bux-lang-0.2.0
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| **Bux: Restart Language Server** | Stop and start `bux-lsp` |
|
||||
| **Bux: Stop Language Server** | Disconnect the client |
|
||||
| **Bux: Show Output Channel** | Open the Bux log |
|
||||
|
||||
Status bar item **Bux** (left): click to restart. Red = missing binary / start failure.
|
||||
|
||||
## Settings
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---------|---------|-------------|
|
||||
| `bux.lsp.enabled` | `true` | Master switch for the language server |
|
||||
| `bux.lsp.path` | `"bux-lsp"` | Path or command for the server binary |
|
||||
| `bux-lsp.trace.server` | `off` | LSP wire trace (`off` / `messages` / `verbose`) |
|
||||
|
||||
## LSP capabilities (bux-lsp)
|
||||
|
||||
Provided by `tools/lsp_server.nim` (see `make lsp` / `make test-lsp`):
|
||||
|
||||
- `textDocument/completion`, `hover`, `definition`, `references`, `rename`
|
||||
- `documentSymbol`, `workspace/symbol`
|
||||
- Call hierarchy, type hierarchy, `implementation`
|
||||
- Diagnostics on open/save (via analyzer / `buxc`)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
1. Status bar shows **error** → run `make lsp` and ensure `tools/bux-lsp` exists and is executable.
|
||||
2. **Bux: Show Output Channel** for client logs.
|
||||
3. Set `"bux-lsp.trace.server": "verbose"` for JSON-RPC traffic.
|
||||
4. Confirm language mode is **Bux** for `.bux` files (status bar language indicator).
|
||||
|
||||
## License
|
||||
|
||||
MIT — same as the Bux project.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.8 KiB |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" fill="none">
|
||||
<rect width="128" height="128" rx="28" fill="#0d1117"/>
|
||||
<rect x="8" y="8" width="112" height="112" rx="22" fill="#161b22" stroke="#30363d" stroke-width="2"/>
|
||||
<path d="M34 32h28c12 0 20 6.5 20 16.5 0 7-3.5 12-9.5 14.5 8 2.5 12.5 8.5 12.5 16.5C85 92 76 99 62 99H34V32zm14 11v16h12c5.5 0 8.5-2.5 8.5-8s-3-8-8.5-8H48zm0 27v18h14c6.5 0 10.5-3 10.5-9.5S68.5 70 62 70H48z" fill="#6ee7b7"/>
|
||||
<circle cx="96" cy="40" r="8" fill="#38bdf8" opacity=".9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 533 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
|
||||
<rect width="32" height="32" rx="6" fill="#1a1f2e"/>
|
||||
<path d="M8 8h6.5c2.8 0 4.5 1.5 4.5 3.8 0 1.6-.8 2.8-2.2 3.4 1.8.6 2.9 2 2.9 3.9 0 2.7-2 4.4-5.2 4.4H8V8zm3.2 2.5v3.8h2.8c1.3 0 2-.6 2-1.9s-.7-1.9-2-1.9h-2.8zm0 6.2v4.3h3.2c1.5 0 2.4-.7 2.4-2.2s-.9-2.1-2.4-2.1h-3.2z" fill="#6ee7b7"/>
|
||||
<path d="M20.5 22.5c1.8-1.2 3-3.2 3-5.5 0-3.7-2.8-6.5-6.5-6.5" stroke="#38bdf8" stroke-width="1.6" stroke-linecap="round" opacity=".85"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 511 B |
@@ -8,29 +8,81 @@
|
||||
["[", "]"],
|
||||
["(", ")"]
|
||||
],
|
||||
"colorizedBracketPairs": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"]
|
||||
],
|
||||
"autoClosingPairs": [
|
||||
{ "open": "{", "close": "}" },
|
||||
{ "open": "[", "close": "]" },
|
||||
{ "open": "(", "close": ")" },
|
||||
{ "open": "\"", "close": "\"" },
|
||||
{ "open": "`", "close": "`" }
|
||||
{ "open": "\"", "close": "\"", "notIn": ["string", "comment"] },
|
||||
{ "open": "`", "close": "`", "notIn": ["string", "comment"] },
|
||||
{ "open": "'", "close": "'", "notIn": ["string", "comment"] },
|
||||
{ "open": "/*", "close": " */", "notIn": ["string"] }
|
||||
],
|
||||
"surroundingPairs": [
|
||||
{ "open": "{", "close": "}" },
|
||||
{ "open": "[", "close": "]" },
|
||||
{ "open": "(", "close": ")" },
|
||||
{ "open": "\"", "close": "\"" },
|
||||
{ "open": "`", "close": "`" }
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
["\"", "\""],
|
||||
["`", "`"],
|
||||
["'", "'"]
|
||||
],
|
||||
"autoCloseBefore": ";:.,=}])>` \n\t",
|
||||
"folding": {
|
||||
"markers": {
|
||||
"start": "^\\s*//\\s*region\\b",
|
||||
"end": "^\\s*//\\s*endregion\\b"
|
||||
"start": "^\\s*//\\s*#?region\\b",
|
||||
"end": "^\\s*//\\s*#?endregion\\b"
|
||||
},
|
||||
"offSide": false
|
||||
},
|
||||
"wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)",
|
||||
"indentationRules": {
|
||||
"increaseIndentPattern": "^((?!\\/\\/).)*(\\{[^}\"'`]*|\\([^)\"'`]*|\\[[^\\]\"'`]*)\\s*$",
|
||||
"decreaseIndentPattern": "^((?!.*?\\/\\*).*\\*/)?\\s*[\\}\\]\\)].*$"
|
||||
},
|
||||
"onEnterRules": [
|
||||
{
|
||||
"beforeText": "^\\s*/\\*(?!/).*[^*/]\\s*$",
|
||||
"afterText": "^\\s*\\*/$",
|
||||
"action": {
|
||||
"indent": "indentOutdent",
|
||||
"appendText": " * "
|
||||
}
|
||||
},
|
||||
"wordPattern": "[a-zA-Z_][a-zA-Z0-9_]*",
|
||||
"indentationRules": {
|
||||
"increaseIndentPattern": "\\{[^}]*$",
|
||||
"decreaseIndentPattern": "^\\s*\\}"
|
||||
{
|
||||
"beforeText": "^\\s*/\\*(?!/).*[^*/]\\s*$",
|
||||
"action": {
|
||||
"indent": "none",
|
||||
"appendText": " * "
|
||||
}
|
||||
},
|
||||
{
|
||||
"beforeText": "^(\\t|(\\ \\ ))*\\ \\*(\\ ([^*]|\\*(?!/))*)?$",
|
||||
"action": {
|
||||
"indent": "none",
|
||||
"appendText": "* "
|
||||
}
|
||||
},
|
||||
{
|
||||
"beforeText": "^(\\t|(\\ \\ ))*\\ */.*$",
|
||||
"action": {
|
||||
"indent": "none"
|
||||
}
|
||||
},
|
||||
{
|
||||
"beforeText": "^\\s*(func|async\\s+func|struct|enum|union|interface|extend|module|macro!|if|else|while|for|do|loop|match|switch|case)\\b.*\\{\\s*$",
|
||||
"action": {
|
||||
"indent": "indent"
|
||||
}
|
||||
},
|
||||
{
|
||||
"beforeText": "^\\s*.*=>\\s*$",
|
||||
"action": {
|
||||
"indent": "indent"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Generated
+128
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"name": "bux-lang",
|
||||
"version": "0.2.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bux-lang",
|
||||
"version": "0.2.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"vscode-languageclient": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"@types/vscode": "^1.85.0",
|
||||
"typescript": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"vscode": "^1.85.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.19.43",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz",
|
||||
"integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/vscode": {
|
||||
"version": "1.125.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.125.0.tgz",
|
||||
"integrity": "sha512-0icm/ZQAaism87P0ekHqi4/Ju9du+Tm0RUW+y7vqRsxY2cY0FNRX1nAnaW7nT6npPt2tfHiheZ55Zm9UhqonFA==",
|
||||
"dev": true
|
||||
},
|
||||
"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=="
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
|
||||
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
|
||||
"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==",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.8.5",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
|
||||
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
|
||||
"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,
|
||||
"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
|
||||
},
|
||||
"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==",
|
||||
"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==",
|
||||
"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==",
|
||||
"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=="
|
||||
}
|
||||
}
|
||||
}
|
||||
+128
-13
@@ -1,37 +1,60 @@
|
||||
{
|
||||
"name": "bux-lang",
|
||||
"displayName": "Bux Language Support",
|
||||
"description": "Syntax highlighting, snippets, and LSP support for the Bux programming language",
|
||||
"version": "0.1.0",
|
||||
"description": "Syntax highlighting, snippets, and full LSP support for Bux — live error underlines, hover, go-to-def, rename, call/type hierarchy",
|
||||
"version": "0.2.1",
|
||||
"publisher": "bux-lang",
|
||||
"license": "MIT",
|
||||
"icon": "icon.png",
|
||||
"galleryBanner": {
|
||||
"color": "#0d1117",
|
||||
"theme": "dark"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/katehonz/bux"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/katehonz/bux/issues"
|
||||
},
|
||||
"homepage": "https://github.com/katehonz/bux",
|
||||
"engines": {
|
||||
"vscode": "^1.85.0"
|
||||
},
|
||||
"categories": [
|
||||
"Programming Languages",
|
||||
"Snippets",
|
||||
"Linters"
|
||||
"Linters",
|
||||
"Other"
|
||||
],
|
||||
"keywords": [
|
||||
"bux",
|
||||
"bux-lang",
|
||||
"programming-language",
|
||||
"lsp",
|
||||
"syntax"
|
||||
],
|
||||
"activationEvents": [
|
||||
"onLanguage:bux"
|
||||
"onLanguage:bux",
|
||||
"workspaceContains:**/*.bux",
|
||||
"workspaceContains:bux.toml"
|
||||
],
|
||||
"main": "./out/extension.js",
|
||||
"contributes": {
|
||||
"languages": [
|
||||
{
|
||||
"id": "bux",
|
||||
"aliases": ["Bux", "bux"],
|
||||
"extensions": [".bux"],
|
||||
"aliases": [
|
||||
"Bux",
|
||||
"bux"
|
||||
],
|
||||
"extensions": [
|
||||
".bux"
|
||||
],
|
||||
"configuration": "./language-configuration.json",
|
||||
"icon": {
|
||||
"dark": "./icon.png",
|
||||
"light": "./icon.png"
|
||||
"dark": "./icons/bux-file.svg",
|
||||
"light": "./icons/bux-file.svg"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -48,30 +71,122 @@
|
||||
"path": "./snippets/bux.json"
|
||||
}
|
||||
],
|
||||
"commands": [
|
||||
{
|
||||
"command": "bux.restartLsp",
|
||||
"title": "Bux: Restart Language Server",
|
||||
"category": "Bux"
|
||||
},
|
||||
{
|
||||
"command": "bux.stopLsp",
|
||||
"title": "Bux: Stop Language Server",
|
||||
"category": "Bux"
|
||||
},
|
||||
{
|
||||
"command": "bux.showOutput",
|
||||
"title": "Bux: Show Output Channel",
|
||||
"category": "Bux"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "bux.restartLsp"
|
||||
},
|
||||
{
|
||||
"command": "bux.stopLsp"
|
||||
},
|
||||
{
|
||||
"command": "bux.showOutput"
|
||||
}
|
||||
]
|
||||
},
|
||||
"configuration": {
|
||||
"title": "Bux",
|
||||
"properties": {
|
||||
"bux.lsp.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Enable LSP server for diagnostics and autocomplete"
|
||||
"description": "Enable the Bux language server (diagnostics, hover, go-to-definition, rename, hierarchy)."
|
||||
},
|
||||
"bux.lsp.path": {
|
||||
"type": "string",
|
||||
"default": "bux-lsp",
|
||||
"description": "Path to the bux-lsp binary"
|
||||
"description": "Path to the bux-lsp binary. Absolute path, workspace-relative path, or command on PATH. If unset/default, the extension also searches tools/bux-lsp under workspace folders."
|
||||
},
|
||||
"bux-lsp.trace.server": {
|
||||
"type": "string",
|
||||
"scope": "window",
|
||||
"enum": [
|
||||
"off",
|
||||
"messages",
|
||||
"verbose"
|
||||
],
|
||||
"default": "off",
|
||||
"description": "Traces the communication between VS Code and the Bux language server."
|
||||
}
|
||||
}
|
||||
},
|
||||
"configurationDefaults": {
|
||||
"[bux]": {
|
||||
"editor.semanticHighlighting.enabled": true,
|
||||
"editor.tabSize": 4,
|
||||
"editor.insertSpaces": true,
|
||||
"editor.detectIndentation": false,
|
||||
"editor.quickSuggestions": {
|
||||
"other": true,
|
||||
"comments": false,
|
||||
"strings": false
|
||||
},
|
||||
"editor.suggest.snippetsPreventQuickSuggestions": false,
|
||||
"editor.wordBasedSuggestions": "off"
|
||||
}
|
||||
},
|
||||
"problemMatchers": [
|
||||
{
|
||||
"name": "buxc",
|
||||
"owner": "bux",
|
||||
"fileLocation": [
|
||||
"relative",
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"pattern": {
|
||||
"regexp": "^(.*):(\\d+):(\\d+):\\s+(error|warning|note):\\s+(.*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
"severity": 4,
|
||||
"message": 5
|
||||
}
|
||||
}
|
||||
],
|
||||
"taskDefinitions": [
|
||||
{
|
||||
"type": "bux",
|
||||
"required": [
|
||||
"task"
|
||||
],
|
||||
"properties": {
|
||||
"task": {
|
||||
"type": "string",
|
||||
"description": "bux subcommand (build, run, test, check)"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "npm run compile",
|
||||
"compile": "tsc -p ./",
|
||||
"watch": "tsc -watch -p ./"
|
||||
"watch": "tsc -watch -p ./",
|
||||
"package": "vsce package --no-dependencies 2>/dev/null || npx @vscode/vsce package --no-dependencies"
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-languageclient": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"@types/vscode": "^1.85.0",
|
||||
"typescript": "^5.0.0",
|
||||
"vscode-languageclient": "^9.0.0"
|
||||
"typescript": "^5.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
+169
-27
@@ -9,11 +9,23 @@
|
||||
],
|
||||
"description": "Main entry point function"
|
||||
},
|
||||
"Hello World": {
|
||||
"prefix": "hello",
|
||||
"body": [
|
||||
"import Std::Io::{PrintLine};",
|
||||
"",
|
||||
"func Main() -> int {",
|
||||
" PrintLine(\"${1:Hello, Bux!}\");",
|
||||
" return 0;",
|
||||
"}"
|
||||
],
|
||||
"description": "Minimal Hello World program"
|
||||
},
|
||||
"Function": {
|
||||
"prefix": "func",
|
||||
"body": [
|
||||
"func ${1:Name}(${2:params}) -> ${3:void} {",
|
||||
" $4",
|
||||
" $0",
|
||||
"}"
|
||||
],
|
||||
"description": "Function declaration"
|
||||
@@ -22,11 +34,20 @@
|
||||
"prefix": "afunc",
|
||||
"body": [
|
||||
"async func ${1:Name}(${2:params}) -> ${3:void} {",
|
||||
" $4",
|
||||
" $0",
|
||||
"}"
|
||||
],
|
||||
"description": "Async function declaration"
|
||||
},
|
||||
"Generic function": {
|
||||
"prefix": "gfunc",
|
||||
"body": [
|
||||
"func ${1:Name}<${2:T}>(${3:param}: ${2:T}) -> ${2:T} {",
|
||||
" $0",
|
||||
"}"
|
||||
],
|
||||
"description": "Generic function"
|
||||
},
|
||||
"Variable (let)": {
|
||||
"prefix": "let",
|
||||
"body": [
|
||||
@@ -41,6 +62,13 @@
|
||||
],
|
||||
"description": "Mutable variable"
|
||||
},
|
||||
"Const": {
|
||||
"prefix": "const",
|
||||
"body": [
|
||||
"const ${1:NAME}: ${2:Type} = ${3:value};"
|
||||
],
|
||||
"description": "Constant declaration"
|
||||
},
|
||||
"Struct": {
|
||||
"prefix": "struct",
|
||||
"body": [
|
||||
@@ -60,11 +88,52 @@
|
||||
],
|
||||
"description": "Enum declaration"
|
||||
},
|
||||
"Generic enum": {
|
||||
"prefix": "genum",
|
||||
"body": [
|
||||
"enum ${1:Name}<${2:T}> {",
|
||||
" ${3:Some}(${2:T}),",
|
||||
" ${4:None},",
|
||||
"}"
|
||||
],
|
||||
"description": "Generic enum declaration"
|
||||
},
|
||||
"Interface": {
|
||||
"prefix": "interface",
|
||||
"body": [
|
||||
"interface ${1:Name} {",
|
||||
" func ${2:Method}(${3:self}) -> ${4:void};",
|
||||
"}"
|
||||
],
|
||||
"description": "Interface declaration"
|
||||
},
|
||||
"Extend methods": {
|
||||
"prefix": "extend",
|
||||
"body": [
|
||||
"extend ${1:Type} {",
|
||||
" func ${2:Method}(self) -> ${3:void} {",
|
||||
" $0",
|
||||
" }",
|
||||
"}"
|
||||
],
|
||||
"description": "Extend block for inherent methods"
|
||||
},
|
||||
"Extend for interface": {
|
||||
"prefix": "extendfor",
|
||||
"body": [
|
||||
"extend ${1:Type} for ${2:Interface} {",
|
||||
" func ${3:Method}(self) -> ${4:void} {",
|
||||
" $0",
|
||||
" }",
|
||||
"}"
|
||||
],
|
||||
"description": "Implement interface for type"
|
||||
},
|
||||
"If statement": {
|
||||
"prefix": "if",
|
||||
"body": [
|
||||
"if ${1:condition} {",
|
||||
" ${2:body}",
|
||||
" $0",
|
||||
"}"
|
||||
],
|
||||
"description": "If statement"
|
||||
@@ -75,7 +144,7 @@
|
||||
"if ${1:condition} {",
|
||||
" ${2:body}",
|
||||
"} else {",
|
||||
" ${3:elseBody}",
|
||||
" $0",
|
||||
"}"
|
||||
],
|
||||
"description": "If-else statement"
|
||||
@@ -84,20 +153,29 @@
|
||||
"prefix": "while",
|
||||
"body": [
|
||||
"while ${1:condition} {",
|
||||
" ${2:body}",
|
||||
" $0",
|
||||
"}"
|
||||
],
|
||||
"description": "While loop"
|
||||
},
|
||||
"For loop": {
|
||||
"For-in loop": {
|
||||
"prefix": "for",
|
||||
"body": [
|
||||
"for ${1:item} in ${2:iterable} {",
|
||||
" ${3:body}",
|
||||
" $0",
|
||||
"}"
|
||||
],
|
||||
"description": "For-in loop"
|
||||
},
|
||||
"Loop": {
|
||||
"prefix": "loop",
|
||||
"body": [
|
||||
"loop {",
|
||||
" $0",
|
||||
"}"
|
||||
],
|
||||
"description": "Infinite loop"
|
||||
},
|
||||
"Match": {
|
||||
"prefix": "match",
|
||||
"body": [
|
||||
@@ -108,40 +186,61 @@
|
||||
],
|
||||
"description": "Pattern match expression"
|
||||
},
|
||||
"Switch": {
|
||||
"prefix": "switch",
|
||||
"body": [
|
||||
"switch ${1:expr} {",
|
||||
" case ${2:value}:",
|
||||
" $0",
|
||||
" break;",
|
||||
" default:",
|
||||
" break;",
|
||||
"}"
|
||||
],
|
||||
"description": "Switch statement"
|
||||
},
|
||||
"Import": {
|
||||
"prefix": "import",
|
||||
"body": [
|
||||
"import Std::${1:Module}::{${2:items}};"
|
||||
"import Std::${1:Io}::{${2:PrintLine}};"
|
||||
],
|
||||
"description": "Import statement"
|
||||
"description": "Import from Std"
|
||||
},
|
||||
"Module": {
|
||||
"prefix": "module",
|
||||
"body": [
|
||||
"module ${1:Name} {",
|
||||
"${2:body}",
|
||||
" $0",
|
||||
"}"
|
||||
],
|
||||
"description": "Module declaration"
|
||||
},
|
||||
"Extend": {
|
||||
"prefix": "extend",
|
||||
"body": [
|
||||
"extend ${1:Type} {",
|
||||
" ${2:methods}",
|
||||
"}"
|
||||
],
|
||||
"description": "Extend block for methods"
|
||||
},
|
||||
"@Checked": {
|
||||
"@Checked function": {
|
||||
"prefix": "checked",
|
||||
"body": [
|
||||
"@[Checked]",
|
||||
"func ${1:Name}(${2:params}) -> ${3:void} {",
|
||||
" ${4:body}",
|
||||
" $0",
|
||||
"}"
|
||||
],
|
||||
"description": "Checked function with borrow checker enabled"
|
||||
"description": "Checked function with borrow checker"
|
||||
},
|
||||
"@Release function": {
|
||||
"prefix": "release",
|
||||
"body": [
|
||||
"@[Release]",
|
||||
"func ${1:Name}(${2:params}) -> ${3:void} {",
|
||||
" $0",
|
||||
"}"
|
||||
],
|
||||
"description": "Release (unchecked) function"
|
||||
},
|
||||
"Defer": {
|
||||
"prefix": "defer",
|
||||
"body": [
|
||||
"defer ${1:cleanup};"
|
||||
],
|
||||
"description": "Defer cleanup"
|
||||
},
|
||||
"PrintLine": {
|
||||
"prefix": "pl",
|
||||
@@ -150,13 +249,56 @@
|
||||
],
|
||||
"description": "Print with newline"
|
||||
},
|
||||
"Generic function": {
|
||||
"prefix": "gfunc",
|
||||
"Interpolated string": {
|
||||
"prefix": "fstr",
|
||||
"body": [
|
||||
"func ${1:Name}<${2:T}>(${3:param}: ${2:T}) -> ${2:T} {",
|
||||
" ${4:body}",
|
||||
"f\"${1:Hello, }{${2:name}}\""
|
||||
],
|
||||
"description": "Interpolated f-string"
|
||||
},
|
||||
"Macro definition": {
|
||||
"prefix": "macro",
|
||||
"body": [
|
||||
"macro! ${1:name} {",
|
||||
" (\\$${2:x}:expr) => {",
|
||||
" $0",
|
||||
" }",
|
||||
"}"
|
||||
],
|
||||
"description": "Generic function"
|
||||
"description": "Declarative macro! definition"
|
||||
},
|
||||
"Test assert": {
|
||||
"prefix": "tassert",
|
||||
"body": [
|
||||
"Test_AssertEq${1:Int}(${2:got}, ${3:want});"
|
||||
],
|
||||
"description": "Test equality assertion"
|
||||
},
|
||||
"Spawn task": {
|
||||
"prefix": "spawn",
|
||||
"body": [
|
||||
"let ${1:handle}: *void = spawn ${2:Worker}(${3:args});"
|
||||
],
|
||||
"description": "Spawn green-thread task"
|
||||
},
|
||||
"Result Ok/Err match": {
|
||||
"prefix": "resultmatch",
|
||||
"body": [
|
||||
"match ${1:result} {",
|
||||
" Ok(${2:value}) => ${3:value},",
|
||||
" Err(${4:err}) => ${5:/* handle */},",
|
||||
"}"
|
||||
],
|
||||
"description": "Match on Result"
|
||||
},
|
||||
"Option Some/None match": {
|
||||
"prefix": "optmatch",
|
||||
"body": [
|
||||
"match ${1:opt} {",
|
||||
" Some(${2:value}) => ${3:value},",
|
||||
" None => ${4:/* handle */},",
|
||||
"}"
|
||||
],
|
||||
"description": "Match on Option"
|
||||
}
|
||||
}
|
||||
|
||||
+230
-32
@@ -1,54 +1,252 @@
|
||||
/* Bux Language Server Protocol client for VS Code
|
||||
* Launches bux-lsp binary and connects via stdin/stdout.
|
||||
* Discovers and launches the bux-lsp binary, connects via stdio.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import {
|
||||
LanguageClient,
|
||||
LanguageClientOptions,
|
||||
ServerOptions,
|
||||
TransportKind
|
||||
TransportKind,
|
||||
RevealOutputChannelOn,
|
||||
} from 'vscode-languageclient/node';
|
||||
|
||||
let client: LanguageClient;
|
||||
let client: LanguageClient | undefined;
|
||||
let outputChannel: vscode.OutputChannel | undefined;
|
||||
let statusBar: vscode.StatusBarItem | undefined;
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
const config = vscode.workspace.getConfiguration('bux.lsp');
|
||||
const enabled = config.get<boolean>('enabled', true);
|
||||
if (!enabled) {
|
||||
vscode.window.showInformationMessage('Bux LSP is disabled in settings');
|
||||
return;
|
||||
const SETTING_SECTION = 'bux';
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext): Promise<void> {
|
||||
outputChannel = vscode.window.createOutputChannel('Bux');
|
||||
context.subscriptions.push(outputChannel);
|
||||
|
||||
statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 50);
|
||||
statusBar.command = 'bux.restartLsp';
|
||||
statusBar.tooltip = 'Bux Language Server — click to restart';
|
||||
context.subscriptions.push(statusBar);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('bux.restartLsp', async () => {
|
||||
await restartClient(true);
|
||||
}),
|
||||
vscode.commands.registerCommand('bux.stopLsp', async () => {
|
||||
await stopClient();
|
||||
setStatus('off', 'Bux LSP stopped');
|
||||
outputChannel?.appendLine('[bux] LSP stopped by user');
|
||||
}),
|
||||
vscode.commands.registerCommand('bux.showOutput', () => {
|
||||
outputChannel?.show(true);
|
||||
}),
|
||||
vscode.workspace.onDidChangeConfiguration(async (e) => {
|
||||
if (
|
||||
e.affectsConfiguration('bux.lsp.enabled') ||
|
||||
e.affectsConfiguration('bux.lsp.path')
|
||||
) {
|
||||
outputChannel?.appendLine('[bux] Configuration changed — restarting LSP');
|
||||
await restartClient(false);
|
||||
}
|
||||
|
||||
const lspPath = config.get<string>('path', 'bux-lsp');
|
||||
|
||||
const serverOptions: ServerOptions = {
|
||||
command: lspPath,
|
||||
transport: TransportKind.stdio
|
||||
};
|
||||
|
||||
const clientOptions: LanguageClientOptions = {
|
||||
documentSelector: [{ scheme: 'file', language: 'bux' }],
|
||||
synchronize: {
|
||||
fileEvents: vscode.workspace.createFileSystemWatcher('**/*.bux')
|
||||
}
|
||||
};
|
||||
|
||||
client = new LanguageClient(
|
||||
'bux-lsp',
|
||||
'Bux Language Server',
|
||||
serverOptions,
|
||||
clientOptions
|
||||
})
|
||||
);
|
||||
|
||||
client.start();
|
||||
vscode.window.showInformationMessage('Bux LSP started');
|
||||
await startClient();
|
||||
}
|
||||
|
||||
export function deactivate(): Thenable<void> | undefined {
|
||||
return stopClient();
|
||||
}
|
||||
|
||||
async function restartClient(userInitiated: boolean): Promise<void> {
|
||||
await stopClient();
|
||||
await startClient();
|
||||
if (userInitiated) {
|
||||
vscode.window.setStatusBarMessage('Bux LSP restarted', 3000);
|
||||
}
|
||||
}
|
||||
|
||||
async function stopClient(): Promise<void> {
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
const c = client;
|
||||
client = undefined;
|
||||
try {
|
||||
await c.stop();
|
||||
} catch (err) {
|
||||
outputChannel?.appendLine(`[bux] Error stopping client: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function startClient(): Promise<void> {
|
||||
const config = vscode.workspace.getConfiguration(SETTING_SECTION);
|
||||
const enabled = config.get<boolean>('lsp.enabled', true);
|
||||
|
||||
if (!enabled) {
|
||||
setStatus('off', 'Bux LSP disabled');
|
||||
outputChannel?.appendLine('[bux] LSP disabled in settings (bux.lsp.enabled)');
|
||||
return;
|
||||
}
|
||||
|
||||
const configuredPath = config.get<string>('lsp.path', 'bux-lsp') || 'bux-lsp';
|
||||
const resolved = resolveLspPath(configuredPath);
|
||||
|
||||
if (!resolved) {
|
||||
setStatus('error', 'bux-lsp not found');
|
||||
const msg =
|
||||
'Bux LSP binary not found. Build with `make lsp` (produces tools/bux-lsp) ' +
|
||||
'or set bux.lsp.path to the full path of the binary.';
|
||||
outputChannel?.appendLine(`[bux] ${msg}`);
|
||||
outputChannel?.appendLine(`[bux] Configured path: ${configuredPath}`);
|
||||
const choice = await vscode.window.showWarningMessage(
|
||||
'Bux: language server (bux-lsp) not found',
|
||||
'Open Output',
|
||||
'Open Settings'
|
||||
);
|
||||
if (choice === 'Open Output') {
|
||||
outputChannel?.show(true);
|
||||
} else if (choice === 'Open Settings') {
|
||||
await vscode.commands.executeCommand('workbench.action.openSettings', 'bux.lsp');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
outputChannel?.appendLine(`[bux] Using LSP binary: ${resolved}`);
|
||||
setStatus('starting', 'Starting Bux LSP…');
|
||||
|
||||
const serverOptions: ServerOptions = {
|
||||
command: resolved,
|
||||
transport: TransportKind.stdio,
|
||||
options: { env: process.env },
|
||||
};
|
||||
|
||||
const clientOptions: LanguageClientOptions = {
|
||||
documentSelector: [
|
||||
{ scheme: 'file', language: 'bux' },
|
||||
{ scheme: 'untitled', language: 'bux' },
|
||||
],
|
||||
synchronize: {
|
||||
fileEvents: vscode.workspace.createFileSystemWatcher('**/*.{bux,toml}'),
|
||||
},
|
||||
outputChannel,
|
||||
revealOutputChannelOn: RevealOutputChannelOn.Error,
|
||||
traceOutputChannel: outputChannel,
|
||||
};
|
||||
|
||||
client = new LanguageClient('bux-lsp', 'Bux Language Server', serverOptions, clientOptions);
|
||||
|
||||
try {
|
||||
await client.start();
|
||||
setStatus('ready', `Bux LSP ready (${path.basename(resolved)})`);
|
||||
outputChannel?.appendLine('[bux] Language server started');
|
||||
} catch (err) {
|
||||
setStatus('error', 'Bux LSP failed to start');
|
||||
outputChannel?.appendLine(`[bux] Failed to start language server: ${err}`);
|
||||
vscode.window.showErrorMessage(
|
||||
`Bux LSP failed to start: ${err instanceof Error ? err.message : String(err)}`
|
||||
);
|
||||
client = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve bux-lsp:
|
||||
* 1. Configured absolute / relative path
|
||||
* 2. Workspace tools/bux-lsp (and nearby monorepo parents)
|
||||
* 3. Command on PATH
|
||||
*/
|
||||
function resolveLspPath(configured: string): string | undefined {
|
||||
const candidates: string[] = [];
|
||||
|
||||
if (path.isAbsolute(configured)) {
|
||||
candidates.push(configured);
|
||||
} else if (configured.includes('/') || configured.includes('\\')) {
|
||||
candidates.push(path.resolve(configured));
|
||||
for (const folder of vscode.workspace.workspaceFolders ?? []) {
|
||||
candidates.push(path.join(folder.uri.fsPath, configured));
|
||||
}
|
||||
}
|
||||
|
||||
for (const folder of vscode.workspace.workspaceFolders ?? []) {
|
||||
const root = folder.uri.fsPath;
|
||||
candidates.push(
|
||||
path.join(root, 'tools', 'bux-lsp'),
|
||||
path.join(root, 'tools', 'bux-lsp.exe'),
|
||||
path.join(root, 'bin', 'bux-lsp'),
|
||||
path.join(root, 'bux-lsp'),
|
||||
path.resolve(root, '..', 'tools', 'bux-lsp'),
|
||||
path.resolve(root, '..', '..', 'tools', 'bux-lsp')
|
||||
);
|
||||
}
|
||||
|
||||
for (const c of candidates) {
|
||||
if (isExecutable(c)) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
// Bare name: search PATH
|
||||
const bare = configured.includes('/') || configured.includes('\\') ? 'bux-lsp' : configured;
|
||||
return findInPath(bare);
|
||||
}
|
||||
|
||||
function findInPath(cmd: string): string | undefined {
|
||||
const pathEnv = process.env.PATH ?? process.env.Path ?? '';
|
||||
const sep = process.platform === 'win32' ? ';' : ':';
|
||||
const exts =
|
||||
process.platform === 'win32'
|
||||
? (process.env.PATHEXT ?? '.EXE;.CMD;.BAT').split(';')
|
||||
: [''];
|
||||
|
||||
for (const dir of pathEnv.split(sep)) {
|
||||
if (!dir) continue;
|
||||
for (const ext of exts) {
|
||||
const candidate = path.join(dir, cmd + ext);
|
||||
if (isExecutable(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
return client.stop();
|
||||
|
||||
function isExecutable(filePath: string): boolean {
|
||||
try {
|
||||
const st = fs.statSync(filePath);
|
||||
if (!st.isFile()) {
|
||||
return false;
|
||||
}
|
||||
if (process.platform === 'win32') {
|
||||
return true;
|
||||
}
|
||||
fs.accessSync(filePath, fs.constants.X_OK);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function setStatus(state: 'ready' | 'starting' | 'error' | 'off', text: string): void {
|
||||
if (!statusBar) return;
|
||||
switch (state) {
|
||||
case 'ready':
|
||||
statusBar.text = '$(check) Bux';
|
||||
statusBar.backgroundColor = undefined;
|
||||
break;
|
||||
case 'starting':
|
||||
statusBar.text = '$(sync~spin) Bux';
|
||||
statusBar.backgroundColor = undefined;
|
||||
break;
|
||||
case 'error':
|
||||
statusBar.text = '$(error) Bux';
|
||||
statusBar.backgroundColor = new vscode.ThemeColor('statusBarItem.errorBackground');
|
||||
break;
|
||||
case 'off':
|
||||
statusBar.text = '$(circle-slash) Bux';
|
||||
statusBar.backgroundColor = undefined;
|
||||
break;
|
||||
}
|
||||
statusBar.tooltip = text;
|
||||
statusBar.show();
|
||||
}
|
||||
|
||||
@@ -3,33 +3,19 @@
|
||||
"name": "Bux",
|
||||
"scopeName": "source.bux",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#comments"
|
||||
},
|
||||
{
|
||||
"include": "#strings"
|
||||
},
|
||||
{
|
||||
"include": "#keywords"
|
||||
},
|
||||
{
|
||||
"include": "#types"
|
||||
},
|
||||
{
|
||||
"include": "#functions"
|
||||
},
|
||||
{
|
||||
"include": "#constants"
|
||||
},
|
||||
{
|
||||
"include": "#operators"
|
||||
},
|
||||
{
|
||||
"include": "#numbers"
|
||||
},
|
||||
{
|
||||
"include": "#attributes"
|
||||
}
|
||||
{ "include": "#comments" },
|
||||
{ "include": "#attributes" },
|
||||
{ "include": "#macros" },
|
||||
{ "include": "#strings" },
|
||||
{ "include": "#chars" },
|
||||
{ "include": "#keywords" },
|
||||
{ "include": "#types" },
|
||||
{ "include": "#constants" },
|
||||
{ "include": "#lifetimes" },
|
||||
{ "include": "#functions" },
|
||||
{ "include": "#paths" },
|
||||
{ "include": "#numbers" },
|
||||
{ "include": "#operators" }
|
||||
],
|
||||
"repository": {
|
||||
"comments": {
|
||||
@@ -37,20 +23,129 @@
|
||||
{
|
||||
"name": "comment.block.bux",
|
||||
"begin": "/\\*",
|
||||
"end": "\\*/"
|
||||
"end": "\\*/",
|
||||
"patterns": [
|
||||
{ "include": "#comments" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "comment.line.bux",
|
||||
"name": "comment.line.double-slash.bux",
|
||||
"match": "//.*$"
|
||||
}
|
||||
]
|
||||
},
|
||||
"attributes": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "meta.attribute.bux",
|
||||
"begin": "@\\[",
|
||||
"end": "\\]",
|
||||
"beginCaptures": {
|
||||
"0": { "name": "punctuation.definition.attribute.bux" }
|
||||
},
|
||||
"endCaptures": {
|
||||
"0": { "name": "punctuation.definition.attribute.bux" }
|
||||
},
|
||||
"patterns": [
|
||||
{
|
||||
"name": "storage.modifier.attribute.bux",
|
||||
"match": "\\b(Checked|Release|Shared|Import|Drop)\\b"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "storage.modifier.attribute.bux",
|
||||
"match": "@(Checked|Release|Shared|Import|Drop)\\b"
|
||||
}
|
||||
]
|
||||
},
|
||||
"macros": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "meta.macro.definition.bux",
|
||||
"begin": "\\b(macro!)\\s+([A-Za-z_][A-Za-z0-9_]*)",
|
||||
"beginCaptures": {
|
||||
"1": { "name": "keyword.declaration.macro.bux" },
|
||||
"2": { "name": "entity.name.function.macro.bux" }
|
||||
},
|
||||
"end": "(?<=\\})",
|
||||
"patterns": [
|
||||
{ "include": "$self" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "meta.macro.invocation.bux",
|
||||
"begin": "\\b([A-Za-z_][A-Za-z0-9_]*!)\\s*(\\()",
|
||||
"beginCaptures": {
|
||||
"1": { "name": "entity.name.function.macro.bux" },
|
||||
"2": { "name": "punctuation.section.parens.begin.bux" }
|
||||
},
|
||||
"end": "\\)",
|
||||
"endCaptures": {
|
||||
"0": { "name": "punctuation.section.parens.end.bux" }
|
||||
},
|
||||
"patterns": [
|
||||
{ "include": "$self" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "variable.other.macro.fragment.bux",
|
||||
"match": "\\$([A-Za-z_][A-Za-z0-9_]*)"
|
||||
},
|
||||
{
|
||||
"name": "keyword.operator.macro.repeat.bux",
|
||||
"match": "\\$\\(|\\)\\s*[*+?]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"strings": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "string.quoted.double.bux",
|
||||
"begin": "\"",
|
||||
"name": "string.interpolated.bux",
|
||||
"begin": "f\"",
|
||||
"end": "\"",
|
||||
"beginCaptures": {
|
||||
"0": { "name": "punctuation.definition.string.begin.bux" }
|
||||
},
|
||||
"endCaptures": {
|
||||
"0": { "name": "punctuation.definition.string.end.bux" }
|
||||
},
|
||||
"patterns": [
|
||||
{
|
||||
"name": "constant.character.escape.bux",
|
||||
"match": "\\\\[ntr\\\\\"'{}]|\\\\u\\{[0-9a-fA-F]+\\}|\\\\x[0-9a-fA-F]{2}"
|
||||
},
|
||||
{
|
||||
"name": "meta.interpolation.bux",
|
||||
"begin": "\\{",
|
||||
"end": "\\}",
|
||||
"beginCaptures": {
|
||||
"0": { "name": "punctuation.section.embedded.begin.bux" }
|
||||
},
|
||||
"endCaptures": {
|
||||
"0": { "name": "punctuation.section.embedded.end.bux" }
|
||||
},
|
||||
"patterns": [
|
||||
{ "include": "#keywords" },
|
||||
{ "include": "#types" },
|
||||
{ "include": "#constants" },
|
||||
{ "include": "#numbers" },
|
||||
{ "include": "#functions" },
|
||||
{ "include": "#operators" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "string.quoted.double.cstr.bux",
|
||||
"begin": "c(8|16|32)\"",
|
||||
"end": "\"",
|
||||
"beginCaptures": {
|
||||
"0": { "name": "punctuation.definition.string.begin.bux" }
|
||||
},
|
||||
"endCaptures": {
|
||||
"0": { "name": "punctuation.definition.string.end.bux" }
|
||||
},
|
||||
"patterns": [
|
||||
{
|
||||
"name": "constant.character.escape.bux",
|
||||
@@ -59,9 +154,40 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "string.quoted.backtick.bux",
|
||||
"name": "string.quoted.double.bux",
|
||||
"begin": "\"",
|
||||
"end": "\"",
|
||||
"beginCaptures": {
|
||||
"0": { "name": "punctuation.definition.string.begin.bux" }
|
||||
},
|
||||
"endCaptures": {
|
||||
"0": { "name": "punctuation.definition.string.end.bux" }
|
||||
},
|
||||
"patterns": [
|
||||
{
|
||||
"name": "constant.character.escape.bux",
|
||||
"match": "\\\\[ntr\\\\\"']|\\\\u\\{[0-9a-fA-F]+\\}|\\\\x[0-9a-fA-F]{2}|\\\\."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "string.quoted.raw.bux",
|
||||
"begin": "`",
|
||||
"end": "`"
|
||||
"end": "`",
|
||||
"beginCaptures": {
|
||||
"0": { "name": "punctuation.definition.string.begin.bux" }
|
||||
},
|
||||
"endCaptures": {
|
||||
"0": { "name": "punctuation.definition.string.end.bux" }
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"chars": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "string.quoted.single.bux",
|
||||
"match": "c?(8|16|32)?'(\\\\.|[^'\\\\])'"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -69,55 +195,87 @@
|
||||
"patterns": [
|
||||
{
|
||||
"name": "keyword.control.bux",
|
||||
"match": "\\b(if|else|while|for|do|loop|in|break|continue|return|match|case|default|spawn|async|await|try|catch|throw|discard)\\b"
|
||||
"match": "\\b(if|else|while|for|do|loop|in|break|continue|return|match|switch|case|default|spawn|async|await|try|catch|throw|discard|defer)\\b"
|
||||
},
|
||||
{
|
||||
"name": "keyword.declaration.bux",
|
||||
"match": "\\b(func|var|let|const|type|struct|enum|union|interface|extend|module|import|extern|pub)\\b"
|
||||
"match": "\\b(func|var|let|const|type|struct|enum|union|interface|extend|module|import|extern|pub|macro)\\b"
|
||||
},
|
||||
{
|
||||
"name": "keyword.operator.bux",
|
||||
"name": "keyword.operator.word.bux",
|
||||
"match": "\\b(as|is|sizeof|comptime|static_assert|dyn)\\b"
|
||||
},
|
||||
{
|
||||
"name": "keyword.ownership.bux",
|
||||
"match": "\\b(own|mut|borrow)\\b"
|
||||
},
|
||||
{
|
||||
"name": "storage.modifier.bux",
|
||||
"match": "@\\[(Checked|Shared|Import)\\]"
|
||||
"match": "\\b(own|mut|borrow|checked)\\b"
|
||||
}
|
||||
]
|
||||
},
|
||||
"types": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "support.type.bux",
|
||||
"match": "\\b(int|int8|int16|int32|int64|uint|uint8|uint16|uint32|uint64|float32|float64|bool|char8|char32|String|void)\\b"
|
||||
"name": "support.type.primitive.bux",
|
||||
"match": "\\b(int|int8|int16|int32|int64|uint|uint8|uint16|uint32|uint64|float32|float64|bool|char8|char16|char32|String|void)\\b"
|
||||
},
|
||||
{
|
||||
"name": "support.type.stdlib.bux",
|
||||
"match": "\\b(Result|Option|Array|Map|Set|Slice|Channel|TaskHandle)\\b"
|
||||
},
|
||||
{
|
||||
"name": "entity.name.type.bux",
|
||||
"match": "\\b[A-Z][a-zA-Z0-9_]*\\b"
|
||||
}
|
||||
]
|
||||
},
|
||||
"functions": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "entity.name.function.bux",
|
||||
"match": "\\b([a-z_][a-zA-Z0-9_]*)(?=\\s*\\()"
|
||||
"match": "\\b[A-Z][A-Za-z0-9_]*\\b"
|
||||
}
|
||||
]
|
||||
},
|
||||
"constants": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "constant.language.bux",
|
||||
"match": "\\b(true|false|null)\\b"
|
||||
"name": "constant.language.boolean.bux",
|
||||
"match": "\\b(true|false)\\b"
|
||||
},
|
||||
{
|
||||
"name": "constant.numeric.bux",
|
||||
"match": "\\b(true|false|null|self|super)\\b"
|
||||
"name": "constant.language.null.bux",
|
||||
"match": "\\bnull\\b"
|
||||
},
|
||||
{
|
||||
"name": "variable.language.bux",
|
||||
"match": "\\b(self|super)\\b"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lifetimes": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "storage.modifier.lifetime.bux",
|
||||
"match": "'[A-Za-z_][A-Za-z0-9_]*\\b"
|
||||
}
|
||||
]
|
||||
},
|
||||
"functions": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "meta.function.declaration.bux",
|
||||
"match": "\\b(func|async\\s+func)\\s+([A-Za-z_][A-Za-z0-9_]*)",
|
||||
"captures": {
|
||||
"1": { "name": "keyword.declaration.bux" },
|
||||
"2": { "name": "entity.name.function.bux" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "entity.name.function.bux",
|
||||
"match": "\\b([A-Za-z_][A-Za-z0-9_]*)(?=\\s*[<(])"
|
||||
}
|
||||
]
|
||||
},
|
||||
"paths": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "meta.path.bux",
|
||||
"match": "\\b([A-Za-z_][A-Za-z0-9_]*)\\s*(::)",
|
||||
"captures": {
|
||||
"1": { "name": "entity.name.namespace.bux" },
|
||||
"2": { "name": "punctuation.accessor.bux" }
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -125,31 +283,67 @@
|
||||
"patterns": [
|
||||
{
|
||||
"name": "constant.numeric.float.bux",
|
||||
"match": "\\b[0-9]+\\.[0-9]+(?:[eE][+-]?[0-9]+)?\\b"
|
||||
"match": "\\b[0-9]+\\.[0-9]+(?:[eE][+-]?[0-9]+)?(?:f32|f64)?\\b"
|
||||
},
|
||||
{
|
||||
"name": "constant.numeric.hex.bux",
|
||||
"match": "\\b0[xX][0-9a-fA-F]+\\b"
|
||||
"match": "\\b0[xX][0-9a-fA-F_]+(?:[iu](?:8|16|32|64)?)?\\b"
|
||||
},
|
||||
{
|
||||
"name": "constant.numeric.bux",
|
||||
"match": "\\b[0-9]+\\b"
|
||||
"name": "constant.numeric.octal.bux",
|
||||
"match": "\\b0[oO][0-7_]+(?:[iu](?:8|16|32|64)?)?\\b"
|
||||
},
|
||||
{
|
||||
"name": "constant.numeric.binary.bux",
|
||||
"match": "\\b0[bB][01_]+(?:[iu](?:8|16|32|64)?)?\\b"
|
||||
},
|
||||
{
|
||||
"name": "constant.numeric.integer.bux",
|
||||
"match": "\\b[0-9][0-9_]*(?:[iu](?:8|16|32|64)?|f32|f64)?\\b"
|
||||
}
|
||||
]
|
||||
},
|
||||
"operators": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "keyword.operator.bux",
|
||||
"match": "->|=>|==|!=|<=|>=|&&|\\|\\||[+\\-*/%<>=!&|^~@]"
|
||||
}
|
||||
]
|
||||
"name": "keyword.operator.arrow.bux",
|
||||
"match": "->|=>"
|
||||
},
|
||||
"attributes": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "storage.modifier.bux",
|
||||
"match": "@(Checked|Shared|Import)"
|
||||
"name": "keyword.operator.range.bux",
|
||||
"match": "\\.\\.=|\\.\\.\\.|\\.\\."
|
||||
},
|
||||
{
|
||||
"name": "keyword.operator.comparison.bux",
|
||||
"match": "==|!=|<=|>=|<|>"
|
||||
},
|
||||
{
|
||||
"name": "keyword.operator.logical.bux",
|
||||
"match": "&&|\\|\\||!"
|
||||
},
|
||||
{
|
||||
"name": "keyword.operator.assignment.bux",
|
||||
"match": "\\+=|-=|\\*=|/=|%=|=|:=|\\*\\*="
|
||||
},
|
||||
{
|
||||
"name": "keyword.operator.arithmetic.bux",
|
||||
"match": "\\*\\*|[+\\-*/%]"
|
||||
},
|
||||
{
|
||||
"name": "keyword.operator.bitwise.bux",
|
||||
"match": "&|\\||\\^|~|<<|>>"
|
||||
},
|
||||
{
|
||||
"name": "keyword.operator.try.bux",
|
||||
"match": "\\?"
|
||||
},
|
||||
{
|
||||
"name": "keyword.operator.borrow.bux",
|
||||
"match": "&mut\\b|&"
|
||||
},
|
||||
{
|
||||
"name": "punctuation.accessor.bux",
|
||||
"match": "::|\\."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,12 +2,17 @@
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "ES2020",
|
||||
"lib": ["ES2020"],
|
||||
"outDir": "out",
|
||||
"rootDir": "src",
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"esModuleInterop": true
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "node",
|
||||
"types": ["node", "vscode"]
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", ".vscode-test"]
|
||||
"exclude": ["node_modules", ".vscode-test", "out"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user