Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,31 @@ jobs:
python-version: '3.12'
- run: pip install cffconvert==2.0.0
- run: cffconvert --validate -i CITATION.cff

# Build and smoke-test the VS Code extension (editors/vscode/): lint, type-check, bundle, run the
# activation test in a headless VS Code (xvfb), and package the .vsix so a broken manifest fails
# here, not at release. Node-only, mirroring the grammar-build job's toolchain.
vscode:
name: vscode extension
runs-on: ubuntu-latest
timeout-minutes: 20
defaults:
run:
working-directory: editors/vscode
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: '22'
cache: npm
cache-dependency-path: editors/vscode/package-lock.json
- run: npm ci
- name: Lint, type-check, and run the activation smoke test (headless)
run: xvfb-run -a npm test
- name: Package the .vsix
run: npx @vscode/vsce package --out gmat-script.vsix
- uses: actions/upload-artifact@v7
with:
name: vscode-extension-vsix
path: editors/vscode/gmat-script.vsix
if-no-files-found: error
47 changes: 47 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,53 @@ jobs:
npm publish
fi

# Publish the VS Code extension (editors/vscode/) to the VS Code Marketplace and Open VSX. Auth is
# user-provided tokens stored as the VSCE_PAT and OVSX_PAT repository secrets (the Marketplace
# publisher and the Open VSX namespace must be created once, like the PyPI pending publisher). Each
# publish step is skipped when its token is absent and is idempotent: it checks the registry for the
# current version first, so a tag whose extension version is already published is a no-op.
publish-vscode:
name: publish VS Code extension
runs-on: ubuntu-latest
timeout-minutes: 15
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}
OVSX_PAT: ${{ secrets.OVSX_PAT }}
defaults:
run:
working-directory: editors/vscode
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: '22'
cache: npm
cache-dependency-path: editors/vscode/package-lock.json
- run: npm ci
- name: Package the .vsix
run: npx @vscode/vsce package --out gmat-script.vsix
- name: Publish to the VS Code Marketplace
if: env.VSCE_PAT != ''
run: |
version="$(node -p "require('./package.json').version")"
id="$(node -p "require('./package.json').publisher + '.' + require('./package.json').name")"
if npx @vscode/vsce show "${id}" --json > /tmp/ext.json 2>/dev/null \
&& node -e "process.exit((JSON.parse(require('fs').readFileSync('/tmp/ext.json','utf8')).versions||[]).some(v => v.version === '${version}') ? 0 : 1)"; then
echo "${id}@${version} is already on the Marketplace — skipping."
else
npx @vscode/vsce publish --packagePath gmat-script.vsix
fi
- name: Publish to Open VSX
if: env.OVSX_PAT != ''
run: |
version="$(node -p "require('./package.json').version")"
id="$(node -p "require('./package.json').publisher + '.' + require('./package.json').name")"
if npx ovsx get "${id}" -t "${version}" -o /tmp/ovsx.vsix > /dev/null 2>&1; then
echo "${id}@${version} is already on Open VSX — skipping."
else
npx ovsx publish gmat-script.vsix
fi

github-release:
name: github release
needs: publish-pypi
Expand Down
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ node_modules/
tree-sitter-gmat/build/
*.wasm

# VS Code extension build artifacts (editors/vscode/); source stays committed. dist/ is already
# covered by the global rule above — out/, the test runner's download cache, and packaged .vsix
# files are not.
editors/vscode/out/
editors/vscode/.vscode-test/
*.vsix

# The grammar's queries are vendored into the package by the build hook (hatch_build.py) so the
# wheel and editable installs can load them at runtime; the canonical copies live under
# tree-sitter-gmat/queries/.
Expand Down
8 changes: 6 additions & 2 deletions docs/lsp.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,12 @@ vim.api.nvim_create_autocmd("FileType", {

### VS Code

The VS Code extension bundles a client that launches this server for you — install it from the
Marketplace rather than configuring the command by hand.
Install the [**GMAT Script**](https://marketplace.visualstudio.com/items?itemName=astro-tools.gmat-script)
extension (also on [Open VSX](https://open-vsx.org/extension/astro-tools/gmat-script)). It bundles a
client that launches this server for you, and adds standalone syntax highlighting that works even
before the server is installed. Add the server with `pip install "gmat-script[lsp]"` — the extension
picks up `gmat-script-lsp` from your `PATH`, or point `gmatScript.server.pythonPath` at the Python
environment that has it. Format-on-save is enabled for GMAT files by default.

## How it fits together

Expand Down
13 changes: 13 additions & 0 deletions editors/vscode/.vscode-test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { defineConfig } from "@vscode/test-cli";

// Compiled test suites (tsc -> out/) run in a clean VS Code instance with no other
// extensions, so the activation smoke test sees only this extension. The extension under
// development is always loaded regardless of --disable-extensions.
export default defineConfig({
files: "out/test/**/*.test.js",
launchArgs: ["--disable-extensions"],
mocha: {
ui: "tdd",
timeout: 60000,
},
});
15 changes: 15 additions & 0 deletions editors/vscode/.vscodeignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Everything is excluded except the bundled extension and the static contributions
# it references (grammar, language config, icons). Source, tests, and tooling stay out
# of the .vsix.
.vscode-test/
.vscode-test.mjs
out/**
src/**
node_modules/**
esbuild.js
eslint.config.mjs
tsconfig.json
**/*.map
**/*.ts
.gitignore
.eslintrc*
21 changes: 21 additions & 0 deletions editors/vscode/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 astro-tools

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
58 changes: 58 additions & 0 deletions editors/vscode/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# GMAT Script for VS Code

Language support for [GMAT](https://gmat.atlassian.net/wiki/spaces/GW/overview) mission scripts —
`.script` and `.gmf` files — built on the [`gmat-script`](https://github.com/astro-tools/gmat-script)
tree-sitter grammar and language server.

<!-- A short highlighting + diagnostics demo lands here for the release.
![GMAT Script in VS Code](images/demo.gif) -->

## Features

- **Syntax highlighting** for resources, commands, fields, control flow, solver blocks, and
GmatFunction headers — works on its own, no extra setup.
- **Hover** documentation: a field's type, default, allowed values, units, and reference target.
- **Live diagnostics** as you type: syntax errors plus linter warnings (unknown fields, enum
violations, undeclared references, duplicate names, and more).
- **Go to definition** and **find all references** for resources.
- **Document outline / symbols**: every `Create`'d resource and GmatFunction header.
- **Completion**: resource names, the fields valid for the resource under the cursor, and a field's
enum values.
- **Format on save** via the canonical, idempotent `gmat-script` formatter.

Highlighting comes from a bundled TextMate grammar and needs nothing else. Everything richer is
served by the `gmat-script` language server.

## Requirements

The richer features (hover, diagnostics, completion, definition, references, formatting) are powered
by the `gmat-script` language server, which runs on Python. Install it into a Python environment:

```console
$ pip install "gmat-script[lsp]"
```

This puts a `gmat-script-lsp` command on your `PATH`, which the extension launches automatically. If
it is not found, syntax highlighting still works and the extension points you here. To use a specific
interpreter or virtual environment instead, set `gmatScript.server.pythonPath`.

## Settings

| Setting | Default | Description |
|---|---|---|
| `gmatScript.server.path` | `gmat-script-lsp` | Command used to launch the language server. Set an absolute path if it is not on your `PATH`. |
| `gmatScript.server.pythonPath` | _(empty)_ | A Python interpreter with `gmat-script[lsp]` installed; when set, the server runs as `<python> -m gmat_script.lsp` and takes precedence over `server.path`. |
| `gmatScript.server.args` | `[]` | Extra arguments passed to the server on launch. |
| `gmatScript.trace.server` | `off` | Trace JSON-RPC traffic to the output channel (`off` / `messages` / `verbose`). |

Format on save is enabled for GMAT files by default; override it per the usual
`"[gmat]": { "editor.formatOnSave": false }` setting.

## Documentation

Full documentation for the grammar, formatter, linter, and language server lives at
<https://astro-tools.github.io/gmat-script/>.

## License

MIT.
34 changes: 34 additions & 0 deletions editors/vscode/esbuild.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"use strict";

const esbuild = require("esbuild");

const production = process.argv.includes("--production");
const watch = process.argv.includes("--watch");

async function main() {
const ctx = await esbuild.context({
entryPoints: ["src/extension.ts"],
bundle: true,
format: "cjs",
platform: "node",
// VS Code's Electron floor for engines ^1.85; Node 18 features are safe.
target: "node18",
outfile: "dist/extension.js",
// `vscode` is provided by the host at runtime and must never be bundled.
external: ["vscode"],
sourcemap: !production,
minify: production,
logLevel: "info",
});
if (watch) {
await ctx.watch();
} else {
await ctx.rebuild();
await ctx.dispose();
}
}

main().catch((err) => {
console.error(err);
process.exit(1);
});
27 changes: 27 additions & 0 deletions editors/vscode/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import js from "@eslint/js";
import tseslint from "typescript-eslint";

export default tseslint.config(
{
ignores: ["dist", "out", "node_modules", ".vscode-test"],
},
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ["src/**/*.ts"],
languageOptions: {
ecmaVersion: 2022,
sourceType: "module",
},
rules: {
"@typescript-eslint/naming-convention": [
"warn",
{ selector: "import", format: ["camelCase", "PascalCase"] },
],
curly: "warn",
eqeqeq: "warn",
"no-throw-literal": "warn",
semi: "warn",
},
},
);
8 changes: 8 additions & 0 deletions editors/vscode/icons/gmat-file.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added editors/vscode/icons/gmat-script.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions editors/vscode/images/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Marketing assets

`demo.gif` — a short animated capture of the extension in action (highlighting + live diagnostics +
hover on a stock GMAT sample), shown at the top of the extension's marketplace listing.

To add it: record a `.script` editing session in VS Code (type into a sample, trigger a diagnostic,
hover a field), export an optimized GIF (≈800 px wide, a few seconds, looping), save it here as
`demo.gif`, and uncomment the image reference in `../README.md`.
23 changes: 23 additions & 0 deletions editors/vscode/language-configuration.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"comments": {
"lineComment": "%"
},
"brackets": [
["(", ")"],
["[", "]"],
["{", "}"]
],
"autoClosingPairs": [
{ "open": "(", "close": ")" },
{ "open": "[", "close": "]" },
{ "open": "{", "close": "}" },
{ "open": "'", "close": "'", "notIn": ["string", "comment"] }
],
"surroundingPairs": [
["(", ")"],
["[", "]"],
["{", "}"],
["'", "'"]
],
"wordPattern": "[A-Za-z_][A-Za-z0-9_]*"
}
Loading
Loading