diff --git a/.gitignore b/.gitignore
index a53d198..35c79c5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,4 +9,7 @@ yarn.lock
package-lock.json
-.vscode
\ No newline at end of file
+test.whi
+
+out
+.vscode-test
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..71e8d9a
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,29 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "extensionHost",
+ "request": "launch",
+ "name": "Launch Client",
+ "runtimeExecutable": "${execPath}",
+ "args": [
+ "--extensionDevelopmentPath=${workspaceRoot}"
+ ],
+ "outFiles": [
+ "${workspaceRoot}/client/out/**/*.js"
+ ],
+ "preLaunchTask": {
+ "type": "npm",
+ "script": "watch"
+ },
+ }
+ ],
+ "compounds": [
+ {
+ "name": "Client + Server",
+ "configurations": [
+ "Launch Client",
+ ]
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..f129682
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,33 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "type": "npm",
+ "script": "compile",
+ "group": "build",
+ "presentation": {
+ "panel": "dedicated",
+ "reveal": "never"
+ },
+ "problemMatcher": [
+ "$tsc"
+ ]
+ },
+ {
+ "type": "npm",
+ "script": "watch",
+ "isBackground": true,
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ },
+ "presentation": {
+ "panel": "dedicated",
+ "reveal": "never"
+ },
+ "problemMatcher": [
+ "$tsc-watch"
+ ]
+ }
+ ]
+}
diff --git a/.vscodeignore b/.vscodeignore
index f369b5e..c9506bb 100644
--- a/.vscodeignore
+++ b/.vscodeignore
@@ -2,3 +2,4 @@
.vscode-test/**
.gitignore
vsc-extension-quickstart.md
+src
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e6c6c57
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020-2022 the Whistle authors
+
+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.
diff --git a/README.md b/README.md
index 481e88d..cadd989 100644
--- a/README.md
+++ b/README.md
@@ -1 +1 @@
-# whistle-lang vscode support
\ No newline at end of file
+# whistle-lang vscode support
diff --git a/icons/icon_dark.svg b/icons/icon_dark.svg
new file mode 100644
index 0000000..42a9968
--- /dev/null
+++ b/icons/icon_dark.svg
@@ -0,0 +1,44 @@
+
+
diff --git a/icons/icon_light.svg b/icons/icon_light.svg
new file mode 100644
index 0000000..99eba95
--- /dev/null
+++ b/icons/icon_light.svg
@@ -0,0 +1,48 @@
+
+
diff --git a/package.json b/package.json
index 0fc835b..8f8841f 100644
--- a/package.json
+++ b/package.json
@@ -4,10 +4,11 @@
"author": "Whistle Team",
"publisher": "whistle",
"icon": "icons/icon128.png",
+ "license": "MIT",
"description": "Support for the whistle language",
- "version": "1.7.0",
+ "version": "1.8.4",
"engines": {
- "vscode": "^1.47.0"
+ "vscode": "1.82.0"
},
"repository": {
"type": "git",
@@ -26,31 +27,90 @@
"languages": [
{
"id": "whistle",
+ "icon": {
+ "light": "icons/icon_light.svg",
+ "dark": "icons/icon_dark.svg"
+ },
"aliases": [
"Whistle",
"whistle"
],
"extensions": [
- "whi"
+ ".whi"
],
"configuration": "./language-configuration.json"
}
],
+ "configuration": {
+ "type": "object",
+ "title": "whistle",
+ "properties": {
+ "whistle.trace.server": {
+ "type": "string",
+ "scope": "window",
+ "enum": [
+ "off",
+ "messages",
+ "verbose"
+ ],
+ "enumDescriptions": [
+ "No traces",
+ "Error only",
+ "Full log"
+ ],
+ "default": "off",
+ "description": "Traces the communication between VS Code and the language server."
+ },
+ "whistle.whistlePath": {
+ "type": "string",
+ "default": "whistle",
+ "markdownDescription": "A path to the `whistle` executable. The extension looks for `whistle` in the `PATH` By default",
+ "scope": "window"
+ }
+ }
+ },
"grammars": [
{
"language": "whistle",
"scopeName": "source.whistle",
"path": "./syntaxes/whistle.tmLanguage.json"
}
+ ],
+ "commands": [
+ {
+ "command": "whistle.restartServer",
+ "title": "Restart Whistle Server",
+ "category": "whistle"
+ }
]
},
+ "activationEvents": [
+ "onDebugInitialConfigurations"
+ ],
+ "enabledApiProposals": [],
+ "main": "./out/extension",
"devDependencies": {
- "js-yaml": "^3.14.0",
- "vsce": "^1.87.1"
+ "@types/mocha": "10.0.1",
+ "@types/node": "20.6.2",
+ "@types/semver": "7.5.2",
+ "@types/vscode": "1.82.0",
+ "js-yaml": "4.1.0",
+ "vsce": "2.15.0",
+ "vscode": "1.1.37",
+ "vscode-test": "1.6.1"
+ },
+ "dependencies": {
+ "esbuild": "0.19.3",
+ "lodash-es": "4.17.21",
+ "lodash.debounce": "4.0.8",
+ "vscode-languageclient": "9.0.0"
},
"scripts": {
+ "compile": "esbuild --bundle --sourcemap=external --minify --external:vscode src/extension.ts --outdir=out --platform=node --format=cjs",
"build": "js-yaml syntaxes/whistle.tmLanguage.yml > syntaxes/whistle.tmLanguage.json && js-yaml snippets/whistle.yml > snippets/whistle.json",
"package": "vsce package",
- "package:npm": "vsce package --no-yarn"
+ "package:npm": "vsce package --no-yarn",
+ "test": "npm run compile && npm run package:npm && node ./node_modules/vscode/bin/test",
+ "all": "npm run compile && npm run build && npm run package:npm"
}
}
diff --git a/snippets/whistle.yml b/snippets/whistle.yml
index 0190c27..c56bd97 100644
--- a/snippets/whistle.yml
+++ b/snippets/whistle.yml
@@ -1,21 +1,9 @@
---
-Import external module:
- prefix: import statement
+Import module:
+ prefix: import
body:
- - import { $0 } from "${1:module}"
- description: Import external module.
-Print string to the console:
- prefix: pstr
- body:
- - printString($1)
- - "$0"
- description: Print string to the console
-Print int to the console:
- prefix: pint
- body:
- - printInt($1)
- - "$0"
- description: Print int to the console
+ - import from "${0:module}"
+ description: Import a whistle file.
Function Statement:
prefix: fn
body:
@@ -23,6 +11,13 @@ Function Statement:
- "\t$TM_SELECTED_TEXT$0"
- "}"
description: Function Statement
+Extern module:
+ prefix: extern
+ body:
+ - extern "${0:namespace}" {
+ - "\tfn ${1:name}(${2:params}:${3:type}):${4:return_type},"
+ - "}"
+ description: Import an external function
If Statement:
prefix: if
body:
diff --git a/src/extension.ts b/src/extension.ts
new file mode 100644
index 0000000..aef31f5
--- /dev/null
+++ b/src/extension.ts
@@ -0,0 +1,187 @@
+// deno-lint-ignore-file require-await no-unused-vars
+import {
+ CancellationToken,
+ commands,
+ EventEmitter,
+ ExtensionContext,
+ InlayHint,
+ InlayHintsProvider,
+ languages,
+ ProviderResult,
+ Range,
+ Selection,
+ TextDocument,
+ TextDocumentChangeEvent,
+ TextEdit,
+ Uri,
+ window,
+ workspace,
+ WorkspaceEdit,
+} from "vscode";
+
+import {
+ Disposable,
+ Executable,
+ LanguageClient,
+ LanguageClientOptions,
+ ServerOptions,
+} from "vscode-languageclient/node";
+
+let client: LanguageClient;
+
+function createLanguageClient() {
+ const command = workspace.getConfiguration("whistle").get("whistlePath");
+ console.log(command);
+ const run: Executable = {
+ command,
+ args: ["lsp"],
+ options: {
+ env: {
+ ...process.env,
+ RUST_LOG: "debug",
+ },
+ },
+ };
+
+ const serverOptions: ServerOptions = {
+ run,
+ debug: run,
+ };
+
+ const clientOptions: LanguageClientOptions = {
+ documentSelector: [{ scheme: "file", language: "whistle" }],
+ synchronize: {
+ fileEvents: workspace.createFileSystemWatcher("**/.clientrc"),
+ },
+ };
+
+ return new LanguageClient(
+ "whistle-language-server",
+ "whistle language server",
+ serverOptions,
+ clientOptions,
+ );
+}
+
+export async function activate(context: ExtensionContext) {
+ const restartCommand = commands.registerCommand(
+ "whistle.restartServer",
+ async () => {
+ if (!client) {
+ window.showErrorMessage("whistle client not found");
+ return;
+ }
+
+ try {
+ if (client.isRunning()) {
+ await client.restart();
+ window.showInformationMessage("whistle server restarted.");
+ } else {
+ await client.start();
+ }
+ } catch (err) {
+ client.error("Restarting client failed", err, "force");
+ }
+ },
+ );
+
+ context.subscriptions.push(restartCommand);
+
+ client = createLanguageClient();
+ activateInlayHints(context);
+ client.start();
+}
+
+export function deactivate() {
+ return client?.stop();
+}
+
+export function activateInlayHints(ctx: ExtensionContext) {
+ const maybeUpdater = {
+ hintsProvider: null as Disposable | null,
+ updateHintsEventEmitter: new EventEmitter(),
+
+ async onConfigChange() {
+ this.dispose();
+
+ const event = this.updateHintsEventEmitter.event;
+ this.hintsProvider = languages.registerInlayHintsProvider(
+ { scheme: "file", language: "whistle" },
+ new (class implements InlayHintsProvider {
+ onDidChangeInlayHints = event;
+ resolveInlayHint(
+ hint: InlayHint,
+ _token: CancellationToken,
+ ): ProviderResult {
+ const ret = {
+ label: hint.label,
+ ...hint,
+ };
+ return ret;
+ }
+
+ async provideInlayHints(
+ document: TextDocument,
+ _range: Range,
+ _token: CancellationToken,
+ ): Promise {
+ const hints = (await client
+ .sendRequest("custom/inlay_hint", {
+ path: document.uri.toString(),
+ })
+ .catch((_err: unknown) => null)) as [number, number, string][];
+ if (hints == null) {
+ return [];
+ } else {
+ return hints.map((item) => {
+ const [start, end, label] = item;
+ const _startPosition = document.positionAt(start);
+ const endPosition = document.positionAt(end);
+ return {
+ position: endPosition,
+ paddingLeft: true,
+ label: [
+ {
+ value: `${label}`,
+ command: {
+ title: "hello world",
+ command: "helloworld.helloWorld",
+ arguments: [document.uri],
+ },
+ },
+ ],
+ };
+ });
+ }
+ }
+ })(),
+ );
+ },
+
+ onDidChangeTextDocument(
+ { contentChanges, document }: TextDocumentChangeEvent,
+ ) {
+ // this.updateHintsEventEmitter.fire();
+ },
+
+ dispose() {
+ this.hintsProvider?.dispose();
+ this.hintsProvider = null;
+ this.updateHintsEventEmitter.dispose();
+ },
+ };
+
+ workspace.onDidChangeConfiguration(
+ maybeUpdater.onConfigChange,
+ maybeUpdater,
+ ctx.subscriptions,
+ );
+
+ workspace.onDidChangeTextDocument(
+ maybeUpdater.onDidChangeTextDocument,
+ maybeUpdater,
+ ctx.subscriptions,
+ );
+
+ maybeUpdater.onConfigChange().catch(console.error);
+}
diff --git a/syntaxes/whistle.tmLanguage.yml b/syntaxes/whistle.tmLanguage.yml
index 3634955..a33c49d 100644
--- a/syntaxes/whistle.tmLanguage.yml
+++ b/syntaxes/whistle.tmLanguage.yml
@@ -173,7 +173,7 @@ repository:
name: constant.other.caps.whistle
match: "\\b[A-Z]{2}[A-Z0-9_]*\\b"
- comment: constant declarations
- match: "\\b(const)\\s+([A-Z][A-Za-z0-9_]*)\\b"
+ match: "\\b(val)\\s+([A-Z][A-Za-z0-9_]*)\\b"
captures:
'1':
name: storage.type.whistle
@@ -181,7 +181,7 @@ repository:
name: constant.other.caps.whistle
- comment: decimal integers and floats
name: constant.numeric.decimal.whistle
- match: "\\b\\d[\\d_]*(\\.?)[\\d_]*(?:(E)([+-])([\\d_]+))?(f32|f64|i128|i16|i32|i64|i8|isize|u128|u16|u32|u64|u8|usize)?\\b"
+ match: "\\b\\d[\\d_]*(\\.?)[\\d_]*(?:(E)([+-])([\\d_]+))?(f32|f64|i8|i16|i32|i64|u8|u16|u32|u64|number|int)?\\b"
captures:
'1':
name: punctuation.separator.dot.decimal.whistle
@@ -195,19 +195,19 @@ repository:
name: entity.name.type.numeric.whistle
- comment: hexadecimal integers
name: constant.numeric.hex.whistle
- match: "\\b0x[\\da-fA-F_]+(i128|i16|i32|i64|i8|isize|u128|u16|u32|u64|u8|usize)?\\b"
+ match: "\\b0x[\\da-fA-F_]+(i8|i16|i32|i64|u8|u16|u32|u64|number)?\\b"
captures:
'1':
name: entity.name.type.numeric.whistle
- comment: octal integers
name: constant.numeric.oct.whistle
- match: "\\b0o[0-7_]+(i128|i16|i32|i64|i8|isize|u128|u16|u32|u64|u8|usize)?\\b"
+ match: "\\b0o[0-7_]+(i8|i16|i32|i64|u8|u16|u32|u64|number|int)?\\b"
captures:
'1':
name: entity.name.type.numeric.whistle
- comment: binary integers
name: constant.numeric.bin.whistle
- match: "\\b0b[01_]+(i128|i16|i32|i64|i8|isize|u128|u16|u32|u64|u8|usize)?\\b"
+ match: "\\b0b[01_]+(i8|i16|i32|i64|u8|u16|u32|u64|number|int)?\\b"
captures:
'1':
name: entity.name.type.numeric.whistle
@@ -328,10 +328,10 @@ repository:
match: "\\b(await|break|continue|do|else|for|if|loop|match|return|try|while|yield)\\b"
- comment: storage keywords
name: keyword.other.whistle storage.type.whistle
- match: "\\b(extern|let|var|val|macro|mod)\\b"
+ match: "\\b(extern|var|val)\\b"
- comment: const keyword
name: storage.modifier.whistle
- match: "\\b(const)\\b"
+ match: "\\b(val)\\b"
- comment: type keyword
name: keyword.declaration.type.whistle storage.type.whistle
match: "\\b(type)\\b"
@@ -349,7 +349,7 @@ repository:
match: "\\b(abstract|static)\\b"
- comment: other keywords
name: keyword.other.whistle
- match: "\\b(as|async|import|export|from|become|box|dyn|move|final|impl|in|override|private|public|ref|typeof|union|unsafe|unsized|use|virtual|where)\\b"
+ match: "\\b(as|inline|async|import|export|extern|from|become|box|dyn|move|final|impl|in|override|private|public|ref|typeof|union|unsafe|unsized|virtual|where)\\b"
- comment: fun
name: keyword.other.fun.whistle
match: "\\bfn\\b"
@@ -475,7 +475,7 @@ repository:
types:
patterns:
- comment: numeric types
- match: "(?