cluaupp 1.4.0 → 1.5.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.
Files changed (34) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +2 -2
  3. package/editors/vscode/README.md +17 -0
  4. package/editors/vscode/extension.js +140 -21
  5. package/editors/vscode/icons/flare.svg +5 -0
  6. package/editors/vscode/language-configuration.json +21 -0
  7. package/editors/vscode/package.json +61 -7
  8. package/editors/vscode/snippets/flare.json +27 -0
  9. package/editors/vscode/syntaxes/flare.tmLanguage.json +75 -0
  10. package/examples/game/.vscode/extensions.json +3 -1
  11. package/examples/game/.vscode/settings.json +2 -1
  12. package/examples/game/include/clpp/libs/roster.clh +6 -0
  13. package/examples/game/src/ReplicatedStorage/Shared/Net/Net.flare +5 -0
  14. package/examples/game/src/ServerScriptService/Boot/DataBoot.server.clpp +1 -1
  15. package/generated/cli.js +3 -3
  16. package/generated/flare/index.js +15 -3
  17. package/generated/flare/language.js +561 -0
  18. package/generated/flare/parse.js +7 -6
  19. package/generated/intellisense.js +20 -2
  20. package/generated/lsp.js +118 -7
  21. package/include/clpp/libs/roster.clh +6 -0
  22. package/package.json +1 -1
  23. package/runtime/Roster/init.luau +6 -0
  24. package/src/cli.ts +3 -3
  25. package/src/flare/index.ts +20 -1
  26. package/src/flare/language.ts +708 -0
  27. package/src/flare/parse.ts +6 -6
  28. package/src/intellisense.ts +19 -2
  29. package/src/lsp.ts +151 -8
  30. package/templates/game/.vscode/extensions.json +3 -1
  31. package/templates/game/.vscode/settings.json +2 -1
  32. package/templates/game/include/clpp/libs/roster.clh +6 -0
  33. package/templates/game/src/ReplicatedStorage/Shared/Net/Net.flare +5 -0
  34. package/templates/game/src/ServerScriptService/Boot/DataBoot.server.clpp +1 -1
@@ -1,6 +1,6 @@
1
1
  import type { FlareField, FlareQuery, FlarePacket, FlareSchema, FlareSide, FlareType } from "./types.js";
2
2
 
3
- const TYPES: Record<string, FlareType> = {
3
+ export const FLARE_TYPE_ALIASES: Record<string, FlareType> = {
4
4
  u8: "u8",
5
5
  u16: "u16",
6
6
  u32: "u32",
@@ -28,14 +28,14 @@ const TYPES: Record<string, FlareType> = {
28
28
  Player: "Player",
29
29
  };
30
30
 
31
- const IDENT = "[A-Za-z_][A-Za-z0-9_]*";
31
+ export const FLARE_IDENT = "[A-Za-z_][A-Za-z0-9_]*";
32
32
 
33
33
  function fail(file: string, line: number, message: string): never {
34
34
  throw new Error(`cluaupp flare: ${file}:${line}: ${message}`);
35
35
  }
36
36
 
37
37
  function parseType(raw: string, file: string, line: number): FlareType {
38
- const mapped = TYPES[raw];
38
+ const mapped = FLARE_TYPE_ALIASES[raw];
39
39
  if (!mapped) {
40
40
  fail(file, line, `unknown type '${raw}'`);
41
41
  }
@@ -53,7 +53,7 @@ function parseFields(list: string, file: string, line: number): FlareField[] {
53
53
  if (!piece) {
54
54
  continue;
55
55
  }
56
- const match = piece.match(new RegExp(`^(${IDENT})\\s+(${IDENT})$`));
56
+ const match = piece.match(new RegExp(`^(${FLARE_IDENT})\\s+(${FLARE_IDENT})$`));
57
57
  if (!match) {
58
58
  fail(file, line, `expected 'Type name' in '${piece}'`);
59
59
  }
@@ -101,7 +101,7 @@ export function parseFlare(source: string, filePath: string, defaultName: string
101
101
 
102
102
  const packet = line.match(
103
103
  new RegExp(
104
- `^packet\\s+(${IDENT})\\s*\\((.*)\\)\\s+from\\s+(Client|Server)(?:\\s+(unreliable|reliable))?\\s*$`,
104
+ `^packet\\s+(${FLARE_IDENT})\\s*\\((.*)\\)\\s+from\\s+(Client|Server)(?:\\s+(unreliable|reliable))?\\s*$`,
105
105
  "i",
106
106
  ),
107
107
  );
@@ -121,7 +121,7 @@ export function parseFlare(source: string, filePath: string, defaultName: string
121
121
  continue;
122
122
  }
123
123
 
124
- const query = line.match(new RegExp(`^query\\s+(${IDENT})\\s*\\((.*)\\)\\s*->\\s*(${IDENT})\\s*$`, "i"));
124
+ const query = line.match(new RegExp(`^query\\s+(${FLARE_IDENT})\\s*\\((.*)\\)\\s*->\\s*(${FLARE_IDENT})\\s*$`, "i"));
125
125
  if (query) {
126
126
  if (queryId > 255) {
127
127
  fail(file, lineNo, "too many queries (max 255)");
@@ -82,6 +82,7 @@ function writeVscode(root: string): void {
82
82
  "*.server.clpp": "clpp",
83
83
  "*.client.clpp": "clpp",
84
84
  "*.plugin.clpp": "clpp",
85
+ "*.flare": "flare",
85
86
  };
86
87
  writeJson(settingsPath, {
87
88
  ...current,
@@ -92,7 +93,7 @@ function writeVscode(root: string): void {
92
93
  const extensions = readJsonObject(extensionsPath);
93
94
  writeJson(extensionsPath, {
94
95
  ...extensions,
95
- recommendations: uniqueStrings(extensions.recommendations),
96
+ recommendations: uniqueStrings(extensions.recommendations, ["kartzdev.cluaupp-flare"]),
96
97
  unwantedRecommendations: uniqueStrings(extensions.unwantedRecommendations, ["ms-vscode.cpptools"]),
97
98
  });
98
99
  }
@@ -112,7 +113,7 @@ export function installEditorExtension(cluauppRoot = PACKAGE_ROOT): string[] {
112
113
  if (!fs.existsSync(from)) {
113
114
  return [];
114
115
  }
115
- const id = `kartzdev.cluaupp-diagnostics-${pkg.version}`;
116
+ const id = `kartzdev.cluaupp-flare-${pkg.version}`;
116
117
  const homes = [path.join(os.homedir(), ".cursor", "extensions"), path.join(os.homedir(), ".vscode", "extensions")];
117
118
  const installed: string[] = [];
118
119
  for (const home of homes) {
@@ -123,6 +124,16 @@ export function installEditorExtension(cluauppRoot = PACKAGE_ROOT): string[] {
123
124
  const dest = path.join(home, id);
124
125
  copyDir(from, dest);
125
126
  fs.writeFileSync(path.join(dest, "cluaupp.root"), path.resolve(cluauppRoot), "utf8");
127
+ const manifestPath = path.join(dest, "package.json");
128
+ if (fs.existsSync(manifestPath)) {
129
+ try {
130
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")) as Record<string, unknown>;
131
+ manifest.version = pkg.version;
132
+ fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, "\t")}\n`, "utf8");
133
+ } catch {
134
+ // keep the copied manifest
135
+ }
136
+ }
126
137
  installed.push(dest);
127
138
  }
128
139
  return installed;
@@ -130,6 +141,12 @@ export function installEditorExtension(cluauppRoot = PACKAGE_ROOT): string[] {
130
141
 
131
142
  export async function installEditorSupport() {
132
143
  const local = installEditorExtension();
144
+ if (local.length > 0) {
145
+ console.log("cluaupp: Flare IntelliSense installed");
146
+ for (const dest of local) {
147
+ console.log(" ", dest);
148
+ }
149
+ }
133
150
  if (hasClpp()) {
134
151
  const result = spawnSync("clpp", ["install"], { encoding: "utf8", windowsHide: true });
135
152
  if (result.status === 0) {
package/src/lsp.ts CHANGED
@@ -1,30 +1,173 @@
1
1
  import {
2
- createConnection,
2
+ CompletionItemKind,
3
+ DiagnosticSeverity,
4
+ MarkupKind,
5
+ ParameterInformation,
3
6
  ProposedFeatures,
7
+ SignatureInformation,
8
+ SymbolKind,
4
9
  TextDocuments,
5
10
  TextDocumentSyncKind,
11
+ createConnection,
12
+ type CompletionItem,
13
+ type Diagnostic,
14
+ type DocumentSymbol,
15
+ type Hover,
16
+ type SignatureHelp,
6
17
  } from "vscode-languageserver/node";
7
18
  import { TextDocument } from "vscode-languageserver-textdocument";
19
+ import {
20
+ completionsAt,
21
+ hoverAt,
22
+ isFlareDocument,
23
+ signatureAt,
24
+ symbolsIn,
25
+ diagnoseFlare,
26
+ type FlareCompletionKind,
27
+ type FlareSeverity,
28
+ } from "./flare/language.js";
8
29
  import { pkg } from "./package-info.js";
9
30
 
31
+ const KIND: Record<FlareCompletionKind, CompletionItemKind> = {
32
+ keyword: CompletionItemKind.Keyword,
33
+ type: CompletionItemKind.TypeParameter,
34
+ snippet: CompletionItemKind.Snippet,
35
+ value: CompletionItemKind.EnumMember,
36
+ field: CompletionItemKind.Field,
37
+ };
38
+
39
+ const SEVERITY: Record<FlareSeverity, DiagnosticSeverity> = {
40
+ error: DiagnosticSeverity.Error,
41
+ warning: DiagnosticSeverity.Warning,
42
+ hint: DiagnosticSeverity.Hint,
43
+ };
44
+
45
+ const SYMBOL: Record<"packet" | "query" | "option", SymbolKind> = {
46
+ packet: SymbolKind.Event,
47
+ query: SymbolKind.Method,
48
+ option: SymbolKind.Namespace,
49
+ };
50
+
51
+ function flareDiagnostics(document: TextDocument): Diagnostic[] {
52
+ return diagnoseFlare(document.getText(), document.uri).map((item) => ({
53
+ range: {
54
+ start: { line: item.line, character: item.start },
55
+ end: { line: item.line, character: Math.max(item.end, item.start + 1) },
56
+ },
57
+ message: item.message,
58
+ severity: SEVERITY[item.severity],
59
+ source: "flare",
60
+ }));
61
+ }
62
+
10
63
  export function start(_options: { projectRoot?: string } = {}): void {
11
64
  const connection = createConnection(ProposedFeatures.all);
12
65
  const documents = new TextDocuments(TextDocument);
13
66
 
14
67
  connection.onInitialize(() => ({
15
68
  capabilities: {
16
- textDocumentSync: TextDocumentSyncKind.Full,
69
+ textDocumentSync: TextDocumentSyncKind.Incremental,
70
+ completionProvider: {
71
+ triggerCharacters: [" ", "(", ",", ">", "="],
72
+ resolveProvider: false,
73
+ },
74
+ hoverProvider: true,
75
+ documentSymbolProvider: true,
76
+ signatureHelpProvider: {
77
+ triggerCharacters: ["(", ","],
78
+ },
17
79
  },
18
- serverInfo: { name: "cluaupp", version: pkg.version },
80
+ serverInfo: { name: "cluaupp-flare", version: pkg.version },
19
81
  }));
20
82
 
21
- const clear = (uri: string) => {
22
- connection.sendDiagnostics({ uri, diagnostics: [] });
83
+ const publish = (document: TextDocument) => {
84
+ if (!isFlareDocument(document.uri)) {
85
+ connection.sendDiagnostics({ uri: document.uri, diagnostics: [] });
86
+ return;
87
+ }
88
+ connection.sendDiagnostics({ uri: document.uri, diagnostics: flareDiagnostics(document) });
23
89
  };
24
90
 
25
- documents.onDidOpen((event) => clear(event.document.uri));
26
- documents.onDidChangeContent((event) => clear(event.document.uri));
27
- documents.onDidClose((event) => clear(event.document.uri));
91
+ documents.onDidOpen((event) => publish(event.document));
92
+ documents.onDidChangeContent((event) => publish(event.document));
93
+ documents.onDidClose((event) => {
94
+ connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] });
95
+ });
96
+
97
+ connection.onCompletion((params): CompletionItem[] => {
98
+ const document = documents.get(params.textDocument.uri);
99
+ if (!document || !isFlareDocument(document.uri)) {
100
+ return [];
101
+ }
102
+ return completionsAt(document.getText(), params.position.line, params.position.character).map((item) => ({
103
+ label: item.label,
104
+ kind: KIND[item.kind],
105
+ detail: item.detail,
106
+ documentation: { kind: MarkupKind.Markdown, value: item.documentation },
107
+ insertText: item.insertText,
108
+ insertTextFormat: item.snippet ? 2 : 1,
109
+ sortText: item.sortText,
110
+ }));
111
+ });
112
+
113
+ connection.onHover((params): Hover | null => {
114
+ const document = documents.get(params.textDocument.uri);
115
+ if (!document || !isFlareDocument(document.uri)) {
116
+ return null;
117
+ }
118
+ const hover = hoverAt(document.getText(), params.position.line, params.position.character);
119
+ if (!hover) {
120
+ return null;
121
+ }
122
+ return {
123
+ contents: { kind: MarkupKind.Markdown, value: hover.contents },
124
+ range: {
125
+ start: { line: params.position.line, character: hover.start },
126
+ end: { line: params.position.line, character: hover.end },
127
+ },
128
+ };
129
+ });
130
+
131
+ connection.onDocumentSymbol((params): DocumentSymbol[] => {
132
+ const document = documents.get(params.textDocument.uri);
133
+ if (!document || !isFlareDocument(document.uri)) {
134
+ return [];
135
+ }
136
+ return symbolsIn(document.getText()).map((symbol) => ({
137
+ name: symbol.name,
138
+ detail: symbol.detail,
139
+ kind: SYMBOL[symbol.kind],
140
+ range: {
141
+ start: { line: symbol.line, character: 0 },
142
+ end: { line: symbol.line, character: Math.max(symbol.end, 1) },
143
+ },
144
+ selectionRange: {
145
+ start: { line: symbol.line, character: symbol.start },
146
+ end: { line: symbol.line, character: Math.max(symbol.end, symbol.start + 1) },
147
+ },
148
+ }));
149
+ });
150
+
151
+ connection.onSignatureHelp((params): SignatureHelp | null => {
152
+ const document = documents.get(params.textDocument.uri);
153
+ if (!document || !isFlareDocument(document.uri)) {
154
+ return null;
155
+ }
156
+ const signature = signatureAt(document.getText(), params.position.line, params.position.character);
157
+ if (!signature) {
158
+ return null;
159
+ }
160
+ const info = SignatureInformation.create(
161
+ signature.label,
162
+ signature.documentation,
163
+ ...signature.parameters.map((parameter) => ParameterInformation.create(parameter.label, parameter.documentation)),
164
+ );
165
+ return {
166
+ signatures: [info],
167
+ activeSignature: 0,
168
+ activeParameter: signature.activeParameter,
169
+ };
170
+ });
28
171
 
29
172
  documents.listen(connection);
30
173
  connection.listen();
@@ -2,5 +2,7 @@
2
2
  "unwantedRecommendations": [
3
3
  "ms-vscode.cpptools"
4
4
  ],
5
- "recommendations": []
5
+ "recommendations": [
6
+ "kartzdev.cluaupp-flare"
7
+ ]
6
8
  }
@@ -5,6 +5,7 @@
5
5
  "*.clh": "clpp",
6
6
  "*.server.clpp": "clpp",
7
7
  "*.client.clpp": "clpp",
8
- "*.plugin.clpp": "clpp"
8
+ "*.plugin.clpp": "clpp",
9
+ "*.flare": "flare"
9
10
  }
10
11
  }
@@ -5,7 +5,10 @@
5
5
  // Table = u16 count + key/value pairs. Array = u16 len + values. Methods also work as Roster::X(rawTable).
6
6
 
7
7
  struct Roster {
8
+ // Construct with `new Roster()` / `new Roster(table)` — emits Luau Roster.new.
9
+ // `New` is the CL++ name because `new` is a keyword.
8
10
  static Roster New();
11
+ static Roster New(auto initial);
9
12
 
10
13
  int Size();
11
14
  int Length();
@@ -13,6 +16,7 @@ struct Roster {
13
16
  auto GetData();
14
17
 
15
18
  auto At(int index);
19
+ // Array value search (1-based index). Dictionary keys: Has / Get / Set / Remove.
16
20
  int Find(auto value);
17
21
  int Find(auto value, int initPos);
18
22
  int Find(auto value, int initPos, int maxPos);
@@ -23,6 +27,7 @@ struct Roster {
23
27
  auto Shift();
24
28
  int Unshift(auto value);
25
29
  auto RemoveAt(int index);
30
+ Roster Remove(auto key);
26
31
 
27
32
  Roster Map(func mapper);
28
33
  Roster Filter(func predicate);
@@ -74,6 +79,7 @@ struct Roster {
74
79
  static auto Shift(auto data);
75
80
  static int Unshift(auto data, auto value);
76
81
  static auto RemoveAt(auto data, int index);
82
+ static Roster Remove(auto data, auto key);
77
83
  static Roster Map(auto data, func mapper);
78
84
  static Roster Filter(auto data, func predicate);
79
85
  static auto Reduce(auto data, func reducer, auto initial);
@@ -0,0 +1,5 @@
1
+ opt name = Net
2
+
3
+ packet Ready() from Client
4
+ packet Welcome(i32 userId, f64 clock) from Server
5
+ query Session() -> i32
@@ -4,7 +4,7 @@
4
4
  #include "../../ReplicatedStorage/Shared/Constants/Datas/TemplateData.clh"
5
5
 
6
6
  void init() {
7
- PlayerTemplate playerData;
7
+ PlayerTemplate playerData = PlayerTemplate();
8
8
  Keep.Server.Init(DataServiceOptions {
9
9
  .Template = playerData,
10
10
  .StoreName = "PlayerData",