This commit is contained in:
Pavel Flegr 2026-03-05 14:55:09 +01:00
commit 8f4f858d86
30 changed files with 9765 additions and 0 deletions

View file

@ -0,0 +1,58 @@
# vscode-gotlin
Minimal VS Code extension for `.gt` files.
It does two things:
- registers `.gt` as the `gotlin` language
- launches `gotlin-lsp` over stdio
It also includes:
- syntax highlighting
- bracket/comment configuration
- basic Gotlin snippets
- optional `gopls` bridge for hover/definition on Go-imported symbols
## Setup
From this folder:
```bash
npm install
npm run build
```
Then in VS Code:
1. Open this folder as an extension project.
2. Press `F5` to launch an Extension Development Host.
3. Open your Gotlin workspace in that host.
## Server path
By default the extension looks for:
```text
<workspace>/bin/gotlin-lsp
```
If your binary lives somewhere else, set:
```json
"gotlin.serverPath": "/absolute/path/to/gotlin-lsp"
```
If `gopls` is not on your PATH, also set:
```json
"gotlin.goplsPath": "/absolute/path/to/gopls"
```
## Build the language server
From the repo root:
```bash
go build -o ./bin/gotlin-lsp ./cmd/gotlin-lsp
```

View file

@ -0,0 +1,56 @@
{
"comments": {
"lineComment": "//"
},
"wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\$\\%\\^\\&\\*\\(\\)\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\\"\\'\\,\\.\\<\\>\\/\\?\\s]+)",
"brackets": [
[
"{",
"}"
],
[
"(",
")"
]
],
"autoClosingPairs": [
{
"open": "{",
"close": "}"
},
{
"open": "(",
"close": ")"
},
{
"open": "\"",
"close": "\""
}
],
"surroundingPairs": [
[
"{",
"}"
],
[
"(",
")"
],
[
"\"",
"\""
]
],
"indentationRules": {
"increaseIndentPattern": "^.*\\{\\s*$",
"decreaseIndentPattern": "^\\s*\\}"
},
"onEnterRules": [
{
"beforeText": "^.*\\{\\s*$",
"action": {
"indent": "indent"
}
}
]
}

140
tools/vscode-gotlin/package-lock.json generated Normal file
View file

@ -0,0 +1,140 @@
{
"name": "gotlin-vscode",
"version": "0.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "gotlin-vscode",
"version": "0.0.1",
"license": "UNLICENSED",
"dependencies": {
"vscode-languageclient": "^9.0.1"
},
"devDependencies": {
"@types/node": "^20.14.10",
"@types/vscode": "^1.90.0",
"typescript": "^5.5.4"
},
"engines": {
"vscode": "^1.90.0"
}
},
"node_modules/@types/node": {
"version": "20.19.35",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz",
"integrity": "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/@types/vscode": {
"version": "1.109.0",
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.109.0.tgz",
"integrity": "sha512-0Pf95rnwEIwDbmXGC08r0B4TQhAbsHQ5UyTIgVgoieDe4cOnf92usuR5dEczb6bTKEp7ziZH4TV1TRGPPCExtw==",
"dev": true,
"license": "MIT"
},
"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==",
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"license": "MIT",
"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==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"license": "ISC",
"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,
"license": "Apache-2.0",
"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,
"license": "MIT"
},
"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==",
"license": "MIT",
"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==",
"license": "MIT",
"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==",
"license": "MIT",
"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==",
"license": "MIT"
}
}
}

View file

@ -0,0 +1,73 @@
{
"name": "gotlin-vscode",
"displayName": "Gotlin",
"description": "VS Code support for Gotlin (.gt) files",
"version": "0.0.1",
"publisher": "local",
"license": "UNLICENSED",
"engines": {
"vscode": "^1.90.0"
},
"categories": [
"Programming Languages"
],
"activationEvents": [
],
"main": "./out/extension.js",
"contributes": {
"languages": [
{
"id": "gotlin",
"aliases": [
"Gotlin",
"gotlin"
],
"extensions": [
".gt"
],
"configuration": "./language-configuration.json"
}
],
"snippets": [
{
"language": "gotlin",
"path": "./snippets/gotlin.code-snippets"
}
],
"grammars": [
{
"language": "gotlin",
"scopeName": "source.gotlin",
"path": "./syntaxes/gotlin.tmLanguage.json"
}
],
"configuration": {
"title": "Gotlin",
"properties": {
"gotlin.serverPath": {
"type": "string",
"default": "",
"description": "Absolute path to the gotlin-lsp binary. If empty, the extension tries <workspace>/bin/gotlin-lsp first, then 'gotlin-lsp' on PATH."
},
"gotlin.goplsPath": {
"type": "string",
"default": "",
"description": "Absolute path to gopls. If empty, gotlin-lsp tries 'gopls' on PATH."
}
}
}
},
"scripts": {
"build": "tsc -p .",
"watch": "tsc -w -p ."
},
"dependencies": {
"vscode-languageclient": "^9.0.1"
},
"devDependencies": {
"@types/node": "^20.14.10",
"@types/vscode": "^1.90.0",
"typescript": "^5.5.4"
}
}

View file

@ -0,0 +1,59 @@
{
"Function": {
"prefix": "fun",
"body": [
"fun ${1:name}(${2}): ${3:Unit} {",
" $0",
"}"
],
"description": "Gotlin function"
},
"Main Function": {
"prefix": "main",
"body": [
"fun main() {",
" $0",
"}"
],
"description": "Gotlin main function"
},
"Package": {
"prefix": "package",
"body": [
"package ${1:demo}"
],
"description": "Gotlin package declaration"
},
"Go Import": {
"prefix": "importgo",
"body": [
"import go.${1:fmt}"
],
"description": "Import a Go package"
},
"If": {
"prefix": "if",
"body": [
"if (${1:condition}) {",
" $0",
"}"
],
"description": "If statement"
},
"Lambda": {
"prefix": "lambda",
"body": [
"{ ${1:it} -> $0 }"
],
"description": "Lambda expression"
},
"Override Method": {
"prefix": "override",
"body": [
"override fun ${1:name}(${2}): ${3:Unit} {",
" $0",
"}"
],
"description": "Override a class or interface method"
}
}

View file

@ -0,0 +1,86 @@
import * as fs from "node:fs";
import * as path from "node:path";
import * as vscode from "vscode";
import {
LanguageClient,
LanguageClientOptions,
ServerOptions
} from "vscode-languageclient/node";
let client: LanguageClient | undefined;
export async function activate(context: vscode.ExtensionContext): Promise<void> {
const serverPath = resolveServerPath();
if (!serverPath) {
void vscode.window.showErrorMessage(
"Gotlin LSP binary was not found. Build ./bin/gotlin-lsp or set gotlin.serverPath."
);
return;
}
const serverOptions: ServerOptions = {
command: serverPath,
args: [],
options: {
env: {
...process.env,
GOTLIN_GOPLS_PATH: resolveGoplsPath()
}
}
};
const clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: "file", language: "gotlin" }],
outputChannelName: "Gotlin Language Server"
};
client = new LanguageClient(
"gotlin-lsp",
"Gotlin Language Server",
serverOptions,
clientOptions
);
await client.start();
}
export async function deactivate(): Promise<void> {
if (client) {
await client.stop();
client = undefined;
}
}
function resolveServerPath(): string | undefined {
const configured = vscode.workspace
.getConfiguration("gotlin")
.get<string>("serverPath", "")
.trim();
if (configured) {
return configured;
}
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (workspaceFolder) {
const candidate = path.join(workspaceFolder.uri.fsPath, "bin", platformBinaryName("gotlin-lsp"));
if (fs.existsSync(candidate)) {
return candidate;
}
}
return "gotlin-lsp";
}
function platformBinaryName(base: string): string {
return process.platform === "win32" ? `${base}.exe` : base;
}
function resolveGoplsPath(): string {
const configured = vscode.workspace
.getConfiguration("gotlin")
.get<string>("goplsPath", "")
.trim();
return configured;
}

View file

@ -0,0 +1,203 @@
{
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
"name": "Gotlin",
"scopeName": "source.gotlin",
"patterns": [
{
"include": "#comments"
},
{
"include": "#imports"
},
{
"include": "#package"
},
{
"include": "#functions"
},
{
"include": "#typesDecl"
},
{
"include": "#literals"
},
{
"include": "#keywords"
},
{
"include": "#types"
},
{
"include": "#strings"
},
{
"include": "#numbers"
},
{
"include": "#operators"
}
],
"repository": {
"comments": {
"patterns": [
{
"name": "comment.line.double-slash.gotlin",
"match": "//.*$"
}
]
},
"imports": {
"patterns": [
{
"name": "meta.import.gotlin",
"match": "\\b(import)\\b\\s+((?:[A-Za-z_][\\w-]*\\.)*[A-Za-z_][\\w-]*)(?:\\s+((?:[A-Za-z_][\\w-]*\\.)*[A-Za-z_][\\w-]*))?",
"captures": {
"1": {
"name": "keyword.control.import.gotlin"
},
"2": {
"name": "meta.path.gotlin"
},
"3": {
"name": "meta.path.gotlin"
}
}
}
]
},
"package": {
"patterns": [
{
"name": "meta.package.gotlin",
"match": "\\b(package)\\b\\s+((?:[A-Za-z_][\\w-]*\\.)*[A-Za-z_][\\w-]*)",
"captures": {
"1": {
"name": "keyword.control.package.gotlin"
},
"2": {
"name": "meta.path.gotlin"
}
}
}
]
},
"functions": {
"patterns": [
{
"name": "meta.function.gotlin",
"match": "\\b(?:(override)\\s+)?(fun)\\b\\s+([A-Za-z_][A-Za-z0-9_]*)",
"captures": {
"1": {
"name": "storage.modifier.gotlin"
},
"2": {
"name": "keyword.control.function.gotlin"
},
"3": {
"name": "entity.name.function.gotlin"
}
}
}
]
},
"typesDecl": {
"patterns": [
{
"name": "meta.interface.gotlin",
"match": "\\b(interface)\\b\\s+([A-Za-z_][A-Za-z0-9_]*)",
"captures": {
"1": {
"name": "storage.type.interface.gotlin"
},
"2": {
"name": "entity.name.type.interface.gotlin"
}
}
},
{
"name": "meta.class.gotlin",
"match": "\\b(class)\\b\\s+([A-Za-z_][A-Za-z0-9_]*)",
"captures": {
"1": {
"name": "storage.type.class.gotlin"
},
"2": {
"name": "entity.name.type.class.gotlin"
}
}
}
]
},
"literals": {
"patterns": [
{
"name": "constant.language.boolean.gotlin",
"match": "\\b(true|false)\\b"
},
{
"name": "constant.language.null.gotlin",
"match": "\\bnull\\b"
},
{
"name": "variable.language.this.gotlin",
"match": "\\bthis\\b"
}
]
},
"keywords": {
"patterns": [
{
"name": "keyword.control.gotlin",
"match": "\\b(fun|val|var|override|if|else|while|return|try|catch|throw)\\b"
}
]
},
"types": {
"patterns": [
{
"name": "storage.type.gotlin",
"match": "\\b(Int|String|Boolean|Unit)\\b"
},
{
"name": "support.type.gotlin",
"match": "\\b[A-Za-z_][A-Za-z0-9_]*(?:\\.[A-Za-z_][A-Za-z0-9_]*)+\\b"
},
{
"name": "entity.name.type.gotlin",
"match": "\\b[A-Z][A-Za-z0-9_]*\\b"
}
]
},
"strings": {
"patterns": [
{
"name": "string.quoted.double.gotlin",
"begin": "\"",
"end": "\"",
"patterns": [
{
"name": "constant.character.escape.gotlin",
"match": "\\\\."
}
]
}
]
},
"numbers": {
"patterns": [
{
"name": "constant.numeric.gotlin",
"match": "\\b\\d+\\b"
}
]
},
"operators": {
"patterns": [
{
"name": "keyword.operator.gotlin",
"match": "->|==|!=|<=|>=|&&|\\|\\||[=+\\-*/%<>!:.,]"
}
]
}
}
}

View file

@ -0,0 +1,18 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "ES2020",
"lib": [
"ES2020"
],
"outDir": "out",
"rootDir": "src",
"strict": true,
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": [
"src/**/*.ts"
]
}