@telorun/ide-support 0.5.0 → 0.6.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/dist/completions/import-source.d.ts +7 -3
- package/dist/completions/import-source.d.ts.map +1 -1
- package/dist/completions/import-source.js +54 -36
- package/dist/types.d.ts +21 -12
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/completions/import-source.ts +56 -38
- package/src/types.ts +21 -12
|
@@ -7,9 +7,13 @@ import type { CompletionResult, IdeEnvironmentAdapter } from "../types.js";
|
|
|
7
7
|
* Branches by prefix shape:
|
|
8
8
|
* "" → relative dirs under the manifest dir, plus `./` / `../` seeds.
|
|
9
9
|
* "./..", "../", "/..." → subdirs of the typed path (any subdir; existing manifest gets a hint).
|
|
10
|
-
* "<word>"
|
|
11
|
-
* "<
|
|
12
|
-
* "http(s)://", "file://" → no suggestions (opaque URLs).
|
|
10
|
+
* "<word>", "oci://…" → hub ref autocomplete (fuzzy substring over registered refs).
|
|
11
|
+
* "<ref>@<partial>" → version list for that ref.
|
|
12
|
+
* "http(s)://", "file://" → no suggestions (opaque URLs the author types verbatim).
|
|
13
|
+
*
|
|
14
|
+
* `oci://` is deliberately NOT opaque: without an `@` it routes to the ref
|
|
15
|
+
* search below, whose query is the whole typed prefix — so the hub fuzzy-matches
|
|
16
|
+
* `oci://ghcr.io/aws/telo-s3` as readily as a bare `s3`.
|
|
13
17
|
*
|
|
14
18
|
* `valueStartColumn` is forwarded onto every result so the host can replace
|
|
15
19
|
* the whole typed value, not just the trailing word (Monaco / VSCode word
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-source.d.ts","sourceRoot":"","sources":["../../src/completions/import-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"import-source.d.ts","sourceRoot":"","sources":["../../src/completions/import-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAY3E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,MAAM,EACxB,OAAO,EAAE,qBAAqB,GAAG,SAAS,GACzC,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAyB7B"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
/** Maximum
|
|
2
|
-
*
|
|
3
|
-
|
|
1
|
+
/** Maximum ref hits to surface in a single completion request. Keeps the
|
|
2
|
+
* popover scannable when a broad `q=` query matches many registered refs
|
|
3
|
+
* (the hub already caps `/refs` server-side; this is a client backstop). */
|
|
4
|
+
const REF_LIMIT = 50;
|
|
4
5
|
/** Caps the number of directory entries we probe with `hasManifest` per
|
|
5
6
|
* request. Each probe is a host-side filesystem stat; the popover would not
|
|
6
7
|
* show more than ~50 entries anyway, so probing further only adds latency. */
|
|
@@ -13,9 +14,13 @@ const PATH_PROBE_LIMIT = 50;
|
|
|
13
14
|
* Branches by prefix shape:
|
|
14
15
|
* "" → relative dirs under the manifest dir, plus `./` / `../` seeds.
|
|
15
16
|
* "./..", "../", "/..." → subdirs of the typed path (any subdir; existing manifest gets a hint).
|
|
16
|
-
* "<word>"
|
|
17
|
-
* "<
|
|
18
|
-
* "http(s)://", "file://" → no suggestions (opaque URLs).
|
|
17
|
+
* "<word>", "oci://…" → hub ref autocomplete (fuzzy substring over registered refs).
|
|
18
|
+
* "<ref>@<partial>" → version list for that ref.
|
|
19
|
+
* "http(s)://", "file://" → no suggestions (opaque URLs the author types verbatim).
|
|
20
|
+
*
|
|
21
|
+
* `oci://` is deliberately NOT opaque: without an `@` it routes to the ref
|
|
22
|
+
* search below, whose query is the whole typed prefix — so the hub fuzzy-matches
|
|
23
|
+
* `oci://ghcr.io/aws/telo-s3` as readily as a bare `s3`.
|
|
19
24
|
*
|
|
20
25
|
* `valueStartColumn` is forwarded onto every result so the host can replace
|
|
21
26
|
* the whole typed value, not just the trailing word (Monaco / VSCode word
|
|
@@ -33,11 +38,13 @@ export async function importSourceCompletions(prefix, valueStartColumn, adapter)
|
|
|
33
38
|
if (isRelativeShape) {
|
|
34
39
|
return relativePathCompletions(prefix, valueStartColumn, adapter);
|
|
35
40
|
}
|
|
36
|
-
|
|
41
|
+
// The version (or `@sha256:` digest) is the trailing `@`-segment, so split on
|
|
42
|
+
// the LAST `@` — a digest-pinned ref keeps everything before it as the ref.
|
|
43
|
+
const atIdx = prefix.lastIndexOf("@");
|
|
37
44
|
if (atIdx > 0) {
|
|
38
45
|
return versionCompletions(prefix, atIdx, valueStartColumn, adapter);
|
|
39
46
|
}
|
|
40
|
-
return
|
|
47
|
+
return refSearchCompletions(prefix, valueStartColumn, adapter);
|
|
41
48
|
}
|
|
42
49
|
async function relativePathCompletions(prefix, valueStartColumn, adapter) {
|
|
43
50
|
// Empty prefix → seed `./` and `../` so the user gets traction; otherwise
|
|
@@ -95,52 +102,63 @@ async function relativePathCompletions(prefix, valueStartColumn, adapter) {
|
|
|
95
102
|
};
|
|
96
103
|
}));
|
|
97
104
|
}
|
|
98
|
-
async function
|
|
99
|
-
// The
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const id = `${m.namespace}/${m.name}@${m.version}`;
|
|
105
|
+
async function refSearchCompletions(prefix, valueStartColumn, adapter) {
|
|
106
|
+
// The whole typed prefix is the fuzzy query — the hub matches it as a
|
|
107
|
+
// substring over each registered ref, so no client-side splitting is needed
|
|
108
|
+
// (and `oci://ghcr.io/aws/telo-s3` matches without mangling the `//`).
|
|
109
|
+
const hits = await adapter.searchRefs(prefix);
|
|
110
|
+
return hits.slice(0, REF_LIMIT).map((m) => {
|
|
111
|
+
// Seed the pinned `ref@latestVersion` so the completion is directly usable;
|
|
112
|
+
// the author can still narrow the version afterwards (the `@` re-triggers
|
|
113
|
+
// version completion).
|
|
114
|
+
const id = m.latestVersion ? `${m.ref}@${m.latestVersion}` : m.ref;
|
|
115
|
+
const name = refDisplayName(m.ref);
|
|
116
|
+
// Lead the label with the module name so the interesting part isn't cut off
|
|
117
|
+
// behind the transport/host boilerplate (`oci://ghcr.io/telorun/…`). The
|
|
118
|
+
// full ref moves to `detail`, and `insertText`/`filterText` stay the ref so
|
|
119
|
+
// acceptance still inserts it and a fully-typed ref still filters.
|
|
114
120
|
return {
|
|
115
|
-
label:
|
|
121
|
+
label: m.latestVersion ? `${name}@${m.latestVersion}` : name,
|
|
116
122
|
kind: "module",
|
|
117
|
-
detail: m.description ??
|
|
123
|
+
detail: m.description ?? m.ref,
|
|
124
|
+
documentation: m.description ? m.ref : undefined,
|
|
118
125
|
insertText: id,
|
|
119
126
|
filterText: id,
|
|
120
127
|
replaceFromColumn: valueStartColumn,
|
|
121
128
|
};
|
|
122
129
|
});
|
|
123
130
|
}
|
|
131
|
+
/** The `org/name` tail of a location ref: its last two path segments, with the
|
|
132
|
+
* transport scheme (`oci://`, `https://`, …) and registry host dropped.
|
|
133
|
+
* `oci://ghcr.io/telorun/telo-console` → `telorun/telo-console`; `std/console`
|
|
134
|
+
* → `std/console`. Falls back to fewer segments (or the whole ref) when there
|
|
135
|
+
* aren't two. */
|
|
136
|
+
function refDisplayName(ref) {
|
|
137
|
+
const withoutScheme = ref.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "");
|
|
138
|
+
const segments = withoutScheme.split("/").filter(Boolean);
|
|
139
|
+
return segments.slice(-2).join("/") || ref;
|
|
140
|
+
}
|
|
124
141
|
async function versionCompletions(prefix, atIdx, valueStartColumn, adapter) {
|
|
125
|
-
const
|
|
142
|
+
const ref = prefix.slice(0, atIdx);
|
|
126
143
|
const partialVersion = prefix.slice(atIdx + 1);
|
|
127
|
-
|
|
128
|
-
if (slashIdx <= 0 || slashIdx === beforeAt.length - 1)
|
|
144
|
+
if (ref === "")
|
|
129
145
|
return [];
|
|
130
|
-
const
|
|
131
|
-
const name = beforeAt.slice(slashIdx + 1);
|
|
132
|
-
const versions = await adapter.listRegistryVersions(namespace, name);
|
|
146
|
+
const versions = await adapter.listVersionsForRef(ref);
|
|
133
147
|
const matches = versions.filter((v) => v.startsWith(partialVersion));
|
|
134
148
|
return matches.map((version, idx) => {
|
|
135
|
-
const id = `${
|
|
149
|
+
const id = `${ref}@${version}`;
|
|
150
|
+
// The ref is already typed and visible on the line, so the label is just the
|
|
151
|
+
// version — no point repeating the full ref on every row. `insertText` /
|
|
152
|
+
// `filterText` stay the full id so acceptance replaces the whole value and
|
|
153
|
+
// the already-typed ref prefix keeps the item in the filtered set.
|
|
136
154
|
return {
|
|
137
|
-
label:
|
|
155
|
+
label: version,
|
|
138
156
|
kind: "value",
|
|
139
|
-
detail: idx === 0 ? "latest" :
|
|
157
|
+
detail: idx === 0 ? "latest" : undefined,
|
|
140
158
|
insertText: id,
|
|
141
159
|
filterText: id,
|
|
142
160
|
replaceFromColumn: valueStartColumn,
|
|
143
|
-
// Preserve
|
|
161
|
+
// Preserve the hub's ordering (newest first) so the latest version is
|
|
144
162
|
// suggested at the top regardless of lexical comparison.
|
|
145
163
|
sortText: String(idx).padStart(4, "0"),
|
|
146
164
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -18,16 +18,22 @@ export interface CompletionResult {
|
|
|
18
18
|
* word boundary would not include in the replaced range. */
|
|
19
19
|
replaceFromColumn?: number;
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
/** A candidate module ref surfaced by the hub's `/refs` lexical autocomplete.
|
|
22
|
+
* Identity is the location ref, never `namespace/name` — an OCI module has no
|
|
23
|
+
* addressable `namespace/name`. `latestVersion` seeds a pinned `ref@version`
|
|
24
|
+
* insert so a picked completion is directly usable. */
|
|
25
|
+
export interface HubRef {
|
|
26
|
+
ref: string;
|
|
27
|
+
latestVersion: string;
|
|
25
28
|
description?: string;
|
|
26
29
|
}
|
|
27
30
|
/** Host-supplied bridge that lets ide-support reach the filesystem and the
|
|
28
|
-
*
|
|
31
|
+
* federated telo hub without depending on Node, Tauri, or vscode APIs. Each
|
|
29
32
|
* host (VSCode extension, Telo editor) builds an adapter scoped to the
|
|
30
|
-
* currently-edited manifest before calling `buildCompletions`.
|
|
33
|
+
* currently-edited manifest before calling `buildCompletions`. Hub lookups are
|
|
34
|
+
* ref-keyed: the hub aggregates modules across every transport (OCI, HTTP,
|
|
35
|
+
* direct URL), so completion speaks its `/refs` + `/module/versions` verbs
|
|
36
|
+
* rather than any single registry. */
|
|
31
37
|
export interface IdeEnvironmentAdapter {
|
|
32
38
|
/** Subdirectory names within `relPath` (resolved against the manifest's
|
|
33
39
|
* directory). Returns [] if the path doesn't exist or isn't a directory.
|
|
@@ -36,12 +42,15 @@ export interface IdeEnvironmentAdapter {
|
|
|
36
42
|
/** True iff `<relPath>/telo.yaml` exists relative to the manifest dir.
|
|
37
43
|
* Used to mark directories that are valid import targets. */
|
|
38
44
|
hasManifest(relPath: string): Promise<boolean>;
|
|
39
|
-
/**
|
|
40
|
-
*
|
|
41
|
-
* (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
/** Fuzzy lexical ref autocomplete against the configured telo hub
|
|
46
|
+
* (`GET /refs?q=`). The query is matched as a substring over every
|
|
47
|
+
* registered ref, so a bare token (`youtrack`) hits the same ref as its full
|
|
48
|
+
* `oci://…` form. Best-effort — hosts swallow network errors and return []. */
|
|
49
|
+
searchRefs(query: string): Promise<HubRef[]>;
|
|
50
|
+
/** All tracked versions for a location ref, newest first
|
|
51
|
+
* (`GET /module/versions?ref=`). The browser cannot call OCI `tags/list`;
|
|
52
|
+
* the hub holds them from ingest. */
|
|
53
|
+
listVersionsForRef(ref: string): Promise<string[]>;
|
|
45
54
|
}
|
|
46
55
|
export interface NormalizedDiagnostic {
|
|
47
56
|
range: Range;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAGzE,YAAY,EACV,QAAQ,EACR,KAAK,EACL,kBAAkB,EAClB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAEpG,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,YAAY,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEjG,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;iEAG6D;IAC7D,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAGzE,YAAY,EACV,QAAQ,EACR,KAAK,EACL,kBAAkB,EAClB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAEpG,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,YAAY,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEjG,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;iEAG6D;IAC7D,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;wDAGwD;AACxD,MAAM,WAAW,MAAM;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;uCAMuC;AACvC,MAAM,WAAW,qBAAqB;IACpC;;2DAEuD;IACvD,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACpD;kEAC8D;IAC9D,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C;;;oFAGgF;IAChF,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7C;;0CAEsC;IACtC,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CACpD;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,cAAc,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnE;;;0EAGsE;IACtE,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { CompletionResult, IdeEnvironmentAdapter } from "../types.js";
|
|
2
2
|
|
|
3
|
-
/** Maximum
|
|
4
|
-
*
|
|
5
|
-
|
|
3
|
+
/** Maximum ref hits to surface in a single completion request. Keeps the
|
|
4
|
+
* popover scannable when a broad `q=` query matches many registered refs
|
|
5
|
+
* (the hub already caps `/refs` server-side; this is a client backstop). */
|
|
6
|
+
const REF_LIMIT = 50;
|
|
6
7
|
|
|
7
8
|
/** Caps the number of directory entries we probe with `hasManifest` per
|
|
8
9
|
* request. Each probe is a host-side filesystem stat; the popover would not
|
|
@@ -17,9 +18,13 @@ const PATH_PROBE_LIMIT = 50;
|
|
|
17
18
|
* Branches by prefix shape:
|
|
18
19
|
* "" → relative dirs under the manifest dir, plus `./` / `../` seeds.
|
|
19
20
|
* "./..", "../", "/..." → subdirs of the typed path (any subdir; existing manifest gets a hint).
|
|
20
|
-
* "<word>"
|
|
21
|
-
* "<
|
|
22
|
-
* "http(s)://", "file://" → no suggestions (opaque URLs).
|
|
21
|
+
* "<word>", "oci://…" → hub ref autocomplete (fuzzy substring over registered refs).
|
|
22
|
+
* "<ref>@<partial>" → version list for that ref.
|
|
23
|
+
* "http(s)://", "file://" → no suggestions (opaque URLs the author types verbatim).
|
|
24
|
+
*
|
|
25
|
+
* `oci://` is deliberately NOT opaque: without an `@` it routes to the ref
|
|
26
|
+
* search below, whose query is the whole typed prefix — so the hub fuzzy-matches
|
|
27
|
+
* `oci://ghcr.io/aws/telo-s3` as readily as a bare `s3`.
|
|
23
28
|
*
|
|
24
29
|
* `valueStartColumn` is forwarded onto every result so the host can replace
|
|
25
30
|
* the whole typed value, not just the trailing word (Monaco / VSCode word
|
|
@@ -46,12 +51,14 @@ export async function importSourceCompletions(
|
|
|
46
51
|
return relativePathCompletions(prefix, valueStartColumn, adapter);
|
|
47
52
|
}
|
|
48
53
|
|
|
49
|
-
|
|
54
|
+
// The version (or `@sha256:` digest) is the trailing `@`-segment, so split on
|
|
55
|
+
// the LAST `@` — a digest-pinned ref keeps everything before it as the ref.
|
|
56
|
+
const atIdx = prefix.lastIndexOf("@");
|
|
50
57
|
if (atIdx > 0) {
|
|
51
58
|
return versionCompletions(prefix, atIdx, valueStartColumn, adapter);
|
|
52
59
|
}
|
|
53
60
|
|
|
54
|
-
return
|
|
61
|
+
return refSearchCompletions(prefix, valueStartColumn, adapter);
|
|
55
62
|
}
|
|
56
63
|
|
|
57
64
|
async function relativePathCompletions(
|
|
@@ -120,32 +127,31 @@ async function relativePathCompletions(
|
|
|
120
127
|
);
|
|
121
128
|
}
|
|
122
129
|
|
|
123
|
-
async function
|
|
130
|
+
async function refSearchCompletions(
|
|
124
131
|
prefix: string,
|
|
125
132
|
valueStartColumn: number,
|
|
126
133
|
adapter: IdeEnvironmentAdapter,
|
|
127
134
|
): Promise<CompletionResult[]> {
|
|
128
|
-
// The
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
return filtered.slice(0, REGISTRY_LIMIT).map((m) => {
|
|
144
|
-
const id = `${m.namespace}/${m.name}@${m.version}`;
|
|
135
|
+
// The whole typed prefix is the fuzzy query — the hub matches it as a
|
|
136
|
+
// substring over each registered ref, so no client-side splitting is needed
|
|
137
|
+
// (and `oci://ghcr.io/aws/telo-s3` matches without mangling the `//`).
|
|
138
|
+
const hits = await adapter.searchRefs(prefix);
|
|
139
|
+
|
|
140
|
+
return hits.slice(0, REF_LIMIT).map((m) => {
|
|
141
|
+
// Seed the pinned `ref@latestVersion` so the completion is directly usable;
|
|
142
|
+
// the author can still narrow the version afterwards (the `@` re-triggers
|
|
143
|
+
// version completion).
|
|
144
|
+
const id = m.latestVersion ? `${m.ref}@${m.latestVersion}` : m.ref;
|
|
145
|
+
const name = refDisplayName(m.ref);
|
|
146
|
+
// Lead the label with the module name so the interesting part isn't cut off
|
|
147
|
+
// behind the transport/host boilerplate (`oci://ghcr.io/telorun/…`). The
|
|
148
|
+
// full ref moves to `detail`, and `insertText`/`filterText` stay the ref so
|
|
149
|
+
// acceptance still inserts it and a fully-typed ref still filters.
|
|
145
150
|
return {
|
|
146
|
-
label:
|
|
151
|
+
label: m.latestVersion ? `${name}@${m.latestVersion}` : name,
|
|
147
152
|
kind: "module",
|
|
148
|
-
detail: m.description ??
|
|
153
|
+
detail: m.description ?? m.ref,
|
|
154
|
+
documentation: m.description ? m.ref : undefined,
|
|
149
155
|
insertText: id,
|
|
150
156
|
filterText: id,
|
|
151
157
|
replaceFromColumn: valueStartColumn,
|
|
@@ -153,32 +159,44 @@ async function registrySearchCompletions(
|
|
|
153
159
|
});
|
|
154
160
|
}
|
|
155
161
|
|
|
162
|
+
/** The `org/name` tail of a location ref: its last two path segments, with the
|
|
163
|
+
* transport scheme (`oci://`, `https://`, …) and registry host dropped.
|
|
164
|
+
* `oci://ghcr.io/telorun/telo-console` → `telorun/telo-console`; `std/console`
|
|
165
|
+
* → `std/console`. Falls back to fewer segments (or the whole ref) when there
|
|
166
|
+
* aren't two. */
|
|
167
|
+
function refDisplayName(ref: string): string {
|
|
168
|
+
const withoutScheme = ref.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "");
|
|
169
|
+
const segments = withoutScheme.split("/").filter(Boolean);
|
|
170
|
+
return segments.slice(-2).join("/") || ref;
|
|
171
|
+
}
|
|
172
|
+
|
|
156
173
|
async function versionCompletions(
|
|
157
174
|
prefix: string,
|
|
158
175
|
atIdx: number,
|
|
159
176
|
valueStartColumn: number,
|
|
160
177
|
adapter: IdeEnvironmentAdapter,
|
|
161
178
|
): Promise<CompletionResult[]> {
|
|
162
|
-
const
|
|
179
|
+
const ref = prefix.slice(0, atIdx);
|
|
163
180
|
const partialVersion = prefix.slice(atIdx + 1);
|
|
164
|
-
|
|
165
|
-
if (slashIdx <= 0 || slashIdx === beforeAt.length - 1) return [];
|
|
181
|
+
if (ref === "") return [];
|
|
166
182
|
|
|
167
|
-
const
|
|
168
|
-
const name = beforeAt.slice(slashIdx + 1);
|
|
169
|
-
const versions = await adapter.listRegistryVersions(namespace, name);
|
|
183
|
+
const versions = await adapter.listVersionsForRef(ref);
|
|
170
184
|
|
|
171
185
|
const matches = versions.filter((v) => v.startsWith(partialVersion));
|
|
172
186
|
return matches.map((version, idx) => {
|
|
173
|
-
const id = `${
|
|
187
|
+
const id = `${ref}@${version}`;
|
|
188
|
+
// The ref is already typed and visible on the line, so the label is just the
|
|
189
|
+
// version — no point repeating the full ref on every row. `insertText` /
|
|
190
|
+
// `filterText` stay the full id so acceptance replaces the whole value and
|
|
191
|
+
// the already-typed ref prefix keeps the item in the filtered set.
|
|
174
192
|
return {
|
|
175
|
-
label:
|
|
193
|
+
label: version,
|
|
176
194
|
kind: "value",
|
|
177
|
-
detail: idx === 0 ? "latest" :
|
|
195
|
+
detail: idx === 0 ? "latest" : undefined,
|
|
178
196
|
insertText: id,
|
|
179
197
|
filterText: id,
|
|
180
198
|
replaceFromColumn: valueStartColumn,
|
|
181
|
-
// Preserve
|
|
199
|
+
// Preserve the hub's ordering (newest first) so the latest version is
|
|
182
200
|
// suggested at the top regardless of lexical comparison.
|
|
183
201
|
sortText: String(idx).padStart(4, "0"),
|
|
184
202
|
};
|
package/src/types.ts
CHANGED
|
@@ -31,17 +31,23 @@ export interface CompletionResult {
|
|
|
31
31
|
replaceFromColumn?: number;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
namespace
|
|
36
|
-
name
|
|
37
|
-
|
|
34
|
+
/** A candidate module ref surfaced by the hub's `/refs` lexical autocomplete.
|
|
35
|
+
* Identity is the location ref, never `namespace/name` — an OCI module has no
|
|
36
|
+
* addressable `namespace/name`. `latestVersion` seeds a pinned `ref@version`
|
|
37
|
+
* insert so a picked completion is directly usable. */
|
|
38
|
+
export interface HubRef {
|
|
39
|
+
ref: string;
|
|
40
|
+
latestVersion: string;
|
|
38
41
|
description?: string;
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
/** Host-supplied bridge that lets ide-support reach the filesystem and the
|
|
42
|
-
*
|
|
45
|
+
* federated telo hub without depending on Node, Tauri, or vscode APIs. Each
|
|
43
46
|
* host (VSCode extension, Telo editor) builds an adapter scoped to the
|
|
44
|
-
* currently-edited manifest before calling `buildCompletions`.
|
|
47
|
+
* currently-edited manifest before calling `buildCompletions`. Hub lookups are
|
|
48
|
+
* ref-keyed: the hub aggregates modules across every transport (OCI, HTTP,
|
|
49
|
+
* direct URL), so completion speaks its `/refs` + `/module/versions` verbs
|
|
50
|
+
* rather than any single registry. */
|
|
45
51
|
export interface IdeEnvironmentAdapter {
|
|
46
52
|
/** Subdirectory names within `relPath` (resolved against the manifest's
|
|
47
53
|
* directory). Returns [] if the path doesn't exist or isn't a directory.
|
|
@@ -50,12 +56,15 @@ export interface IdeEnvironmentAdapter {
|
|
|
50
56
|
/** True iff `<relPath>/telo.yaml` exists relative to the manifest dir.
|
|
51
57
|
* Used to mark directories that are valid import targets. */
|
|
52
58
|
hasManifest(relPath: string): Promise<boolean>;
|
|
53
|
-
/**
|
|
54
|
-
*
|
|
55
|
-
* (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
/** Fuzzy lexical ref autocomplete against the configured telo hub
|
|
60
|
+
* (`GET /refs?q=`). The query is matched as a substring over every
|
|
61
|
+
* registered ref, so a bare token (`youtrack`) hits the same ref as its full
|
|
62
|
+
* `oci://…` form. Best-effort — hosts swallow network errors and return []. */
|
|
63
|
+
searchRefs(query: string): Promise<HubRef[]>;
|
|
64
|
+
/** All tracked versions for a location ref, newest first
|
|
65
|
+
* (`GET /module/versions?ref=`). The browser cannot call OCI `tags/list`;
|
|
66
|
+
* the hub holds them from ingest. */
|
|
67
|
+
listVersionsForRef(ref: string): Promise<string[]>;
|
|
59
68
|
}
|
|
60
69
|
|
|
61
70
|
export interface NormalizedDiagnostic {
|