@vedivad/typst-web-service 0.7.11 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -27,6 +27,9 @@ if (result.vector) {
27
27
  document.querySelector("#preview")!.innerHTML = svg;
28
28
  }
29
29
 
30
+ // result.diagnostics are returned in deterministic order
31
+ // (path, start position, end position, message)
32
+
30
33
  // Multi-file
31
34
  const result = await compiler.compile({
32
35
  "/main.typ": '#import "template.typ": greet\n#greet("World")',
@@ -63,7 +66,7 @@ Config options ([typstyle docs](https://github.com/typstyle-rs/typstyle)):
63
66
  | `reorder_import_items` | `boolean` | -- | Sort import items alphabetically |
64
67
  | `wrap_text` | `boolean` | -- | Wrap text to fit within `max_width` |
65
68
 
66
- ## LSP analysis with tinymist
69
+ ## Completion and hover with tinymist
67
70
 
68
71
  `TypstAnalyzer` runs a [tinymist](https://github.com/Myriad-Dreamin/tinymist) language server in a Web Worker. The `wasmUrl` option must point to the `tinymist_bg.wasm` binary from the `tinymist-web` package.
69
72
 
@@ -73,25 +76,29 @@ import tinymistWasmUrl from "tinymist-web/pkg/tinymist_bg.wasm?url";
73
76
 
74
77
  const analyzer = await TypstAnalyzer.create({ wasmUrl: tinymistWasmUrl });
75
78
 
76
- analyzer.onDiagnostics((uri, diagnostics) => {
77
- console.log(uri, diagnostics);
78
- });
79
-
80
79
  await analyzer.didChange("untitled:project/main.typ", source);
81
- const completions = await analyzer.completion("untitled:project/main.typ", line, character);
82
- const hover = await analyzer.hover("untitled:project/main.typ", line, character);
80
+ const completions = await analyzer.completion(
81
+ "untitled:project/main.typ",
82
+ line,
83
+ character,
84
+ );
85
+ const hover = await analyzer.hover(
86
+ "untitled:project/main.typ",
87
+ line,
88
+ character,
89
+ );
83
90
 
84
91
  analyzer.destroy();
85
92
  ```
86
93
 
87
94
  ## Service classes
88
95
 
89
- | Class | Runs on | WASM loading | Purpose |
90
- | ---------------- | ----------- | ----------------------- | --------------------------------------------------------- |
96
+ | Class | Runs on | WASM loading | Purpose |
97
+ | ---------------- | ----------- | ----------------------- | ---------------------------------------------------------- |
91
98
  | `TypstCompiler` | Web Worker | CDN (automatic) | `compile()` -> diagnostics + vector, `compilePdf()` -> PDF |
92
99
  | `TypstRenderer` | Main thread | CDN (automatic) | `renderSvg(vector)` -> SVG string |
93
- | `TypstFormatter` | Main thread | Bundler (static import) | `format(source)`, `formatRange(source, start, end)` |
94
- | `TypstAnalyzer` | Web Worker | User-provided `wasmUrl` | LSP diagnostics, completion, hover via tinymist |
100
+ | `TypstFormatter` | Main thread | Bundler (static import) | `format(source)`, `formatRange(source, start, end)` |
101
+ | `TypstAnalyzer` | Web Worker | User-provided `wasmUrl` | Completion + hover via tinymist |
95
102
 
96
103
  ## License
97
104
 
@@ -1,3 +1,6 @@
1
+ // src/analyzer-worker.ts
2
+ import * as Comlink from "comlink";
3
+
1
4
  // ../../node_modules/.bun/tinymist-web@https+++github.com+Myriad-Dreamin+tinymist+releases+download+v0.14.10+tinymist-web.tar.gz/node_modules/tinymist-web/pkg/tinymist.js
2
5
  var wasm;
3
6
  function addToExternrefTable0(obj) {
@@ -911,168 +914,121 @@ async function __wbg_init(module_or_path) {
911
914
  }
912
915
  var tinymist_default = __wbg_init;
913
916
 
914
- // src/worker-utils.ts
915
- function postError(id, err) {
916
- const message = err instanceof Error ? err.message : String(err);
917
- self.postMessage({ type: "error", id, message });
918
- }
919
-
920
917
  // src/analyzer-worker.ts
921
- var server = null;
922
- var events = [];
923
- function normalizeUri(uri) {
924
- return uri.startsWith("untitled:/") ? `untitled:${uri.slice("untitled:/".length)}` : uri;
925
- }
926
- function flushEvents() {
927
- if (!server) return;
928
- while (events.length > 0) {
929
- for (const event of events.splice(0)) {
930
- server.on_event(event);
931
- }
918
+ var AnalyzerWorker = class {
919
+ server = null;
920
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- events are opaque values from WASM
921
+ events = [];
922
+ ensureServer() {
923
+ if (!this.server) throw new Error("Analyzer not initialized");
924
+ return this.server;
932
925
  }
933
- }
934
- async function initServer(wasmUrl) {
935
- await tinymist_default(wasmUrl);
936
- server = new TinymistLanguageServer({
937
- sendEvent: (event) => void events.push(event),
938
- sendRequest({ id }) {
939
- server.on_response({ id, result: null });
940
- },
941
- sendNotification: ({
942
- method,
943
- params
944
- }) => {
945
- if (method === "textDocument/publishDiagnostics") {
946
- const { uri, diagnostics } = params;
947
- self.postMessage({
948
- type: "diagnostics",
949
- uri: normalizeUri(uri),
950
- diagnostics
951
- });
952
- }
953
- },
954
- resolveFn: () => void 0
955
- });
956
- const initResult = server.on_request("initialize", {
957
- capabilities: {
958
- textDocument: {
959
- publishDiagnostics: { relatedInformation: true },
960
- completion: { completionItem: { snippetSupport: true } },
961
- hover: { contentFormat: ["markdown", "plaintext"] }
962
- }
963
- },
964
- rootUri: "file:///"
965
- });
966
- if (initResult && typeof initResult === "object" && "then" in initResult) {
967
- await initResult;
926
+ notifyDidOpen(uri, content) {
927
+ this.ensureServer().on_notification("textDocument/didOpen", {
928
+ textDocument: { uri, languageId: "typst", version: 1, text: content }
929
+ });
968
930
  }
969
- flushEvents();
970
- server.on_notification("initialized", {});
971
- flushEvents();
972
- }
973
- self.onmessage = async (e) => {
974
- const req = e.data;
975
- if (req.type === "init") {
976
- try {
977
- await initServer(req.wasmUrl);
978
- self.postMessage({
979
- type: "ready",
980
- id: req.id
981
- });
982
- } catch (err) {
983
- postError(req.id, err);
984
- }
985
- return;
931
+ notifyDidClose(uri) {
932
+ this.ensureServer().on_notification("textDocument/didClose", {
933
+ textDocument: { uri }
934
+ });
986
935
  }
987
- if (!server) {
988
- postError(req.id, new Error("Analyzer not initialized"));
989
- return;
936
+ notifyDidChange(uri, version, content) {
937
+ this.ensureServer().on_notification("textDocument/didChange", {
938
+ textDocument: { uri, version },
939
+ contentChanges: [{ text: content }]
940
+ });
990
941
  }
991
- if (req.type === "didOpen") {
992
- try {
993
- server.on_notification("textDocument/didOpen", {
942
+ flushEvents() {
943
+ if (!this.server) return;
944
+ while (this.events.length > 0) {
945
+ for (const event of this.events.splice(0)) {
946
+ this.server.on_event(event);
947
+ }
948
+ }
949
+ }
950
+ async init(wasmUrl) {
951
+ await tinymist_default({ module_or_path: wasmUrl });
952
+ this.server = new TinymistLanguageServer({
953
+ sendEvent: (event) => {
954
+ this.events.push(event);
955
+ },
956
+ sendRequest: ({
957
+ id
958
+ }) => {
959
+ this.server.on_response({ id, result: null });
960
+ },
961
+ sendNotification: () => {
962
+ },
963
+ resolveFn: () => void 0
964
+ });
965
+ const initResult = this.server.on_request("initialize", {
966
+ capabilities: {
994
967
  textDocument: {
995
- uri: req.uri,
996
- languageId: "typst",
997
- version: 1,
998
- text: req.content
968
+ completion: { completionItem: { snippetSupport: true } },
969
+ hover: { contentFormat: ["markdown", "plaintext"] }
999
970
  }
1000
- });
1001
- flushEvents();
1002
- self.postMessage({ type: "ack", id: req.id });
1003
- } catch (err) {
1004
- postError(req.id, err);
971
+ },
972
+ rootUri: "file:///"
973
+ });
974
+ if (initResult && typeof initResult === "object" && "then" in initResult) {
975
+ await initResult;
1005
976
  }
1006
- return;
977
+ this.flushEvents();
978
+ this.server.on_notification("initialized", {});
979
+ this.flushEvents();
1007
980
  }
1008
- if (req.type === "didClose") {
1009
- try {
1010
- server.on_notification("textDocument/didClose", {
1011
- textDocument: { uri: req.uri }
1012
- });
1013
- flushEvents();
1014
- self.postMessage({ type: "ack", id: req.id });
1015
- } catch (err) {
1016
- postError(req.id, err);
1017
- }
1018
- return;
981
+ async didOpen(uri, content) {
982
+ this.notifyDidOpen(uri, content);
983
+ this.flushEvents();
1019
984
  }
1020
- if (req.type === "didChange") {
1021
- try {
1022
- server.on_notification("textDocument/didChange", {
1023
- textDocument: { uri: req.uri, version: req.version },
1024
- contentChanges: [{ text: req.content }]
1025
- });
1026
- flushEvents();
1027
- self.postMessage({ type: "ack", id: req.id });
1028
- } catch (err) {
1029
- postError(req.id, err);
1030
- }
1031
- return;
985
+ async didClose(uri) {
986
+ this.notifyDidClose(uri);
987
+ this.flushEvents();
1032
988
  }
1033
- if (req.type === "completion") {
1034
- try {
1035
- const result = server.on_request("textDocument/completion", {
1036
- textDocument: { uri: req.uri },
1037
- position: { line: req.line, character: req.character }
1038
- });
1039
- const resolved = result && typeof result === "object" && "then" in result ? await result : result;
1040
- flushEvents();
1041
- self.postMessage({
1042
- type: "completionResult",
1043
- id: req.id,
1044
- result: resolved ?? null
1045
- });
1046
- } catch (err) {
1047
- postError(req.id, err);
989
+ async didChange(uri, version, content) {
990
+ this.notifyDidChange(uri, version, content);
991
+ this.flushEvents();
992
+ }
993
+ async didChangeMany(opens, changes) {
994
+ this.ensureServer();
995
+ for (const { uri, content } of opens) {
996
+ this.notifyDidOpen(uri, content);
997
+ }
998
+ for (const { uri, version, content } of changes) {
999
+ this.notifyDidChange(uri, version, content);
1048
1000
  }
1049
- return;
1001
+ this.flushEvents();
1050
1002
  }
1051
- if (req.type === "hover") {
1052
- try {
1053
- const result = server.on_request("textDocument/hover", {
1054
- textDocument: { uri: req.uri },
1055
- position: { line: req.line, character: req.character }
1056
- });
1057
- const resolved = result && typeof result === "object" && "then" in result ? await result : result;
1058
- flushEvents();
1059
- self.postMessage({
1060
- type: "hoverResult",
1061
- id: req.id,
1062
- result: resolved ?? null
1063
- });
1064
- } catch (err) {
1065
- postError(req.id, err);
1003
+ async didCloseMany(uris) {
1004
+ this.ensureServer();
1005
+ for (const uri of uris) {
1006
+ this.notifyDidClose(uri);
1066
1007
  }
1067
- return;
1008
+ this.flushEvents();
1009
+ }
1010
+ async completion(uri, line, character) {
1011
+ const result = this.ensureServer().on_request("textDocument/completion", {
1012
+ textDocument: { uri },
1013
+ position: { line, character }
1014
+ });
1015
+ const resolved = result && typeof result === "object" && "then" in result ? await result : result;
1016
+ this.flushEvents();
1017
+ return resolved ?? null;
1068
1018
  }
1069
- if (req.type === "destroy") {
1070
- server.free();
1071
- server = null;
1072
- self.postMessage({
1073
- type: "destroyed",
1074
- id: req.id
1019
+ async hover(uri, line, character) {
1020
+ const result = this.ensureServer().on_request("textDocument/hover", {
1021
+ textDocument: { uri },
1022
+ position: { line, character }
1075
1023
  });
1024
+ const resolved = result && typeof result === "object" && "then" in result ? await result : result;
1025
+ this.flushEvents();
1026
+ return resolved ?? null;
1027
+ }
1028
+ destroy() {
1029
+ this.server?.free();
1030
+ this.server = null;
1076
1031
  }
1077
1032
  };
1033
+ Comlink.expose(new AnalyzerWorker());
1078
1034
  //# sourceMappingURL=analyzer-worker.js.map