@codecanvas-ide/engine 0.0.0-stage → 0.2.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.
@@ -0,0 +1,123 @@
1
+ import ts from 'typescript';
2
+ import type { TextEdit } from './edits.js';
3
+ import type { CodeDiagnostic, FileModel, IncomingUsage, Usage } from './types.js';
4
+ import { type ConfigFile, type Workspace } from './workspace.js';
5
+ export type { ConfigFile, Workspace } from './workspace.js';
6
+ export interface ProjectOptions {
7
+ /**
8
+ * Standard library declaration files by file name (`lib.es2022.d.ts` → text), including every
9
+ * file they reference. Without them only syntax errors are reported, since every built-in
10
+ * (`Math`, `Array`, `console`) would otherwise be a type error.
11
+ */
12
+ libFiles?: Record<string, string>;
13
+ /** Lib entry files to load from `libFiles`. Defaults to ES2022 + DOM. */
14
+ lib?: string[];
15
+ compilerOptions?: ts.CompilerOptions;
16
+ }
17
+ export type RenameResult = {
18
+ ok: true;
19
+ edits: Record<string, TextEdit[]>;
20
+ } | {
21
+ ok: false;
22
+ error: string;
23
+ };
24
+ /** Usage links and errors of one file, found when typing pauses. */
25
+ export interface FileResolution {
26
+ usages: Usage[];
27
+ /** Usages in other files that point at blocks in this file. */
28
+ incoming: IncomingUsage[];
29
+ diagnostics: CodeDiagnostic[];
30
+ }
31
+ /**
32
+ * What the canvas needs from a project. `Project` does all of it in place; `RemoteProject` keeps only
33
+ * the per-keystroke parse and does the rest in a worker, so a pause in a big file can't stall the page.
34
+ */
35
+ export interface CanvasProject {
36
+ /** Per-keystroke path: parse this file into blocks, right away. */
37
+ setFile(path: string, text: string): FileModel;
38
+ getModel(path: string): FileModel | undefined;
39
+ getText(path: string): string | undefined;
40
+ /** Leave out the type errors when the host reports its own (`CodeCanvas.setDiagnostics`). */
41
+ resolve(path: string, options?: {
42
+ diagnostics?: boolean;
43
+ }): Promise<FileResolution>;
44
+ renameBlock(path: string, blockId: string, newName: string): RenameResult | Promise<RenameResult>;
45
+ }
46
+ /**
47
+ * Every file of a folder, parsed into blocks, with TypeScript programs for resolving usages, type
48
+ * errors and renames across files: one per config file (a monorepo has one per package), else one.
49
+ *
50
+ * Two speeds: `setFile` is the per-keystroke path (incremental parse of that file only); usages,
51
+ * diagnostics and renames go through the language service and are meant to run when typing pauses.
52
+ */
53
+ export declare class Project implements CanvasProject {
54
+ private projectOptions;
55
+ private files;
56
+ private projectVersion;
57
+ private libFiles;
58
+ private hasLib;
59
+ private libOptions;
60
+ private projectNamesCache;
61
+ private workspace;
62
+ private configPrograms;
63
+ /** Language services by config path, created when a file of that config is first resolved. */
64
+ private services;
65
+ /** Shared by the services, so a file several programs include is parsed once. */
66
+ private documentRegistry;
67
+ /** Changes when files are added or removed, or the settings change: where imports point may change then. */
68
+ private fileSetVersion;
69
+ constructor(files?: Record<string, string>, projectOptions?: ProjectOptions);
70
+ /** Load the standard library later, e.g. once it has been downloaded. Type errors show from then on. */
71
+ setLibFiles(libFiles: Record<string, string>): void;
72
+ /** Use the folder's tsconfig.json (or none, with null). */
73
+ setConfigFile(config: ConfigFile | null): void;
74
+ /** Use the folder's config files and workspace packages (or none, with null). */
75
+ setWorkspace(workspace: Workspace | null): void;
76
+ private reconfigure;
77
+ private configure;
78
+ /** Options for a program: the defaults, then the config's, then `ProjectOptions.compilerOptions`. */
79
+ private compilerOptions;
80
+ /** Which config each file uses, and the options of each. Worked out again when files are added or removed. */
81
+ private programs;
82
+ private configOf;
83
+ /** The options a file's imports resolve with: its own config's, whichever program includes it. */
84
+ private optionsOfFile;
85
+ private serviceOf;
86
+ private serviceOfConfig;
87
+ /** Add or update a file. Parses only this file, incrementally from its previous version. */
88
+ setFile(path: string, text: string): FileModel;
89
+ removeFile(path: string): void;
90
+ hasFile(path: string): boolean;
91
+ paths(): string[];
92
+ getModel(path: string): FileModel | undefined;
93
+ getText(path: string): string | undefined;
94
+ resolve(path: string, { diagnostics }?: {
95
+ diagnostics?: boolean;
96
+ }): Promise<FileResolution>;
97
+ /** Usage links from this file: within the file, and to blocks in other files (`toPath`). */
98
+ usages(path: string): Usage[];
99
+ /** Usages in other files that point at blocks in this file. */
100
+ usagesInto(path: string): IncomingUsage[];
101
+ /** Syntax errors, plus type errors when the standard library is loaded and the syntax is valid. */
102
+ diagnostics(path: string): CodeDiagnostic[];
103
+ /**
104
+ * Text edits that rename a block and every usage of it, in every file of the project.
105
+ * Usages in other files are renamed too, including imports (no `as` aliases are added).
106
+ */
107
+ renameBlock(path: string, blockId: string, newName: string): RenameResult;
108
+ /** The configs of a file and of the files that can use its blocks: its importers, also through re-exports. */
109
+ private configsUsing;
110
+ /** Files that import `path`, or import a file that re-exports from it (an `index.ts` of a folder or package). */
111
+ private importersThroughReExports;
112
+ private otherFileResolver;
113
+ private projectNames;
114
+ /** Files whose imports resolve to `path`. */
115
+ private importersOf;
116
+ private importsOf;
117
+ private moduleResolutionHost;
118
+ private fileExists;
119
+ private readFile;
120
+ private directoryExists;
121
+ /** The host of one config's program. Its files are those that use the config; their imports come in from the rest. */
122
+ private createHost;
123
+ }
@@ -0,0 +1,20 @@
1
+ import { type ConfigFile, type FileResolution, type RenameResult, type Workspace } from './project.js';
2
+ /** Methods a `RemoteProject` can call. Arguments and results are plain data, so any message channel carries them. */
3
+ declare const SERVER_METHODS: readonly ["setLibFiles", "setConfigFile", "setWorkspace", "setFiles", "setFile", "removeFile", "resolve", "renameBlock"];
4
+ export type ProjectServerMethod = (typeof SERVER_METHODS)[number];
5
+ /** A `Project` behind plain-data methods: the worker side of a `RemoteProject`. */
6
+ export declare class ProjectServer {
7
+ private project;
8
+ setLibFiles(libFiles: Record<string, string>): void;
9
+ setConfigFile(config: ConfigFile | null): void;
10
+ setWorkspace(workspace: Workspace | null): void;
11
+ setFiles(files: Record<string, string>): void;
12
+ setFile(path: string, text: string): void;
13
+ removeFile(path: string): void;
14
+ resolve(path: string, options?: {
15
+ diagnostics?: boolean;
16
+ }): Promise<FileResolution>;
17
+ renameBlock(path: string, blockId: string, newName: string): RenameResult;
18
+ }
19
+ export declare function callProjectServer(server: ProjectServer, method: string, args: unknown[]): unknown;
20
+ export {};
@@ -0,0 +1,14 @@
1
+ import { RemoteProject } from './remoteProject.js';
2
+ /** The part of a `Worker`, the worker's `self`, or a `MessagePort` that the project calls use. */
3
+ export interface MessagePortLike {
4
+ postMessage(message: unknown): void;
5
+ addEventListener(type: 'message', listener: (event: {
6
+ data: unknown;
7
+ }) => void): void;
8
+ /** MessagePorts only deliver messages after `start()`. */
9
+ start?(): void;
10
+ }
11
+ /** In a worker script: answer the `RemoteProject` on the other side of `port` (usually `self`). */
12
+ export declare function serveProject(port: MessagePortLike): void;
13
+ /** On the main thread: a project whose TypeScript work runs in the worker behind `port`. */
14
+ export declare function connectProjectWorker(port: MessagePortLike): RemoteProject;
@@ -0,0 +1,33 @@
1
+ import type { CanvasProject, ConfigFile, FileResolution, RenameResult, Workspace } from './project.js';
2
+ import type { ProjectServerMethod } from './projectServer.js';
3
+ import type { FileModel } from './types.js';
4
+ /** Sends one call to the `ProjectServer` (in a worker) and resolves with its result. Calls arrive in order. */
5
+ export type ProjectServerCall = (method: ProjectServerMethod, args: unknown[]) => Promise<unknown>;
6
+ /**
7
+ * The canvas's project when the TypeScript work runs in a worker. The main thread only parses files
8
+ * into blocks (the per-keystroke path, and lazily for files no canvas shows); usage links, errors and
9
+ * renames are computed by a `ProjectServer` in the worker.
10
+ */
11
+ export declare class RemoteProject implements CanvasProject {
12
+ private callServer;
13
+ private files;
14
+ constructor(callServer: ProjectServerCall);
15
+ /** Load many files at once. They're parsed in the worker; here only when a canvas needs them. */
16
+ setFiles(files: Record<string, string>): void;
17
+ setFile(path: string, text: string): FileModel;
18
+ /** Update a file no canvas shows (e.g. typed in a text editor): no parse on this thread. */
19
+ setFileText(path: string, text: string): void;
20
+ removeFile(path: string): void;
21
+ setLibFiles(libFiles: Record<string, string>): void;
22
+ /** The folder's tsconfig.json (or none, with null). */
23
+ setConfigFile(config: ConfigFile | null): void;
24
+ /** The folder's config files (one per package in a monorepo) and package.json files (or none, with null). */
25
+ setWorkspace(workspace: Workspace | null): void;
26
+ getModel(path: string): FileModel | undefined;
27
+ getText(path: string): string | undefined;
28
+ paths(): string[];
29
+ resolve(path: string, options?: {
30
+ diagnostics?: boolean;
31
+ }): Promise<FileResolution>;
32
+ renameBlock(path: string, blockId: string, newName: string): Promise<RenameResult>;
33
+ }
@@ -0,0 +1,3 @@
1
+ /** Canvas styles. Injected once. Hosts can override the CSS variables on `.cc-root`. */
2
+ export declare const CSS = "\n.cc-root {\n --cc-bg: #15171c;\n --cc-dot: rgba(255,255,255,.07);\n --cc-frame-bg: rgba(255,255,255,.025);\n --cc-frame-border: rgba(255,255,255,.10);\n --cc-text: #e8eaee;\n --cc-muted: #9aa1ad;\n --cc-faint: #c4cad4;\n --cc-panel: #1f2229;\n --cc-panel-border: rgba(255,255,255,.12);\n --cc-accent: #7aa2ff;\n --cc-danger: #ff6b6b;\n --cc-shadow: 0 10px 30px rgba(0,0,0,.45);\n\n --k-import: #6b7280; --k-import-head: #3b3f46; --k-import-bg: #25272c;\n --k-class: #34b27a; --k-class-head: #1d6b4a; --k-class-bg: rgba(52,178,122,.08);\n --k-function: #ec5fa5; --k-function-head: #8f2f62; --k-function-bg: rgba(236,95,165,.09);\n --k-variable: #4b8ef7; --k-variable-head: #23508f; --k-variable-bg: rgba(75,142,247,.09);\n --k-type: #d4a92a; --k-type-head: #6e5714; --k-type-bg: rgba(212,169,42,.08);\n --k-namespace: #9b8cf8; --k-namespace-head: #4d4291; --k-namespace-bg: rgba(155,140,248,.08);\n --k-statement: #7c8799; --k-statement-head: #39414f; --k-statement-bg: rgba(124,135,153,.08);\n\n position: relative; overflow: hidden; width: 100%; height: 100%;\n background-color: var(--cc-bg);\n background-image: radial-gradient(var(--cc-dot) 1.2px, transparent 1.3px);\n color: var(--cc-text);\n font: 13px/1.3 ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif;\n touch-action: none; user-select: none; -webkit-user-select: none; outline: none;\n -webkit-tap-highlight-color: transparent;\n}\n.cc-root[data-theme=\"light\"] {\n --cc-bg: #f6f7f9; --cc-dot: rgba(0,0,0,.09);\n --cc-frame-bg: rgba(255,255,255,.7); --cc-frame-border: rgba(0,0,0,.12);\n --cc-text: #1d2129; --cc-muted: #5d6573; --cc-faint: #39404c;\n --cc-panel: #ffffff; --cc-panel-border: rgba(0,0,0,.12); --cc-accent: #3366e6;\n --cc-shadow: 0 10px 30px rgba(20,30,50,.18);\n --k-import: #6b7280; --k-import-head: #4b5058; --k-import-bg: #eceef1;\n --k-class: #1f9963; --k-class-head: #1f9963; --k-class-bg: rgba(31,153,99,.07);\n --k-function: #d63e8c; --k-function-head: #d63e8c; --k-function-bg: rgba(214,62,140,.07);\n --k-variable: #2f6fe0; --k-variable-head: #2f6fe0; --k-variable-bg: rgba(47,111,224,.07);\n --k-type: #b58a0c; --k-type-head: #a87f08; --k-type-bg: rgba(181,138,12,.08);\n --k-namespace: #6f5ce6; --k-namespace-head: #6f5ce6; --k-namespace-bg: rgba(111,92,230,.07);\n --k-statement: #64708a; --k-statement-head: #64708a; --k-statement-bg: rgba(100,112,138,.07);\n}\n.cc-world { position: absolute; left: 0; top: 0; transform-origin: 0 0; }\n.cc-root.cc-moving .cc-world { will-change: transform; }\n.cc-frame { position: absolute; border: 1px solid var(--cc-frame-border); background: var(--cc-frame-bg); border-radius: 18px; }\n.cc-frame-title { position: absolute; left: 20px; top: 12px; font: 600 15px/20px var(--cc-font, \"JetBrains Mono\", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace); color: var(--cc-muted); white-space: nowrap; }\n.cc-empty { position: absolute; font-size: 15px; color: var(--cc-muted); white-space: nowrap; }\n\n.cc-block {\n position: absolute; left: 0; top: 0; box-sizing: border-box; border-radius: 10px;\n border: 1.5px solid var(--k); background: var(--kbg); overflow: hidden; contain: layout style;\n cursor: default;\n}\n.cc-block.k-import { --k: var(--k-import); --kh: var(--k-import-head); --kbg: var(--k-import-bg); }\n.cc-block.k-class { --k: var(--k-class); --kh: var(--k-class-head); --kbg: var(--k-class-bg); }\n.cc-block.k-function { --k: var(--k-function); --kh: var(--k-function-head); --kbg: var(--k-function-bg); }\n.cc-block.k-variable { --k: var(--k-variable); --kh: var(--k-variable-head); --kbg: var(--k-variable-bg); }\n.cc-block.k-type { --k: var(--k-type); --kh: var(--k-type-head); --kbg: var(--k-type-bg); }\n.cc-block.k-namespace { --k: var(--k-namespace); --kh: var(--k-namespace-head); --kbg: var(--k-namespace-bg); }\n.cc-block.k-statement { --k: var(--k-statement); --kh: var(--k-statement-head); --kbg: var(--k-statement-bg); }\n.cc-block.k-import, .cc-root:not([data-theme=\"light\"]) .cc-block.k-import { border-color: #555a63; }\n\n.cc-head {\n display: flex; align-items: center; gap: 7px; height: 32px; padding: 0 10px; box-sizing: border-box;\n background: var(--kh); color: #fff; white-space: nowrap; overflow: hidden;\n font: 13px/32px var(--cc-font, \"JetBrains Mono\", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);\n}\n.cc-kind { font-size: 10px; text-transform: uppercase; letter-spacing: .06em; opacity: .75; flex: none; }\n.cc-name { font-weight: 700; flex: none; max-width: 70%; overflow: hidden; text-overflow: ellipsis; }\n.cc-detail { opacity: .8; overflow: hidden; text-overflow: ellipsis; min-width: 0; flex: 1 1 auto; }\n.cc-tags { font-size: 10px; opacity: .7; flex: none; }\n.cc-exp { width: 6px; height: 6px; border-radius: 50%; background: #fff; opacity: .8; flex: none; }\n.cc-fold {\n flex: none; margin-left: auto; width: 22px; height: 22px; border: 0; border-radius: 6px; padding: 0;\n background: rgba(255,255,255,.14); color: #fff; font-size: 12px; line-height: 22px; cursor: pointer;\n}\n.cc-fold:hover { background: rgba(255,255,255,.28); }\n.cc-prev {\n margin: 0; padding: 5px 12px 0; white-space: pre; overflow: hidden; color: var(--cc-faint); opacity: .5;\n font: 11px/15px var(--cc-font, \"JetBrains Mono\", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);\n}\n.cc-more { opacity: .7; font-style: italic; }\n.cc-mini { position: relative; margin: 6px 12px 0; height: 34px; opacity: .45; }\n.cc-mini > i { position: absolute; border-radius: 2px; border: 1px solid var(--cc-faint); background: rgba(255,255,255,.06); }\n.cc-mini > i.k-class { border-color: var(--k-class); } .cc-mini > i.k-function { border-color: var(--k-function); }\n.cc-mini > i.k-variable { border-color: var(--k-variable); } .cc-mini > i.k-type { border-color: var(--k-type); }\n\n.cc-err {\n flex: none; min-width: 18px; height: 18px; padding: 0 5px; box-sizing: border-box; border-radius: 9px;\n background: var(--cc-danger); color: #fff; font: 700 11px/18px ui-sans-serif, system-ui, sans-serif; text-align: center;\n}\n.cc-block.cc-has-error { border-color: var(--cc-danger); }\n.cc-root.lod-2 .cc-err { min-width: 34px; height: 34px; border-radius: 17px; font-size: 22px; line-height: 34px; }\n.cc-rename {\n font: inherit; font-weight: 700; color: #fff; background: rgba(0,0,0,.35); border: 1px solid var(--cc-accent);\n border-radius: 4px; padding: 0 4px; height: 22px; outline: none; user-select: text; -webkit-user-select: text; max-width: 100%;\n}\n.cc-rename.invalid { border-color: var(--cc-danger); }\n.cc-name:has(.cc-rename) { max-width: none; overflow: visible; }\n\n/* merge conflicts */\n.cc-block.cc-conflict { border-color: #f0883e; box-shadow: 0 0 0 2px rgba(240, 136, 62, .35); }\n.cc-conflict-chip {\n flex: none; min-width: 18px; height: 18px; border-radius: 9px; background: #f0883e; color: #1b1300;\n font: 900 13px/18px ui-sans-serif, system-ui, sans-serif; text-align: center;\n}\n.cc-root.lod-2 .cc-conflict-chip { min-width: 34px; height: 34px; border-radius: 17px; font-size: 24px; line-height: 34px; }\n.cc-block.cc-conflict-incoming {\n --k: #f0883e; --kh: #8a4a17; --kbg: rgba(240, 136, 62, .08);\n border-style: dashed; pointer-events: none; opacity: .9;\n}\n.cc-diff-chip.incoming { background: #f0883e; }\n.cc-banner.cc-banner-conflicts { border-color: #f0883e; }\n\n/* changes against a base version (setDiffBase) */\n.cc-block.cc-diff-unchanged { filter: saturate(.3) brightness(.92); }\n.cc-diff-chip {\n flex: none; min-width: 18px; height: 18px; padding: 0 4px; box-sizing: border-box; border-radius: 5px;\n font: 800 13px/18px ui-sans-serif, system-ui, sans-serif; text-align: center; color: #fff;\n}\n.cc-diff-chip.added { background: #2ea043; }\n.cc-diff-chip.changed { background: #d29922; }\n.cc-diff-chip.removed { background: var(--cc-danger); }\n.cc-root.lod-2 .cc-diff-chip { min-width: 34px; height: 34px; border-radius: 8px; font-size: 24px; line-height: 34px; }\n.cc-block.cc-diff-removed {\n --k: var(--cc-danger); --kh: #9c2f2f; --kbg: rgba(255, 107, 107, .10);\n border-style: dashed; pointer-events: none; opacity: .9;\n}\n.cc-root[data-theme=\"light\"] .cc-block.cc-diff-removed { --kh: #d64545; --kbg: rgba(214, 69, 69, .08); }\n.cc-prev.cc-diff-prev { opacity: .95; }\n.cc-diff-prev > span { display: block; margin: 0 -12px; padding: 0 12px; }\n.cc-diff-prev .cc-line-same { opacity: .5; }\n.cc-diff-prev .cc-line-added { background: rgba(46, 160, 67, .30); }\n.cc-diff-prev .cc-line-removed { background: rgba(248, 81, 73, .26); text-decoration: line-through; text-decoration-color: rgba(248, 81, 73, .7); }\n.cc-diff-summary { padding: 0 8px; font-size: 12px; color: var(--cc-muted); white-space: nowrap; }\n\n.cc-block.cc-sel { box-shadow: 0 0 0 2px var(--cc-bg), 0 0 0 4px var(--k); }\n.cc-block.cc-editing { box-shadow: 0 0 0 2px var(--cc-bg), 0 0 0 4px var(--cc-accent); }\n.cc-block.cc-dragging { box-shadow: var(--cc-shadow); opacity: .92; z-index: 9999 !important; }\n.cc-root.has-hl .cc-block.cc-linked { box-shadow: 0 0 0 2px var(--k); }\n\n.cc-edges { position: absolute; left: 0; top: 0; overflow: visible; pointer-events: none; }\n.cc-edge { fill: none; stroke-width: 1.5; opacity: .22; }\n.cc-edge.u-type { stroke-dasharray: 4 4; }\n.cc-root.has-hl .cc-edge { opacity: .07; }\n.cc-root.has-hl .cc-edge.hl { opacity: .95; stroke-width: 2.5; }\n\n/* level of detail when zoomed out */\n.cc-root.lod-1 .cc-tags { visibility: hidden; }\n.cc-root.lod-2 .cc-prev, .cc-root.lod-2 .cc-detail, .cc-root.lod-2 .cc-mini { visibility: hidden; }\n.cc-root.lod-2 .cc-head { font-size: 26px; }\n.cc-root.lod-2 .cc-kind, .cc-root.lod-2 .cc-fold { display: none; }\n.cc-root.lod-2 .cc-frame-title { font-size: 40px; line-height: 44px; top: -8px; }\n\n/* overlays (screen space) */\n.cc-banner {\n position: absolute; left: 50%; top: 12px; transform: translateX(-50%); z-index: 20; display: flex; gap: 10px; align-items: center;\n padding: 8px 12px; border-radius: 10px; background: var(--cc-panel); border: 1px solid var(--cc-danger); box-shadow: var(--cc-shadow);\n font-size: 13px; max-width: calc(100% - 32px);\n}\n.cc-banner[hidden], .cc-bar[hidden], .cc-refs[hidden] { display: none; }\n.cc-toast {\n position: absolute; left: 50%; bottom: 76px; transform: translateX(-50%); z-index: 30; padding: 8px 14px; border-radius: 10px;\n background: var(--cc-panel); border: 1px solid var(--cc-panel-border); box-shadow: var(--cc-shadow); font-size: 13px;\n transition: opacity .2s; pointer-events: none; max-width: calc(100% - 32px);\n}\n.cc-bar {\n position: absolute; left: 50%; bottom: 16px; transform: translateX(-50%); z-index: 20; display: flex; align-items: center; gap: 4px;\n padding: 5px; border-radius: 12px; background: var(--cc-panel); border: 1px solid var(--cc-panel-border); box-shadow: var(--cc-shadow);\n width: max-content; max-width: calc(100% - 24px); box-sizing: border-box; flex-wrap: wrap; justify-content: center;\n}\n.cc-bar .cc-chip { padding: 0 10px 0 6px; font: 600 12px/30px var(--cc-font, \"JetBrains Mono\", ui-monospace, Menlo, monospace); white-space: nowrap; display: flex; align-items: center; gap: 6px; max-width: 220px; overflow: hidden; text-overflow: ellipsis; }\n.cc-bar .cc-chip i { width: 10px; height: 10px; border-radius: 3px; background: var(--k); flex: none; }\n.cc-btn {\n border: 0; background: transparent; color: var(--cc-text); font: 500 13px/30px inherit; font-family: inherit; padding: 0 10px; border-radius: 8px;\n cursor: pointer; white-space: nowrap; min-height: 32px;\n}\n.cc-btn:hover { background: rgba(127,127,127,.16); }\n.cc-btn.danger { color: var(--cc-danger); }\n.cc-btn.primary { background: var(--cc-accent); color: #fff; }\n.cc-sep { width: 1px; height: 20px; background: var(--cc-panel-border); margin: 0 2px; flex: none; }\n.cc-refs {\n position: absolute; left: 50%; bottom: 64px; transform: translateX(-50%); z-index: 21; width: min(360px, calc(100% - 24px)); max-height: 45%;\n overflow: auto; border-radius: 12px; background: var(--cc-panel); border: 1px solid var(--cc-panel-border); box-shadow: var(--cc-shadow); padding: 6px;\n}\n.cc-refs h4 { margin: 8px 8px 4px; font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--cc-muted); }\n.cc-refs button {\n display: flex; width: 100%; align-items: center; gap: 8px; border: 0; background: transparent; color: var(--cc-text); text-align: left;\n padding: 6px 8px; border-radius: 8px; cursor: pointer; font: 12px/18px var(--cc-font, \"JetBrains Mono\", ui-monospace, Menlo, monospace);\n}\n.cc-refs button:hover { background: rgba(127,127,127,.16); }\n.cc-refs button i { width: 10px; height: 10px; border-radius: 3px; background: var(--k); flex: none; }\n.cc-refs button span:last-child { margin-left: auto; color: var(--cc-muted); }\n.cc-refs button:disabled { cursor: default; opacity: .7; }\n.cc-refs button:disabled:hover { background: transparent; }\n.cc-refs .cc-file { color: var(--cc-muted); }\n.cc-refs .cc-error-row { --k: var(--cc-danger); align-items: flex-start; }\n.cc-refs .cc-error-row i { border-radius: 50%; margin-top: 4px; }\n.cc-refs .cc-error-row span:nth-child(2) { white-space: pre-wrap; font-family: ui-sans-serif, system-ui, sans-serif; }\n.cc-refs .cc-error-row span:last-child { white-space: nowrap; }\n.cc-refs .none { padding: 4px 8px 8px; color: var(--cc-muted); font-size: 12px; }\n\n.cc-editor {\n position: absolute; z-index: 25; display: flex; flex-direction: column; border-radius: 12px; overflow: hidden;\n background: var(--cc-panel); border: 1px solid var(--cc-panel-border); box-shadow: var(--cc-shadow);\n}\n.cc-editor-head { display: flex; align-items: center; gap: 8px; padding: 4px 6px 4px 12px; border-bottom: 1px solid var(--cc-panel-border); }\n.cc-editor-head .cc-chip { flex: 1; min-width: 0; font: 600 12px/30px var(--cc-font, \"JetBrains Mono\", ui-monospace, Menlo, monospace); display: flex; align-items: center; gap: 6px; overflow: hidden; white-space: nowrap; }\n.cc-editor-head .cc-chip i { width: 10px; height: 10px; border-radius: 3px; background: var(--k); flex: none; }\n.cc-editor-body { flex: 1; min-height: 0; overflow: auto; user-select: text; -webkit-user-select: text; touch-action: auto; }\n.cc-editor-hint { font-size: 11px; color: var(--cc-muted); white-space: nowrap; }\n@media (max-width: 640px) { .cc-editor-hint { display: none; } }\n";
3
+ export declare function injectStyles(doc?: Document): void;
@@ -0,0 +1,89 @@
1
+ /** What a block represents. Each kind has its own color on the canvas. */
2
+ export type BlockKind = 'import' | 'class' | 'function' | 'variable' | 'type' | 'namespace' | 'statement';
3
+ /** Where new child code goes when a block is used as a container. */
4
+ export type InsertStyle = 'class' | 'body' | null;
5
+ export interface Block {
6
+ /** Stable id, e.g. `Cart/addItem`. Survives edits, reorders and moves in the text. */
7
+ id: string;
8
+ /** Last segment of the id, unique among siblings. */
9
+ key: string;
10
+ kind: BlockKind;
11
+ name: string;
12
+ /** Parameters and return type, e.g. `(a: number): string`. */
13
+ detail: string;
14
+ /** Small labels such as `export`, `async`, `static`. */
15
+ tags: string[];
16
+ /** Text range in the file, including attached doc comments. */
17
+ start: number;
18
+ end: number;
19
+ /** Offset of the declared name in the file, or -1. */
20
+ nameStart: number;
21
+ /** Faint code preview (own code only, nested blocks removed). */
22
+ preview: string[];
23
+ /** Lines left out of the preview. */
24
+ previewMore: number;
25
+ children: Block[];
26
+ parentId: string | null;
27
+ depth: number;
28
+ /** How to insert new nested blocks, or null if this block can't hold them. */
29
+ insert: InsertStyle;
30
+ /** Offset where new nested code is inserted (before the closing brace). */
31
+ insertAt: number;
32
+ /** Indentation of the line the block starts on. */
33
+ indent: string;
34
+ }
35
+ export type UsageKind = 'call' | 'read' | 'write' | 'type';
36
+ /** A line on the canvas: code in `from` uses the declaration in `to`. */
37
+ export interface Usage {
38
+ from: string;
39
+ to: string;
40
+ kind: UsageKind;
41
+ count: number;
42
+ /** Set when `to` is a block in another file (project path). Not drawn as a line on the canvas. */
43
+ toPath?: string;
44
+ /** For usages of imported names: the import block they go through. */
45
+ via?: string;
46
+ }
47
+ /** An error found by the host (e.g. VS Code's Problems), given to `CodeCanvas.setDiagnostics`. */
48
+ export type HostDiagnostic = Omit<CodeDiagnostic, 'blockId'>;
49
+ /** A usage from another file into this one. */
50
+ export interface IncomingUsage extends Usage {
51
+ fromPath: string;
52
+ }
53
+ export interface CodeDiagnostic {
54
+ /** Offset in the file. */
55
+ start: number;
56
+ length: number;
57
+ /** 1-based. */
58
+ line: number;
59
+ message: string;
60
+ source: 'syntax' | 'type';
61
+ severity: 'error' | 'warning';
62
+ /** Innermost block containing the error, or null when it's outside every block. */
63
+ blockId: string | null;
64
+ }
65
+ export interface FileModel {
66
+ path: string;
67
+ text: string;
68
+ roots: Block[];
69
+ blocks: Map<string, Block>;
70
+ usages: Usage[];
71
+ /** Number of syntax errors. When > 0 the structure may be unreliable. */
72
+ syntaxErrors: number;
73
+ firstErrorLine: number;
74
+ /** Syntax errors from parsing. Type errors come from the project (`Project.diagnostics`). */
75
+ diagnostics: CodeDiagnostic[];
76
+ /** Indentation unit detected in the file. */
77
+ indentUnit: string;
78
+ }
79
+ export interface Point {
80
+ x: number;
81
+ y: number;
82
+ }
83
+ /** Saved layout of one file. Stored in the repo under `.codecanvas/`. */
84
+ export interface FileLayout {
85
+ version: 1;
86
+ /** Block positions relative to the parent (file or container block). */
87
+ blocks: Record<string, Point>;
88
+ collapsed?: string[];
89
+ }
@@ -0,0 +1,45 @@
1
+ import ts from 'typescript';
2
+ /**
3
+ * A TypeScript config file of the folder (`tsconfig.json`, `tsconfig.app.json`, `jsconfig.json`).
4
+ * Path aliases, `baseUrl` and module resolution decide which file an import points at.
5
+ */
6
+ export interface ConfigFile {
7
+ /** Project path, e.g. `tsconfig.json` or `packages/web/tsconfig.app.json`. */
8
+ path: string;
9
+ text: string;
10
+ /** Configs it `extends`, by project path. Only relative `extends` are followed. */
11
+ extendedFiles?: Record<string, string>;
12
+ }
13
+ /**
14
+ * The folder's TypeScript settings. In a monorepo every package can have its own config: a file
15
+ * uses the nearest config above it that includes it (`files`, `include`, `exclude`), as in VS Code.
16
+ */
17
+ export interface Workspace {
18
+ configFiles: ConfigFile[];
19
+ /**
20
+ * `package.json` files by project path. Packages of the workspace import each other by name
21
+ * (`@acme/utils`); without `node_modules`, the name is resolved to the package's source.
22
+ */
23
+ packageFiles?: Record<string, string>;
24
+ }
25
+ /** One config file, read: its options, and which of the project's files it includes. */
26
+ export interface ParsedConfig {
27
+ path: string;
28
+ /** Folder of the config, `''` at the root, else ending in `/`. */
29
+ folder: string;
30
+ options: ts.CompilerOptions;
31
+ includes(projectPath: string): boolean;
32
+ }
33
+ /** Read a config file. `projectPaths` are the project's files, for its `include` and `exclude` patterns. */
34
+ export declare function parseConfig(config: ConfigFile, projectPaths: string[]): ParsedConfig | null;
35
+ /**
36
+ * The config a file uses: the nearest one above it that includes it; failing that, the nearest one
37
+ * above it (a file left out still gets its package's aliases). Null: no config is above it.
38
+ */
39
+ export declare function configForFile(projectPath: string, configs: ParsedConfig[]): ParsedConfig | null;
40
+ /**
41
+ * Path aliases that point each workspace package's name at its source: the file its `package.json`
42
+ * names (`types`, `exports`, `module`, `main`), its TypeScript source when that is a build output
43
+ * (`dist/index.js` → `src/index.ts`), or `src/index.ts`.
44
+ */
45
+ export declare function workspacePackagePaths(packageFiles: Record<string, string>, hasFile: (projectPath: string) => boolean): Record<string, string[]>;
package/package.json CHANGED
@@ -1,6 +1,55 @@
1
1
  {
2
2
  "name": "@codecanvas-ide/engine",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "The canvas of CodeCanvas: TypeScript and JavaScript files as an editable diagram of blocks, with usage links, references across files, rename, errors, diffs and merge conflicts.",
5
+ "keywords": [
6
+ "typescript",
7
+ "javascript",
8
+ "code-visualization",
9
+ "diagram",
10
+ "canvas",
11
+ "editor",
12
+ "ide"
13
+ ],
14
+ "license": "MIT",
15
+ "author": "Daniel Feliz",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/d-feliz/codecanvas.git",
19
+ "directory": "packages/engine"
20
+ },
21
+ "type": "module",
22
+ "main": "./dist/index.js",
23
+ "types": "./dist/types/index.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/types/index.d.ts",
27
+ "default": "./dist/index.js"
28
+ },
29
+ "./dist/codecanvas-engine.js": {
30
+ "types": "./dist/codecanvas-engine.d.ts",
31
+ "default": "./dist/codecanvas-engine.js"
32
+ },
33
+ "./package.json": "./package.json"
34
+ },
35
+ "files": [
36
+ "dist/index.js",
37
+ "dist/index.js.map",
38
+ "dist/codecanvas-engine.js",
39
+ "dist/codecanvas-engine.d.ts",
40
+ "dist/types",
41
+ "LICENSE",
42
+ "README.md"
43
+ ],
44
+ "sideEffects": false,
45
+ "dependencies": {
46
+ "typescript": "5.9.3"
47
+ },
48
+ "devDependencies": {
49
+ "esbuild": "0.28.2"
50
+ },
51
+ "scripts": {
52
+ "build": "node scripts/build.mjs",
53
+ "prepack": "node scripts/build.mjs"
54
+ }
55
+ }