@spyglassmc/core 0.4.7 → 0.4.9

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.
@@ -122,7 +122,7 @@ class BrowserFileSystem {
122
122
  delete this.states[location];
123
123
  this.saveStates();
124
124
  }
125
- watch(_location) {
125
+ watch(_locations) {
126
126
  return new BrowserFsWatcher();
127
127
  }
128
128
  async writeFile(location, data, _options) {
@@ -105,8 +105,8 @@ export const NodeJsExternals = {
105
105
  unlink(location) {
106
106
  return fsp.unlink(toFsPathLike(location));
107
107
  },
108
- watch(location) {
109
- return new ChokidarWatcherWrapper(chokidar.watch(toPath(location)));
108
+ watch(locations) {
109
+ return new ChokidarWatcherWrapper(chokidar.watch(locations.map(toPath)));
110
110
  },
111
111
  writeFile(location, data, options) {
112
112
  return fsp.writeFile(toFsPathLike(location), data, options);
@@ -72,7 +72,7 @@ export interface ExternalFileSystem {
72
72
  isFile(): boolean;
73
73
  }>;
74
74
  unlink(location: FsLocation): Promise<void>;
75
- watch(location: FsLocation): FsWatcher;
75
+ watch(locations: FsLocation[]): FsWatcher;
76
76
  /**
77
77
  * @param options `mode` - File mode bit mask (e.g. `0o775`).
78
78
  */
@@ -54,6 +54,9 @@ export interface StringBaseNode extends AstNode {
54
54
  value: string;
55
55
  readonly valueMap: IndexMap;
56
56
  }
57
+ export declare namespace StringBaseNode {
58
+ function is(obj: object | undefined): obj is StringBaseNode;
59
+ }
57
60
  export interface StringNode extends StringBaseNode {
58
61
  readonly type: 'string';
59
62
  }
@@ -18,6 +18,14 @@ export const EscapeTable = new Map([
18
18
  ['r', '\r'],
19
19
  ['t', '\t'],
20
20
  ]);
21
+ export var StringBaseNode;
22
+ (function (StringBaseNode) {
23
+ function is(obj) {
24
+ return Array.isArray(obj?.valueMap)
25
+ && typeof obj?.options === 'object';
26
+ }
27
+ StringBaseNode.is = is;
28
+ })(StringBaseNode || (StringBaseNode = {}));
21
29
  export var StringNode;
22
30
  (function (StringNode) {
23
31
  /* istanbul ignore next */
@@ -3,6 +3,7 @@ import type { SymbolAccessType, SymbolUsageType } from '../symbol/index.js';
3
3
  import type { AstNode } from './AstNode.js';
4
4
  export interface SymbolOptions {
5
5
  category: string;
6
+ subcategory?: string;
6
7
  parentPath?: string[];
7
8
  accessType?: SymbolAccessType;
8
9
  usageType?: SymbolUsageType;
@@ -12,15 +12,15 @@ export function string(options) {
12
12
  value: '',
13
13
  valueMap: [],
14
14
  };
15
- let start = src.cursor;
15
+ let start;
16
16
  if (options.quotes?.length && (src.peek() === '"' || src.peek() === "'")) {
17
17
  const currentQuote = src.read();
18
18
  ans.quote = currentQuote;
19
- const contentStart = src.cursor;
19
+ let cStart = src.cursor;
20
+ start = cStart;
20
21
  while (src.canRead() && src.peek() !== currentQuote) {
21
22
  const c = src.peek();
22
23
  if (options.escapable && c === '\\') {
23
- const cStart = src.cursor;
24
24
  src.skip();
25
25
  const c2 = src.read();
26
26
  if (c2 === '\\'
@@ -61,10 +61,19 @@ export function string(options) {
61
61
  });
62
62
  ans.value += c2;
63
63
  }
64
+ cStart = src.cursor;
64
65
  }
65
66
  else {
66
67
  src.skip();
68
+ const cEnd = src.cursor;
69
+ if (cEnd - cStart > 1) {
70
+ ans.valueMap.push({
71
+ inner: Range.create(ans.value.length, ans.value.length + 1),
72
+ outer: Range.create(cStart, cEnd),
73
+ });
74
+ }
67
75
  ans.value += c;
76
+ cStart = cEnd;
68
77
  }
69
78
  }
70
79
  if (!src.trySkip(currentQuote)) {
@@ -73,9 +82,9 @@ export function string(options) {
73
82
  if (!options.quotes.includes(currentQuote)) {
74
83
  ctx.err.report(localize('parser.string.illegal-quote', options.quotes), ans);
75
84
  }
76
- start = contentStart;
77
85
  }
78
86
  else if (options.unquotable) {
87
+ start = src.cursor;
79
88
  while (src.canRead() && isAllowedCharacter(src.peek(), options.unquotable)) {
80
89
  ans.value += src.read();
81
90
  }
@@ -84,6 +93,7 @@ export function string(options) {
84
93
  }
85
94
  }
86
95
  else {
96
+ start = src.cursor;
87
97
  ctx.err.report(localize('expected', options.quotes), src);
88
98
  }
89
99
  ans.valueMap.unshift({ inner: Range.create(0), outer: Range.create(start) });
@@ -1,7 +1,7 @@
1
1
  import { localize } from '@spyglassmc/locales';
2
2
  import { SequenceUtil, SequenceUtilDiscriminator } from '../node/index.js';
3
3
  import { ErrorReporter } from '../service/index.js';
4
- import { IndexMap, Range, Source } from '../source/index.js';
4
+ import { Range, Source } from '../source/index.js';
5
5
  import { Failure } from './Parser.js';
6
6
  export function attempt(parser, src, ctx) {
7
7
  const tmpSrc = src.clone();
@@ -227,7 +227,7 @@ export function concatOnTrailingBackslash(parser) {
227
227
  const to = src.getCharRange(-1);
228
228
  indexMap.push({ inner: Range.create(wrappedStr.length), outer: Range.span(from, to) });
229
229
  }
230
- const wrappedSrc = new Source(wrappedStr, IndexMap.merge(src.indexMap, indexMap));
230
+ const wrappedSrc = new Source(wrappedStr, indexMap);
231
231
  wrappedSrc.innerCursor = wrappedSrcCursor;
232
232
  const ans = parser(wrappedSrc, ctx);
233
233
  src.cursor = wrappedSrc.cursor;
@@ -10,6 +10,7 @@ export type Color = [number, number, number, number];
10
10
  export declare namespace Color {
11
11
  const NamedColors: Map<string, number>;
12
12
  const ColorNames: string[];
13
+ function fromNamed(value: string): Color | undefined;
13
14
  /**
14
15
  * @param r A decimal within [0.0, 1.0].
15
16
  * @param g A decimal within [0.0, 1.0].
@@ -36,10 +37,18 @@ export declare namespace Color {
36
37
  * @param b An integer within [0, 255].
37
38
  */
38
39
  function fromIntRGB(r: number, g: number, b: number): Color;
40
+ /**
41
+ * @param value A string in the format `#rrggbb`
42
+ */
43
+ function fromHexRGB(value: string): Color;
39
44
  /**
40
45
  * @param value `R << 16 + G << 8 + B`. Negative values result in white.
41
46
  */
42
- function fromCompositeInt(value: number): Color;
47
+ function fromCompositeRGB(value: number): Color;
48
+ /**
49
+ * @param value `A << 24 + R << 16 + G << 8 + B`.
50
+ */
51
+ function fromCompositeARGB(value: number): Color;
43
52
  }
44
53
  export declare enum ColorFormat {
45
54
  /**
@@ -69,7 +78,11 @@ export declare enum ColorFormat {
69
78
  /**
70
79
  * `16620441`
71
80
  */
72
- CompositeInt = 6
81
+ CompositeRGB = 6,
82
+ /**
83
+ * `4294945365`
84
+ */
85
+ CompositeARGB = 7
73
86
  }
74
87
  export type FormattableColor = {
75
88
  value: Color;
@@ -19,6 +19,14 @@ export var Color;
19
19
  ['yellow', 0xffff55],
20
20
  ]);
21
21
  Color.ColorNames = [...Color.NamedColors.keys()];
22
+ function fromNamed(value) {
23
+ const composite = Color.NamedColors.get(value);
24
+ if (composite === undefined) {
25
+ return undefined;
26
+ }
27
+ return fromCompositeRGB(composite);
28
+ }
29
+ Color.fromNamed = fromNamed;
22
30
  /**
23
31
  * @param r A decimal within [0.0, 1.0].
24
32
  * @param g A decimal within [0.0, 1.0].
@@ -57,21 +65,46 @@ export var Color;
57
65
  return fromIntRGBA(r, g, b, 255);
58
66
  }
59
67
  Color.fromIntRGB = fromIntRGB;
68
+ /**
69
+ * @param value A string in the format `#rrggbb`
70
+ */
71
+ function fromHexRGB(value) {
72
+ var bigint = parseInt(value.slice(1), 16);
73
+ var r = (bigint >> 16) & 255;
74
+ var g = (bigint >> 8) & 255;
75
+ var b = bigint & 255;
76
+ return fromIntRGB(r, g, b);
77
+ }
78
+ Color.fromHexRGB = fromHexRGB;
60
79
  /**
61
80
  * @param value `R << 16 + G << 8 + B`. Negative values result in white.
62
81
  */
63
- function fromCompositeInt(value) {
82
+ function fromCompositeRGB(value) {
64
83
  if (value < 0) {
65
84
  return fromDecRGB(1.0, 1.0, 1.0);
66
85
  }
67
86
  const b = value % 256;
68
- value >>= 8;
87
+ value >>>= 8;
69
88
  const g = value % 256;
70
- value >>= 8;
89
+ value >>>= 8;
71
90
  const r = value % 256;
72
91
  return fromIntRGB(r, g, b);
73
92
  }
74
- Color.fromCompositeInt = fromCompositeInt;
93
+ Color.fromCompositeRGB = fromCompositeRGB;
94
+ /**
95
+ * @param value `A << 24 + R << 16 + G << 8 + B`.
96
+ */
97
+ function fromCompositeARGB(value) {
98
+ const b = value % 256;
99
+ value >>>= 8;
100
+ const g = value % 256;
101
+ value >>>= 8;
102
+ const r = value % 256;
103
+ value >>>= 8;
104
+ const a = value % 256;
105
+ return fromIntRGBA(r, g, b, a);
106
+ }
107
+ Color.fromCompositeARGB = fromCompositeARGB;
75
108
  })(Color || (Color = {}));
76
109
  export var ColorFormat;
77
110
  (function (ColorFormat) {
@@ -102,7 +135,11 @@ export var ColorFormat;
102
135
  /**
103
136
  * `16620441`
104
137
  */
105
- ColorFormat[ColorFormat["CompositeInt"] = 6] = "CompositeInt";
138
+ ColorFormat[ColorFormat["CompositeRGB"] = 6] = "CompositeRGB";
139
+ /**
140
+ * `4294945365`
141
+ */
142
+ ColorFormat[ColorFormat["CompositeARGB"] = 7] = "CompositeARGB";
106
143
  })(ColorFormat || (ColorFormat = {}));
107
144
  export var ColorPresentation;
108
145
  (function (ColorPresentation) {
@@ -128,8 +165,13 @@ export var ColorPresentation;
128
165
  case ColorFormat.HexRGB:
129
166
  return `#${Math.round(((color[0] * 255) << 16) + ((color[1] * 255) << 8) + color[2] * 255)
130
167
  .toString(16).padStart(6, '0')}`;
131
- case ColorFormat.CompositeInt:
168
+ case ColorFormat.CompositeRGB:
132
169
  return `${Math.round(((color[0] * 255) << 16) + ((color[1] * 255) << 8) + color[2] * 255)}`;
170
+ case ColorFormat.CompositeARGB:
171
+ return `${(BigInt(Math.round(color[3] * 255)) << 24n)
172
+ + (BigInt(Math.round(color[0] * 255)) << 16n)
173
+ + (BigInt(Math.round(color[1] * 255)) << 8n)
174
+ + BigInt(Math.round(color[2] * 255))}`;
133
175
  }
134
176
  }
135
177
  })(ColorPresentation || (ColorPresentation = {}));
@@ -107,6 +107,7 @@ export const symbol = SyncBinder.create((node, ctx) => {
107
107
  if (node.value) {
108
108
  const path = node.options.parentPath ? [...node.options.parentPath, node.value] : [node.value];
109
109
  ctx.symbols.query(ctx.doc, node.options.category, ...path).enter({
110
+ data: { subcategory: node.options.subcategory },
110
111
  usage: { type: node.options.usageType, node, accessType: node.options.accessType },
111
112
  });
112
113
  }
@@ -35,6 +35,7 @@ export interface CompletionItem {
35
35
  label: string;
36
36
  range: Range;
37
37
  kind?: CompletionKind;
38
+ labelSuffix?: string;
38
39
  detail?: string;
39
40
  documentation?: string;
40
41
  deprecated?: boolean;
@@ -150,7 +150,7 @@ export const resourceLocation = (node, ctx) => {
150
150
  if (node.options.category) {
151
151
  const symbols = ctx.symbols.getVisibleSymbols(node.options.category, ctx.doc.uri);
152
152
  const thisKey = Object.entries(symbols).flatMap(([key, symbol]) => {
153
- if ((symbol.declaration?.[0] ?? symbol.definition?.[0])?.uri == ctx.doc.uri) {
153
+ if ((symbol.declaration?.[0] ?? symbol.definition?.[0])?.uri === ctx.doc.uri) {
154
154
  return [key];
155
155
  }
156
156
  return [];
@@ -7,7 +7,7 @@ import type { Project } from './Project.js';
7
7
  * The format version of the cache. Should be increased when any changes that
8
8
  * could invalidate the cache are introduced to the Spyglass codebase.
9
9
  */
10
- export declare const LatestCacheVersion = 3;
10
+ export declare const LatestCacheVersion = 4;
11
11
  /**
12
12
  * Checksums of cached files or roots.
13
13
  */
@@ -40,11 +40,6 @@ export declare class CacheService {
40
40
  * @param project
41
41
  */
42
42
  constructor(cacheRoot: RootUriString, project: Project);
43
- /**
44
- * @throws
45
- *
46
- * @returns `${cacheRoot}symbols/${sha1(projectRoot)}.json`
47
- */
48
43
  private getCacheFileUri;
49
44
  load(): Promise<LoadResult>;
50
45
  validate(): Promise<ValidateResult>;
@@ -5,7 +5,7 @@ import { fileUtil } from './fileUtil.js';
5
5
  * The format version of the cache. Should be increased when any changes that
6
6
  * could invalidate the cache are introduced to the Spyglass codebase.
7
7
  */
8
- export const LatestCacheVersion = 3;
8
+ export const LatestCacheVersion = 4;
9
9
  var Checksums;
10
10
  (function (Checksums) {
11
11
  function create() {
@@ -65,21 +65,24 @@ export class CacheService {
65
65
  });
66
66
  }
67
67
  #cacheFilePath;
68
- /**
69
- * @throws
70
- *
71
- * @returns `${cacheRoot}symbols/${sha1(projectRoot)}.json`
72
- */
73
68
  async getCacheFileUri() {
74
- return (this.#cacheFilePath ??= new Uri(`symbols/${await this.project.externals.crypto.getSha1(this.project.projectRoot)}.json.gz`, this.cacheRoot).toString());
69
+ if (!this.#cacheFilePath) {
70
+ const sortedRoots = [...this.project.projectRoots].sort();
71
+ const hash = await this.project.externals.crypto.getSha1(sortedRoots.join(':'));
72
+ this.#cacheFilePath = new Uri(`symbols/${hash}.json.gz`, this.cacheRoot).toString();
73
+ }
74
+ return this.#cacheFilePath;
75
75
  }
76
76
  async load() {
77
- const __profiler = this.project.profilers.get('cache#load');
78
77
  const ans = { symbols: {} };
78
+ if (this.project.projectRoots.length === 0) {
79
+ return ans;
80
+ }
81
+ const __profiler = this.project.profilers.get('cache#load');
79
82
  let filePath;
80
83
  try {
81
84
  filePath = await this.getCacheFileUri();
82
- this.project.logger.info(`[CacheService#load] symbolCachePath = “${filePath}”`);
85
+ this.project.logger.info(`[CacheService#load] symbolCachePath = ${filePath}`);
83
86
  const cache = (await fileUtil.readGzippedJson(this.project.externals, filePath));
84
87
  __profiler.task('Read File');
85
88
  if (cache.version === LatestCacheVersion) {
@@ -164,13 +167,16 @@ export class CacheService {
164
167
  * @returns If the cache file was saved successfully.
165
168
  */
166
169
  async save() {
170
+ if (this.project.projectRoots.length === 0) {
171
+ return false;
172
+ }
167
173
  const __profiler = this.project.profilers.get('cache#save');
168
174
  let filePath;
169
175
  try {
170
176
  filePath = await this.getCacheFileUri();
171
177
  const cache = {
172
178
  version: LatestCacheVersion,
173
- projectRoot: this.project.projectRoot,
179
+ projectRoots: this.project.projectRoots,
174
180
  checksums: this.checksums,
175
181
  symbols: SymbolTable.unlink(this.project.symbols.global),
176
182
  errors: this.errors,
@@ -181,7 +187,7 @@ export class CacheService {
181
187
  return true;
182
188
  }
183
189
  catch (e) {
184
- this.project.logger.error(`[CacheService#save] path = “${filePath}”`, e);
190
+ this.project.logger.error(`[CacheService#save] path = ${filePath}`, e);
185
191
  }
186
192
  return false;
187
193
  }
@@ -236,22 +236,25 @@ export class ConfigService {
236
236
  return this.#eventEmitter.emit(event, ...args);
237
237
  }
238
238
  async load() {
239
- let ans = this.defaultConfig;
240
- for (const name of ConfigService.ConfigFileNames) {
241
- const uri = this.project.projectRoot + name;
242
- try {
243
- ans = JSON.parse(bufferToString(await this.project.externals.fs.readFile(uri)));
244
- }
245
- catch (e) {
246
- if (this.project.externals.error.isKind(e, 'ENOENT')) {
247
- // File doesn't exist.
248
- continue;
239
+ const overrides = [];
240
+ for (const projectRoot of this.project.projectRoots) {
241
+ for (const name of ConfigService.ConfigFileNames) {
242
+ const uri = projectRoot + name;
243
+ try {
244
+ const contents = await this.project.externals.fs.readFile(uri);
245
+ overrides.push(JSON.parse(bufferToString(contents)));
246
+ }
247
+ catch (e) {
248
+ if (this.project.externals.error.isKind(e, 'ENOENT')) {
249
+ // File doesn't exist.
250
+ continue;
251
+ }
252
+ this.emit('error', { error: e, uri });
249
253
  }
250
- this.emit('error', { error: e, uri });
254
+ break;
251
255
  }
252
- break;
253
256
  }
254
- return ConfigService.merge(this.defaultConfig, ans);
257
+ return ConfigService.merge(this.defaultConfig, ...overrides);
255
258
  }
256
259
  static isConfigFile(uri) {
257
260
  return ConfigService.ConfigFileNames.some((n) => uri.endsWith(`/${n}`));
@@ -57,7 +57,7 @@ export class Downloader {
57
57
  return ans;
58
58
  }
59
59
  catch (e) {
60
- this.logger.error(`[Downloader] [${id}] Loading cached file “${cacheUri}”`, e);
60
+ this.logger.error(`[Downloader] [${id}] Loading cached file ${cacheUri}`, e);
61
61
  if (this.externals.error.isKind(e, 'ENOENT')) {
62
62
  // Cache checksum exists, but cached file doesn't.
63
63
  // Remove the invalid cache checksum.
@@ -65,7 +65,7 @@ export class Downloader {
65
65
  await this.externals.fs.unlink(cacheChecksumUri);
66
66
  }
67
67
  catch (e) {
68
- this.logger.error(`[Downloader] [${id}] Removing invalid cache checksum “${cacheChecksumUri}”`, e);
68
+ this.logger.error(`[Downloader] [${id}] Removing invalid cache checksum ${cacheChecksumUri}`, e);
69
69
  }
70
70
  }
71
71
  }
@@ -73,12 +73,12 @@ export class Downloader {
73
73
  }
74
74
  catch (e) {
75
75
  if (!this.externals.error.isKind(e, 'ENOENT')) {
76
- this.logger.error(`[Downloader] [${id}] Loading cache checksum “${cacheChecksumUri}”`, e);
76
+ this.logger.error(`[Downloader] [${id}] Loading cache checksum ${cacheChecksumUri}`, e);
77
77
  }
78
78
  }
79
79
  }
80
80
  catch (e) {
81
- this.logger.error(`[Downloader] [${id}] Fetching latest checksum “${checksumJob.uri}”`, e);
81
+ this.logger.error(`[Downloader] [${id}] Fetching latest checksum ${checksumJob.uri}`, e);
82
82
  }
83
83
  }
84
84
  try {
@@ -92,7 +92,7 @@ export class Downloader {
92
92
  await fileUtil.writeFile(this.externals, cacheChecksumUri, `${checksum}\n`);
93
93
  }
94
94
  catch (e) {
95
- this.logger.error(`[Downloader] [${id}] Saving cache checksum “${cacheChecksumUri}”`, e);
95
+ this.logger.error(`[Downloader] [${id}] Saving cache checksum ${cacheChecksumUri}`, e);
96
96
  }
97
97
  }
98
98
  try {
@@ -100,24 +100,24 @@ export class Downloader {
100
100
  await fileUtil.writeFile(this.externals, cacheUri, serializer(buffer));
101
101
  }
102
102
  catch (e) {
103
- this.logger.error(`[Downloader] [${id}] Caching file “${cacheUri}”`, e);
103
+ this.logger.error(`[Downloader] [${id}] Caching file ${cacheUri}`, e);
104
104
  }
105
105
  }
106
- this.logger.info(`[Downloader] [${id}] Downloaded from “${uri}”`);
106
+ this.logger.info(`[Downloader] [${id}] Downloaded from ${uri}`);
107
107
  return await transformer(buffer);
108
108
  }
109
109
  catch (e) {
110
- this.logger.error(`[Downloader] [${id}] Downloading “${uri}”`, e);
110
+ this.logger.error(`[Downloader] [${id}] Downloading ${uri}`, e);
111
111
  if (cache && cacheUri) {
112
112
  try {
113
113
  const cachedBuffer = await fileUtil.readFile(this.externals, cacheUri);
114
114
  const deserializer = cache.deserializer ?? ((b) => b);
115
115
  const ans = await transformer(deserializer(cachedBuffer));
116
- this.logger.warn(`[Downloader] [${id}] Fell back to cached file “${cacheUri}”`);
116
+ this.logger.warn(`[Downloader] [${id}] Fell back to cached file ${cacheUri}`);
117
117
  return ans;
118
118
  }
119
119
  catch (e) {
120
- this.logger.error(`[Downloader] [${id}] Fallback: loading cached file “${cacheUri}”`, e);
120
+ this.logger.error(`[Downloader] [${id}] Fallback: loading cached file ${cacheUri}`, e);
121
121
  }
122
122
  }
123
123
  }
@@ -146,7 +146,7 @@ export class FileUriSupporter {
146
146
  }
147
147
  }
148
148
  catch (e) {
149
- logger.error(`[FileUriSupporter#create] Bad dependency “${uri}”`, e);
149
+ logger.error(`[FileUriSupporter#create] Bad dependency ${uri}`, e);
150
150
  }
151
151
  }
152
152
  return new FileUriSupporter(externals, roots, files);
@@ -217,11 +217,11 @@ export class ArchiveUriSupporter {
217
217
  */
218
218
  static decodeUri(uri) {
219
219
  if (uri.protocol !== ArchiveUriSupporter.Protocol) {
220
- throw new Error(`Expected protocol “${ArchiveUriSupporter.Protocol}” in “${uri}”`);
220
+ throw new Error(`Expected protocol “${ArchiveUriSupporter.Protocol}” in ${uri}`);
221
221
  }
222
222
  const path = uri.pathname;
223
223
  if (!path) {
224
- throw new Error(`Missing path in archive uri “${uri.toString()}”`);
224
+ throw new Error(`Missing path in archive uri ${uri}`);
225
225
  }
226
226
  return { archiveName: uri.host, pathInArchive: path.charAt(0) === '/' ? path.slice(1) : path };
227
227
  }
@@ -241,7 +241,7 @@ export class ArchiveUriSupporter {
241
241
  }
242
242
  }
243
243
  catch (e) {
244
- logger.error(`[SpyglassUriSupporter#create] Bad dependency “${uri}”`, e);
244
+ logger.error(`[SpyglassUriSupporter#create] Bad dependency ${uri}`, e);
245
245
  }
246
246
  }
247
247
  return new ArchiveUriSupporter(externals, entries);
@@ -14,7 +14,7 @@ import { FileService } from './FileService.js';
14
14
  import type { RootUriString } from './fileUtil.js';
15
15
  import { MetaRegistry } from './MetaRegistry.js';
16
16
  import { ProfilerFactory } from './Profiler.js';
17
- export type ProjectInitializerContext = Pick<Project, 'cacheRoot' | 'config' | 'downloader' | 'externals' | 'isDebugging' | 'logger' | 'meta' | 'projectRoot'>;
17
+ export type ProjectInitializerContext = Pick<Project, 'cacheRoot' | 'config' | 'downloader' | 'externals' | 'isDebugging' | 'logger' | 'meta' | 'projectRoots'>;
18
18
  export type SyncProjectInitializer = (this: void, ctx: ProjectInitializerContext) => Record<string, string> | void;
19
19
  export type AsyncProjectInitializer = (this: void, ctx: ProjectInitializerContext) => PromiseLike<Record<string, string> | void>;
20
20
  export type ProjectInitializer = SyncProjectInitializer | AsyncProjectInitializer;
@@ -29,9 +29,9 @@ export interface ProjectOptions {
29
29
  logger?: Logger;
30
30
  profilers?: ProfilerFactory;
31
31
  /**
32
- * A file URI to the root of this project.
32
+ * File URIs to the roots of this project.
33
33
  */
34
- projectRoot: RootUriString;
34
+ projectRoots: RootUriString[];
35
35
  symbols?: SymbolUtil;
36
36
  }
37
37
  export interface DocAndNode {
@@ -57,7 +57,7 @@ interface SymbolRegistrarEvent {
57
57
  id: string;
58
58
  checksum: string | undefined;
59
59
  }
60
- export type ProjectData = Pick<Project, 'cacheRoot' | 'config' | 'downloader' | 'ensureBindingStarted' | 'externals' | 'fs' | 'isDebugging' | 'logger' | 'meta' | 'profilers' | 'projectRoot' | 'roots' | 'symbols' | 'ctx'>;
60
+ export type ProjectData = Pick<Project, 'cacheRoot' | 'config' | 'downloader' | 'ensureBindingStarted' | 'externals' | 'fs' | 'isDebugging' | 'logger' | 'meta' | 'profilers' | 'projectRoots' | 'roots' | 'symbols' | 'ctx'>;
61
61
  /**
62
62
  * Manage all tracked documents and errors.
63
63
  *
@@ -110,7 +110,7 @@ export declare class Project implements ExternalEventEmitter {
110
110
  readonly logger: Logger;
111
111
  readonly meta: MetaRegistry;
112
112
  readonly profilers: ProfilerFactory;
113
- readonly projectRoot: RootUriString;
113
+ readonly projectRoots: RootUriString[];
114
114
  symbols: SymbolUtil;
115
115
  /**
116
116
  * All tracked root URIs. Each URI in this array is guaranteed to end with a slash (`/`).
@@ -159,7 +159,7 @@ export declare class Project implements ExternalEventEmitter {
159
159
  * are not loaded into the memory.
160
160
  */
161
161
  getTrackedFiles(): string[];
162
- constructor({ cacheRoot, defaultConfig, downloader, externals, fs, initializers, isDebugging, logger, profilers, projectRoot, }: ProjectOptions);
162
+ constructor({ cacheRoot, defaultConfig, downloader, externals, fs, initializers, isDebugging, logger, profilers, projectRoots, }: ProjectOptions);
163
163
  private setInitPromise;
164
164
  private readGitignore;
165
165
  private setReadyPromise;
@@ -93,7 +93,7 @@ export class Project {
93
93
  logger;
94
94
  meta = new MetaRegistry();
95
95
  profilers;
96
- projectRoot;
96
+ projectRoots;
97
97
  symbols;
98
98
  #dependencyRoots;
99
99
  #dependencyFiles;
@@ -125,7 +125,7 @@ export class Project {
125
125
  return this.#cacheRoot;
126
126
  }
127
127
  updateRoots() {
128
- const rawRoots = [...this.#dependencyRoots ?? [], this.projectRoot];
128
+ const rawRoots = [...this.#dependencyRoots ?? [], ...this.projectRoots];
129
129
  const ans = new Set(rawRoots);
130
130
  // Identify roots indicated by `pack.mcmeta`.
131
131
  for (const file of this.getTrackedFiles()) {
@@ -160,7 +160,7 @@ export class Project {
160
160
  const filteredFiles = this.ignore.filter(supportedFiles);
161
161
  return filteredFiles;
162
162
  }
163
- constructor({ cacheRoot, defaultConfig, downloader, externals, fs = FileService.create(externals, cacheRoot), initializers = [], isDebugging = false, logger = Logger.create(), profilers = ProfilerFactory.noop(), projectRoot, }) {
163
+ constructor({ cacheRoot, defaultConfig, downloader, externals, fs = FileService.create(externals, cacheRoot), initializers = [], isDebugging = false, logger = Logger.create(), profilers = ProfilerFactory.noop(), projectRoots, }) {
164
164
  this.#cacheRoot = cacheRoot;
165
165
  this.#eventEmitter = new externals.event.EventEmitter();
166
166
  this.externals = externals;
@@ -169,17 +169,18 @@ export class Project {
169
169
  this.isDebugging = isDebugging;
170
170
  this.logger = logger;
171
171
  this.profilers = profilers;
172
- this.projectRoot = projectRoot;
172
+ this.projectRoots = projectRoots;
173
173
  this.cacheService = new CacheService(cacheRoot, this);
174
174
  this.#configService = new ConfigService(this, defaultConfig);
175
175
  this.downloader = downloader ?? new Downloader(cacheRoot, externals, logger);
176
176
  this.symbols = new SymbolUtil({}, externals.event.EventEmitter);
177
177
  this.#ctx = {};
178
- this.logger.info(`[Project] [init] cacheRoot = “${cacheRoot}”`);
178
+ this.logger.info(`[Project] [init] cacheRoot = ${cacheRoot}`);
179
+ this.logger.info(`[Project] [init] projectRoots = ${projectRoots.join(' ')}`);
179
180
  this.#configService.on('changed', ({ config }) => {
180
181
  this.config = config;
181
182
  this.logger.info('[Project] [Config] Changed');
182
- }).on('error', ({ error, uri }) => this.logger.error(`[Project] [Config] Failed loading “${uri}”`, error));
183
+ }).on('error', ({ error, uri }) => this.logger.error(`[Project] [Config] Failed loading ${uri}`, error));
183
184
  this.setInitPromise();
184
185
  this.setReadyPromise();
185
186
  this.#cacheSaverIntervalId = setInterval(() => this.cacheService.save(), CacheAutoSaveInterval);
@@ -247,7 +248,7 @@ export class Project {
247
248
  isDebugging: this.isDebugging,
248
249
  logger: this.logger,
249
250
  meta: this.meta,
250
- projectRoot: this.projectRoot,
251
+ projectRoots: this.projectRoots,
251
252
  };
252
253
  const results = await Promise.allSettled(this.#initializers.map((init) => init(initCtx)));
253
254
  let ctx = {};
@@ -275,8 +276,11 @@ export class Project {
275
276
  this.#initPromise = init();
276
277
  }
277
278
  async readGitignore() {
279
+ if (this.projectRoots.length === 0) {
280
+ return undefined;
281
+ }
278
282
  try {
279
- const uri = this.projectRoot + Project.GitIgnore;
283
+ const uri = this.projectRoots[0] + Project.GitIgnore;
280
284
  const contents = await this.externals.fs.readFile(uri);
281
285
  return bufferToString(contents);
282
286
  }
@@ -320,9 +324,13 @@ export class Project {
320
324
  this.fs.register(ArchiveUriSupporter.Protocol, archiveUriSupporter, true);
321
325
  };
322
326
  const listProjectFiles = () => new Promise((resolve) => {
327
+ if (this.projectRoots.length === 0) {
328
+ resolve();
329
+ return;
330
+ }
323
331
  this.#watchedFiles.clear();
324
332
  this.#watcherReady = false;
325
- this.#watcher = this.externals.fs.watch(this.projectRoot).once('ready', () => {
333
+ this.#watcher = this.externals.fs.watch(this.projectRoots).once('ready', () => {
326
334
  this.#watcherReady = true;
327
335
  resolve();
328
336
  }).on('add', (uri) => {
@@ -467,7 +475,7 @@ export class Project {
467
475
  return TextDocument.create(uri, languageId, -1, content);
468
476
  }
469
477
  catch (e) {
470
- this.logger.warn(`[Project] [read] Failed creating TextDocument for “${uri}”`, e);
478
+ this.logger.warn(`[Project] [read] Failed creating TextDocument for ${uri}`, e);
471
479
  return undefined;
472
480
  }
473
481
  };
@@ -517,7 +525,7 @@ export class Project {
517
525
  if (result) {
518
526
  return result.doc;
519
527
  }
520
- throw new Error(`[Project] [read] Client-managed URI “${uri} does not have a TextDocument in the cache`);
528
+ throw new Error(`[Project] [read] Client-managed URI ${uri} does not have a TextDocument in the cache`);
521
529
  }
522
530
  return getCacheHandlingPromise(uri);
523
531
  }
@@ -554,7 +562,7 @@ export class Project {
554
562
  this.#symbolUpToDateUris.add(doc.uri);
555
563
  }
556
564
  catch (e) {
557
- this.logger.error(`[Project] [bind] Failed for “${doc.uri} #${doc.version}`, e);
565
+ this.logger.error(`[Project] [bind] Failed for ${doc.uri} # ${doc.version}`, e);
558
566
  }
559
567
  }
560
568
  async check(doc, node) {
@@ -572,7 +580,7 @@ export class Project {
572
580
  });
573
581
  }
574
582
  catch (e) {
575
- this.logger.error(`[Project] [check] Failed for “${doc.uri} #${doc.version}`, e);
583
+ this.logger.error(`[Project] [check] Failed for ${doc.uri} # ${doc.version}`, e);
576
584
  }
577
585
  }
578
586
  lint(doc, node) {
@@ -609,7 +617,7 @@ export class Project {
609
617
  }
610
618
  }
611
619
  catch (e) {
612
- this.logger.error(`[Project] [lint] Failed for “${doc.uri} #${doc.version}`, e);
620
+ this.logger.error(`[Project] [lint] Failed for ${doc.uri} # ${doc.version}`, e);
613
621
  }
614
622
  }
615
623
  // @SingletonPromise()
@@ -668,7 +676,7 @@ export class Project {
668
676
  }
669
677
  const doc = this.#clientManagedDocAndNodes.get(uri)?.doc;
670
678
  if (!doc) {
671
- throw new Error(`TextDocument for “${uri} is ${!doc ? 'not cached' : 'a Promise'}. This should not happen. Did the language client send a didChange notification without sending a didOpen one, or is there a logic error on our side resulting the 'read' function overriding the 'TextDocument' created in the 'didOpen' notification handler?`);
679
+ throw new Error(`TextDocument for ${uri} is not cached. This should not happen. Did the language client send a didChange notification without sending a didOpen one, or is there a logic error on our side resulting the 'read' function overriding the 'TextDocument' created in the 'didOpen' notification handler?`);
672
680
  }
673
681
  TextDocument.update(doc, changes, version);
674
682
  const node = this.parse(doc);
@@ -27,18 +27,18 @@ export class Service {
27
27
  }
28
28
  colorize(node, doc, range) {
29
29
  try {
30
- this.debug(`Colorizing '${doc.uri}' # ${doc.version}`);
30
+ this.debug(`Colorizing ${doc.uri} # ${doc.version}`);
31
31
  const colorizer = this.project.meta.getColorizer(node.type);
32
32
  return colorizer(node, ColorizerContext.create(this.project, { doc, range }));
33
33
  }
34
34
  catch (e) {
35
- this.logger.error(`[Service] [colorize] Failed for “${doc.uri} #${doc.version}`, e);
35
+ this.logger.error(`[Service] [colorize] Failed for ${doc.uri} # ${doc.version}`, e);
36
36
  }
37
37
  return [];
38
38
  }
39
39
  getColorInfo(node, doc) {
40
40
  try {
41
- this.debug(`Getting color info for '${doc.uri}' # ${doc.version}`);
41
+ this.debug(`Getting color info for ${doc.uri} # ${doc.version}`);
42
42
  const ans = [];
43
43
  traversePreOrder(node, (_) => true, (node) => node.color, (node) => ans.push({
44
44
  color: Array.isArray(node.color) ? node.color : node.color.value,
@@ -47,13 +47,13 @@ export class Service {
47
47
  return ans;
48
48
  }
49
49
  catch (e) {
50
- this.logger.error(`[Service] [getColorInfo] Failed for “${doc.uri} #${doc.version}`, e);
50
+ this.logger.error(`[Service] [getColorInfo] Failed for ${doc.uri} # ${doc.version}`, e);
51
51
  }
52
52
  return [];
53
53
  }
54
54
  getColorPresentation(file, doc, range, color) {
55
55
  try {
56
- this.debug(`Getting color presentation for '${doc.uri}' # ${doc.version} @ ${Range.toString(range)}`);
56
+ this.debug(`Getting color presentation for ${doc.uri} # ${doc.version} @ ${Range.toString(range)}`);
57
57
  let node = AstNode.findDeepestChild({ node: file, needle: range.start });
58
58
  while (node) {
59
59
  const nodeColor = node.color;
@@ -65,20 +65,20 @@ export class Service {
65
65
  }
66
66
  }
67
67
  catch (e) {
68
- this.logger.error(`[Service] [getColorPresentation] Failed for “${doc.uri} #${doc.version}`, e);
68
+ this.logger.error(`[Service] [getColorPresentation] Failed for ${doc.uri} # ${doc.version}`, e);
69
69
  }
70
70
  return [];
71
71
  }
72
72
  complete(node, doc, offset, triggerCharacter) {
73
73
  try {
74
- this.debug(`Getting completion for '${doc.uri}' # ${doc.version} @ ${offset}`);
74
+ this.debug(`Getting completion for ${doc.uri} # ${doc.version} @ ${offset}`);
75
75
  const shouldComplete = this.project.meta.shouldComplete(doc.languageId, triggerCharacter);
76
76
  if (shouldComplete) {
77
77
  return completer.file(node, CompleterContext.create(this.project, { doc, offset, triggerCharacter }));
78
78
  }
79
79
  }
80
80
  catch (e) {
81
- this.logger.error(`[Service] [complete] Failed for “${doc.uri} #${doc.version}`, e);
81
+ this.logger.error(`[Service] [complete] Failed for ${doc.uri} # ${doc.version}`, e);
82
82
  }
83
83
  return [];
84
84
  }
@@ -99,18 +99,18 @@ export class Service {
99
99
  }
100
100
  format(node, doc, tabSize, insertSpaces) {
101
101
  try {
102
- this.debug(`Formatting '${doc.uri}' # ${doc.version}`);
102
+ this.debug(`Formatting ${doc.uri} # ${doc.version}`);
103
103
  const formatter = this.project.meta.getFormatter(node.type);
104
104
  return formatter(node, FormatterContext.create(this.project, { doc, tabSize, insertSpaces }));
105
105
  }
106
106
  catch (e) {
107
- this.logger.error(`[Service] [format] Failed for “${doc.uri} #${doc.version}`, e);
107
+ this.logger.error(`[Service] [format] Failed for ${doc.uri} # ${doc.version}`, e);
108
108
  throw e;
109
109
  }
110
110
  }
111
111
  getHover(file, doc, offset) {
112
112
  try {
113
- this.debug(`Getting hover for '${doc.uri}' # ${doc.version} @ ${offset}`);
113
+ this.debug(`Getting hover for ${doc.uri} # ${doc.version} @ ${offset}`);
114
114
  let node = AstNode.findDeepestChild({ node: file, needle: offset });
115
115
  while (node) {
116
116
  const symbol = this.project.symbols.resolveAlias(node.symbol);
@@ -125,14 +125,14 @@ export class Service {
125
125
  }
126
126
  }
127
127
  catch (e) {
128
- this.logger.error(`[Service] [getHover] Failed for “${doc.uri} #${doc.version}`, e);
128
+ this.logger.error(`[Service] [getHover] Failed for ${doc.uri} # ${doc.version}`, e);
129
129
  }
130
130
  return undefined;
131
131
  }
132
132
  getInlayHints(node, doc, range) {
133
133
  try {
134
134
  // TODO: `range` argument is not used.
135
- this.debug(`Getting inlay hints for '${doc.uri}' # ${doc.version}`);
135
+ this.debug(`Getting inlay hints for ${doc.uri} # ${doc.version}`);
136
136
  const ans = [];
137
137
  const ctx = ProcessorContext.create(this.project, { doc });
138
138
  for (const provider of this.project.meta.inlayHintProviders) {
@@ -141,13 +141,13 @@ export class Service {
141
141
  return ans;
142
142
  }
143
143
  catch (e) {
144
- this.logger.error(`[Service] [getInlayHints] Failed for “${doc.uri} #${doc.version}`, e);
144
+ this.logger.error(`[Service] [getInlayHints] Failed for ${doc.uri} # ${doc.version}`, e);
145
145
  }
146
146
  return [];
147
147
  }
148
148
  getSignatureHelp(node, doc, offset) {
149
149
  try {
150
- this.debug(`Getting signature help for '${doc.uri}' # ${doc.version} @ ${offset}`);
150
+ this.debug(`Getting signature help for ${doc.uri} # ${doc.version} @ ${offset}`);
151
151
  const ctx = SignatureHelpProviderContext.create(this.project, { doc, offset });
152
152
  for (const provider of this.project.meta.signatureHelpProviders) {
153
153
  const result = provider(node, ctx);
@@ -157,7 +157,7 @@ export class Service {
157
157
  }
158
158
  }
159
159
  catch (e) {
160
- this.logger.error(`[Service] [getSignatureHelp] Failed for “${doc.uri} #${doc.version}`, e);
160
+ this.logger.error(`[Service] [getSignatureHelp] Failed for ${doc.uri} # ${doc.version}`, e);
161
161
  }
162
162
  return undefined;
163
163
  }
@@ -169,7 +169,7 @@ export class Service {
169
169
  */
170
170
  async getSymbolLocations(file, doc, offset, searchedUsages = SymbolUsageTypes, currentFileOnly = false) {
171
171
  try {
172
- this.debug(`Getting symbol locations of usage '${searchedUsages.join(',')}' for '${doc.uri}' # ${doc.version} @ ${offset} with currentFileOnly=${currentFileOnly}`);
172
+ this.debug(`Getting symbol locations of usage '${searchedUsages.join(',')}' for ${doc.uri} # ${doc.version} @ ${offset} with currentFileOnly=${currentFileOnly}`);
173
173
  let node = AstNode.findDeepestChild({ node: file, needle: offset });
174
174
  while (node) {
175
175
  const symbol = this.project.symbols.resolveAlias(node.symbol);
@@ -197,7 +197,7 @@ export class Service {
197
197
  }
198
198
  }
199
199
  catch (e) {
200
- this.logger.error(`[Service] [getSymbolLocations] Failed for “${doc.uri} #${doc.version}`, e);
200
+ this.logger.error(`[Service] [getSymbolLocations] Failed for ${doc.uri} # ${doc.version}`, e);
201
201
  }
202
202
  return undefined;
203
203
  }
@@ -11,6 +11,5 @@ export declare namespace IndexMap {
11
11
  function toInnerRange(map: IndexMap, outer: Range): Range;
12
12
  function toOuterOffset(map: IndexMap, offset: number): number;
13
13
  function toOuterRange(map: IndexMap, inner: Range): Range;
14
- function merge(outerMap: IndexMap, innerMap: IndexMap): IndexMap;
15
14
  }
16
15
  //# sourceMappingURL=IndexMap.d.ts.map
@@ -32,9 +32,5 @@ export var IndexMap;
32
32
  return Range.create(toOuterOffset(map, inner.start), toOuterOffset(map, inner.end));
33
33
  }
34
34
  IndexMap.toOuterRange = toOuterRange;
35
- function merge(outerMap, innerMap) {
36
- return innerMap.map((p) => ({ inner: p.inner, outer: toOuterRange(outerMap, p.outer) }));
37
- }
38
- IndexMap.merge = merge;
39
35
  })(IndexMap || (IndexMap = {}));
40
36
  //# sourceMappingURL=IndexMap.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spyglassmc/core",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "type": "module",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -25,7 +25,7 @@
25
25
  "rfdc": "^1.3.0",
26
26
  "vscode-languageserver-textdocument": "^1.0.4",
27
27
  "whatwg-url": "^14.0.0",
28
- "@spyglassmc/locales": "0.3.7"
28
+ "@spyglassmc/locales": "0.3.8"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/decompress": "^4.2.3",