Skip to content

Commit

Permalink
Add TypeScript support
Browse files Browse the repository at this point in the history
Squashed commit of the following:

commit 705f025
Merge: 915fe99 17e5377
Author: Sonny Piers <[email protected]>
Date:   Thu May 30 17:35:46 2024 +0300

    Merge branch 'main' into typescript

commit 915fe99
Author: Angelo Verlain Shema <[email protected]>
Date:   Tue May 28 11:19:04 2024 +0200

    Compile TypeScript to JavaScript before executing (#941)

    This basically compiles from TypeScript to JavaScript at runtime when
    the "Run" button is clicked.

    However, there are currently 2 issues worth mentioning:

    ### 1. Speed

    Notice that this is noticeably slow because it's using `tsc`. It could
    possibly be improved by using `esbuild`, `swc`, `babel` or something
    similar but then there will be no typechecking when the "Run" button is
    clicked.

    However, I think the above typechecking caveat will not make much sense
    when we have real-time Intellisense in the editor for TypeScript.

    ### 2. Sourcemaps

    Another consideration is the lack of sourcemap support in GJS. While tsc
    can generate sourcemaps, this feature is disabled because GJS won't use
    them. This means that some errors will have the wrong line:column
    information.

    For example:

    ![image](https://github.com/workbenchdev/Workbench/assets/37999241/c6487292-18a9-4e50-85a0-5c8771f107fc)

    Workbench/GJS reports the error is on line number 17 even if it's
    actually on line number 22 because that's where it ends up after it's
    compiled to JavaScript (many compilers will eat up unnecessary line
    breaks even though the minify option is turned off).

    I left some TODOs in here where some decisions need to be made and hope
    to get some feedback

commit 773669f
Author: Angelo Verlain Shema <[email protected]>
Date:   Tue May 7 15:53:06 2024 +0200

    Add base TypeScript view to Workbench (#938)
  • Loading branch information
vixalien authored and sonnyp committed Jun 6, 2024
1 parent 17a8e4a commit 84f1bfa
Show file tree
Hide file tree
Showing 23 changed files with 412 additions and 54 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ SHELL:=/bin/bash -O globstar

setup:
flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak install --or-update --user --noninteractive flathub org.gnome.Sdk//46 org.flatpak.Builder org.freedesktop.Sdk.Extension.rust-stable//23.08 org.freedesktop.Sdk.Extension.vala//23.08 org.freedesktop.Sdk.Extension.llvm16//23.08
flatpak install --or-update --user --noninteractive flathub org.gnome.Sdk//46 org.flatpak.Builder org.freedesktop.Sdk.Extension.rust-stable//23.08 org.freedesktop.Sdk.Extension.vala//23.08 org.freedesktop.Sdk.Extension.llvm16//23.08 org.freedesktop.Sdk.Extension.node18//23.08 org.freedesktop.Sdk.Extension.typescript//23.08
# flatpak remote-add --user --if-not-exists flathub-beta https://flathub.org/beta-repo/flathub-beta.flatpakrepo
flatpak remote-add --user --if-not-exists gnome-nightly https://nightly.gnome.org/gnome-nightly.flatpakrepo
flatpak install --or-update --user --noninteractive gnome-nightly org.gnome.Sdk//master
Expand Down
3 changes: 3 additions & 0 deletions build-aux/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ const demos = [];
if (demo_dir.get_child("main.js").query_exists(null)) {
languages.push("javascript");
}
if (demo_dir.get_child("main.ts").query_exists(null)) {
languages.push("typescript");
}
if (demo_dir.get_child("main.vala").query_exists(null)) {
languages.push("vala");
}
Expand Down
6 changes: 4 additions & 2 deletions build-aux/re.sonny.Workbench.Devel.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
"sdk-extensions": [
"org.freedesktop.Sdk.Extension.vala",
"org.freedesktop.Sdk.Extension.rust-stable",
"org.freedesktop.Sdk.Extension.llvm16"
"org.freedesktop.Sdk.Extension.llvm16",
"org.freedesktop.Sdk.Extension.node18",
"org.freedesktop.Sdk.Extension.typescript"
],
"build-options": {
"append-path": "/usr/lib/sdk/vala/bin:/usr/lib/sdk/rust-stable/bin",
"append-path": "/usr/lib/sdk/vala/bin:/usr/lib/sdk/rust-stable/bin:/usr/lib/sdk/node18/bin:/usr/lib/sdk/typescript/bin",
"append-ld-library-path": "/usr/lib/sdk/vala/lib"
},
"command": "workbench",
Expand Down
6 changes: 4 additions & 2 deletions build-aux/re.sonny.Workbench.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
"sdk-extensions": [
"org.freedesktop.Sdk.Extension.vala",
"org.freedesktop.Sdk.Extension.rust-stable",
"org.freedesktop.Sdk.Extension.llvm16"
"org.freedesktop.Sdk.Extension.llvm16",
"org.freedesktop.Sdk.Extension.node18",
"org.freedesktop.Sdk.Extension.typescript"
],
"build-options": {
"append-path": "/usr/lib/sdk/vala/bin:/usr/lib/sdk/rust-stable/bin",
"append-path": "/usr/lib/sdk/vala/bin:/usr/lib/sdk/rust-stable/bin:/usr/lib/sdk/node18/bin:/usr/lib/sdk/typescript/bin",
"append-ld-library-path": "/usr/lib/sdk/vala/lib"
},
"command": "workbench",
Expand Down
5 changes: 5 additions & 0 deletions src/Extensions/Extensions.blp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ Adw.Dialog dialog {
title: _("Vala");
command: "flatpak install flathub org.freedesktop.Sdk.Extension.vala//23.08";
}

$Extension extension_typescript {
title: _("TypeScript");
command: "flatpak install flathub org.freedesktop.Sdk.Extension.node18//23.08 org.freedesktop.Sdk.Extension.typescript//23.08";
}
}

Label restart_hint {
Expand Down
16 changes: 15 additions & 1 deletion src/Extensions/Extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export function Extensions({ window }) {
picture_illustration,
extension_rust,
extension_vala,
extension_typescript,
restart_hint,
all_set_hint,
} = build(Interface);
Expand All @@ -26,8 +27,13 @@ export function Extensions({ window }) {

extension_rust.enabled = isRustEnabled();
extension_vala.enabled = isValaEnabled();
extension_typescript.enabled = isTypeScriptEnabled();

for (const extension of [extension_rust, extension_vala]) {
for (const extension of [
extension_rust,
extension_vala,
extension_typescript,
]) {
if (!extension.enabled) {
all_set_hint.set_visible(false);
restart_hint.set_visible(true);
Expand Down Expand Up @@ -55,3 +61,11 @@ export function isValaEnabled() {
Gio.File.new_for_path("/usr/lib/sdk/vala").query_exists(null);
return vala_enabled;
}

let typescript_enabled;
export function isTypeScriptEnabled() {
typescript_enabled ??=
Gio.File.new_for_path("/usr/lib/sdk/typescript").query_exists(null) &&
Gio.File.new_for_path("/usr/lib/sdk/node18").query_exists(null);
return typescript_enabled;
}
7 changes: 6 additions & 1 deletion src/PanelCode.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import Gio from "gi://Gio";
import GObject from "gi://GObject";

import { settings as global_settings, makeDropdownFlat } from "./util.js";
import { makeDropdownFlat, settings as global_settings } from "./util.js";
import { setupRustProject } from "./langs/rust/rust.js";
import { setupTypeScriptProject } from "./langs/typescript/typescript.js";

export default function PanelCode({
builder,
Expand Down Expand Up @@ -56,6 +57,10 @@ export default function PanelCode({
if (panel.language.toLowerCase() === "rust") {
setupRustProject(file).catch(console.error);
}

if (panel.language.toLowerCase() === "typescript") {
setupTypeScriptProject(file).catch(console.error);
}
}
switchLanguage();

Expand Down
22 changes: 22 additions & 0 deletions src/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,28 @@ export const languages = [
tabSize: 4,
},
},
{
id: "typescript",
name: "TypeScript",
panel: "code",
extensions: [".ts", ".mts"],
types: [],
document: null,
default_file: "main.ts",
index: 4,
language_server: [
"biome",
"lsp-proxy",
// src/meson.build installs biome.json there
GLib.getenv("FLATPAK_ID")
? `--config-path=${pkg.pkgdatadir}`
: `--config-path=src/langs/typescript`,
],
formatting_options: {
...formatting_options,
tabSize: 2,
},
},
];

export function getLanguage(id) {
Expand Down
38 changes: 38 additions & 0 deletions src/langs/javascript/Builder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Gio from "gi://Gio";
import GLib from "gi://GLib";

import { buildRuntimePath } from "../../util.js";

export default function JavascriptBuilder() {
async function run(text) {
// We have to create a new file each time
// because gjs doesn't appear to use etag for module caching
// ?foo=Date.now() also does not work as expected
// https://gitlab.gnome.org/GNOME/gjs/-/issues/618
const path = buildRuntimePath(`workbench-${Date.now()}`);
const file_javascript = Gio.File.new_for_path(path);
await file_javascript.replace_contents_async(
new GLib.Bytes(text),
null,
false,
Gio.FileCreateFlags.NONE,
null,
);

let exports;
try {
exports = await import(`file://${file_javascript.get_path()}`);
} catch (err) {
console.error(err);
return false;
} finally {
file_javascript
.delete_async(GLib.PRIORITY_DEFAULT, null)
.catch(console.error);
}

return exports;
}

return { run };
}
21 changes: 1 addition & 20 deletions src/langs/rust/rust.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import Gio from "gi://Gio";
import GLib from "gi://GLib";

import { createLSPClient } from "../../common.js";
import { getLanguage } from "../../util.js";
import { getLanguage, copy } from "../../util.js";
import { isRustEnabled } from "../../Extensions/Extensions.js";

export function setup({ document }) {
Expand Down Expand Up @@ -59,21 +58,3 @@ export async function installRustLibraries(destination) {
),
]);
}

async function copy(filename, source_dir, dest_dir, flags) {
const file = source_dir.get_child(filename);

try {
await file.copy_async(
dest_dir.get_child(file.get_basename()), // destination
flags, // flags
GLib.PRIORITY_DEFAULT, // priority
null, // cancellable
null, // progress_callback
);
} catch (err) {
if (!err.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.EXISTS)) {
throw err;
}
}
}
55 changes: 55 additions & 0 deletions src/langs/typescript/Compiler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import Gio from "gi://Gio";
import GLib from "gi://GLib";

import { buildRuntimePath, copy } from "../../util.js";

export default function Compiler({ session }) {
const { file } = session;

async function compile() {
const tsc_launcher = new Gio.SubprocessLauncher();
tsc_launcher.set_cwd(file.get_path());

const tsc = tsc_launcher.spawnv(["tsc", "--project", file.get_path()]);
await tsc.wait_async(null);

const result = tsc.get_successful();
tsc_launcher.close();
return result;
}

async function run() {
// We have to create a new file each time
// because gjs doesn't appear to use etag for module caching
// ?foo=Date.now() also does not work as expected
// https://gitlab.gnome.org/GNOME/gjs/-/issues/618
const path = buildRuntimePath(`workbench-${Date.now()}`);
const compiled_dir = Gio.File.new_for_path(path);
if (!compiled_dir.query_exists(null)) {
await compiled_dir.make_directory_async(GLib.PRIORITY_DEFAULT, null);
}
await copy(
"main.js",
file.get_child("compiled_javascript"),
compiled_dir,
Gio.FileCopyFlags.NONE,
);
const compiled_file = compiled_dir.get_child("main.js");

let exports;
try {
exports = await import(`file://${compiled_file.get_path()}`);
} catch (err) {
console.error(err);
return false;
} finally {
compiled_file
.delete_async(GLib.PRIORITY_DEFAULT, null)
.catch(console.error);
}

return exports;
}

return { compile, run };
}
34 changes: 34 additions & 0 deletions src/langs/typescript/TypeScriptDocument.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { setup } from "./typescript.js";

import Document from "../../Document.js";
import { applyTextEdits } from "../../lsp/sourceview.js";

export class TypeScriptDocument extends Document {
constructor(...args) {
super(...args);

this.lspc = setup({ document: this });
}

async format() {
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_formatting
const text_edits = await this.lspc.request("textDocument/formatting", {
textDocument: {
uri: this.file.get_uri(),
},
options: {
tabSize: 2,
insertSpaces: true,
trimTrailingWhitespace: true,
insertFinalNewline: true,
trimFinalNewlines: true,
},
});

// Biome doesn't support diff - it just returns one edit
// we don't want to loose the cursor position so we use this
const state = this.code_view.saveState();
applyTextEdits(text_edits, this.buffer);
await this.code_view.restoreState(state);
}
}
19 changes: 19 additions & 0 deletions src/langs/typescript/biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$schema": "https://biomejs.dev/schemas/1.3.3/schema.json",
"javascript": {
"globals": ["workbench"]
},
"formatter": {
"indentStyle": "space",
"indentWidth": 2
},
"linter": {
"rules": {
"recommended": false,
"correctness": {
"noUndeclaredVariables": "error",
"noUnusedVariables": "warn"
}
}
}
}
3 changes: 3 additions & 0 deletions src/langs/typescript/template/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
install_data(['types/ambient.d.ts', 'types/gi-module.d.ts', 'tsconfig.json'],
install_dir : join_paths(pkgdatadir, 'langs/typescript/template'),
preserve_path: true)
14 changes: 14 additions & 0 deletions src/langs/typescript/template/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "Bundler",
// TODO: should probably be fixed to ES2023, or whatever standard is
// currently supported by the latest GJS
"target": "ESNext",
"outDir": "compiled_javascript",
"paths": {
"gi://*": ["./types/gi-module.d.ts"]
}
},
"include": ["main.ts", "types/ambient.d.ts", "types/gi-module.d.ts"]
}
36 changes: 36 additions & 0 deletions src/langs/typescript/template/types/ambient.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// additional type declarations for GJS

// additional GJS log utils
declare function print(...args: any[]): void;
declare function log(...args: any[]): void;

// GJS pkg global
declare const pkg: {
version: string;
name: string;
};

// old GJS global imports
// used like: imports.format.printf("...");
declare module imports {
// format import
const format: {
format(this: String, ...args: any[]): string;
printf(fmt: string, ...args: any[]): string;
vprintf(fmt: string, args: any[]): string;
};
}

// gettext import
declare module "gettext" {
export function gettext(id: string): string;
export function ngettext(
singular: string,
plural: string,
n: number,
): string;
}

// global workbench object
// TODO: use correct typings
declare const workbench: any;
6 changes: 6 additions & 0 deletions src/langs/typescript/template/types/gi-module.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// dummy exports for a module imported with "gi://*"
// will be replaced later with actual gi-types

declare const module: any;

export default module;
Loading

0 comments on commit 84f1bfa

Please sign in to comment.