@px-lsp/protocol 0.1.0 → 0.2.1

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/src/kinds.ts ADDED
@@ -0,0 +1,205 @@
1
+ /**
2
+ * One kind map, four surfaces.
3
+ *
4
+ * Every place the product names a concept - the hover badge, the completion
5
+ * list icon, the tree leaf, the breadcrumb/outline entry - reads its glyph from
6
+ * here, so a trigger looks like a trigger everywhere.
7
+ *
8
+ * The colour is not a second decision. VS Code paints a completion row from the
9
+ * `symbolIcon.*Foreground` token of the `CompletionItemKind` we send, and we
10
+ * cannot override it, so the kind IS the colour. The hover badge reuses that
11
+ * same token, which is why there is no colour column to keep in sync. Four
12
+ * groups come out of that, and choosing the kind is choosing the group:
13
+ *
14
+ * purple asks a question Method
15
+ * orange makes it happen Class, Event, Enum, Value
16
+ * blue you stored it Variable, Field, Interface, EnumMember
17
+ * grey syntax, everything else all the rest
18
+ *
19
+ * Three facts shape the table and are easy to re-break:
20
+ *
21
+ * 1. **Codicon aliases collapse.** `symbol-method`, `symbol-function` and
22
+ * `symbol-constructor` are one codepoint, so they are one picture. Same for
23
+ * `symbol-enum`/`symbol-value`, `symbol-key`/`symbol-text`,
24
+ * `symbol-struct`/`symbol-structure`, `symbol-unit`/`symbol-ruler` and
25
+ * `symbol-type-parameter`/`symbol-parameter`. Check a proposed mapping
26
+ * against codepoints, not against the names. Prefer the canonical name of a
27
+ * pair: only it carries the `symbolIcon.*Foreground` rule, so a themed tree
28
+ * leaf tints and an alias does not.
29
+ * 2. **Only `CompletionItemKind` reaches the suggest widget.** 25 values,
30
+ * 22 distinct pictures after the collapse. A concept that appears in a
31
+ * completion list cannot use a glyph from outside that set in the list,
32
+ * even though the hover and the tree can draw all 461 codicons.
33
+ * 3. **Only `SymbolKind` reaches the outline.** Breadcrumbs, the outline,
34
+ * sticky scroll and Ctrl+T take an LSP `SymbolKind`, and VS Code draws
35
+ * member `X` with the codicon `symbol-<kebab X>`. `symbolKind` names the
36
+ * member drawing the same picture as `codicon`, so the breadcrumb bar and
37
+ * the hover badge cannot disagree; it is null for a picture no member
38
+ * draws. The server resolves the name to the numeric enum.
39
+ *
40
+ * `codicon`, `completionKind` and `symbolKind` are separate fields for exactly
41
+ * that reason. They name the same picture everywhere except two entries:
42
+ * `texture`, whose `file-media` glyph no completion kind can produce, and
43
+ * `list`, whose array glyph no *free* completion kind can produce (the colour
44
+ * has to stay blue, and all four blue kinds are taken), so the suggest widget
45
+ * alone still draws it as an enum member.
46
+ *
47
+ * Uniqueness is promised *within a completion list*, not globally: script, gui
48
+ * and datafunction completions never appear together, so they may share glyphs.
49
+ *
50
+ * No imports: this is shared by the server and the VS Code client.
51
+ */
52
+
53
+ export interface KindStyle {
54
+ /** Codicon id, drawn in the hover badge and the tree. */
55
+ codicon: string;
56
+ /** `CompletionItemKind` member name; the server maps it to the enum. */
57
+ completionKind: string;
58
+ /**
59
+ * `SymbolKind` member name drawing the same picture as `codicon`, or null
60
+ * when no member draws it. The server maps it to the enum.
61
+ */
62
+ symbolKind: string | null;
63
+ /** Hover badge colour as a `--vscode-symbolIcon-*` var, or null for none. */
64
+ color: string | null;
65
+ }
66
+
67
+ /**
68
+ * The `SymbolKind` member VS Code draws with each picture the table uses. VS
69
+ * Code renders member `X` as `symbol-<kebab X>`, so this is that rule read
70
+ * backwards, with the alias pairs resolved to the member owning the codepoint
71
+ * (`symbol-value` is `symbol-enum`, `symbol-text` is `symbol-key`). A codicon
72
+ * absent here has no `SymbolKind` at all: there are 26 members against 461
73
+ * codicons.
74
+ */
75
+ const SYMBOL_KIND_BY_CODICON: Record<string, string> = {
76
+ "symbol-array": "Array",
77
+ "symbol-class": "Class",
78
+ "symbol-constant": "Constant",
79
+ "symbol-enum-member": "EnumMember",
80
+ "symbol-event": "Event",
81
+ "symbol-field": "Field",
82
+ "symbol-interface": "Interface",
83
+ "symbol-method": "Method",
84
+ "symbol-module": "Module",
85
+ "symbol-operator": "Operator",
86
+ "symbol-property": "Property",
87
+ "symbol-struct": "Struct",
88
+ "symbol-text": "Key",
89
+ "symbol-type-parameter": "TypeParameter",
90
+ "symbol-value": "Enum",
91
+ "symbol-variable": "Variable",
92
+ };
93
+
94
+ /**
95
+ * The only completion kinds VS Code tints; the other 15 render in the plain
96
+ * editor foreground, so their badge emits no span at all.
97
+ */
98
+ const TINT: Record<string, string> = {
99
+ Method: "method",
100
+ Function: "function",
101
+ Constructor: "constructor",
102
+ Class: "class",
103
+ Enum: "enumerator",
104
+ Value: "enumerator",
105
+ Event: "event",
106
+ Variable: "variable",
107
+ Field: "field",
108
+ Interface: "interface",
109
+ EnumMember: "enumeratorMember",
110
+ };
111
+
112
+ /**
113
+ * `colorFrom` defaults to the completion kind, which is what keeps the badge
114
+ * and the row the same colour. `on_action` is the one entry that overrides it:
115
+ * it wants the interface glyph with the orange of the group it belongs to, and
116
+ * a completion row cannot have both.
117
+ */
118
+ const c = (codicon: string, completionKind: string, colorFrom = completionKind): KindStyle => ({
119
+ codicon,
120
+ completionKind,
121
+ symbolKind: SYMBOL_KIND_BY_CODICON[codicon] ?? null,
122
+ color: TINT[colorFrom] ? `var(--vscode-symbolIcon-${TINT[colorFrom]}Foreground)` : null,
123
+ });
124
+
125
+ const SCRIPT: Record<string, KindStyle> = {
126
+ // purple: asks a question.
127
+ trigger: c("symbol-method", "Method"),
128
+ scripted_trigger: c("symbol-method", "Method"),
129
+ datafn: c("symbol-method", "Method"),
130
+
131
+ // orange: makes it happen.
132
+ effect: c("symbol-event", "Event"),
133
+ scripted_effect: c("symbol-event", "Event"),
134
+ event: c("symbol-class", "Class"),
135
+ decision: c("symbol-class", "Class"),
136
+ gui_type: c("symbol-class", "Class"),
137
+ data_type: c("symbol-class", "Class"),
138
+ on_action: c("symbol-interface", "Interface", "Class"),
139
+ trait: c("symbol-value", "Value"),
140
+
141
+ // blue: you stored it. A name that resolves to a scope or a stored value.
142
+ variable: c("symbol-variable", "Variable"),
143
+ local_variable: c("symbol-variable", "Variable"),
144
+ global_variable: c("symbol-variable", "Variable"),
145
+ promote: c("symbol-variable", "Variable"),
146
+ saved_scope: c("symbol-field", "Field"),
147
+ event_target: c("symbol-interface", "Interface"),
148
+ // The four list kinds each get their own picture. `add_to_list` builds a
149
+ // collection that lives for one effect block and is never saved, so it takes
150
+ // the array picture; the three `*_variable_list` kinds are entries in
151
+ // variable storage, split by storage class: the object-attached one keeps
152
+ // the enum-member picture, the event-chain-local one the plain list, the
153
+ // game-global one the globe.
154
+ // All four stay on `EnumMember`: the four blue completion kinds are already
155
+ // spoken for by variable, saved_scope and event_target, so the suggest widget
156
+ // draws every list as one blue enum-member row; the split shows in the hover
157
+ // badge and the tree. Of the four pictures only `symbol-enum-member` carries
158
+ // a `symbolIcon` colour rule, so the other three render tree leaves in the
159
+ // plain icon foreground while their badges stay blue (the map emits the
160
+ // colour); `list-unordered` and `globe` are pictures no SymbolKind draws, so
161
+ // those two fall back to Object in symbol lists.
162
+ list: c("symbol-array", "EnumMember"),
163
+ variable_list: c("symbol-enum-member", "EnumMember"),
164
+ local_variable_list: c("list-unordered", "EnumMember"),
165
+ global_variable_list: c("globe", "EnumMember"),
166
+
167
+ // grey: syntax and everything else.
168
+ scope_word: c("symbol-constant", "Constant"),
169
+ structure_key: c("symbol-struct", "Struct"),
170
+ descriptor_field: c("symbol-struct", "Struct"),
171
+ keyword: c("symbol-keyword", "Keyword"),
172
+ modifier: c("symbol-property", "Property"),
173
+ scripted_modifier: c("symbol-property", "Property"),
174
+ gui_property: c("symbol-property", "Property"),
175
+ script_value: c("symbol-operator", "Operator"),
176
+ define: c("symbol-unit", "Unit"),
177
+ namespace: c("symbol-module", "Module"),
178
+ loc_key: c("symbol-text", "Text"),
179
+ macro_param: c("symbol-type-parameter", "TypeParameter"),
180
+ text_format: c("symbol-color", "Color"),
181
+ gui_enum_value: c("symbol-constant", "Constant"),
182
+ format_suffix: c("symbol-constant", "Constant"),
183
+ gui_template: c("symbol-snippet", "Snippet"),
184
+ // The picture-frame glyph no completion kind can draw: the hover and the tree
185
+ // get `file-media`, the completion row falls back to the plain file glyph.
186
+ texture: c("file-media", "File"),
187
+ };
188
+
189
+ /** Anything the map does not name: a definition we have no opinion about. */
190
+ export const DEFAULT_KIND_STYLE: KindStyle = c("go-to-file", "Reference");
191
+
192
+ /** Glyph, completion kind and badge colour for a kind name. Never throws. */
193
+ export function kindStyle(kind: string): KindStyle {
194
+ return SCRIPT[kind] ?? DEFAULT_KIND_STYLE;
195
+ }
196
+
197
+ /** True when the map has an opinion, i.e. the kind is not falling through. */
198
+ export function hasKindStyle(kind: string): boolean {
199
+ return Object.prototype.hasOwnProperty.call(SCRIPT, kind);
200
+ }
201
+
202
+ /** Every mapped kind, for the coverage test that keeps this table honest. */
203
+ export function mappedKinds(): string[] {
204
+ return Object.keys(SCRIPT);
205
+ }
@@ -1,43 +1,43 @@
1
- /**
2
- * Script properties whose right-hand side is a localization key.
3
- *
4
- * BROAD: properties that often hold a loc key; a resolved inlay hint is shown
5
- * when the value exists in the loc index, silence otherwise (these keys also
6
- * hold non-loc values, e.g. `name` on a title history entry).
7
- *
8
- * STRICT: properties that virtually always hold a loc key; an unresolved value
9
- * here renders a `missing loc` hint.
10
- */
11
-
12
- export const STRICT_LOC_PROPERTIES = new Set<string>([
13
- "title",
14
- "desc",
15
- "flavor",
16
- "custom_tooltip",
17
- "confirm_text",
18
- "confirm_title",
19
- "prompt",
20
- "failure_desc",
21
- "success_desc",
22
- ]);
23
-
24
- export const BROAD_LOC_PROPERTIES = new Set<string>([
25
- ...STRICT_LOC_PROPERTIES,
26
- "name",
27
- "text",
28
- "tooltip",
29
- "first_valid",
30
- "reason",
31
- "format",
32
- "header",
33
- "opinion_text",
34
- "what",
35
- "who",
36
- ]);
37
-
38
- export function isLocProperty(prop: string): "strict" | "broad" | null {
39
- const p = prop.toLowerCase();
40
- if (STRICT_LOC_PROPERTIES.has(p)) return "strict";
41
- if (BROAD_LOC_PROPERTIES.has(p)) return "broad";
42
- return null;
43
- }
1
+ /**
2
+ * Script properties whose right-hand side is a localization key.
3
+ *
4
+ * BROAD: properties that often hold a loc key; a resolved inlay hint is shown
5
+ * when the value exists in the loc index, silence otherwise (these keys also
6
+ * hold non-loc values, e.g. `name` on a title history entry).
7
+ *
8
+ * STRICT: properties that virtually always hold a loc key; an unresolved value
9
+ * here renders a `missing loc` hint.
10
+ */
11
+
12
+ export const STRICT_LOC_PROPERTIES = new Set<string>([
13
+ "title",
14
+ "desc",
15
+ "flavor",
16
+ "custom_tooltip",
17
+ "confirm_text",
18
+ "confirm_title",
19
+ "prompt",
20
+ "failure_desc",
21
+ "success_desc",
22
+ ]);
23
+
24
+ export const BROAD_LOC_PROPERTIES = new Set<string>([
25
+ ...STRICT_LOC_PROPERTIES,
26
+ "name",
27
+ "text",
28
+ "tooltip",
29
+ "first_valid",
30
+ "reason",
31
+ "format",
32
+ "header",
33
+ "opinion_text",
34
+ "what",
35
+ "who",
36
+ ]);
37
+
38
+ export function isLocProperty(prop: string): "strict" | "broad" | null {
39
+ const p = prop.toLowerCase();
40
+ if (STRICT_LOC_PROPERTIES.has(p)) return "strict";
41
+ if (BROAD_LOC_PROPERTIES.has(p)) return "broad";
42
+ return null;
43
+ }
package/src/locRefs.ts CHANGED
@@ -1,38 +1,38 @@
1
- /**
2
- * Line-level detection of localization-key references in script and of key
3
- * definitions in loc yml. Used by the server (inlay hints, code actions) and
4
- * the client (loc reference tracker), so it lives in shared/.
5
- *
6
- * No `vscode` imports here: this module is unit-tested in plain Node.
7
- */
8
- import { isLocProperty } from "./locProperties";
9
-
10
- const PROP_VALUE = /([A-Za-z_][A-Za-z0-9_.-]*)\s*=\s*("?)([A-Za-z_][A-Za-z0-9_.-]*)\2/g;
11
-
12
- export interface LocKeyRef {
13
- prop: string;
14
- key: string;
15
- /** Character range of the key on the line. */
16
- start: number;
17
- end: number;
18
- strictness: "strict" | "broad";
19
- }
20
-
21
- export function findLocKeyRefs(lineText: string): LocKeyRef[] {
22
- const refs: LocKeyRef[] = [];
23
- PROP_VALUE.lastIndex = 0;
24
- let m: RegExpExecArray | null;
25
- while ((m = PROP_VALUE.exec(lineText)) !== null) {
26
- const strictness = isLocProperty(m[1]);
27
- if (!strictness) continue;
28
- const end = m.index + m[0].length - (m[2] === '"' ? 1 : 0);
29
- refs.push({ prop: m[1], key: m[3], start: end - m[3].length, end, strictness });
30
- }
31
- return refs;
32
- }
33
-
34
- /** The loc key defined on the given line of a loc yml, if any. */
35
- export function locKeyOnLine(lineText: string): string | null {
36
- const m = /^\s*([A-Za-z0-9_.\-']+):\d*\s*"/.exec(lineText.replace(/^/, ""));
37
- return m ? m[1] : null;
38
- }
1
+ /**
2
+ * Line-level detection of localization-key references in script and of key
3
+ * definitions in loc yml. Used by the server (inlay hints, code actions) and
4
+ * the client (loc reference tracker), so it lives in shared/.
5
+ *
6
+ * No `vscode` imports here: this module is unit-tested in plain Node.
7
+ */
8
+ import { isLocProperty } from "./locProperties";
9
+
10
+ const PROP_VALUE = /([A-Za-z_][A-Za-z0-9_.-]*)\s*=\s*("?)([A-Za-z_][A-Za-z0-9_.-]*)\2/g;
11
+
12
+ export interface LocKeyRef {
13
+ prop: string;
14
+ key: string;
15
+ /** Character range of the key on the line. */
16
+ start: number;
17
+ end: number;
18
+ strictness: "strict" | "broad";
19
+ }
20
+
21
+ export function findLocKeyRefs(lineText: string): LocKeyRef[] {
22
+ const refs: LocKeyRef[] = [];
23
+ PROP_VALUE.lastIndex = 0;
24
+ let m: RegExpExecArray | null;
25
+ while ((m = PROP_VALUE.exec(lineText)) !== null) {
26
+ const strictness = isLocProperty(m[1]);
27
+ if (!strictness) continue;
28
+ const end = m.index + m[0].length - (m[2] === '"' ? 1 : 0);
29
+ refs.push({ prop: m[1], key: m[3], start: end - m[3].length, end, strictness });
30
+ }
31
+ return refs;
32
+ }
33
+
34
+ /** The loc key defined on the given line of a loc yml, if any. */
35
+ export function locKeyOnLine(lineText: string): string | null {
36
+ const m = /^\s*([A-Za-z0-9_.\-']+):\d*\s*"/.exec(lineText.replace(/^/, ""));
37
+ return m ? m[1] : null;
38
+ }
package/src/modName.ts CHANGED
@@ -1,18 +1,18 @@
1
- /**
2
- * One display name for a mod folder, whichever descriptor convention it uses.
3
- * Every surface that names a mod (setup report, sidebar, pickers, the Project
4
- * view, hover origins) goes through this, so a mod is called what its author
5
- * called it instead of "3385002128".
6
- */
7
- import * as path from "path";
8
- import { readDescriptorName } from "./descriptorMod";
9
- import { readMetadataName } from "./descriptorMetadata";
10
-
11
- /**
12
- * The mod's display name: the launcher descriptor's `name=`, else
13
- * `.metadata/metadata.json`'s `name`, else the folder's own name. Never null,
14
- * so callers need no fallback of their own.
15
- */
16
- export function readModName(dir: string): string {
17
- return readDescriptorName(dir) ?? readMetadataName(dir) ?? path.basename(dir);
18
- }
1
+ /**
2
+ * One display name for a mod folder, whichever descriptor convention it uses.
3
+ * Every surface that names a mod (setup report, sidebar, pickers, the Project
4
+ * view, hover origins) goes through this, so a mod is called what its author
5
+ * called it instead of "3385002128".
6
+ */
7
+ import * as path from "path";
8
+ import { readDescriptorName } from "./descriptorMod";
9
+ import { readMetadataName } from "./descriptorMetadata";
10
+
11
+ /**
12
+ * The mod's display name: the launcher descriptor's `name=`, else
13
+ * `.metadata/metadata.json`'s `name`, else the folder's own name. Never null,
14
+ * so callers need no fallback of their own.
15
+ */
16
+ export function readModName(dir: string): string {
17
+ return readDescriptorName(dir) ?? readMetadataName(dir) ?? path.basename(dir);
18
+ }